Makes editing work correctly for non-admins

This commit is contained in:
2024-04-03 20:08:46 -07:00
parent d2ed08f6f9
commit dfccbf35cd
7 changed files with 341 additions and 139 deletions

View File

@@ -1,13 +1,13 @@
(ns auto-ap.ssr.components.aside (ns auto-ap.ssr.components.aside
(:require [auto-ap.client-routes :as client-routes] (:require [auto-ap.client-routes :as client-routes]
[auto-ap.logging :as alog] [auto-ap.permissions :refer [can?]]
[auto-ap.routes.admin.clients :as ac-routes] [auto-ap.routes.admin.clients :as ac-routes]
[auto-ap.routes.admin.excel-invoices :as ei-routes] [auto-ap.routes.admin.excel-invoices :as ei-routes]
[auto-ap.routes.admin.import-batch :as ib-routes] [auto-ap.routes.admin.import-batch :as ib-routes]
[auto-ap.routes.outgoing-invoice :as oi-routes]
[auto-ap.routes.admin.transaction-rules :as transaction-rules] [auto-ap.routes.admin.transaction-rules :as transaction-rules]
[auto-ap.routes.admin.vendors :as v-routes] [auto-ap.routes.admin.vendors :as v-routes]
[auto-ap.routes.invoice :as invoice-route] [auto-ap.routes.invoice :as invoice-route]
[auto-ap.routes.outgoing-invoice :as oi-routes]
[auto-ap.routes.payments :as payment-routes] [auto-ap.routes.payments :as payment-routes]
[auto-ap.ssr-routes :as ssr-routes] [auto-ap.ssr-routes :as ssr-routes]
[auto-ap.ssr.hiccup-helper :as hh] [auto-ap.ssr.hiccup-helper :as hh]
@@ -131,53 +131,56 @@
:active? (= ::invoice-route/voided-page (:matched-route request)) :active? (= ::invoice-route/voided-page (:matched-route request))
:hx-boost "true"} :hx-boost "true"}
"Voided") "Voided")
(menu-button- {:href (bidi/path-for ssr-routes/only-routes (menu-button- {:href (bidi/path-for client-routes/routes
::oi-routes/new) :import-invoices)} "Import")
:active? (= ::oi-routes/new (:matched-route request)) #_(menu-button- {:href (bidi/path-for ssr-routes/only-routes
:hx-boost "true"} ::oi-routes/new)
"Create outgoing")) :active? (= ::oi-routes/new (:matched-route request))
:hx-boost "true"}
"Create outgoing"))
(menu-button- {:icon svg/receipt-register-1 (when
(can? (:identity request) {:subject :sales :activity :read})
(list
(menu-button- {:icon svg/receipt-register-1
"@click.prevent" "if (selected == 'sales') {selected = null } else { selected = 'sales'} "} "@click.prevent" "if (selected == 'sales') {selected = null } else { selected = 'sales'} "}
"Sales") "Sales")
(sub-menu- {:selector "sales" (sub-menu- {:selector "sales"
:active? (= "sales" selected)} :active? (= "sales" selected)}
(menu-button- {:href (str (bidi/path-for ssr-routes/only-routes (menu-button- {:href (str (bidi/path-for ssr-routes/only-routes
:pos-sales) :pos-sales)
"?date-range=week") "?date-range=week")
:active? (= :pos-sales (:matched-route request)) :active? (= :pos-sales (:matched-route request))
:hx-boost "true"} :hx-boost "true"}
"Sales") "Sales")
(menu-button- {:href (str (bidi/path-for ssr-routes/only-routes (menu-button- {:href (str (bidi/path-for ssr-routes/only-routes
:pos-expected-deposits) :pos-expected-deposits)
"?date-range=week") "?date-range=week")
:active? (= :pos-expected-deposits (:matched-route request)) :active? (= :pos-expected-deposits (:matched-route request))
:hx-boost "true"} :hx-boost "true"}
"Expected Deposits") "Expected Deposits")
(menu-button- {:href (str (bidi/path-for ssr-routes/only-routes (menu-button- {:href (str (bidi/path-for ssr-routes/only-routes
:pos-tenders) :pos-tenders)
"?date-range=week") "?date-range=week")
:active? (= :pos-tenders (:matched-route request)) :active? (= :pos-tenders (:matched-route request))
:hx-boost "true"} :hx-boost "true"}
"Tenders") "Tenders")
(menu-button- {:href (str (bidi/path-for ssr-routes/only-routes (menu-button- {:href (str (bidi/path-for ssr-routes/only-routes
:pos-refunds) :pos-refunds)
"?date-range=week") "?date-range=week")
:active? (= :pos-refunds (:matched-route request)) :active? (= :pos-refunds (:matched-route request))
:hx-boost "true"} :hx-boost "true"}
"Refunds")
(menu-button- {:href (str (bidi/path-for ssr-routes/only-routes
:pos-cash-drawer-shifts)
"?date-range=week")
:active? (= :cash-drawer-shifts (:matched-route request))
:hx-boost "true"}
"Cash drawer shifts"))))
"Refunds")
(menu-button- {:href (str (bidi/path-for ssr-routes/only-routes
:pos-cash-drawer-shifts)
"?date-range=week")
:active? (= :cash-drawer-shifts (:matched-route request))
:hx-boost "true"}
"Cash drawer shifts"))
;; TODO make specific routes for categories
;; TODO auto-apen sub menus
(menu-button- {"@click.prevent" "if (selected == 'payments') {selected = null } else { selected = 'payments'} " (menu-button- {"@click.prevent" "if (selected == 'payments') {selected = null } else { selected = 'payments'} "
:icon svg/payments} :icon svg/payments}
"Payments") "Payments")
@@ -224,26 +227,35 @@
:requires-feedback-transactions)} "Client Review") :requires-feedback-transactions)} "Client Review")
(menu-button- {:href (bidi/path-for client-routes/routes (menu-button- {:href (bidi/path-for client-routes/routes
:approved-transactions)} "Approved") :approved-transactions)} "Approved")
(menu-button- {:href (bidi/path-for ssr-routes/only-routes (when (can? (:identity request)
:transaction-insights)} "Insights"))] {:subject :transaction :activity :insights})
(menu-button- {:href (bidi/path-for ssr-routes/only-routes
:transaction-insights)} "Insights")))]
(menu-button- {"@click.prevent" "if (selected == 'ledger') {selected = null } else { selected = 'ledger'} "
:icon svg/receipt} (when (can? (:identity request)
"Ledger") {:subject :ledger-page})
(sub-menu- {:selector "ledger" (list
:active? (= "ledger" selected)} (menu-button- {"@click.prevent" "if (selected == 'ledger') {selected = null } else { selected = 'ledger'} "
(menu-button- {:href (bidi/path-for client-routes/routes :icon svg/receipt}
:ledger)} "Register") "Ledger")
(menu-button- {:href (bidi/path-for client-routes/routes (sub-menu- {:selector "ledger"
:profit-and-loss)} "Profit & Loss") :active? (= "ledger" selected)}
(menu-button- {:href (bidi/path-for client-routes/routes (menu-button- {:href (bidi/path-for client-routes/routes
:profit-and-loss-detail)} "Profit & Loss Detail") :ledger)} "Register")
(menu-button- {:href (bidi/path-for client-routes/routes (menu-button- {:href (bidi/path-for client-routes/routes
:cash-flows)} "Cash Flows") :profit-and-loss)} "Profit & Loss")
(menu-button- {:href (bidi/path-for client-routes/routes (menu-button- {:href (bidi/path-for client-routes/routes
:balance-sheet)} "Balance Sheet") :profit-and-loss-detail)} "Profit & Loss Detail")
(menu-button- {:href (bidi/path-for client-routes/routes (menu-button- {:href (bidi/path-for client-routes/routes
:external-import-ledger)} "External Ledger Import"))])) :cash-flows)} "Cash Flows")
(menu-button- {:href (bidi/path-for client-routes/routes
:balance-sheet)} "Balance Sheet")
(when (can? (:identity request)
{:subject :ledger
:activity :import})
(menu-button- {:href (bidi/path-for client-routes/routes
:external-import-ledger)} "External Ledger Import")))))]))
(defn company-aside-nav- [_] (defn company-aside-nav- [_]
@@ -330,7 +342,7 @@
:active? (= :admin-rules matched-route) :active? (= :admin-rules matched-route)
:href (bidi/path-for ssr-routes/only-routes :href (bidi/path-for ssr-routes/only-routes
:admin-history) :admin-history)
:hx-boost "true" } :hx-boost "true"}
"History")] "History")]
[:li [:li
@@ -342,9 +354,10 @@
"Background Jobs")] "Background Jobs")]
(menu-button- {:icon svg/arrow-in (when (can? (:identity request) {:subject :invoice :activity :import})
"@click.prevent" "if (selected == 'import') {selected = null } else { selected = 'import'} "} (menu-button- {:icon svg/arrow-in
"Import") "@click.prevent" "if (selected == 'import') {selected = null } else { selected = 'import'} "}
"Import"))
(sub-menu- {:selector "import"} (sub-menu- {:selector "import"}
(menu-button- {:href (bidi/path-for ssr-routes/only-routes (menu-button- {:href (bidi/path-for ssr-routes/only-routes
@@ -356,10 +369,10 @@
(menu-button- {:href (bidi/path-for ssr-routes/only-routes (menu-button- {:href (bidi/path-for ssr-routes/only-routes
::ib-routes/page) ::ib-routes/page)
:active? (= ::ib-routes/page matched-route) :active? (= ::ib-routes/page matched-route)
:hx-boost true} :hx-boost true}
"Import Batches") "Import Batches")
(menu-button- {:href (bidi/path-for ssr-routes/only-routes (menu-button- {:href (bidi/path-for ssr-routes/only-routes
:admin-ezcater-xls) :admin-ezcater-xls)
:active? (= :admin-ezcater-xls matched-route) :active? (= :admin-ezcater-xls matched-route)
:hx-boost "true"} :hx-boost "true"}
"EZCater XLS Import"))]) "EZCater XLS Import"))])

View File

@@ -20,8 +20,7 @@
:hx-swap "outerHTML" :hx-swap "outerHTML"
:hx-target-400 "#form-errors .error-content" :hx-target-400 "#form-errors .error-content"
:hx-trigger "submit" :hx-trigger "submit"
:hx-target "this" :hx-target "this" })
"x-trap" "true"})
(defprotocol ModalWizardStep (defprotocol ModalWizardStep
(step-key [this]) (step-key [this])

View File

@@ -5,6 +5,7 @@
[auto-ap.datomic.invoices :as d-invoices] [auto-ap.datomic.invoices :as d-invoices]
[auto-ap.graphql.utils :refer [assert-can-see-client [auto-ap.graphql.utils :refer [assert-can-see-client
assert-not-locked exception->4xx]] assert-not-locked exception->4xx]]
[auto-ap.logging :as alog]
[auto-ap.routes.invoice :as route] [auto-ap.routes.invoice :as route]
[auto-ap.routes.utils [auto-ap.routes.utils
:refer [wrap-client-redirect-unauthenticated]] :refer [wrap-client-redirect-unauthenticated]]
@@ -69,8 +70,6 @@
(defn check-vendor-default-account [vendor-id] (defn check-vendor-default-account [vendor-id]
(some? (:vendor/default-account (get-vendor vendor-id)))) (some? (:vendor/default-account (get-vendor vendor-id))))
;; TODO negative expense accounts for negative invoices?
(def new-form-schema (def new-form-schema
[:map [:map
[:db/id {:optional true} [:maybe entity-id]] [:db/id {:optional true} [:maybe entity-id]]
@@ -97,7 +96,7 @@
[:fn {:error/message "Not an allowed account."} [:fn {:error/message "Not an allowed account."}
check-allowance]]] check-allowance]]]
[:invoice-expense-account/location :string] [:invoice-expense-account/location :string]
[:invoice-expense-account/amount [:double {:min 0}]]] [:invoice-expense-account/amount :double]]
[:fn {:error/fn (fn [r x] (:type r)) [:fn {:error/fn (fn [r x] (:type r))
:error/path [:invoice-expense-account/location]} check-invoice-expense-account-location]]]]]) :error/path [:invoice-expense-account/location]} check-invoice-expense-account-location]]]]])
@@ -557,36 +556,67 @@
(when-not (dollars= total expense-account-total) (when-not (dollars= total expense-account-total)
(form-validation-error (str "Expense account total (" expense-account-total ") does not equal invoice total (" total ")"))))) (form-validation-error (str "Expense account total (" expense-account-total ") does not equal invoice total (" total ")")))))
(defn maybe-spread-locations [invoice]
(let [valid-locations (pull-attr (dc/db conn) :client/locations (:invoice/client invoice))] (defn- calculate-spread
(with-precision 2 "Helper function to calculate the amount to be assigned to each location"
(let [expense-accounts (vec (mapcat [shared-amount total-locations]
(fn [ea] (let [base-amount (int (/ shared-amount total-locations))
(let [cents-to-distribute (int (Math/round (Math/abs (* (:invoice-expense-account/amount ea) 100))))] remainder (- shared-amount (* base-amount total-locations))]
(if (= "Shared" (:invoice-expense-account/location ea)) {:base-amount base-amount
(->> valid-locations :remainder remainder}))
(map
(fn [cents location]
(assoc ea (defn- spread-expense-account
:db/id (random-tempid) "Spreads the expense account amount across the given locations"
:invoice-expense-account/amount (* 0.01 cents) [locations expense-account]
:invoice-expense-account/location location)) (if (= "Shared" (:invoice-expense-account/location expense-account))
(rm/spread-cents cents-to-distribute (count valid-locations)))) (let [{:keys [base-amount remainder]} (calculate-spread (:invoice-expense-account/amount expense-account) (count locations))]
[ea]))) (map-indexed (fn [idx _]
(:invoice/expense-accounts invoice))) (assoc expense-account
expense-accounts (mapv :invoice-expense-account/amount (+ base-amount (if (< idx remainder) 1 0))
(fn [a] :invoice-expense-account/location (nth locations idx)))
(update a :invoice-expense-account/amount locations))
#(with-precision 2 [expense-account]))
(double (.setScale (bigdec %) 2 java.math.RoundingMode/HALF_UP)))))
expense-accounts) (defn $->cents [x]
leftover (with-precision 2 (.round (bigdec (- (Math/abs (:invoice/total invoice)) (int
(Math/abs (reduce + 0.0 (map #(:invoice-expense-account/amount %) expense-accounts))))) (let [result (* 100M (bigdec x))]
*math-context*)) (.setScale result 0 java.math.BigDecimal/ROUND_HALF_UP))))
accounts (if (seq expense-accounts)
(update-in expense-accounts [(dec (count expense-accounts)) :invoice-expense-account/amount] #(+ % (double leftover))) (defn cents->$ [x]
[])] (double
(assoc invoice :invoice/expense-accounts (into [] accounts)))))) (let [result (* 0.01M (bigdec x))]
(.setScale result 2 java.math.BigDecimal/ROUND_HALF_UP))))
(defn- apply-total-delta-to-account [invoice-total eas]
(when (seq eas)
(let [leftover (- invoice-total (reduce + 0 (map :invoice-expense-account/amount eas)))
leftover-beyond-a-single-cent? (or (< leftover -1)
(> leftover 1))
leftover (if leftover-beyond-a-single-cent?
0
leftover)
[first-eas & rest] eas]
(cons
(update first-eas :invoice-expense-account/amount #(+ % leftover))
rest))))
(defn maybe-spread-locations
"Converts any expense account for a \"Shared\" location into a separate expense account for all valid locations for that client"
([invoice]
(maybe-spread-locations invoice (pull-attr (dc/db conn) :client/locations (:invoice/client invoice))))
([invoice locations]
(update-in invoice
[:invoice/expense-accounts]
(fn [expense-accounts]
(->> expense-accounts
(map (fn [ea] (update ea :invoice-expense-account/amount $->cents)))
(mapcat (partial spread-expense-account locations))
(apply-total-delta-to-account ($->cents (:invoice/total invoice)))
(map (fn [ea] (update ea :invoice-expense-account/amount cents->$))))))))
(defrecord NewWizard2 [_ current-step] (defrecord NewWizard2 [_ current-step]
mm/LinearModalWizard mm/LinearModalWizard
@@ -622,9 +652,27 @@
new-form-schema) new-form-schema)
(submit [this {:keys [multi-form-state request-method identity] :as request}] (submit [this {:keys [multi-form-state request-method identity] :as request}]
(let [invoice (:snapshot multi-form-state) (let [invoice (:snapshot multi-form-state)
_ (alog/peek invoice)
extant? (:db/id invoice) extant? (:db/id invoice)
client-id (->db-id (:invoice/client invoice)) client-id (->db-id (:invoice/client invoice))
vendor-id (->db-id (:invoice/vendor invoice)) vendor-id (->db-id (:invoice/vendor invoice))
paid-amount (if-let [outstanding-balance
(and extant?
(-
(pull-attr (dc/db conn)
:invoice/total
(:db/id invoice))
(pull-attr (dc/db conn)
:invoice/outstanding-balance
(:db/id invoice))))]
outstanding-balance
0.0)
outstanding-balance (- (or
(:invoice/total (:step-params multi-form-state))
(:invoice/total (:snapshot multi-form-state)))
paid-amount)
transaction [:upsert-invoice (-> multi-form-state transaction [:upsert-invoice (-> multi-form-state
:snapshot :snapshot
(assoc :db/id (or (:db/id invoice) "invoice")) (assoc :db/id (or (:db/id invoice) "invoice"))
@@ -638,11 +686,11 @@
:invoice-expense-account/amount (or (:invoice/total (:step-params multi-form-state)) :invoice-expense-account/amount (or (:invoice/total (:step-params multi-form-state))
(:invoice/total (:snapshot multi-form-state)))}])) (:invoice/total (:snapshot multi-form-state)))}]))
(assoc (assoc
:invoice/outstanding-balance (or :invoice/outstanding-balance outstanding-balance
(:invoice/total (:step-params multi-form-state))
(:invoice/total (:snapshot multi-form-state)))
:invoice/import-status :import-status/imported :invoice/import-status :import-status/imported
:invoice/status :invoice-status/unpaid) :invoice/status (if (dollars= 0.0 outstanding-balance)
:invoice-status/paid
:invoice-status/unpaid))
(maybe-spread-locations) (maybe-spread-locations)
(update :invoice/date coerce/to-date) (update :invoice/date coerce/to-date)
(update :invoice/due coerce/to-date) (update :invoice/due coerce/to-date)

View File

@@ -401,17 +401,19 @@
(when (can? (:identity request) {:subject :invoice :activity :create}) (when (can? (:identity request) {:subject :invoice :activity :create})
(com/button {:hx-get (bidi/path-for ssr-routes/only-routes ::route/new-wizard)} (com/button {:hx-get (bidi/path-for ssr-routes/only-routes ::route/new-wizard)}
"New invoice"))]) "New invoice"))])
:row-buttons (fn [_ entity] :row-buttons (fn [request entity]
[(when (= :invoice-status/unpaid (:invoice/status entity)) [(when (and (= :invoice-status/unpaid (:invoice/status entity))
(can? (:identity request) {:subject :invoice :activity :delete}))
(com/icon-button {:hx-delete (bidi/path-for ssr-routes/only-routes (com/icon-button {:hx-delete (bidi/path-for ssr-routes/only-routes
::route/delete ::route/delete
:db/id (:db/id entity)) :db/id (:db/id entity))
:hx-confirm "Are you sure you want to void this invoice?"} :hx-confirm "Are you sure you want to void this invoice?"}
svg/trash)) svg/trash))
(com/icon-button {:hx-put (bidi/path-for ssr-routes/only-routes (when (can? (:identity request) {:subject :invoice :activity :edit})
::route/edit-wizard (com/icon-button {:hx-put (bidi/path-for ssr-routes/only-routes
:db/id (:db/id entity)) } ::route/edit-wizard
svg/pencil)]) :db/id (:db/id entity))}
svg/pencil))])
:breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes ::route/page)} :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes ::route/page)}
"Invoices"]] "Invoices"]]
:title (fn [r] :title (fn [r]

View File

@@ -19,6 +19,7 @@
[com.brunobonacci.mulog.buffer :as rb] [com.brunobonacci.mulog.buffer :as rb]
[config.core :refer [env]] [config.core :refer [env]]
[datomic.api :as dc] [datomic.api :as dc]
[puget.printer :as puget]
[datomic.api :as d] [datomic.api :as d]
[figwheel.main.api] [figwheel.main.api]
[hawk.core] [hawk.core]
@@ -27,30 +28,32 @@
(:import (org.apache.commons.io.input BOMInputStream) (:import (org.apache.commons.io.input BOMInputStream)
[org.eclipse.jetty.server.handler.gzip GzipHandler])) [org.eclipse.jetty.server.handler.gzip GzipHandler]))
(defn println-event [item] (defn println-event [item]
(printf "%s: %s - %s:%s by %s\n" #_(printf "%s: %s - %s:%s by %s\n"
(str (c/to-date-time (:mulog/timestamp item))) (str (c/to-date-time (:mulog/timestamp item)))
(:mulog/namespace item) (:mulog/event-name item) (:mulog/namespace item) (:mulog/event-name item)
(if (:mulog/duration item) (if (:mulog/duration item)
(str " " (int (/ (:mulog/duration item) 1000000)) "ms") (str " " (int (/ (:mulog/duration item) 1000000)) "ms")
"") "")
(:user-name item)) (:user-name item))
(println (reduce #_(println (reduce
(fn [acc [k v]] (fn [acc [k v]]
(assoc acc k v)) (assoc acc k v))
{} {}
(dissoc (dissoc
item item
:user))) :user)))
#_(puget/cprint (reduce (when (= :auto-ap.logging/peek (:mulog/event-name item))
(fn [acc [k v]] (println "\u001B[31mTEST")
(assoc acc k v)) )
{} (puget/cprint (reduce
(dissoc (fn [acc [k v]]
item (assoc acc k v))
:user)) {}
{:seq-limit 10}) (dissoc
item
:user))
{:seq-limit 10})
(println)) (println))
@@ -354,7 +357,7 @@
`src` or `resources`." `src` or `resources`."
[] []
(println "starting auto reset") (println "starting auto reset")
(hawk.core/watch! [{:paths ["src/"] (hawk.core/watch! [{:paths ["src/" "test/"]
:handler auto-reset-handler}])) :handler auto-reset-handler}]))

View File

@@ -25,6 +25,24 @@
(#{:invoice-page :payment-page :my-company-page :transaction-page :ledger-page} subject) (#{:invoice-page :payment-page :my-company-page :transaction-page :ledger-page} subject)
true true
(= [:invoice :import] [subject activity])
true
(= [:invoice :create] [subject activity])
true
(= [:invoice :pay] [subject activity])
true
(= [:invoice :edit] [subject activity])
true
(= [:invoice :delete] [subject activity])
true
(= [:sales :read] [subject activity])
true
(= [:vendor :create] [subject activity]) (= [:vendor :create] [subject activity])
true true
@@ -44,6 +62,18 @@
(= [:vendor :edit] [subject activity]) (= [:vendor :edit] [subject activity])
true true
(= [:invoice :create] [subject activity])
true
(= [:invoice :pay] [subject activity])
true
(= [:invoice :edit] [subject activity])
true
(= [:invoice :delete] [subject activity])
true
:else false) :else false)
(#{:user-role/read-only "read-only"} role) (#{:user-role/read-only "read-only"} role)
@@ -66,6 +96,19 @@
(= [:signature :edit] [subject activity]) (= [:signature :edit] [subject activity])
true true
(= [:invoice :create] [subject activity])
true
(= [:invoice :pay] [subject activity])
true
(= [:invoice :edit] [subject activity])
true
(= [:invoice :delete] [subject activity])
true
:else false) :else false)
:else :else

View File

@@ -0,0 +1,94 @@
(ns auto-ap.ssr.invoice.new-invoice-wizard-test
(:require [clojure.test :refer [deftest testing is]]
[auto-ap.ssr.invoice.new-invoice-wizard :as sut9]))
(deftest maybe-spread-locations-test
(testing "Shared amount correctly spread across multiple locations"
(let [invoice {:invoice/expense-accounts [{:invoice-expense-account/amount 100.0
:invoice-expense-account/location "Shared"}]
:invoice/total 100.0}
result (sut9/maybe-spread-locations invoice ["Location 1"
"Location 2"])]
(is (=
[{:invoice-expense-account/amount 50.0
:invoice-expense-account/location "Location 1"}
{:invoice-expense-account/amount 50.0
:invoice-expense-account/location "Location 2"}]
(map #(select-keys % #{:invoice-expense-account/amount :invoice-expense-account/location}) (:invoice/expense-accounts result))))))
(testing "Shared amount correctly spread with negative amounts"
(let [invoice {:invoice/expense-accounts [{:invoice-expense-account/amount -100.0
:invoice-expense-account/location "Shared"}]
:invoice/total -100.0}
result (sut9/maybe-spread-locations invoice ["Location 1"
"Location 2"])]
(is (=
[{:invoice-expense-account/amount -50.0
:invoice-expense-account/location "Location 1"}
{:invoice-expense-account/amount -50.0
:invoice-expense-account/location "Location 2"}]
(map #(select-keys % #{:invoice-expense-account/amount :invoice-expense-account/location}) (:invoice/expense-accounts result))))))
(testing "Shared amount correctly spread with leftovers"
(let [invoice {:invoice/expense-accounts [{:invoice-expense-account/amount 100.0
:invoice-expense-account/location "Shared"}]
:invoice/total 100.0}
result (sut9/maybe-spread-locations invoice ["Location 1"
"Location 2"
"Location 3"])]
(is (=
[{:invoice-expense-account/amount 33.34
:invoice-expense-account/location "Location 1"}
{:invoice-expense-account/amount 33.33
:invoice-expense-account/location "Location 2"}
{:invoice-expense-account/amount 33.33
:invoice-expense-account/location "Location 3"}]
(map #(select-keys % #{:invoice-expense-account/amount :invoice-expense-account/location}) (:invoice/expense-accounts result))))))
(testing "Shared amount correctly spread with leftovers in negatives"
(let [invoice {:invoice/expense-accounts [{:invoice-expense-account/amount -33.33333333333333
:invoice-expense-account/location "A"}
{:invoice-expense-account/amount -33.33333333333333
:invoice-expense-account/location "B"}
{:invoice-expense-account/amount -33.33333333333333
:invoice-expense-account/location "C"}]
:invoice/total -100.0}
result (sut9/maybe-spread-locations invoice ["Location 1"])]
(is (= [{:invoice-expense-account/amount -33.34
:invoice-expense-account/location "A"}
{:invoice-expense-account/amount -33.33
:invoice-expense-account/location "B"}
{:invoice-expense-account/amount -33.33
:invoice-expense-account/location "C"}]
(map #(select-keys % #{:invoice-expense-account/amount :invoice-expense-account/location}) (:invoice/expense-accounts result))))))
(testing "Shared amount correctly spread with negative amounts and leftovers"
(let [invoice {:invoice/expense-accounts [{:invoice-expense-account/amount -101.33
:invoice-expense-account/location "Shared"}]
:invoice/total -101.33}
result (sut9/maybe-spread-locations invoice ["Location 1"
"Location 2"])]
(is (=
[{:invoice-expense-account/amount -50.67
:invoice-expense-account/location "Location 1"}
{:invoice-expense-account/amount -50.66
:invoice-expense-account/location "Location 2"}]
(map #(select-keys % #{:invoice-expense-account/amount :invoice-expense-account/location}) (:invoice/expense-accounts result))))))
(testing "Leftovers should not exceed a single cent"
(let [invoice {:invoice/expense-accounts [{:invoice-expense-account/amount -100
:invoice-expense-account/location "Shared"}
{:invoice-expense-account/amount -5
:invoice-expense-account/location "Shared"}]
:invoice/total -101}
result (sut8/maybe-spread-locations invoice ["Location 1" ])]
(is (=
[{:invoice-expense-account/amount -100.0
:invoice-expense-account/location "Location 1"}
{:invoice-expense-account/amount -5.0
:invoice-expense-account/location "Location 1"}]
(map #(select-keys % #{:invoice-expense-account/amount :invoice-expense-account/location}) (:invoice/expense-accounts result)))))))