Makes it so that the edit page works better

This commit is contained in:
2025-03-05 14:25:43 -08:00
parent f881bdcc93
commit 59d48342ea
6 changed files with 118 additions and 76 deletions

File diff suppressed because one or more lines are too long

View File

@@ -901,24 +901,15 @@
:db/cardinality #:db{:ident :db.cardinality/one}, :db/cardinality #:db{:ident :db.cardinality/one},
:db/doc "Location of the entry", :db/doc "Location of the entry",
:db/ident :journal-entry-line/location} :db/ident :journal-entry-line/location}
{:db/valueType #:db{:ident :db.type/double},
:db/cardinality #:db{:ident :db.cardinality/one},
:db/doc "The amount to debit",
:db/ident :journal-entry-line/running-balance}
{:db/valueType :db.type/ref, {:db/valueType :db.type/ref,
:db/cardinality #:db{:ident :db.cardinality/one}, :db/cardinality #:db{:ident :db.cardinality/one},
:db/doc "The client for the journal entry line", :db/doc "The client for the journal entry line",
:db/ident :journal-entry-line/client} :db/ident :journal-entry-line/client}
{:db/valueType :db.type/tuple
:db/tupleAttrs [:journal-entry-line/client
:journal-entry-line/account
:journal-entry-line/location
:journal-entry-line/date
:journal-entry-line/debit
:journal-entry-line/credit
:journal-entry-line/running-balance]
:db/index true
:db/cardinality :db.cardinality/one,
:db/ident :journal-entry-line/running-balance-tuple
:db/doc "[:journal-entry-line/client :journal-entry-line/account :journal-entry-line/location :journal-entry-line/date :db/id :journal-entry-line/debit :journal-entry-line/credit :journal-entry-line/running-balance]",
:db/noHistory true
}
{:db/ident :legal-entity-1099-type/none} {:db/ident :legal-entity-1099-type/none}
{:db/ident :legal-entity-1099-type/landlord} {:db/ident :legal-entity-1099-type/landlord}
@@ -1997,6 +1988,20 @@
{:db/ident :ledger-side/credit} {:db/ident :ledger-side/credit}
{:db/ident :ledger-side/debit} {:db/ident :ledger-side/debit}
{:db/valueType :db.type/tuple
:db/tupleAttrs [:journal-entry-line/client
:journal-entry-line/account
:journal-entry-line/location
:journal-entry-line/date
:journal-entry-line/debit
:journal-entry-line/credit
:journal-entry-line/running-balance]
:db/index true
:db/cardinality :db.cardinality/one,
:db/ident :journal-entry-line/running-balance-tuple
:db/doc "[:journal-entry-line/client :journal-entry-line/account :journal-entry-line/location :journal-entry-line/date :db/id :journal-entry-line/debit :journal-entry-line/credit :journal-entry-line/running-balance]",
:db/noHistory true
}
] ]

View File

@@ -11,6 +11,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.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
:refer [wrap-client-redirect-unauthenticated]] :refer [wrap-client-redirect-unauthenticated]]
@@ -25,7 +26,7 @@
[auto-ap.ssr.svg :as svg] [auto-ap.ssr.svg :as svg]
[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 html-response check-location-belongs entity-id html-response modal-response
ref->enum-schema strip wrap-schema-enforce]] ref->enum-schema strip wrap-schema-enforce]]
[auto-ap.time :as atime] [auto-ap.time :as atime]
[bidi.bidi :as bidi] [bidi.bidi :as bidi]
@@ -143,7 +144,7 @@
(:account/name (d-accounts/clientize (dc/pull (dc/db conn) d-accounts/default-read value) (:account/name (d-accounts/clientize (dc/pull (dc/db conn) d-accounts/default-read value)
client-id)))})]) client-id)))})])
(defn- transaction-account-row* [{:keys [value client-id]}] (defn transaction-account-row* [{:keys [value client-id]}]
(com/data-grid-row (com/data-grid-row
(-> {:x-data (hx/json {:show (boolean (not (fc/field-value (:new? value)))) (-> {:x-data (hx/json {:show (boolean (not (fc/field-value (:new? value))))
:accountId (fc/field-value (:transaction-account/account value))}) :accountId (fc/field-value (:transaction-account/account value))})
@@ -601,7 +602,7 @@
[:div.text-sm.text-gray-600 (-> invoice :invoice/vendor :vendor/name)]] [:div.text-sm.text-gray-600 (-> invoice :invoice/vendor :vendor/name)]]
[:div.text-right [:div.text-right
[:div.font-medium (format "$%.2f" (:invoice/outstanding-balance invoice))] [:div.font-medium (format "$%.2f" (:invoice/outstanding-balance invoice))]
(com/hidden {:name "unpaid-invoice-ids[]" :value (:db/id invoice)})]]) (com/hidden {:name "unpaid-invoice-ids" :value (:db/id invoice)})]])
[:div.flex.justify-end.mt-4 [:div.flex.justify-end.mt-4
(com/button {:color :primary :size :small} "Match")]]])]] (com/button {:color :primary :size :small} "Match")]]])]]
[: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."])]))
@@ -663,7 +664,16 @@
(let [tx-id (or (-> request :multi-form-state :snapshot :db/id) (let [tx-id (or (-> request :multi-form-state :snapshot :db/id)
(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))
payment (:transaction/payment tx)] payment (dc/pull
(dc/db conn)
'[:payment/amount
[:payment/date :xform clj-time.coerce/from-date]
{ [ :payment/status :xform iol-ion.query/ident] [:db/ident]
:payment/vendor [:vendor/name]}
]
(-> tx :transaction/payment :db/id))]
(println "PAYMENT IS" payment)
(when payment (when payment
[:div.my-4.p-4.bg-blue-50.rounded [:div.my-4.p-4.bg-blue-50.rounded
[:h3.text-lg.font-bold.mb-2 "Linked Payment"] [:h3.text-lg.font-bold.mb-2 "Linked Payment"]
@@ -676,20 +686,23 @@
[:div (-> payment :payment/vendor :vendor/name)]] [:div (-> payment :payment/vendor :vendor/name)]]
[:div.flex.justify-between [:div.flex.justify-between
[:div.font-medium "Amount"] [:div.font-medium "Amount"]
[:div (format "$%.2f" (:payment/amount payment))]] [:div (some->> (:payment/amount payment) (format "$%.2f" ))]]
[:div.flex.justify-between [:div.flex.justify-between
[:div.font-medium "Status"] [:div.font-medium "Status"]
[:div (-> payment :payment/status :db/ident name)]] [:div (some-> payment :payment/status name)]]
[:div.flex.justify-between [:div.flex.justify-between
[:div.font-medium "Date"] [:div.font-medium "Date"]
[:div (some-> payment :payment/date coerce/to-date-time (atime/unparse-local atime/normal-date))]] [:div (some-> payment :payment/date (atime/unparse-local atime/normal-date))]]
[:div.mt-4 [:div.mt-4 {:hx-post (bidi/path-for ssr-routes/only-routes ::route/unlink-payment)
[:form {:hx-post (bidi/path-for ssr-routes/only-routes ::route/unlink-payment) :hx-params "unlink-transaction-id"
:hx-target "#modal-holder" :hx-trigger "unlinkPayment"
:hx-swap "outerHTML" :hx-target "#modal-holder"
:hx-confirm "Are you sure you want to unlink this payment?"} :hx-swap "outerHTML"
(com/hidden {:name "transaction-id" :value tx-id}) :hx-confirm "Are you sure you want to unlink this payment?"}
(com/button {:color :danger :size :small} "Unlink Payment")]]]])))
(com/hidden {:name "unlink-transaction-id" :value tx-id})
(com/a-button {:color :red :size :small
"@click" "$dispatch('unlinkPayment')"} "Unlink Payment")]]])))
(defn count-payment-matches [request] (defn count-payment-matches [request]
(count (get-available-payments request))) (count (get-available-payments request)))
@@ -954,7 +967,7 @@
[:p.text-gray-600.mt-2 "The transaction has been linked to the selected payment."] [:p.text-gray-600.mt-2 "The transaction has been linked to the selected payment."]
[:div.mt-6 [:div.mt-6
(com/button {:color :primary (com/button {:color :primary
"@click" "$dispatch('modalclose') $dispatch('refreshTable')"} "Close")]]]))) "@click" "$dispatch('modalclose'); $dispatch('refreshTable')"} "Close")]]])))
(defn match-autopay-invoices [{{:strs [transaction-id autopay-invoice-ids]} :form-params :as request}] (defn match-autopay-invoices [{{:strs [transaction-id autopay-invoice-ids]} :form-params :as request}]
(let [transaction-id (Long/parseLong transaction-id) (let [transaction-id (Long/parseLong transaction-id)
@@ -1003,13 +1016,10 @@
[:p.text-gray-600.mt-2 "The transaction has been linked to the selected autopay invoices."] [:p.text-gray-600.mt-2 "The transaction has been linked to the selected autopay invoices."]
[:div.mt-6 [:div.mt-6
(com/button {:color :primary (com/button {:color :primary
"@click" "$dispatch('modalclose') $dispatch('refreshTable')"} "Close")]]]))) "@click" "$dispatch('modalclose'); $dispatch('refreshTable')"} "Close")]]])))
(defn match-unpaid-invoices [{{:strs [transaction-id unpaid-invoice-ids]} :form-params :as request}] (defn match-unpaid-invoices [{{:keys [transaction-id unpaid-invoice-ids]} :form-params :as request}]
(let [transaction-id (Long/parseLong transaction-id) (let [ _ (println "UNPAID INVOOICES " unpaid-invoice-ids)
unpaid-invoice-ids (if (string? unpaid-invoice-ids)
[(Long/parseLong unpaid-invoice-ids)]
(mapv #(Long/parseLong %) unpaid-invoice-ids))
transaction (d-transactions/get-by-id transaction-id) transaction (d-transactions/get-by-id transaction-id)
db (dc/db conn) 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))
@@ -1021,7 +1031,9 @@
(when (or (> (count invoice-clients) 1) (when (or (> (count invoice-clients) 1)
(not= (-> transaction :transaction/client :db/id) (not= (-> transaction :transaction/client :db/id)
(first invoice-clients))) (first invoice-clients)))
(throw (ex-info "Clients don't match" {:validation-error "Invoice(s) and transaction client do not match."}))) (throw (ex-info "Clients don't match" {:validation-error "Invoice(s) and transaction client do not match."
:transaction-client (-> transaction :transaction/client :db/id)
:invoice-clients invoice-clients})))
(when-not (dollars= (- (:transaction/amount transaction)) (when-not (dollars= (- (:transaction/amount transaction))
invoice-amount) invoice-amount)
@@ -1052,7 +1064,7 @@
[:p.text-gray-600.mt-2 "The transaction has been linked to the selected unpaid invoices."] [:p.text-gray-600.mt-2 "The transaction has been linked to the selected unpaid invoices."]
[:div.mt-6 [:div.mt-6
(com/button {:color :primary (com/button {:color :primary
"@click" "$dispatch('modalclose') $dispatch('refreshTable')"} "Close")]]]))) "@click" "$dispatch('modalclose'); $dispatch('refreshTable')"} "Close")]]])))
(defn apply-rule [{{:strs [transaction-id rule-id]} :form-params :as request}] (defn apply-rule [{{:strs [transaction-id rule-id]} :form-params :as request}]
(let [transaction-id (Long/parseLong transaction-id) (let [transaction-id (Long/parseLong transaction-id)
@@ -1095,26 +1107,27 @@
[: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."]
[:div.mt-6 [:div.mt-6
(com/button {:color :primary (com/button {:color :primary
"@click" "$dispatch('modalclose') $dispatch('refreshTable')"} "Close")]]]))) "@click" "$dispatch('modalclose'); $dispatch('refreshTable')"} "Close")]]])))
(defn unlink-payment [{{:strs [transaction-id]} :form-params :as request}] (defn unlink-payment [{{:keys [unlink-transaction-id]} :form-params :as request}]
(let [transaction-id (Long/parseLong transaction-id) (let [ transaction (dc/pull (dc/db conn)
transaction (dc/pull (dc/db conn) '[:transaction/approval-status
[:transaction/approval-status
:transaction/status :transaction/date
:transaction/date :transaction/location
:transaction/location :transaction/vendor
:transaction/vendor :transaction/accounts
:transaction/accounts :transaction/status
:transaction/client [:db/id] :transaction/client [:db/id]
{:transaction/payment [:payment/date {:payment/status [:db/ident]} :db/id]}] {:transaction/payment [:payment/date
transaction-id) {[ :payment/status :xform iol-ion.query/ident] [:db/ident]} :db/id]}]
unlink-transaction-id)
payment (-> transaction :transaction/payment)] payment (-> transaction :transaction/payment)]
(exception->4xx #(assert-can-see-client (:identity request) (-> transaction :transaction/client :db/id))) (exception->4xx #(assert-can-see-client (:identity request) (-> transaction :transaction/client :db/id)))
(exception->4xx #(assert-not-locked (-> transaction :transaction/client :db/id) (:transaction/date transaction))) (exception->4xx #(assert-not-locked (-> transaction :transaction/client :db/id) (:transaction/date transaction)))
(when (not= :payment-status/cleared (-> payment :payment/status :db/ident)) (when (not= :payment-status/cleared (-> payment :payment/status))
(throw (ex-info "Payment can't be undone because it isn't cleared." (throw (ex-info "Payment can't be undone because it isn't cleared."
{:validation-error "Payment can't be undone because it isn't cleared."}))) {:validation-error "Payment can't be undone because it isn't cleared."})))
@@ -1159,16 +1172,31 @@
:transaction/accounts nil}]] :transaction/accounts nil}]]
(:identity request)))) (:identity request))))
(solr/touch-with-ledger transaction-id) (solr/touch-with-ledger unlink-transaction-id)
(html-response [:div.p-4 (modal-response
[:div.text-2xl.text-center.text-green-600 svg/check] (com/modal {}
[:div.text-center.mt-4 (com/modal-card-advanced
[:h3.text-xl.font-bold "Transaction unlinked successfully"] {:class "transition duration-300 ease-in-out htmx-swapping:-translate-x-2/3 htmx-swapping:opacity-0 htmx-swapping:scale-0 htmx-added:translate-x-2/3 htmx-added:opacity-0 htmx-added:scale-0 scale-100 translate-x-0 opacity-100"}
[:p.text-gray-600.mt-2 "The transaction has been unlinked from its payment."] (com/modal-body {}
[:div.mt-6 [:div.flex.flex-col.mt-4.space-y-4.items-center
(com/button {:color :primary [:div.w-24.h-24.bg-green-50.rounded-full.p-4.text-green-300.animate-gg
"@click" "$dispatch('modalclose') $dispatch('refreshTable')"} "Close")]]]))) svg/thumbs-up]
[:div.text-center.mt-4
[:h3.text-xl.font-bold "Transaction unlinked successfully"]
[:p.text-gray-600.mt-2 "The transaction has been unlinked from its payment."]
[:p "If you'd like to also void the payment, click "
(com/link
{:hx-boost true
:href (hu/url (bidi/path-for ssr-routes/only-routes ::payment-route/all-page)
{:exact-match-id (:db/id payment)})}
"here")
" to view the payment"]
[:div.mt-6
(com/button {:color :primary
"@click" "$dispatch('modalclose'); $dispatch('refreshTable')"} "Close")]]])))
:headers {"hx-trigger" "invalidated"})
))
(defn edit-transaction [{:keys [route-params] :as request}] (defn edit-transaction [{:keys [route-params] :as request}]
(mm/open-wizard-handler (-> request (mm/open-wizard-handler (-> request
@@ -1213,9 +1241,14 @@
[:maybe entity-id]]])) [:maybe entity-id]]]))
::route/match-payment match-payment ::route/match-payment match-payment
::route/match-autopay-invoices match-autopay-invoices ::route/match-autopay-invoices match-autopay-invoices
::route/match-unpaid-invoices match-unpaid-invoices ::route/match-unpaid-invoices (-> match-unpaid-invoices
(wrap-schema-enforce :form-schema
[:map [:transaction-id entity-id]
[:unpaid-invoice-ids [:vector {:coerce? true} entity-id]]]))
::route/apply-rule apply-rule ::route/apply-rule apply-rule
::route/unlink-payment unlink-payment} ::route/unlink-payment (-> unlink-payment
(wrap-schema-enforce :form-schema
[:map [:unlink-transaction-id entity-id]]))}
(fn [h] (fn [h]
(-> h (-> h
(wrap-client-redirect-unauthenticated))))) (wrap-client-redirect-unauthenticated)))))

View File

@@ -67,7 +67,7 @@
(testing "should set running-balance on ledger entries missing them" (testing "should set running-balance on ledger entries missing them"
(sut/refresh-running-balance-cache) (sut/upsert-running-balance)
(println (d/pull (d/db conn) '[*] line-1-1)) (println (d/pull (d/db conn) '[*] line-1-1))
(is (= [-10.0 -60.0 -210.0] (is (= [-10.0 -60.0 -210.0]
@@ -82,7 +82,7 @@
[{:db/id line-1-1 [{:db/id line-1-1
:journal-entry-line/dirty true :journal-entry-line/dirty true
:journal-entry-line/running-balance 123810.23}]) :journal-entry-line/running-balance 123810.23}])
(sut/refresh-running-balance-cache) (sut/upsert-running-balance)
(is (= [-10.0 -60.0 -210.0] (is (= [-10.0 -60.0 -210.0]
(map #(pull-attr (d/db conn) :journal-entry-line/running-balance %) [line-1-1 line-2-1 line-3-1])))) (map #(pull-attr (d/db conn) :journal-entry-line/running-balance %) [line-1-1 line-2-1 line-3-1]))))
@@ -93,7 +93,7 @@
[{:db/id line-1-1 [{:db/id line-1-1
:journal-entry-line/dirty true :journal-entry-line/dirty true
:journal-entry-line/debit 70.0}]) :journal-entry-line/debit 70.0}])
(sut/refresh-running-balance-cache) (sut/upsert-running-balance)
(is (= [-70.0 -120.0 -270.0] (is (= [-70.0 -120.0 -270.0]
(map #(pull-attr (d/db conn) :journal-entry-line/running-balance %) [line-1-1 line-2-1 line-3-1])))) (map #(pull-attr (d/db conn) :journal-entry-line/running-balance %) [line-1-1 line-2-1 line-3-1]))))
(testing "should not recompute entries that aren't dirty" (testing "should not recompute entries that aren't dirty"
@@ -102,7 +102,7 @@
[{:db/id line-1-1 [{:db/id line-1-1
:journal-entry-line/dirty false :journal-entry-line/dirty false
:journal-entry-line/debit 90.0}]) :journal-entry-line/debit 90.0}])
(sut/refresh-running-balance-cache) (sut/upsert-running-balance)
(is (= [-70.0 -120.0 -270.0] (is (= [-70.0 -120.0 -270.0]
(map #(pull-attr (d/db conn) :journal-entry-line/running-balance %) [line-1-1 line-2-1 line-3-1]))) (map #(pull-attr (d/db conn) :journal-entry-line/running-balance %) [line-1-1 line-2-1 line-3-1])))

View File

@@ -84,7 +84,7 @@
{:invoice-expense-account/amount -5 {:invoice-expense-account/amount -5
:invoice-expense-account/location "Shared"}] :invoice-expense-account/location "Shared"}]
:invoice/total -101} :invoice/total -101}
result (sut8/maybe-spread-locations invoice ["Location 1" ])] result (sut9/maybe-spread-locations invoice ["Location 1" ])]
(is (= (is (=
[{:invoice-expense-account/amount -100.0 [{:invoice-expense-account/amount -100.0
:invoice-expense-account/location "Location 1"} :invoice-expense-account/location "Location 1"}

View File

@@ -1,17 +1,20 @@
(ns auto-ap.ssr.transaction.edit-test (ns auto-ap.ssr.transaction.edit-test
(:require (:require
[auto-ap.datomic :refer [conn]] [auto-ap.datomic :refer [conn]]
[auto-ap.datomic.accounts :as d-accounts]
[auto-ap.datomic.invoices :as d-invoices]
[auto-ap.datomic.transactions :as d-transactions] [auto-ap.datomic.transactions :as d-transactions]
[auto-ap.import.transactions :as i-transactions] [auto-ap.integration.util :refer [wrap-setup]]
[auto-ap.ssr.transaction.edit :refer [get-vendor check-vendor-default-account clientize-vendor [auto-ap.ssr.transaction.edit :refer [account-typeahead* apply-rule
transaction-account-row* location-select* account-typeahead* check-vendor-default-account
match-payment match-autopay-invoices match-unpaid-invoices apply-rule unlink-payment]] clientize-vendor get-vendor
location-select*
match-autopay-invoices match-payment
match-unpaid-invoices
transaction-account-row*
unlink-payment]]
[clojure.test :as t] [clojure.test :as t]
[datomic.api :as dc])) [datomic.api :as dc]))
(t/use-fixtures :each (fn [f] #_(t/use-fixtures :each (fn [f]
(dc/transact conn [{:db/id "vendor-id" (dc/transact conn [{:db/id "vendor-id"
:vendor/name "Test Vendor" :vendor/name "Test Vendor"
:vendor/default-account {:db/id "account-id"}} :vendor/default-account {:db/id "account-id"}}
@@ -20,6 +23,7 @@
:client/locations ["Z" "E"] :client/locations ["Z" "E"]
:client/bank-accounts ["bank-account-id"]}]) :client/bank-accounts ["bank-account-id"]}])
(f))) (f)))
(t/use-fixtures :each wrap-setup)
(t/deftest get-vendor-test (t/deftest get-vendor-test
(t/testing "Should fetch vendor details" (t/testing "Should fetch vendor details"
@@ -68,7 +72,7 @@
(let [typeahead (account-typeahead* {:name "account" :value "account-id" :client-id "client-id"})] (let [typeahead (account-typeahead* {:name "account" :value "account-id" :client-id "client-id"})]
(t/is (vector? typeahead)) (t/is (vector? typeahead))
(t/is (= 1 (count (:children typeahead)))) (t/is (= 1 (count (:children typeahead))))
(t/is (= "Search..." (:placeholder (:attrs (:children typeahead))))))) (t/is (= "Search..." (:placeholder (:attrs (:children typeahead))))))))
(t/deftest match-payment-test (t/deftest match-payment-test
(t/testing "Should match a payment to a transaction" (t/testing "Should match a payment to a transaction"