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

@@ -5,6 +5,7 @@
[auto-ap.datomic.invoices :as d-invoices]
[auto-ap.graphql.utils :refer [assert-can-see-client
assert-not-locked exception->4xx]]
[auto-ap.logging :as alog]
[auto-ap.routes.invoice :as route]
[auto-ap.routes.utils
:refer [wrap-client-redirect-unauthenticated]]
@@ -69,8 +70,6 @@
(defn check-vendor-default-account [vendor-id]
(some? (:vendor/default-account (get-vendor vendor-id))))
;; TODO negative expense accounts for negative invoices?
(def new-form-schema
[:map
[:db/id {:optional true} [:maybe entity-id]]
@@ -97,7 +96,7 @@
[:fn {:error/message "Not an allowed account."}
check-allowance]]]
[: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))
:error/path [:invoice-expense-account/location]} check-invoice-expense-account-location]]]]])
@@ -557,36 +556,67 @@
(when-not (dollars= total expense-account-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))]
(with-precision 2
(let [expense-accounts (vec (mapcat
(fn [ea]
(let [cents-to-distribute (int (Math/round (Math/abs (* (:invoice-expense-account/amount ea) 100))))]
(if (= "Shared" (:invoice-expense-account/location ea))
(->> valid-locations
(map
(fn [cents location]
(assoc ea
:db/id (random-tempid)
:invoice-expense-account/amount (* 0.01 cents)
:invoice-expense-account/location location))
(rm/spread-cents cents-to-distribute (count valid-locations))))
[ea])))
(:invoice/expense-accounts invoice)))
expense-accounts (mapv
(fn [a]
(update a :invoice-expense-account/amount
#(with-precision 2
(double (.setScale (bigdec %) 2 java.math.RoundingMode/HALF_UP)))))
expense-accounts)
leftover (with-precision 2 (.round (bigdec (- (Math/abs (:invoice/total invoice))
(Math/abs (reduce + 0.0 (map #(:invoice-expense-account/amount %) expense-accounts)))))
*math-context*))
accounts (if (seq expense-accounts)
(update-in expense-accounts [(dec (count expense-accounts)) :invoice-expense-account/amount] #(+ % (double leftover)))
[])]
(assoc invoice :invoice/expense-accounts (into [] accounts))))))
(defn- calculate-spread
"Helper function to calculate the amount to be assigned to each location"
[shared-amount total-locations]
(let [base-amount (int (/ shared-amount total-locations))
remainder (- shared-amount (* base-amount total-locations))]
{:base-amount base-amount
:remainder remainder}))
(defn- spread-expense-account
"Spreads the expense account amount across the given locations"
[locations expense-account]
(if (= "Shared" (:invoice-expense-account/location expense-account))
(let [{:keys [base-amount remainder]} (calculate-spread (:invoice-expense-account/amount expense-account) (count locations))]
(map-indexed (fn [idx _]
(assoc expense-account
:invoice-expense-account/amount (+ base-amount (if (< idx remainder) 1 0))
:invoice-expense-account/location (nth locations idx)))
locations))
[expense-account]))
(defn $->cents [x]
(int
(let [result (* 100M (bigdec x))]
(.setScale result 0 java.math.BigDecimal/ROUND_HALF_UP))))
(defn cents->$ [x]
(double
(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]
mm/LinearModalWizard
@@ -622,9 +652,27 @@
new-form-schema)
(submit [this {:keys [multi-form-state request-method identity] :as request}]
(let [invoice (:snapshot multi-form-state)
_ (alog/peek invoice)
extant? (:db/id invoice)
client-id (->db-id (:invoice/client 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
:snapshot
(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/total (:snapshot multi-form-state)))}]))
(assoc
:invoice/outstanding-balance (or
(:invoice/total (:step-params multi-form-state))
(:invoice/total (:snapshot multi-form-state)))
:invoice/outstanding-balance outstanding-balance
: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)
(update :invoice/date coerce/to-date)
(update :invoice/due coerce/to-date)