Improvements for transaction page
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -25,6 +25,5 @@
|
|||||||
|
|
||||||
(alog/info ::closed :count (count invoices-to-close))))
|
(alog/info ::closed :count (count invoices-to-close))))
|
||||||
|
|
||||||
|
|
||||||
(defn -main [& _]
|
(defn -main [& _]
|
||||||
(execute "close-auto-invoices" close-auto-invoices))
|
(execute "close-auto-invoices" close-auto-invoices))
|
||||||
|
|||||||
@@ -135,7 +135,7 @@
|
|||||||
:fetch-page fetch-page
|
:fetch-page fetch-page
|
||||||
:page-specific-nav filters
|
:page-specific-nav filters
|
||||||
:row-buttons (fn [_ entity]
|
:row-buttons (fn [_ entity]
|
||||||
[(com/a-icon-button {:href (hu/url (bidi/path-for ssr-routes/only-routes ::transaction-routes/all-page)
|
[(com/a-icon-button {:href (hu/url (bidi/path-for ssr-routes/only-routes ::transaction-routes/page)
|
||||||
{:import-batch-id (:db/id entity)})
|
{:import-batch-id (:db/id entity)})
|
||||||
:hx-boost true}
|
:hx-boost true}
|
||||||
svg/external-link)])
|
svg/external-link)])
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
(atime/unparse-local atime/standard-time)
|
(atime/unparse-local atime/standard-time)
|
||||||
(#(str "Synced " %)))]
|
(#(str "Synced " %)))]
|
||||||
|
|
||||||
(when-let [n (cond (-> b :bank-account/intuit-bank-account)
|
#_(when-let [n (cond (-> b :bank-account/intuit-bank-account)
|
||||||
"Intuit"
|
"Intuit"
|
||||||
(-> b :bank-account/yodlee-account)
|
(-> b :bank-account/yodlee-account)
|
||||||
"Yodlee"
|
"Yodlee"
|
||||||
|
|||||||
@@ -171,6 +171,10 @@
|
|||||||
(merge-query {:query {:in ['?vendor-id]
|
(merge-query {:query {:in ['?vendor-id]
|
||||||
:where ['[?e :transaction/vendor ?vendor-id]]}
|
:where ['[?e :transaction/vendor ?vendor-id]]}
|
||||||
:args [(:db/id (:vendor args))]})
|
:args [(:db/id (:vendor args))]})
|
||||||
|
(:import-batch-id args)
|
||||||
|
(merge-query {:query {:in ['?import-batch-id]
|
||||||
|
:where ['[?import-batch-id :import-batch/entry ?e]]}
|
||||||
|
:args [(:import-batch-id args)]})
|
||||||
|
|
||||||
(:status route-params)
|
(:status route-params)
|
||||||
(merge-query {:query {:in ['?status]
|
(merge-query {:query {:in ['?status]
|
||||||
@@ -251,6 +255,7 @@
|
|||||||
[:amount-gte {:optional true} [:maybe :double]]
|
[:amount-gte {:optional true} [:maybe :double]]
|
||||||
[:amount-lte {:optional true} [:maybe :double]]
|
[:amount-lte {:optional true} [:maybe :double]]
|
||||||
[:client-id {:optional true} [:maybe entity-id]]
|
[:client-id {:optional true} [:maybe entity-id]]
|
||||||
|
[:import-batch-id {:optional true} [:maybe entity-id]]
|
||||||
[:description {:optional true} [:maybe [:string {:decode/string strip}]]]
|
[:description {:optional true} [:maybe [:string {:decode/string strip}]]]
|
||||||
[:vendor {:optional true :default nil} [:maybe [:entity-map {:pull [:db/id :vendor/name]}]]]
|
[:vendor {:optional true :default nil} [:maybe [:entity-map {:pull [:db/id :vendor/name]}]]]
|
||||||
[:bank-account {:optional true :default nil} [:maybe [:entity-map {:pull [:db/id :bank-account/numeric-code]}]]]
|
[:bank-account {:optional true :default nil} [:maybe [:entity-map {:pull [:db/id :bank-account/numeric-code]}]]]
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
exception->4xx]]
|
exception->4xx]]
|
||||||
[auto-ap.import.transactions :as i-transactions]
|
[auto-ap.import.transactions :as i-transactions]
|
||||||
[auto-ap.logging :as alog]
|
[auto-ap.logging :as alog]
|
||||||
|
[auto-ap.permissions :refer [wrap-must]]
|
||||||
[auto-ap.routes.payments :as payment-route]
|
[auto-ap.routes.payments :as payment-route]
|
||||||
[auto-ap.routes.transactions :as route]
|
[auto-ap.routes.transactions :as route]
|
||||||
[auto-ap.routes.utils
|
[auto-ap.routes.utils
|
||||||
@@ -26,11 +27,12 @@
|
|||||||
[auto-ap.ssr.utils
|
[auto-ap.ssr.utils
|
||||||
:refer [->db-id apply-middleware-to-all-handlers check-allowance
|
:refer [->db-id apply-middleware-to-all-handlers check-allowance
|
||||||
check-location-belongs entity-id form-validation-error
|
check-location-belongs entity-id form-validation-error
|
||||||
html-response modal-response ref->enum-schema strip wrap-entity
|
html-response modal-response ref->enum-schema strip temp-id
|
||||||
wrap-schema-enforce]]
|
wrap-entity wrap-schema-enforce]]
|
||||||
[auto-ap.time :as atime]
|
[auto-ap.time :as atime]
|
||||||
[bidi.bidi :as bidi]
|
[bidi.bidi :as bidi]
|
||||||
[clj-time.coerce :as coerce]
|
[clj-time.coerce :as coerce]
|
||||||
|
[clojure.edn :as edn]
|
||||||
[datomic.api :as dc]
|
[datomic.api :as dc]
|
||||||
[hiccup.util :as hu]
|
[hiccup.util :as hu]
|
||||||
[iol-ion.query :refer [dollars=]]
|
[iol-ion.query :refer [dollars=]]
|
||||||
@@ -72,7 +74,7 @@
|
|||||||
[:vector {:coerce? true}
|
[:vector {:coerce? true}
|
||||||
[:and
|
[:and
|
||||||
[:map
|
[:map
|
||||||
[:db/id {:optional true} [:maybe entity-id]]
|
[:db/id {:optional true} [:maybe [:or temp-id entity-id]]]
|
||||||
[:transaction-account/account [:and entity-id
|
[:transaction-account/account [:and entity-id
|
||||||
[:fn {:error/message "Not an allowed account."}
|
[:fn {:error/message "Not an allowed account."}
|
||||||
#(check-allowance % :account/default-allowance)]]]
|
#(check-allowance % :account/default-allowance)]]]
|
||||||
@@ -90,10 +92,11 @@
|
|||||||
[:transaction-id entity-id]]]
|
[:transaction-id entity-id]]]
|
||||||
[:link-unpaid-invoices [:map
|
[:link-unpaid-invoices [:map
|
||||||
|
|
||||||
[:unpaid-invoice-ids [:vector {:coerce? true} entity-id]]]]
|
[:unpaid-invoice-ids {:decode/string (fn [x] (edn/read-string x))}
|
||||||
|
[:vector {:coerce? true} entity-id]]]]
|
||||||
[:link-autopay-invoices [:map
|
[:link-autopay-invoices [:map
|
||||||
|
|
||||||
[:autopay-invoice-ids [:vector {:coerce? true} entity-id]]]]
|
[:autopay-invoice-ids {:decode/string (fn [x] (edn/read-string x))} [:vector {:coerce? true} entity-id]]]]
|
||||||
[:link-payment [:map
|
[:link-payment [:map
|
||||||
[:payment-id entity-id]]]
|
[:payment-id entity-id]]]
|
||||||
[:manual [:map
|
[:manual [:map
|
||||||
@@ -378,9 +381,6 @@
|
|||||||
|
|
||||||
|
|
||||||
;; Editable fields section
|
;; Editable fields section
|
||||||
[:div.mt-6
|
|
||||||
[:h3.text-lg.font-semibold.mb-4 "Editable Fields"]]
|
|
||||||
|
|
||||||
;; Vendor field
|
;; Vendor field
|
||||||
)
|
)
|
||||||
:footer
|
:footer
|
||||||
@@ -416,7 +416,6 @@
|
|||||||
(get-in request [:route-params :db/id]))
|
(get-in request [:route-params :db/id]))
|
||||||
tx (when tx-id (d-transactions/get-by-id tx-id))
|
tx (when tx-id (d-transactions/get-by-id tx-id))
|
||||||
client-id (-> request :entity :transaction/client :db/id)
|
client-id (-> request :entity :transaction/client :db/id)
|
||||||
_ (println "TRANSACTION" client-id)
|
|
||||||
matches-set (when (and tx client-id)
|
matches-set (when (and tx client-id)
|
||||||
(i-transactions/match-transaction-to-unfulfilled-autopayments
|
(i-transactions/match-transaction-to-unfulfilled-autopayments
|
||||||
(:transaction/amount tx)
|
(:transaction/amount tx)
|
||||||
@@ -432,28 +431,20 @@
|
|||||||
(if (seq invoice-matches)
|
(if (seq invoice-matches)
|
||||||
[:div
|
[:div
|
||||||
[:h3.text-lg.font-bold.mb-4 "Available Autopay Invoices"]
|
[:h3.text-lg.font-bold.mb-4 "Available Autopay Invoices"]
|
||||||
[:div {:hx-post (bidi/path-for ssr-routes/only-routes ::route/link-autopay-invoices)
|
(com/hidden {:name "action"
|
||||||
:hx-include "this"
|
:value "link-autopay-invoices"})
|
||||||
:hx-trigger "linkAutopayInvoices"
|
[:div.space-y-2
|
||||||
:hx-target "#modal-holder"
|
[:label.block.text-sm.font-medium.mb-1 "Select an autopay invoice to apply:"]
|
||||||
:hx-swap "outerHTML"}
|
(com/radio-card {:options (for [match-group invoice-matches]
|
||||||
(com/hidden {:name "action"
|
{:value (pr-str (map :db/id match-group))
|
||||||
:value "link-autopay-invoices"})
|
:content (doall (for [invoice match-group]
|
||||||
(com/hidden {:name "transaction-id"
|
[:div.ml-3
|
||||||
:value (get-in request [:multi-form-state :snapshot :db/id])
|
[:span.block.text-sm.font-medium (:invoice/invoice-number invoice)]
|
||||||
:form ""})
|
[:span.block.text-sm.text-gray-500 (-> invoice :invoice/vendor :vendor/name)]
|
||||||
[:div.space-y-2
|
[:span.block.text-sm.font-medium (format "$%.2f" (:invoice/outstanding-balance invoice))]]))})
|
||||||
[:label.block.text-sm.font-medium.mb-1 "Select an autopay invoice to apply:"]
|
:name (fc/with-field :autopay-invoice-ids (fc/field-name ))
|
||||||
(doall (for [match-group invoice-matches]
|
:width "w-full"})]
|
||||||
(doall (for [invoice match-group]
|
]
|
||||||
[:div.flex.items-center
|
|
||||||
[:input {:type :radio :value (:db/id invoice) :name "autopay-invoice-ids"}]
|
|
||||||
[:div.ml-3
|
|
||||||
[:span.block.text-sm.font-medium (:invoice/invoice-number invoice)]
|
|
||||||
[:span.block.text-sm.text-gray-500 (-> invoice :invoice/vendor :vendor/name)]
|
|
||||||
[:span.block.text-sm.font-medium (format "$%.2f" (:invoice/total invoice))]]]))))]
|
|
||||||
(com/a-button {"@click" "$dispatch('linkAutopayInvoices')"} "Match")
|
|
||||||
]]
|
|
||||||
[:div.text-center.py-4.text-gray-500 "No matching autopay invoices available for this transaction."])]))
|
[:div.text-center.py-4.text-gray-500 "No matching autopay invoices available for this transaction."])]))
|
||||||
|
|
||||||
(defn get-available-unpaid-invoices [request]
|
(defn get-available-unpaid-invoices [request]
|
||||||
@@ -477,30 +468,31 @@
|
|||||||
(if (seq invoice-matches)
|
(if (seq invoice-matches)
|
||||||
[:div
|
[:div
|
||||||
[:h3.text-lg.font-bold.mb-4 "Available Unpaid Invoices"]
|
[:h3.text-lg.font-bold.mb-4 "Available Unpaid Invoices"]
|
||||||
[:div {:hx-post (bidi/path-for ssr-routes/only-routes ::route/link-unpaid-invoices)
|
[:div #_{:hx-post (bidi/path-for ssr-routes/only-routes ::route/link-unpaid-invoices)
|
||||||
:hx-include "this"
|
:hx-include "this"
|
||||||
:hx-params "transaction-id, action, unpaid-invoice-ids"
|
:hx-params "transaction-id, action, unpaid-invoice-ids"
|
||||||
:hx-trigger "linkUnpaidInvoices"
|
:hx-trigger "linkUnpaidInvoices"
|
||||||
:hx-target "#modal-holder"
|
:hx-target "#modal-holder"
|
||||||
:hx-swap "outerHTML"}
|
:hx-swap "outerHTML"}
|
||||||
(com/hidden {:name "action"
|
(com/hidden {:name "action"
|
||||||
:value "link-unpaid-invoices"
|
:value "link-unpaid-invoices"
|
||||||
:form ""})
|
:form ""})
|
||||||
(com/hidden {:name "transaction-id"
|
#_(com/hidden {:name "transaction-id"
|
||||||
:value (get-in request [:multi-form-state :snapshot :db/id])
|
:value (get-in request [:multi-form-state :snapshot :db/id])
|
||||||
:form ""})
|
:form ""})
|
||||||
[:div.space-y-2
|
[:div.space-y-2
|
||||||
[:label.block.text-sm.font-medium.mb-1 "Select an unpaid invoice to apply:"]
|
[:label.block.text-sm.font-medium.mb-1 "Select an unpaid invoice to apply:"]
|
||||||
(doall (for [match-group invoice-matches]
|
(com/radio-card {:options (for [match-group invoice-matches]
|
||||||
(doall (for [invoice match-group]
|
{:value (pr-str (map :db/id match-group))
|
||||||
[:div.flex.items-center
|
:content (doall (for [invoice match-group]
|
||||||
[:input {:type :radio :value (:db/id invoice) :name "unpaid-invoice-ids"}]
|
[:div.ml-3
|
||||||
[:div.ml-3
|
[:span.block.text-sm.font-medium (:invoice/invoice-number invoice)]
|
||||||
[:span.block.text-sm.font-medium (:invoice/invoice-number invoice)]
|
[:span.block.text-sm.text-gray-500 (-> invoice :invoice/vendor :vendor/name)]
|
||||||
[:span.block.text-sm.text-gray-500 (-> invoice :invoice/vendor :vendor/name)]
|
[:span.block.text-sm.font-medium (format "$%.2f" (:invoice/outstanding-balance invoice))]]))})
|
||||||
[:span.block.text-sm.font-medium (format "$%.2f" (:invoice/outstanding-balance invoice))]]]))))]
|
:name (fc/with-field :unpaid-invoice-ids (fc/field-name ))
|
||||||
(com/a-button {:color :primary "@click" "$dispatch('linkUnpaidInvoices')"} "Link")
|
:width "w-full"})
|
||||||
]]
|
]
|
||||||
|
#_(com/a-button {:color :primary "@click" "$dispatch('linkUnpaidInvoices')"} "Link")]]
|
||||||
[:div.text-center.py-4.text-gray-500 "No matching unpaid invoices available for this transaction."])]))
|
[:div.text-center.py-4.text-gray-500 "No matching unpaid invoices available for this transaction."])]))
|
||||||
|
|
||||||
(defn get-available-rules [request]
|
(defn get-available-rules [request]
|
||||||
@@ -536,24 +528,20 @@
|
|||||||
(if (seq matching-rules)
|
(if (seq matching-rules)
|
||||||
[:div
|
[:div
|
||||||
[:h3.text-lg.font-bold.mb-4 "Matching Transaction Rules"]
|
[:h3.text-lg.font-bold.mb-4 "Matching Transaction Rules"]
|
||||||
[:div {:hx-post (bidi/path-for ssr-routes/only-routes ::route/apply-rule)
|
(fc/with-field :action
|
||||||
:hx-trigger "applyRule"
|
(com/hidden {:name (fc/field-name)
|
||||||
:hx-include "this"
|
:value "apply-rule"
|
||||||
:hx-target "#modal-holder"
|
:form ""}))
|
||||||
:hx-swap "outerHTML"}
|
[:div.space-y-2
|
||||||
(fc/with-field :action
|
[:label.block.text-sm.font-medium.mb-1 "Select a rule to apply:"]
|
||||||
(com/hidden {:name (fc/field-name)
|
(com/radio-card {:options (for [{:keys [:db/id :transaction-rule/note :transaction-rule/description]} matching-rules]
|
||||||
:value "apply-rule"
|
{:value id
|
||||||
:form ""}))
|
:content [:div.ml-3
|
||||||
[:div.space-y-2
|
[:span.block.text-sm.font-medium note]
|
||||||
[:label.block.text-sm.font-medium.mb-1 "Select a rule to apply:"]
|
[:span.block.text-sm.text-gray-500 description]]})
|
||||||
(doall (for [{:keys [:db/id :transaction-rule/note :transaction-rule/description]} matching-rules]
|
:name (fc/with-field :rule-id (fc/field-name ))
|
||||||
[:div.flex.items-center
|
:width "w-full"}) ]
|
||||||
[:input {:type :radio :value id :name (fc/with-field :rule-id (fc/field-name))}]
|
#_(com/a-button {"@click" "$dispatch('applyRule')"} "Apply")]
|
||||||
[:div.ml-3
|
|
||||||
[:span.block.text-sm.font-medium note]
|
|
||||||
[:span.block.text-sm.text-gray-500 description]]]))]
|
|
||||||
(com/a-button {"@click" "$dispatch('applyRule')"} "Apply")]]
|
|
||||||
[:div.text-center.py-4.text-gray-500 "No matching rules found for this transaction."])]))
|
[:div.text-center.py-4.text-gray-500 "No matching rules found for this transaction."])]))
|
||||||
|
|
||||||
(defn payment-matches-view [request]
|
(defn payment-matches-view [request]
|
||||||
@@ -619,8 +607,7 @@
|
|||||||
" - Amount: $" (format "%.2f" (:payment/amount payment))
|
" - Amount: $" (format "%.2f" (:payment/amount payment))
|
||||||
" • Date: " (some-> payment :payment/date coerce/to-date-time (atime/unparse-local atime/normal-date)))})
|
" • Date: " (some-> payment :payment/date coerce/to-date-time (atime/unparse-local atime/normal-date)))})
|
||||||
:name payment-id-field
|
:name payment-id-field
|
||||||
:width "w-full"})))
|
:width "w-full"}))) ]]
|
||||||
(com/a-button {"@click" "$dispatch('matchPayment')"} "Match" #_[:button.mt-4.w-full.py-2.bg-blue-500.text-white.rounded.hover:bg-blue-600 "Match"])]]
|
|
||||||
[:div.text-center.py-4.text-gray-500 "No matching payments available for this transaction."]))]))
|
[:div.text-center.py-4.text-gray-500 "No matching payments available for this transaction."]))]))
|
||||||
|
|
||||||
(defn count-payment-matches [request]
|
(defn count-payment-matches [request]
|
||||||
@@ -667,7 +654,8 @@
|
|||||||
:placeholder "Optional note"})]))
|
:placeholder "Optional note"})]))
|
||||||
[:div {:x-data (hx/json {:activeForm (if (:transaction/payment (:entity request))
|
[:div {:x-data (hx/json {:activeForm (if (:transaction/payment (:entity request))
|
||||||
"link-payment"
|
"link-payment"
|
||||||
(fc/with-field :action (fc/field-value)))
|
(or (fc/with-field :action (fc/field-value))
|
||||||
|
"manual"))
|
||||||
:canChange (boolean (not (:transaction/payment (:entity request))))})
|
:canChange (boolean (not (:transaction/payment (:entity request))))})
|
||||||
"@unlinked" "canChange=true"}
|
"@unlinked" "canChange=true"}
|
||||||
[:div {:class "flex space-x-2 mb-4"}
|
[:div {:class "flex space-x-2 mb-4"}
|
||||||
@@ -863,7 +851,7 @@
|
|||||||
:headers {"hx-trigger" "invalidated"})))
|
:headers {"hx-trigger" "invalidated"})))
|
||||||
|
|
||||||
(defmethod save-handler :link-autopay-invoices
|
(defmethod save-handler :link-autopay-invoices
|
||||||
[{{:keys [autopay-invoice-ids]} :form-params :as request transaction :entity}]
|
[{{ {:keys [autopay-invoice-ids] :as snapshot} :snapshot} :multi-form-state :as request transaction :entity} ]
|
||||||
(let [db (dc/db conn)
|
(let [db (dc/db conn)
|
||||||
invoice-clients (set (map #(pull-ref db :invoice/client %) autopay-invoice-ids))
|
invoice-clients (set (map #(pull-ref db :invoice/client %) autopay-invoice-ids))
|
||||||
invoice-amount (reduce + 0.0 (map #(pull-attr db :invoice/total %) autopay-invoice-ids))]
|
invoice-amount (reduce + 0.0 (map #(pull-attr db :invoice/total %) autopay-invoice-ids))]
|
||||||
@@ -894,7 +882,8 @@
|
|||||||
autopay-invoice-ids)
|
autopay-invoice-ids)
|
||||||
(-> transaction :transaction/bank-account :db/id)
|
(-> transaction :transaction/bank-account :db/id)
|
||||||
(-> transaction :transaction/client :db/id))]
|
(-> transaction :transaction/client :db/id))]
|
||||||
(audit-transact payment-tx (:identity request)))
|
(audit-transact (conj payment-tx
|
||||||
|
[:upsert-transaction (default-update-tx snapshot {:db/id (:db/id transaction)})]) (:identity request)))
|
||||||
|
|
||||||
(solr/touch-with-ledger (:db/id transaction))
|
(solr/touch-with-ledger (:db/id transaction))
|
||||||
|
|
||||||
@@ -905,7 +894,7 @@
|
|||||||
:headers {"hx-trigger" "invalidated"})))
|
:headers {"hx-trigger" "invalidated"})))
|
||||||
|
|
||||||
(defmethod save-handler :link-unpaid-invoices
|
(defmethod save-handler :link-unpaid-invoices
|
||||||
[{{:keys [unpaid-invoice-ids]} :form-params :as request transaction :entity}]
|
[{{ {:keys [unpaid-invoice-ids] :as snapshot} :snapshot} :multi-form-state :as request transaction :entity} ]
|
||||||
(let [ db (dc/db conn)
|
(let [ db (dc/db conn)
|
||||||
invoice-clients (set (map #(pull-ref db :invoice/client %) unpaid-invoice-ids))
|
invoice-clients (set (map #(pull-ref db :invoice/client %) unpaid-invoice-ids))
|
||||||
invoice-amount (reduce + 0.0 (map #(pull-attr db :invoice/outstanding-balance %) unpaid-invoice-ids))]
|
invoice-amount (reduce + 0.0 (map #(pull-attr db :invoice/outstanding-balance %) unpaid-invoice-ids))]
|
||||||
@@ -938,7 +927,8 @@
|
|||||||
unpaid-invoice-ids)
|
unpaid-invoice-ids)
|
||||||
(-> transaction :transaction/bank-account :db/id)
|
(-> transaction :transaction/bank-account :db/id)
|
||||||
(-> transaction :transaction/client :db/id))]
|
(-> transaction :transaction/client :db/id))]
|
||||||
(audit-transact payment-tx (:identity request)))
|
(audit-transact (conj payment-tx
|
||||||
|
[:upsert-transaction (default-update-tx snapshot {:db/id (:db/id transaction)})]) (:identity request)))
|
||||||
|
|
||||||
(solr/touch-with-ledger (:db/id transaction))
|
(solr/touch-with-ledger (:db/id transaction))
|
||||||
|
|
||||||
@@ -958,7 +948,7 @@
|
|||||||
|
|
||||||
(defmethod save-handler
|
(defmethod save-handler
|
||||||
:apply-rule
|
:apply-rule
|
||||||
[{{{:keys [rule-id]} :snapshot} :multi-form-state :as request transaction :entity}]
|
[{{{:keys [rule-id] :as snapshot} :snapshot} :multi-form-state :as request transaction :entity}]
|
||||||
(let [transaction-rule (dc/pull (dc/db conn)
|
(let [transaction-rule (dc/pull (dc/db conn)
|
||||||
[:transaction-rule/description
|
[:transaction-rule/description
|
||||||
:transaction-rule/vendor
|
:transaction-rule/vendor
|
||||||
@@ -982,7 +972,8 @@
|
|||||||
updated-tx (rm/apply-rule {:db/id (:db/id transaction)
|
updated-tx (rm/apply-rule {:db/id (:db/id transaction)
|
||||||
:transaction/amount (:transaction/amount transaction)}
|
:transaction/amount (:transaction/amount transaction)}
|
||||||
transaction-rule
|
transaction-rule
|
||||||
locations)]
|
locations)
|
||||||
|
updated-tx (default-update-tx snapshot updated-tx)]
|
||||||
(alog/info ::applying-rule-tx :tx-data updated-tx
|
(alog/info ::applying-rule-tx :tx-data updated-tx
|
||||||
:transaction transaction
|
:transaction transaction
|
||||||
:transaction-rule transaction-rule)
|
:transaction-rule transaction-rule)
|
||||||
@@ -996,16 +987,73 @@
|
|||||||
[:p.text-gray-600.mt-2 "The selected rule has been applied to this transaction."])
|
[:p.text-gray-600.mt-2 "The selected rule has been applied to this transaction."])
|
||||||
:headers {"hx-trigger" "invalidated"})))
|
:headers {"hx-trigger" "invalidated"})))
|
||||||
|
|
||||||
|
(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-account
|
||||||
|
"Spreads the expense account amount across the given locations"
|
||||||
|
[locations account]
|
||||||
|
(if (= "Shared" (:transaction-account/location account))
|
||||||
|
(let [{:keys [base-amount remainder]} (calculate-spread (:transaction-account/amount account) (count locations))]
|
||||||
|
(map-indexed (fn [idx _]
|
||||||
|
(assoc account
|
||||||
|
:transaction-account/amount (+ base-amount (if (< idx remainder) 1 0))
|
||||||
|
:transaction-account/location (nth locations idx)))
|
||||||
|
locations))
|
||||||
|
[account]))
|
||||||
|
|
||||||
|
(defn- apply-total-delta-to-account [invoice-total eas]
|
||||||
|
(when (seq eas)
|
||||||
|
(let [leftover (- invoice-total (reduce + 0 (map :transaction-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 :transaction-account/amount #(+ % leftover))
|
||||||
|
rest))))
|
||||||
|
|
||||||
|
(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 maybe-spread-locations
|
||||||
|
"Converts any expense account for a \"Shared\" location into a separate expense account for all valid locations for that client"
|
||||||
|
([transaction]
|
||||||
|
(maybe-spread-locations transaction (pull-attr (dc/db conn) :client/locations (:transaction/client transaction))))
|
||||||
|
([transaction locations]
|
||||||
|
(clojure.pprint/pprint transaction)
|
||||||
|
(update-in transaction
|
||||||
|
[:transaction/accounts]
|
||||||
|
(fn [accounts]
|
||||||
|
(->> accounts
|
||||||
|
(map (fn [ea] (update ea :transaction-account/amount $->cents)))
|
||||||
|
(mapcat (partial spread-account locations))
|
||||||
|
(apply-total-delta-to-account ($->cents (:transaction/amount transaction)))
|
||||||
|
(map (fn [ea] (update ea :transaction-account/amount cents->$))))))))
|
||||||
|
|
||||||
(defmethod save-handler :manual
|
(defmethod save-handler :manual
|
||||||
[{:as request
|
[{:as request
|
||||||
transaction :entity
|
transaction :entity
|
||||||
:keys [multi-form-state]}]
|
:keys [multi-form-state]}]
|
||||||
(let [tx-data (-> multi-form-state :snapshot (dissoc :action))
|
(let [tx-data (-> multi-form-state :snapshot (dissoc :action))
|
||||||
_ (clojure.pprint/pprint tx-data)
|
|
||||||
tx-id (:db/id tx-data)
|
tx-id (:db/id tx-data)
|
||||||
client-id (->db-id (:transaction/client tx-data))
|
client-id (->db-id (:transaction/client tx-data))
|
||||||
existing-tx (d-transactions/get-by-id tx-id)
|
existing-tx (d-transactions/get-by-id tx-id)
|
||||||
transaction [:upsert-transaction (assoc tx-data :db/id tx-id)]]
|
transaction [:upsert-transaction (maybe-spread-locations (assoc tx-data :db/id tx-id))]]
|
||||||
|
|
||||||
(alog/info ::transaction transaction :entity transaction)
|
(alog/info ::transaction transaction :entity transaction)
|
||||||
(exception->4xx #(assert-can-see-client (:identity request) client-id))
|
(exception->4xx #(assert-can-see-client (:identity request) client-id))
|
||||||
@@ -1102,10 +1150,6 @@
|
|||||||
(html-response (fc/with-field :step-params (payment-matches-view request))
|
(html-response (fc/with-field :step-params (payment-matches-view request))
|
||||||
:headers {"hx-trigger" "unlinked"}))))
|
:headers {"hx-trigger" "unlinked"}))))
|
||||||
|
|
||||||
#_(def save-schema
|
|
||||||
(mc/schema
|
|
||||||
))
|
|
||||||
|
|
||||||
|
|
||||||
(defrecord EditWizard [_ current-step]
|
(defrecord EditWizard [_ current-step]
|
||||||
mm/LinearModalWizard
|
mm/LinearModalWizard
|
||||||
@@ -1120,6 +1164,7 @@
|
|||||||
(mm/get-step this :basic-details)))
|
(mm/get-step this :basic-details)))
|
||||||
(render-wizard [this {:keys [multi-form-state] :as request}]
|
(render-wizard [this {:keys [multi-form-state] :as request}]
|
||||||
(println "HERE XYZ" (:form-errors request))
|
(println "HERE XYZ" (:form-errors request))
|
||||||
|
(clojure.pprint/pprint (:snapshot multi-form-state) )
|
||||||
(mm/default-render-wizard
|
(mm/default-render-wizard
|
||||||
this request
|
this request
|
||||||
:form-params
|
:form-params
|
||||||
@@ -1168,15 +1213,18 @@
|
|||||||
(def key->handler
|
(def key->handler
|
||||||
(apply-middleware-to-all-handlers
|
(apply-middleware-to-all-handlers
|
||||||
{::route/edit-wizard (-> mm/open-wizard-handler
|
{::route/edit-wizard (-> mm/open-wizard-handler
|
||||||
|
(wrap-must {:activity :edit :subject :transaction} (fn get-client [request] (-> request :entity :transaction/client)))
|
||||||
(mm/wrap-wizard edit-wizard)
|
(mm/wrap-wizard edit-wizard)
|
||||||
(mm/wrap-init-multi-form-state initial-edit-wizard-state)
|
(mm/wrap-init-multi-form-state initial-edit-wizard-state)
|
||||||
(wrap-entity [:route-params :db/id] d-transactions/default-read)
|
(wrap-entity [:route-params :db/id] d-transactions/default-read)
|
||||||
(wrap-schema-enforce :route-schema [:map [:db/id entity-id]]))
|
(wrap-schema-enforce :route-schema [:map [:db/id entity-id]]))
|
||||||
::route/edit-wizard-navigate (-> mm/next-handler
|
::route/edit-wizard-navigate (-> mm/next-handler
|
||||||
|
(wrap-must {:activity :edit :subject :transaction} (fn get-client [request] (-> request :entity :transaction/client)))
|
||||||
(wrap-entity [:multi-form-state :snapshot :db/id] d-transactions/default-read)
|
(wrap-entity [:multi-form-state :snapshot :db/id] d-transactions/default-read)
|
||||||
(mm/wrap-wizard edit-wizard)
|
(mm/wrap-wizard edit-wizard)
|
||||||
(mm/wrap-decode-multi-form-state))
|
(mm/wrap-decode-multi-form-state))
|
||||||
::route/edit-submit (-> mm/submit-handler
|
::route/edit-submit (-> mm/submit-handler
|
||||||
|
(wrap-must {:activity :edit :subject :transaction} (fn get-client [request] (-> request :entity :transaction/client)))
|
||||||
(wrap-entity [:multi-form-state :snapshot :db/id] d-transactions/default-read)
|
(wrap-entity [:multi-form-state :snapshot :db/id] d-transactions/default-read)
|
||||||
(mm/wrap-wizard edit-wizard)
|
(mm/wrap-wizard edit-wizard)
|
||||||
(mm/wrap-decode-multi-form-state))
|
(mm/wrap-decode-multi-form-state))
|
||||||
@@ -1206,11 +1254,12 @@
|
|||||||
[:maybe entity-id]]]))
|
[:maybe entity-id]]]))
|
||||||
|
|
||||||
::route/unlink-payment (-> unlink-payment
|
::route/unlink-payment (-> unlink-payment
|
||||||
|
(wrap-must {:activity :edit :subject :transaction} (fn get-client [request] (-> request :entity :transaction/client)))
|
||||||
(wrap-entity [:multi-form-state :snapshot :db/id] d-transactions/default-read)
|
(wrap-entity [:multi-form-state :snapshot :db/id] d-transactions/default-read)
|
||||||
(mm/wrap-wizard edit-wizard)
|
(mm/wrap-wizard edit-wizard)
|
||||||
(mm/wrap-decode-multi-form-state)
|
(mm/wrap-decode-multi-form-state)
|
||||||
#_(wrap-schema-enforce :form-schema
|
#_(wrap-schema-enforce :form-schema
|
||||||
save-schema))}
|
save-schema))}
|
||||||
(fn [h]
|
(fn [h]
|
||||||
(-> h
|
(-> h
|
||||||
(wrap-client-redirect-unauthenticated)))))
|
(wrap-client-redirect-unauthenticated)))))
|
||||||
|
|||||||
@@ -142,11 +142,19 @@
|
|||||||
false)))
|
false)))
|
||||||
|
|
||||||
#? (:clj
|
#? (:clj
|
||||||
(defn wrap-must [handler policy]
|
(defn wrap-must
|
||||||
(fn [request]
|
( [handler policy]
|
||||||
(if (can? (:identity request) policy)
|
(fn [request]
|
||||||
(handler request)
|
(if (can? (:identity request) policy)
|
||||||
{:status 302
|
(handler request)
|
||||||
:headers {"Location" (str "/login?"
|
{:status 302
|
||||||
(url/map->query {"redirect-to" (:uri request)}))}}))))
|
:headers {"Location" (str "/login?"
|
||||||
|
(url/map->query {"redirect-to" (:uri request)}))}})))
|
||||||
|
( [handler policy get-client]
|
||||||
|
(fn [request]
|
||||||
|
(if (can? (:identity request) (assoc policy :client (get-client request)))
|
||||||
|
(handler request)
|
||||||
|
{:status 302
|
||||||
|
:headers {"Location" (str "/login?"
|
||||||
|
(url/map->query {"redirect-to" (:uri request)}))}})))))
|
||||||
|
|
||||||
|
|||||||
6
tasks
6
tasks
@@ -1,10 +1,6 @@
|
|||||||
* Add tests for edit transaction.
|
* Add tests for edit transaction.
|
||||||
* Make it so you can create a new vendor again.
|
* Make it so you can create a new vendor again.
|
||||||
* Hide unhelpful report from the dashboard
|
|
||||||
* Check permissions on ledger, transactions, reports
|
* Check permissions on ledger, transactions, reports
|
||||||
* Make sure that you can't change a transaction if its payment is set
|
|
||||||
* also add tests
|
|
||||||
* Make sure that "Shared" really shares locations
|
|
||||||
* make sure transactions support import-batch-id query parameter
|
|
||||||
* make locked transactions clearer on the transaction table
|
* make locked transactions clearer on the transaction table
|
||||||
* Make locked transactions not look butt ugly with errors
|
* Make locked transactions not look butt ugly with errors
|
||||||
|
* Implement bulk actions
|
||||||
Reference in New Issue
Block a user