Makes editing work correctly for non-admins
This commit is contained in:
@@ -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"))])
|
||||||
|
|||||||
@@ -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])
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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}]))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
94
test/clj/auto_ap/ssr/invoice/new_invoice_wizard_test.clj
Normal file
94
test/clj/auto_ap/ssr/invoice/new_invoice_wizard_test.clj
Normal 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)))))))
|
||||||
|
|
||||||
Reference in New Issue
Block a user