Compare commits
No commits in common. "fa2d0601439c57cb228f7b4f7f2f7bc8de753c7c" and "5bd3832d2736eb6bf09f6813c397d8e866ba4e02" have entirely different histories.
fa2d060143
...
5bd3832d27
474
elm/Main.elm
474
elm/Main.elm
@ -1,15 +1,8 @@
|
|||||||
module Main exposing (..)
|
module Main exposing (..)
|
||||||
|
|
||||||
import Browser
|
import Browser
|
||||||
import Dict exposing (Dict)
|
|
||||||
import Html exposing (..)
|
import Html exposing (..)
|
||||||
import Html.Attributes exposing
|
import Html.Attributes exposing (..)
|
||||||
( checked
|
|
||||||
, disabled
|
|
||||||
, style
|
|
||||||
, type_
|
|
||||||
, value
|
|
||||||
)
|
|
||||||
import Html.Events exposing (..)
|
import Html.Events exposing (..)
|
||||||
import Http
|
import Http
|
||||||
import Json.Decode as Dec
|
import Json.Decode as Dec
|
||||||
@ -17,58 +10,40 @@ import Json.Decode.Pipeline exposing (..)
|
|||||||
import Json.Encode as Enc
|
import Json.Encode as Enc
|
||||||
import Set exposing (Set)
|
import Set exposing (Set)
|
||||||
|
|
||||||
main = Browser.document
|
main = Browser.element
|
||||||
{ init = \() -> init
|
{ init = \() -> (init, getUnsoundBarcodes loc)
|
||||||
, subscriptions = \_ -> Sub.none
|
, subscriptions = \_ -> Sub.none
|
||||||
, update = update
|
, update = update
|
||||||
, view = \outerState -> { title = "jon", body = [ view outerState ] }
|
, view = view
|
||||||
}
|
}
|
||||||
|
|
||||||
getOverviewItems : Int -> Cmd Msg
|
getUnsoundBarcodes : Int -> Cmd Msg
|
||||||
getOverviewItems location = Http.post
|
getUnsoundBarcodes location = Http.post
|
||||||
{ url = "/rpc/getOverviewItems"
|
{ url = "/rpc/getUnsoundBarcodes"
|
||||||
, body = Http.jsonBody <| Enc.object
|
, body = Http.jsonBody (Enc.object [("location", Enc.int location)])
|
||||||
[ ("location", Enc.int location)
|
, expect = Http.expectJson RcvUnsoundBarcodes <| Dec.list (Dec.map3 UnsoundBarcode
|
||||||
]
|
(Dec.field "barcode" Dec.string)
|
||||||
, expect = Http.expectJson RcvOverview <| Dec.list decodeOI
|
(Dec.field "name" Dec.string)
|
||||||
|
(Dec.field "entries" Dec.int))
|
||||||
}
|
}
|
||||||
|
|
||||||
rpc : { func : String, args : Enc.Value, expect : Dec.Decoder a } -> (Result Http.Error a -> b) -> Cmd b
|
getActiveItems : String -> Int -> Cmd Msg
|
||||||
rpc { func, args, expect } mkMsg = Http.post
|
getActiveItems barcode location = Http.post
|
||||||
{ url = "/rpc/" ++ func
|
{ url = "/rpc/getActiveItems"
|
||||||
, body = Http.jsonBody args
|
, body = Http.jsonBody (Enc.object
|
||||||
, expect = Http.expectJson mkMsg expect
|
[ ("barcode", Enc.string barcode)
|
||||||
|
, ("location", Enc.int location)
|
||||||
|
])
|
||||||
|
, expect = Http.expectJson (RcvActiveItems barcode) <| Dec.list decodeOI
|
||||||
}
|
}
|
||||||
|
|
||||||
getLocations : Cmd Msg
|
transferInventory : List { from : Int, to : Int, amount : Int } -> Cmd Msg
|
||||||
getLocations = rpc
|
|
||||||
{ func = "getLocations"
|
|
||||||
, args = Enc.object []
|
|
||||||
, expect = Dec.list (Dec.succeed Location
|
|
||||||
|> required "id" Dec.int
|
|
||||||
|> required "name" Dec.string)
|
|
||||||
} RcvLocations
|
|
||||||
|
|
||||||
adjustInventory : Int -> Int -> String -> Cmd Msg
|
|
||||||
adjustInventory itemId adjustment description = Http.post
|
|
||||||
{ url = "/rpc/adjustInventory"
|
|
||||||
, body = Http.jsonBody <| Enc.object
|
|
||||||
[ ("item", Enc.int itemId)
|
|
||||||
, ("amount", Enc.int adjustment)
|
|
||||||
, ("description", Enc.string description)
|
|
||||||
]
|
|
||||||
, expect = Http.expectWhatever (\_ -> RcvOther)
|
|
||||||
}
|
|
||||||
|
|
||||||
type alias InventoryTransferDTO = { from : Int, to : Int, amount : Int }
|
|
||||||
|
|
||||||
transferInventory : List InventoryTransferDTO -> Cmd Msg
|
|
||||||
transferInventory transfers = Http.post
|
transferInventory transfers = Http.post
|
||||||
{ url = "/rpc/transferInventory"
|
{ url = "/rpc/transferInventory"
|
||||||
, body = Http.jsonBody (Enc.object
|
, body = Http.jsonBody (Enc.object
|
||||||
[ ("transfers", Enc.list encodeTransfer transfers)
|
[ ("transfers", Enc.list encodeTransfer transfers)
|
||||||
])
|
])
|
||||||
, expect = Http.expectWhatever (\_ -> RcvOther)
|
, expect = Http.expectWhatever RcvTransferResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
encodeTransfer t = Enc.object
|
encodeTransfer t = Enc.object
|
||||||
@ -83,16 +58,7 @@ disableItems ids = Http.post
|
|||||||
, body = Http.jsonBody (Enc.object
|
, body = Http.jsonBody (Enc.object
|
||||||
[ ("items", Enc.list Enc.int ids)
|
[ ("items", Enc.list Enc.int ids)
|
||||||
])
|
])
|
||||||
, expect = Http.expectWhatever (\_ -> RcvOther)
|
, expect = Http.expectWhatever RcvDisableItemResponse
|
||||||
}
|
|
||||||
|
|
||||||
deleteSnack : Int -> Cmd Msg
|
|
||||||
deleteSnack snack = Http.post
|
|
||||||
{ url = "/rpc/deleteSnack"
|
|
||||||
, body = Http.jsonBody (Enc.object
|
|
||||||
[ ("snack", Enc.int snack)
|
|
||||||
])
|
|
||||||
, expect = Http.expectWhatever (\_ -> RcvOther)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
decodeOI = Dec.succeed OverviewItem
|
decodeOI = Dec.succeed OverviewItem
|
||||||
@ -103,35 +69,13 @@ decodeOI = Dec.succeed OverviewItem
|
|||||||
|> requiredAt ["item", "unitPrice"] Dec.float
|
|> requiredAt ["item", "unitPrice"] Dec.float
|
||||||
|> requiredAt ["item", "bought"] Dec.string
|
|> requiredAt ["item", "bought"] Dec.string
|
||||||
|> requiredAt ["overview", "activeMappings"] Dec.int
|
|> requiredAt ["overview", "activeMappings"] Dec.int
|
||||||
|> requiredAt ["item", "group"] Dec.int
|
|
||||||
|> requiredAt ["overview", "groupName"] Dec.string
|
|
||||||
|
|
||||||
getSnacksByItem : OverviewItem -> Cmd Msg
|
type alias UnsoundBarcode =
|
||||||
getSnacksByItem item = rpc
|
{ barcode : String
|
||||||
{ func = "getSnacksByItemId"
|
|
||||||
, args = Enc.object
|
|
||||||
[ ("item", Enc.int item.id)
|
|
||||||
]
|
|
||||||
, expect = Dec.list decodeSnack
|
|
||||||
} (RcvSnacks item)
|
|
||||||
|
|
||||||
type alias Snack =
|
|
||||||
{ id : Int
|
|
||||||
, name : String
|
, name : String
|
||||||
, barcode : String
|
, entries : Int
|
||||||
, price : Float
|
|
||||||
, location : Int
|
|
||||||
, taxGroup : Int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
decodeSnack = Dec.succeed Snack
|
|
||||||
|> required "id" Dec.int
|
|
||||||
|> required "name" Dec.string
|
|
||||||
|> required "barcode" Dec.string
|
|
||||||
|> required "price" Dec.float
|
|
||||||
|> required "location" Dec.int
|
|
||||||
|> required "taxGroup" Dec.int
|
|
||||||
|
|
||||||
type alias OverviewItem =
|
type alias OverviewItem =
|
||||||
{ id : Int
|
{ id : Int
|
||||||
, barcode : String
|
, barcode : String
|
||||||
@ -140,286 +84,118 @@ type alias OverviewItem =
|
|||||||
, price : Float
|
, price : Float
|
||||||
, bought : String
|
, bought : String
|
||||||
, activeMappings : Int
|
, activeMappings : Int
|
||||||
, groupId : Int
|
|
||||||
, groupName : String
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type alias Location =
|
type Model
|
||||||
{ id : Int
|
= Init
|
||||||
, name : String
|
| UBList (List UnsoundBarcode)
|
||||||
}
|
| Overview String (Set Int) (List OverviewItem)
|
||||||
|
|
||||||
type OuterState
|
|
||||||
= LoadingLocations
|
|
||||||
| LocationSelector (List Location)
|
|
||||||
| Initialized { locations : List Location, location : Location } State
|
|
||||||
|
|
||||||
type State
|
|
||||||
= Overview
|
|
||||||
{ selectedItems : Set Int
|
|
||||||
, desiredInventory : Dict Int Int
|
|
||||||
, overviewItems : List OverviewItem
|
|
||||||
}
|
|
||||||
| ViewingItem
|
|
||||||
{ item : OverviewItem
|
|
||||||
, snacks : List Snack
|
|
||||||
}
|
|
||||||
|
|
||||||
type Msg
|
type Msg
|
||||||
= SelectLocation Location
|
= RcvUnsoundBarcodes (Result Http.Error (List UnsoundBarcode))
|
||||||
| SelectItem Int Bool
|
| GetActiveItems String Int
|
||||||
| ChangeLocation
|
| RcvActiveItems String (Result Http.Error (List OverviewItem))
|
||||||
| SetDesiredInventory Int String
|
| SetSelected Int Bool
|
||||||
| TransferInventory Int
|
| TransferInventory (List { amount : Int, from : Int, to : Int })
|
||||||
|
| RcvTransferResponse (Result Http.Error ())
|
||||||
|
| DisableItem Int
|
||||||
|
| RcvDisableItemResponse (Result Http.Error ())
|
||||||
| GoBack
|
| GoBack
|
||||||
-- RPC calls
|
|
||||||
| CallDisableItems (List Int)
|
|
||||||
| CallAdjustInventory Int Int String
|
|
||||||
| CallGetSnacksById OverviewItem
|
|
||||||
| CallDeleteSnack Int
|
|
||||||
-- Responses
|
|
||||||
| RcvLocations (Result Http.Error (List Location))
|
|
||||||
| RcvOverview (Result Http.Error (List OverviewItem))
|
|
||||||
| RcvSnacks OverviewItem (Result Http.Error (List Snack))
|
|
||||||
| RcvOther
|
|
||||||
|
|
||||||
init = (LoadingLocations, getLocations)
|
init = Init
|
||||||
|
loc = 2
|
||||||
|
|
||||||
update msg outerState = case msg of
|
update msg model = case model of
|
||||||
CallAdjustInventory item amount desc -> (outerState, adjustInventory item amount desc)
|
Init -> case msg of
|
||||||
CallDisableItems items -> (outerState, disableItems items)
|
RcvUnsoundBarcodes (Ok ubs) -> (UBList ubs, Cmd.none)
|
||||||
CallGetSnacksById item -> (outerState, getSnacksByItem item)
|
_ -> (model, Cmd.none)
|
||||||
CallDeleteSnack snack -> (outerState, deleteSnack snack)
|
UBList _ -> case msg of
|
||||||
_ -> case outerState of
|
GetActiveItems barcode location -> (model, getActiveItems barcode location)
|
||||||
LoadingLocations -> case msg of
|
RcvActiveItems barcode (Ok ois) -> (Overview barcode (Set.empty) ois, Cmd.none)
|
||||||
RcvLocations (Ok locations) ->
|
_ -> (model, Cmd.none)
|
||||||
(LocationSelector locations, Cmd.none)
|
Overview barcode selectedItems ois -> case msg of
|
||||||
_ ->
|
SetSelected id checked -> (Overview barcode ((if checked then Set.insert else Set.remove) id selectedItems) ois, Cmd.none)
|
||||||
(outerState, Cmd.none)
|
TransferInventory transfers -> (model, transferInventory transfers)
|
||||||
LocationSelector locations -> case msg of
|
RcvTransferResponse _ -> (model, getActiveItems barcode loc)
|
||||||
SelectLocation location ->
|
RcvActiveItems barcode_ (Ok ois_) -> (Overview barcode_ (Set.empty) ois_, Cmd.none)
|
||||||
(Initialized { locations = locations, location = location } <| Overview
|
DisableItem id -> (model, disableItems [id])
|
||||||
{ selectedItems = Set.empty
|
RcvDisableItemResponse _ -> (model, getActiveItems barcode loc)
|
||||||
, desiredInventory = Dict.empty
|
GoBack -> (model, getUnsoundBarcodes loc)
|
||||||
, overviewItems = []
|
RcvUnsoundBarcodes (Ok ubs) -> (UBList ubs, Cmd.none)
|
||||||
}
|
_ -> (model, Cmd.none)
|
||||||
, getOverviewItems location.id
|
|
||||||
)
|
|
||||||
_ ->
|
|
||||||
(outerState, Cmd.none)
|
|
||||||
Initialized global state -> case msg of
|
|
||||||
ChangeLocation ->
|
|
||||||
(LocationSelector global.locations, Cmd.none)
|
|
||||||
_ ->
|
|
||||||
let
|
|
||||||
(newState, cmd) = stateMachine msg global state
|
|
||||||
in
|
|
||||||
(Initialized global newState, cmd)
|
|
||||||
|
|
||||||
stateMachine msg global state = case state of
|
view model = case model of
|
||||||
Overview model -> case msg of
|
Init -> h1 [] [ text "It works!" ]
|
||||||
RcvOverview (Ok overviewItems) ->
|
UBList ubs -> viewUBList ubs
|
||||||
(Overview
|
Overview barcode selectedItems ois -> viewOverview barcode selectedItems ois
|
||||||
{ selectedItems = Set.empty
|
|
||||||
, desiredInventory = Dict.empty
|
|
||||||
, overviewItems = overviewItems
|
|
||||||
}
|
|
||||||
, Cmd.none
|
|
||||||
)
|
|
||||||
RcvSnacks item (Ok snacks) ->
|
|
||||||
(ViewingItem { item = item, snacks = snacks }, Cmd.none)
|
|
||||||
RcvOther ->
|
|
||||||
(state, getOverviewItems global.location.id)
|
|
||||||
SelectItem itemId selected ->
|
|
||||||
(Overview { model | selectedItems = setSelect itemId selected model.selectedItems }, Cmd.none)
|
|
||||||
SetDesiredInventory itemId invStr -> case String.toInt invStr of
|
|
||||||
Just inv ->
|
|
||||||
(Overview { model | desiredInventory = Dict.insert itemId inv model.desiredInventory }, Cmd.none)
|
|
||||||
Nothing ->
|
|
||||||
(state, Cmd.none)
|
|
||||||
TransferInventory targetId ->
|
|
||||||
let
|
|
||||||
transfers =
|
|
||||||
model.overviewItems
|
|
||||||
|> List.filterMap (\oi ->
|
|
||||||
if Set.member oi.id model.selectedItems
|
|
||||||
then Just { from = oi.id, to = targetId, amount = oi.unitsLeft }
|
|
||||||
else Nothing)
|
|
||||||
in
|
|
||||||
(state, transferInventory transfers)
|
|
||||||
_ ->
|
|
||||||
(state, Cmd.none)
|
|
||||||
ViewingItem { item } -> case msg of
|
|
||||||
GoBack ->
|
|
||||||
(Overview { selectedItems = Set.empty, desiredInventory = Dict.empty, overviewItems = [] }, getOverviewItems global.location.id)
|
|
||||||
RcvSnacks item_ (Ok snacks) ->
|
|
||||||
(ViewingItem { item = item_, snacks = snacks }, Cmd.none)
|
|
||||||
RcvOther ->
|
|
||||||
(state, getSnacksByItem item)
|
|
||||||
_ ->
|
|
||||||
(state, Cmd.none)
|
|
||||||
|
|
||||||
view outerState = case outerState of
|
viewUBList ubs =
|
||||||
LoadingLocations -> progress [] []
|
let
|
||||||
LocationSelector locations ->
|
header = tr []
|
||||||
let
|
[ th [] [ text "Barcode" ]
|
||||||
viewLocationButton location =
|
, th [] [ text "Artikel" ]
|
||||||
button [ onClick <| SelectLocation location ] [ text location.name ]
|
, th [] [ text "Einträge" ]
|
||||||
in
|
, th [] [ text "Aktionen" ]
|
||||||
div []
|
]
|
||||||
[ p [] [ text "Raum auswählen:" ]
|
viewUB ub = tr []
|
||||||
, div [] <| List.map viewLocationButton locations
|
[ td [] [ text ub.barcode ]
|
||||||
]
|
, td [] [ text ub.name ]
|
||||||
Initialized global state ->
|
, td [] [ text <| String.fromInt ub.entries ]
|
||||||
div []
|
, td [] [ button [ onClick (GetActiveItems ub.barcode loc) ] [ text "Einträge ansehen" ] ]
|
||||||
[ h2 []
|
|
||||||
[ text <| "Inventar " ++ global.location.name ++ " "
|
|
||||||
, button [ onClick ChangeLocation ] [ text "Raum ändern" ]
|
|
||||||
]
|
|
||||||
, viewState global state
|
|
||||||
]
|
]
|
||||||
|
|
||||||
viewState global state = case state of
|
|
||||||
Overview { selectedItems, desiredInventory, overviewItems } ->
|
|
||||||
let
|
|
||||||
header = tableCells th <| List.map text [ "", "ID", "Artikel", "EAN", "Preis", "Kaufdatum", "Snackeinträge", "Inventar", "Aktionen" ]
|
|
||||||
viewOverviewItem oi =
|
|
||||||
let
|
|
||||||
adjustedInventory = Maybe.withDefault oi.unitsLeft <| Dict.get oi.id desiredInventory
|
|
||||||
mkAdjustInventoryMsg itemId adjustment =
|
|
||||||
if adjustment > 0
|
|
||||||
then CallAdjustInventory itemId adjustment "Gewinn"
|
|
||||||
else CallAdjustInventory itemId adjustment "Verlust"
|
|
||||||
viewAdjustedInventory adjustment =
|
|
||||||
if adjustment == 0
|
|
||||||
then ""
|
|
||||||
else if adjustment > 0
|
|
||||||
then "(+" ++ String.fromInt adjustment ++ ")"
|
|
||||||
else "(" ++ String.fromInt adjustment ++ ")"
|
|
||||||
|
|
||||||
sumSelected =
|
|
||||||
overviewItems
|
|
||||||
|> List.filter (\x -> Set.member x.id selectedItems)
|
|
||||||
|> List.map .unitsLeft
|
|
||||||
|> List.sum
|
|
||||||
in
|
|
||||||
tableCells td
|
|
||||||
[ input
|
|
||||||
[ type_ "checkbox"
|
|
||||||
, onCheck <| SelectItem oi.id
|
|
||||||
, checked <| Set.member oi.id selectedItems
|
|
||||||
] []
|
|
||||||
, text <| String.fromInt oi.id
|
|
||||||
, text oi.name
|
|
||||||
, code [] [ text oi.barcode ]
|
|
||||||
, text <| showEuros oi.price
|
|
||||||
, text <| Tuple.first <| splitAt 'T' oi.bought
|
|
||||||
, text <| String.fromInt oi.activeMappings
|
|
||||||
, input
|
|
||||||
[ type_ "number"
|
|
||||||
, onInput <| SetDesiredInventory oi.id
|
|
||||||
, value <| String.fromInt adjustedInventory
|
|
||||||
, style "width" "5em"
|
|
||||||
] []
|
|
||||||
, div []
|
|
||||||
[ button
|
|
||||||
(if adjustedInventory == oi.unitsLeft
|
|
||||||
then [ disabled True ]
|
|
||||||
else [ onClick <| mkAdjustInventoryMsg oi.id <| adjustedInventory - oi.unitsLeft ])
|
|
||||||
[ text <| "Inventar korrigieren" ++ viewAdjustedInventory (adjustedInventory - oi.unitsLeft) ]
|
|
||||||
, button
|
|
||||||
(if oi.unitsLeft /= 0
|
|
||||||
then [ disabled True ]
|
|
||||||
else [ onClick <| CallDisableItems [oi.id] ])
|
|
||||||
[ text "Eintrag deaktivieren" ]
|
|
||||||
, button
|
|
||||||
(if Set.member oi.id selectedItems || sumSelected == 0
|
|
||||||
then [ disabled True ]
|
|
||||||
else [ onClick <| TransferInventory oi.id ])
|
|
||||||
[ text <| String.fromInt sumSelected ++ " Einheiten umbuchen" ]
|
|
||||||
, button [ onClick <| CallGetSnacksById oi ] [ text "Anzeigen" ]
|
|
||||||
]
|
|
||||||
]
|
|
||||||
in
|
|
||||||
div []
|
|
||||||
[ table [] <| [header] ++ List.map viewOverviewItem overviewItems
|
|
||||||
]
|
|
||||||
ViewingItem { item, snacks } ->
|
|
||||||
let
|
|
||||||
header = tableCells th <| List.map text [ "ID", "Artikel", "Barcode", "Bruttoverkaufspreis", "Aktionen" ]
|
|
||||||
viewSnack snack = tableCells td
|
|
||||||
[ text <| String.fromInt snack.id
|
|
||||||
, text snack.name
|
|
||||||
, code [] [ text snack.barcode ]
|
|
||||||
, text <| showEuros snack.price ++ " (+" ++ showEuros (roundTo 2 <| snack.price - item.price) ++ ")"
|
|
||||||
, div []
|
|
||||||
[ button [ onClick <| CallDeleteSnack snack.id ] [ text "Deaktivieren" ]
|
|
||||||
]
|
|
||||||
]
|
|
||||||
itemProp label value = tr []
|
|
||||||
[ th [ style "text-align" "left" ] [ text label ]
|
|
||||||
, td [] value
|
|
||||||
]
|
|
||||||
in
|
|
||||||
div []
|
|
||||||
[ button [ onClick GoBack ] [ text "Zurück" ]
|
|
||||||
, fieldset []
|
|
||||||
[ legend [] [ text <| "Inventareintrag " ++ String.fromInt item.id ]
|
|
||||||
, table []
|
|
||||||
[ tbody []
|
|
||||||
[ itemProp "ID" [ text <| String.fromInt item.id ]
|
|
||||||
, itemProp "EAN" [ code [] [ text item.barcode ] ]
|
|
||||||
, itemProp "Artikel" [ text item.name ]
|
|
||||||
, itemProp "Gruppe" [ text <| item.groupName ++ " (" ++ String.fromInt item.groupId ++ ")" ]
|
|
||||||
, itemProp "Inventar" [ text <| String.fromInt item.unitsLeft ]
|
|
||||||
, itemProp "Kaufdatum" [ text <| Tuple.first <| splitAt 'T' item.bought ]
|
|
||||||
, itemProp "Nettoeinkaufspreis" [ text <| showEuros item.price ]
|
|
||||||
]
|
|
||||||
]
|
|
||||||
]
|
|
||||||
, h3 [] [ text "Snacks" ]
|
|
||||||
, table []
|
|
||||||
[ thead [] [ header ]
|
|
||||||
, tbody [] <| List.map viewSnack snacks
|
|
||||||
]
|
|
||||||
]
|
|
||||||
|
|
||||||
-- utils
|
|
||||||
|
|
||||||
tableCells f =
|
|
||||||
let
|
|
||||||
mkTd elem = f [] [ elem ]
|
|
||||||
in
|
in
|
||||||
tr [] << List.map mkTd
|
table [] ([header] ++ List.map viewUB ubs)
|
||||||
|
|
||||||
setSelect elem state =
|
viewOverview : String -> Set Int -> List OverviewItem -> Html Msg
|
||||||
(if state then Set.insert else Set.remove) elem
|
viewOverview barcode selectedItems ois =
|
||||||
|
|
||||||
splitAt : Char -> String -> (String, String)
|
|
||||||
splitAt delim str =
|
|
||||||
let
|
let
|
||||||
locate c s = case String.uncons s of
|
sumSelected =
|
||||||
Nothing -> 0
|
List.sum
|
||||||
Just (x, xs) ->
|
<| List.map (\oi -> oi.unitsLeft)
|
||||||
if x == c then
|
<| List.filter (\oi -> Set.member oi.id selectedItems) ois
|
||||||
0
|
|
||||||
else
|
|
||||||
1 + locate c xs
|
|
||||||
|
|
||||||
firstOcc = locate delim str
|
header = tr []
|
||||||
in
|
[ th [] []
|
||||||
(String.slice 0 firstOcc str, String.slice (firstOcc + 1) (String.length str) str)
|
, th [] [ text "ID" ]
|
||||||
|
, th [] [ text "Barcode" ]
|
||||||
|
, th [] [ text "Artikel" ]
|
||||||
|
, th [] [ text "Kaufdatum" ]
|
||||||
|
, th [] [ text "Inventar (Soll)" ]
|
||||||
|
, th [] [ text "Preis" ]
|
||||||
|
, th [] [ text "Snackeinträge" ]
|
||||||
|
, th [] [ text "Aktionen" ]
|
||||||
|
]
|
||||||
|
viewOI oi = tr []
|
||||||
|
[ th [] [ input [ type_ "checkbox", onCheck <| SetSelected oi.id, selected <| Set.member oi.id selectedItems ] [] ]
|
||||||
|
, td [] [ text <| String.fromInt oi.id ]
|
||||||
|
, td [] [ text oi.barcode ]
|
||||||
|
, td [] [ text oi.name ]
|
||||||
|
, td [] [ text oi.bought ]
|
||||||
|
, td [] [ text <| String.fromInt oi.unitsLeft ]
|
||||||
|
, td [] [ text <| String.fromFloat oi.price ]
|
||||||
|
, td [] [ text <| String.fromInt oi.activeMappings ]
|
||||||
|
, td [] (viewButtons oi)
|
||||||
|
]
|
||||||
|
viewButtons oi =
|
||||||
|
[ button
|
||||||
|
[ disabled <| Set.member oi.id selectedItems || sumSelected == 0
|
||||||
|
, onClick <| mkTransferMessage selectedItems oi.id
|
||||||
|
]
|
||||||
|
[ text <| String.fromInt sumSelected ++ " Einträge umbuchen" ]
|
||||||
|
, button
|
||||||
|
[ disabled <| oi.unitsLeft /= 0 || oi.activeMappings /= 0
|
||||||
|
, onClick <| DisableItem oi.id
|
||||||
|
]
|
||||||
|
[ text "Eintrag deaktivieren" ]
|
||||||
|
]
|
||||||
|
mkTransferMessage fromIds toId =
|
||||||
|
TransferInventory
|
||||||
|
<| List.map (\oi -> { amount = oi.unitsLeft, from = oi.id, to = toId })
|
||||||
|
<| List.filter (\oi -> Set.member oi.id fromIds) ois
|
||||||
|
|
||||||
showEuros : Float -> String
|
|
||||||
showEuros x =
|
|
||||||
let
|
|
||||||
(whole, fractional) = splitAt '.' (String.fromFloat x)
|
|
||||||
in
|
in
|
||||||
whole ++ "," ++ String.slice 0 2 (fractional ++ "00") ++ "€"
|
div []
|
||||||
|
[ button [ onClick GoBack ] [ text "Zurück" ]
|
||||||
roundTo : Int -> Float -> Float
|
, h2 [] [ text barcode ]
|
||||||
roundTo decimals x =
|
, table [] ([header] ++ List.map viewOI ois)
|
||||||
let
|
]
|
||||||
m = toFloat <| 10^decimals
|
|
||||||
in
|
|
||||||
toFloat (round (x * m)) / m
|
|
||||||
|
@ -68,17 +68,6 @@ locations
|
|||||||
:: Q Postgres GarfieldDb s (LocationT (QExpr Postgres s))
|
:: Q Postgres GarfieldDb s (LocationT (QExpr Postgres s))
|
||||||
locations = all_ garfieldDb.locations
|
locations = all_ garfieldDb.locations
|
||||||
|
|
||||||
getSnacksByItemId
|
|
||||||
:: InventoryItemId
|
|
||||||
-> Q Postgres GarfieldDb s (SnackT (QExpr Postgres s))
|
|
||||||
getSnacksByItemId item = do
|
|
||||||
im <- all_ garfieldDb.inventoryMap
|
|
||||||
sn <- related_ garfieldDb.snacks im.snackId
|
|
||||||
sa <- oneToOne_ garfieldDb.snacksAvailable (.snackId) sn
|
|
||||||
guard_ $ im.inventoryId ==. val_ item
|
|
||||||
guard_ $ sa.available
|
|
||||||
pure sn
|
|
||||||
|
|
||||||
-- Inserts
|
-- Inserts
|
||||||
|
|
||||||
runInserts
|
runInserts
|
||||||
@ -87,14 +76,6 @@ runInserts
|
|||||||
-> IO ()
|
-> IO ()
|
||||||
runInserts conn is = runBeamPostgresDebug putStrLn conn $ mapM_ runInsert is
|
runInserts conn is = runBeamPostgresDebug putStrLn conn $ mapM_ runInsert is
|
||||||
|
|
||||||
adjustInventory
|
|
||||||
:: InventoryItemId
|
|
||||||
-> Int64
|
|
||||||
-> Text
|
|
||||||
-> SqlInsert Postgres CorrectionT
|
|
||||||
adjustInventory item amount desc = insert garfieldDb.inventoryCorrections $
|
|
||||||
insertExpressions [Correction (val_ item) default_ (val_ amount) (val_ desc)]
|
|
||||||
|
|
||||||
transfer
|
transfer
|
||||||
:: InventoryItemId -- ^ to
|
:: InventoryItemId -- ^ to
|
||||||
-> InventoryItemId -- ^ from
|
-> InventoryItemId -- ^ from
|
||||||
|
@ -20,9 +20,6 @@ module Jon.Garfield.Types
|
|||||||
, InventoryItemGroup
|
, InventoryItemGroup
|
||||||
, InventoryItemGroupId
|
, InventoryItemGroupId
|
||||||
, InventoryItemGroupT(..)
|
, InventoryItemGroupT(..)
|
||||||
, InventoryMap
|
|
||||||
, InventoryMapId
|
|
||||||
, InventoryMapT(..)
|
|
||||||
, TaxGroup
|
, TaxGroup
|
||||||
, TaxGroupId
|
, TaxGroupId
|
||||||
, TaxGroupT(..)
|
, TaxGroupT(..)
|
||||||
@ -35,9 +32,6 @@ module Jon.Garfield.Types
|
|||||||
, Snack
|
, Snack
|
||||||
, SnackId
|
, SnackId
|
||||||
, SnackT(..)
|
, SnackT(..)
|
||||||
, SnackAvailable
|
|
||||||
, SnackAvailableId
|
|
||||||
, SnackAvailableT(..)
|
|
||||||
, Sale
|
, Sale
|
||||||
, SaleId
|
, SaleId
|
||||||
, SaleT(..)
|
, SaleT(..)
|
||||||
@ -49,18 +43,16 @@ module Jon.Garfield.Types
|
|||||||
, mkInventoryItemId
|
, mkInventoryItemId
|
||||||
, mkTaxGroupId
|
, mkTaxGroupId
|
||||||
, mkLocationId
|
, mkLocationId
|
||||||
, mkSaleId
|
|
||||||
, mkSnackId
|
, mkSnackId
|
||||||
, mkSnackAvailableId
|
, mkSaleId
|
||||||
, mkUserId
|
, mkUserId
|
||||||
, unOverviewId
|
, unOverviewId
|
||||||
, unInventoryItemId
|
, unInventoryItemId
|
||||||
, unInventoryItemGroupId
|
, unInventoryItemGroupId
|
||||||
, unTaxGroupId
|
, unTaxGroupId
|
||||||
, unLocationId
|
, unLocationId
|
||||||
, unSaleId
|
|
||||||
, unSnackId
|
, unSnackId
|
||||||
, unSnackAvailableId
|
, unSaleId
|
||||||
, unUserId
|
, unUserId
|
||||||
) where
|
) where
|
||||||
|
|
||||||
@ -105,8 +97,6 @@ data GarfieldDb f = GarfieldDb
|
|||||||
, snacks :: f (TableEntity SnackT)
|
, snacks :: f (TableEntity SnackT)
|
||||||
, sales :: f (TableEntity SaleT)
|
, sales :: f (TableEntity SaleT)
|
||||||
, users :: f (TableEntity UserT)
|
, users :: f (TableEntity UserT)
|
||||||
, inventoryMap :: f (TableEntity InventoryMapT)
|
|
||||||
, snacksAvailable :: f (TableEntity SnackAvailableT)
|
|
||||||
} deriving (Generic, Database be)
|
} deriving (Generic, Database be)
|
||||||
|
|
||||||
setGarfieldEntityName name = setEntitySchema (Just "garfield") <> setEntityName name
|
setGarfieldEntityName name = setEntitySchema (Just "garfield") <> setEntityName name
|
||||||
@ -178,14 +168,6 @@ garfieldDb = defaultDbSettings `withDbModification`
|
|||||||
, alwaysSendBalanceMail = "always_send_balance_mail"
|
, alwaysSendBalanceMail = "always_send_balance_mail"
|
||||||
, sendHistoryMail = "send_history_mail"
|
, sendHistoryMail = "send_history_mail"
|
||||||
}
|
}
|
||||||
, inventoryMap = setGarfieldEntityName "inventory_map" <> modifyTableFields tableModification
|
|
||||||
{ inventoryId = InventoryItemId "inventory_id"
|
|
||||||
, snackId = SnackId "snack_id"
|
|
||||||
}
|
|
||||||
, snacksAvailable = setGarfieldEntityName "snacks_available" <> modifyTableFields tableModification
|
|
||||||
{ snackId = SnackId "snack_id"
|
|
||||||
, available = "snack_available"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
-- Views
|
-- Views
|
||||||
@ -329,11 +311,11 @@ instance Table CorrectionT where
|
|||||||
data SnackT f = Snack
|
data SnackT f = Snack
|
||||||
{ id :: C f Int32
|
{ id :: C f Int32
|
||||||
, name :: C f Text
|
, name :: C f Text
|
||||||
, barcode :: C (Nullable f) Text
|
, barcode :: C f Text
|
||||||
, price :: C f Scientific
|
, price :: C f Scientific
|
||||||
, location :: PrimaryKey LocationT f
|
, location :: PrimaryKey LocationT f
|
||||||
-- , snackModifiedBy :: PrimaryKey UserT f
|
-- , snackModifiedBy :: PrimaryKey UserT f
|
||||||
, timestamp :: C (Nullable f) UTCTime
|
, timestamp :: C f UTCTime
|
||||||
, taxGroup :: PrimaryKey TaxGroupT f
|
, taxGroup :: PrimaryKey TaxGroupT f
|
||||||
} deriving (Beamable, Generic)
|
} deriving (Beamable, Generic)
|
||||||
|
|
||||||
@ -400,40 +382,3 @@ instance Table UserT where
|
|||||||
data PrimaryKey UserT f
|
data PrimaryKey UserT f
|
||||||
= UserId { unUserId :: C f Int32 } deriving (Beamable, Generic)
|
= UserId { unUserId :: C f Int32 } deriving (Beamable, Generic)
|
||||||
primaryKey = UserId . (.id)
|
primaryKey = UserId . (.id)
|
||||||
|
|
||||||
|
|
||||||
data InventoryMapT f = InventoryMap
|
|
||||||
{ inventoryId :: PrimaryKey InventoryItemT f
|
|
||||||
, snackId :: PrimaryKey SnackT f
|
|
||||||
} deriving (Beamable, Generic)
|
|
||||||
|
|
||||||
type InventoryMap = InventoryMapT Identity
|
|
||||||
type InventoryMapId = PrimaryKey InventoryMapT Identity
|
|
||||||
|
|
||||||
deriving instance Show InventoryMap
|
|
||||||
deriving instance Show InventoryMapId
|
|
||||||
|
|
||||||
instance Table InventoryMapT where
|
|
||||||
data PrimaryKey InventoryMapT f
|
|
||||||
= InventoryMapId deriving (Beamable, Generic)
|
|
||||||
primaryKey _ = InventoryMapId
|
|
||||||
|
|
||||||
|
|
||||||
data SnackAvailableT f = SnackAvailable
|
|
||||||
{ snackId :: PrimaryKey SnackT f
|
|
||||||
, available :: C f Bool
|
|
||||||
} deriving (Beamable, Generic)
|
|
||||||
|
|
||||||
type SnackAvailable = SnackAvailableT Identity
|
|
||||||
type SnackAvailableId = PrimaryKey SnackAvailableT Identity
|
|
||||||
|
|
||||||
mkSnackAvailableId :: SnackId -> SnackAvailableId
|
|
||||||
mkSnackAvailableId = SnackAvailableId
|
|
||||||
|
|
||||||
deriving instance Show SnackAvailable
|
|
||||||
deriving instance Show SnackAvailableId
|
|
||||||
|
|
||||||
instance Table SnackAvailableT where
|
|
||||||
data PrimaryKey SnackAvailableT f
|
|
||||||
= SnackAvailableId { unSnackAvailableId :: PrimaryKey SnackT f } deriving (Beamable, Generic)
|
|
||||||
primaryKey = SnackAvailableId . (.snackId)
|
|
||||||
|
@ -45,8 +45,6 @@ type JonAPI =
|
|||||||
:> Post '[JSON] [OverviewItemDTO]
|
:> Post '[JSON] [OverviewItemDTO]
|
||||||
:<|> "getLocations" :> Summary "Get a list of all locations"
|
:<|> "getLocations" :> Summary "Get a list of all locations"
|
||||||
:> Post '[JSON] [Location]
|
:> Post '[JSON] [Location]
|
||||||
:<|> "adjustInventory" :> ReqBody '[JSON] AdjustInventoryP
|
|
||||||
:> PostNoContent
|
|
||||||
:<|> "transferInventory" :> Summary "Transfer inventory between items"
|
:<|> "transferInventory" :> Summary "Transfer inventory between items"
|
||||||
:> Description "If `amount` is negative, its absolute value is transferred in the opposite direction."
|
:> Description "If `amount` is negative, its absolute value is transferred in the opposite direction."
|
||||||
:> ReqBody '[JSON] TransferInventoryP
|
:> ReqBody '[JSON] TransferInventoryP
|
||||||
@ -61,15 +59,9 @@ type SnackAPI =
|
|||||||
"createSnack" :> Summary "Create a snack"
|
"createSnack" :> Summary "Create a snack"
|
||||||
:> ReqBody '[JSON] CreateSnackP
|
:> ReqBody '[JSON] CreateSnackP
|
||||||
:> Post '[JSON] SnackId
|
:> Post '[JSON] SnackId
|
||||||
:<|> "getSnacksByItemId" :> Summary "Get active snacks by item id"
|
|
||||||
:> ReqBody '[JSON] GetSnacksByItemIdP
|
|
||||||
:> Post '[JSON] [Snack]
|
|
||||||
:<|> "updateSnack" :> Summary "Update a snack"
|
:<|> "updateSnack" :> Summary "Update a snack"
|
||||||
:> ReqBody '[JSON] UpdateSnackP
|
:> ReqBody '[JSON] UpdateSnackP
|
||||||
:> Post '[JSON] SnackId
|
:> Post '[JSON] SnackId
|
||||||
:<|> "deleteSnack" :> Summary "Delete a snack"
|
|
||||||
:> ReqBody '[JSON] DeleteSnackP
|
|
||||||
:> PostNoContent
|
|
||||||
|
|
||||||
data GetUnsoundBarcodesP = GetUnsoundBarcodesP
|
data GetUnsoundBarcodesP = GetUnsoundBarcodesP
|
||||||
{ location :: LocationId
|
{ location :: LocationId
|
||||||
@ -98,12 +90,6 @@ data GetActiveItemsP = GetActiveItemsP
|
|||||||
, location :: LocationId
|
, location :: LocationId
|
||||||
} deriving (Generic, FromJSON, ToSchema)
|
} deriving (Generic, FromJSON, ToSchema)
|
||||||
|
|
||||||
data AdjustInventoryP = AdjustInventoryP
|
|
||||||
{ item :: InventoryItemId
|
|
||||||
, amount :: Int64
|
|
||||||
, description :: Text
|
|
||||||
} deriving (Generic, FromJSON, ToSchema)
|
|
||||||
|
|
||||||
data TransferInventoryP = TransferInventoryP
|
data TransferInventoryP = TransferInventoryP
|
||||||
{ transfers :: [InventoryTransferDTO]
|
{ transfers :: [InventoryTransferDTO]
|
||||||
} deriving (Generic, FromJSON, ToSchema)
|
} deriving (Generic, FromJSON, ToSchema)
|
||||||
@ -126,10 +112,6 @@ data CreateSnackP = CreateSnackP
|
|||||||
, location :: LocationId
|
, location :: LocationId
|
||||||
} deriving (Generic, FromJSON, ToSchema)
|
} deriving (Generic, FromJSON, ToSchema)
|
||||||
|
|
||||||
data GetSnacksByItemIdP = GetSnacksByItemIdP
|
|
||||||
{ item :: InventoryItemId
|
|
||||||
} deriving (Generic, FromJSON, ToSchema)
|
|
||||||
|
|
||||||
data UpdateSnackP = UpdateSnackP
|
data UpdateSnackP = UpdateSnackP
|
||||||
{ snack :: SnackId
|
{ snack :: SnackId
|
||||||
, name :: Text
|
, name :: Text
|
||||||
@ -138,10 +120,6 @@ data UpdateSnackP = UpdateSnackP
|
|||||||
, taxGroup :: TaxGroupId
|
, taxGroup :: TaxGroupId
|
||||||
} deriving (Generic, FromJSON, ToSchema)
|
} deriving (Generic, FromJSON, ToSchema)
|
||||||
|
|
||||||
data DeleteSnackP = DeleteSnackP
|
|
||||||
{ snack :: SnackId
|
|
||||||
} deriving (Generic, FromJSON, ToSchema)
|
|
||||||
|
|
||||||
-- Orphan instances for database types
|
-- Orphan instances for database types
|
||||||
-- needed for serialization and swagger doc
|
-- needed for serialization and swagger doc
|
||||||
|
|
||||||
@ -187,9 +165,6 @@ deriving instance ToSchema InventoryItem
|
|||||||
deriving instance ToJSON Location
|
deriving instance ToJSON Location
|
||||||
deriving instance ToSchema Location
|
deriving instance ToSchema Location
|
||||||
|
|
||||||
deriving instance ToJSON Snack
|
|
||||||
deriving instance ToSchema Snack
|
|
||||||
|
|
||||||
-- server
|
-- server
|
||||||
|
|
||||||
server :: Connection -> Server JonAPI
|
server :: Connection -> Server JonAPI
|
||||||
@ -198,13 +173,10 @@ server conn =
|
|||||||
:<|> getOverviewItems
|
:<|> getOverviewItems
|
||||||
:<|> getActiveItems
|
:<|> getActiveItems
|
||||||
:<|> getLocations
|
:<|> getLocations
|
||||||
:<|> adjustInventory
|
|
||||||
:<|> transferInventory
|
:<|> transferInventory
|
||||||
:<|> disableItems
|
:<|> disableItems
|
||||||
:<|> createSnack
|
:<|> createSnack
|
||||||
:<|> getSnacksByItemId
|
|
||||||
:<|> updateSnack
|
:<|> updateSnack
|
||||||
:<|> deleteSnack
|
|
||||||
where
|
where
|
||||||
getUnsoundBarcodes :: GetUnsoundBarcodesP -> Handler [UnsoundBarcodeDTO]
|
getUnsoundBarcodes :: GetUnsoundBarcodesP -> Handler [UnsoundBarcodeDTO]
|
||||||
getUnsoundBarcodes params = do
|
getUnsoundBarcodes params = do
|
||||||
@ -227,10 +199,6 @@ server conn =
|
|||||||
getLocations = do
|
getLocations = do
|
||||||
liftIO $ Queries.runSelect conn Queries.locations
|
liftIO $ Queries.runSelect conn Queries.locations
|
||||||
|
|
||||||
adjustInventory params = do
|
|
||||||
liftIO $ Queries.runInserts conn [Queries.adjustInventory params.item params.amount params.description]
|
|
||||||
pure NoContent
|
|
||||||
|
|
||||||
transferInventory :: TransferInventoryP -> Handler NoContent
|
transferInventory :: TransferInventoryP -> Handler NoContent
|
||||||
transferInventory params = do
|
transferInventory params = do
|
||||||
liftIO $ Queries.runInserts conn $ map (\t -> Queries.transfer t.from t.to t.amount) params.transfers
|
liftIO $ Queries.runInserts conn $ map (\t -> Queries.transfer t.from t.to t.amount) params.transfers
|
||||||
@ -249,9 +217,6 @@ server conn =
|
|||||||
params.taxGroup
|
params.taxGroup
|
||||||
params.location
|
params.location
|
||||||
|
|
||||||
getSnacksByItemId params = do
|
|
||||||
liftIO $ Queries.runSelect conn $ Queries.getSnacksByItemId params.item
|
|
||||||
|
|
||||||
updateSnack params = do
|
updateSnack params = do
|
||||||
liftIO $ Queries.runFunction conn $ Queries.snackUpdate
|
liftIO $ Queries.runFunction conn $ Queries.snackUpdate
|
||||||
params.snack
|
params.snack
|
||||||
@ -260,14 +225,9 @@ server conn =
|
|||||||
params.price
|
params.price
|
||||||
params.taxGroup
|
params.taxGroup
|
||||||
|
|
||||||
deleteSnack params = do
|
|
||||||
liftIO $ Queries.runFunction conn $ Queries.snackDelete params.snack
|
|
||||||
pure NoContent
|
|
||||||
|
|
||||||
jonSwaggerDoc :: Swagger
|
jonSwaggerDoc :: Swagger
|
||||||
jonSwaggerDoc = toSwagger (Proxy :: Proxy JonAPI)
|
jonSwaggerDoc = toSwagger (Proxy :: Proxy JonAPI)
|
||||||
& info . title .~ "jon API"
|
& info . title .~ "jon API"
|
||||||
& info . version .~ "0.1.1"
|
& info . version .~ "0.1.1"
|
||||||
& applyTags [Tag "jon RPC" (Just "Crudely improvised Garfield API") Nothing]
|
-- & applyTags [Tag "jon RPC" (Just "Crudely improvised Garfield API") Nothing]
|
||||||
-- Doesn't work :(
|
& applyTagsFor (operationsOf $ toSwagger (Proxy :: Proxy SnackAPI)) [Tag "Snacks" Nothing Nothing]
|
||||||
-- & applyTagsFor (operationsOf $ toSwagger (Proxy :: Proxy SnackAPI)) [Tag "Snacks" Nothing Nothing]
|
|
||||||
|
@ -5,10 +5,11 @@
|
|||||||
<link rel="stylesheet" href="./jon.css">
|
<link rel="stylesheet" href="./jon.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<div class="jon-elm"></div>
|
||||||
<!-- Compiled by Elm -->
|
<!-- Compiled by Elm -->
|
||||||
<script src="./jon.js"></script>
|
<script src="./jon.js"></script>
|
||||||
<script>
|
<script>
|
||||||
Elm.Main.init();
|
Elm.Main.init({ node: document.querySelector('.jon-elm') });
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
html {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button, input {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
|
|
||||||
th, td {
|
|
||||||
padding: .2em .4em;
|
|
||||||
}
|
|
||||||
|
|
||||||
tr:not(:first-child):hover, tbody tr:hover {
|
|
||||||
background-color: lightblue;
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user