Add vendor pre-population for bulk code and individual edit forms

- Add vendor-changed HTMX handlers for both bulk code and individual edit
- Pre-populate default account at 100% when vendor is selected and no accounts exist
- Fix render-accounts-section to render from step-params correctly
- Change bulk code vendor-changed from hx-get to hx-post to include form data
- Add routes for vendor-changed endpoints
- Update e2e tests to cover vendor pre-population
- Run lein cljfmt fix across codebase
This commit is contained in:
2026-05-21 14:45:19 -07:00
parent 8bd0cee1b1
commit ba87805d4c
210 changed files with 8694 additions and 9627 deletions

View File

@@ -1,4 +1,4 @@
(ns auto-ap.ssr.dashboard
(ns auto-ap.ssr.dashboard
(:require
[auto-ap.datomic :refer [conn]]
[auto-ap.graphql.ledger :refer [get-profit-and-loss-raw]]
@@ -25,11 +25,11 @@
[hiccup.util :as hu]))
(defn bank-accounts-card [request]
(html-response
(html-response
(com/card {:class "h-full"}
[:div.p-4.h-full
[:h1.text-2xl.font-bold "Bank Accounts"]
[:div (hx/htmx-transition-appear {:class "h-full overflow-scroll" })
[:div (hx/htmx-transition-appear {:class "h-full overflow-scroll"})
(for [c (:valid-trimmed-client-ids request)
b (:client/bank-accounts (dc/pull (dc/db conn) '[{:client/bank-accounts
@@ -58,43 +58,42 @@
(#(str "Synced " %)))]
#_(when-let [n (cond (-> b :bank-account/intuit-bank-account)
"Intuit"
(-> b :bank-account/yodlee-account)
"Yodlee"
(-> b :bank-account/plaid-account)
"Plaid"
:else
nil)]
(list
[:div (str n " Balance")]
[:div.text-right (format "$%,.2f" (or (-> b :bank-account/intuit-bank-account :intuit-bank-account/current-balance)
(-> b :bank-account/yodlee-account :yodlee-account/available-balance)
(-> b :bank-account/plaid-account :plaid-account/balance)
0.0))]
"Intuit"
(-> b :bank-account/yodlee-account)
"Yodlee"
(-> b :bank-account/plaid-account)
"Plaid"
:else
nil)]
(list
[:div (str n " Balance")]
[:div.text-right (format "$%,.2f" (or (-> b :bank-account/intuit-bank-account :intuit-bank-account/current-balance)
(-> b :bank-account/yodlee-account :yodlee-account/available-balance)
(-> b :bank-account/plaid-account :plaid-account/balance)
0.0))]
[:div.text-xs.text-gray-400.text-right (or (some-> (:bank-account/intuit-bank-account b)
(:intuit-bank-account/last-synced)
(atime/unparse-local atime/standard-time)
(#(str "Synced " %)))
(some-> (:bank-account/yodlee-account b)
(:yodlee-account/last-synced)
(atime/unparse-local atime/standard-time)
(#(str "Synced " %)))
(some-> (:bank-account/plaid-account b)
(:plaid-account/last-synced)
(atime/unparse-local atime/standard-time)
(#(str "Synced " %))))]
(when-let [pending-balance (-> b :bank-account/yodlee-account :yodlee-account/pending-balance)]
(list
[:div (str n " Pending Txs")]
[:div.text-right (format "$%,.2f" pending-balance)]))
[:div.inline-flex.justify-end.text-xs.text-gray-400.it]))
[:div.text-xs.text-gray-400.text-right (or (some-> (:bank-account/intuit-bank-account b)
(:intuit-bank-account/last-synced)
(atime/unparse-local atime/standard-time)
(#(str "Synced " %)))
(some-> (:bank-account/yodlee-account b)
(:yodlee-account/last-synced)
(atime/unparse-local atime/standard-time)
(#(str "Synced " %)))
(some-> (:bank-account/plaid-account b)
(:plaid-account/last-synced)
(atime/unparse-local atime/standard-time)
(#(str "Synced " %))))]
(when-let [pending-balance (-> b :bank-account/yodlee-account :yodlee-account/pending-balance)]
(list
[:div (str n " Pending Txs")]
[:div.text-right (format "$%,.2f" pending-balance)]))
[:div.inline-flex.justify-end.text-xs.text-gray-400.it]))
#_[:div.inline-flex.justify-between.items-baseline]]])]])))
(defn sales-chart-card [request]
(html-response
(let [ totals
(html-response
(let [totals
(->> (dc/q '[:find ?sd (sum ?total)
:with ?e
:in $ [?clients ?start-date ?end-date]
@@ -113,7 +112,7 @@
[:canvas.w-full.h-full.p-8 {:x-data (hx/json {:chart nil
:labels (map first totals)
:data (map second totals)})
:x-init
:x-init
"new Chart($el, {
type: 'bar',
data: {
@@ -136,8 +135,8 @@
});"}]]))))
(defn expense-pie-card [request]
(html-response
(let [ totals
(html-response
(let [totals
(->> (dc/q '[:find ?an (sum ?amt)
:with ?iea
:in $ [?clients ?start-date ?end-date]
@@ -179,19 +178,18 @@
});"}]]))))
(defn pnl-card [request]
(html-response
(com/card {:class "w-full h-full p-4"}
(html-response
(com/card {:class "w-full h-full p-4"}
[:h1.text-2xl.font-bold.text-gray-700
"Profit and Loss, last month" ]
(let [ data (<-graphql (get-profit-and-loss-raw (:valid-trimmed-client-ids request)
[{:start (time/plus (time/now) (time/months -1))
:end (time/now)}]))
"Profit and Loss, last month"]
(let [data (<-graphql (get-profit-and-loss-raw (:valid-trimmed-client-ids request)
[{:start (time/plus (time/now) (time/months -1))
:end (time/now)}]))
data (r/->PNLData {} (:accounts (first (:periods data))) {})
sales (r/aggregate-accounts (r/filter-categories data [ :sales]))
expenses (r/aggregate-accounts (r/filter-categories data [ :cogs :payroll :controllable :fixed-overhead :ownership-controllable ]))]
(list
#_(when (not= (count all-clients) (count clients))
)
sales (r/aggregate-accounts (r/filter-categories data [:sales]))
expenses (r/aggregate-accounts (r/filter-categories data [:cogs :payroll :controllable :fixed-overhead :ownership-controllable]))]
(list
#_(when (not= (count all-clients) (count clients)))
[:canvas.w-full.h-full.p-8 {:x-data (hx/json {:chart nil
:labels [(format "Income $%,.2f" sales) (format "Expenses $%,.2f" expenses)]
:data [sales expenses]})
@@ -217,12 +215,12 @@
}
}
});"}]
[:div
[:div
"Income: " (format "$%,.2f" sales)]
[:div
[:div
"Expenses: " (format "$%,.2f" expenses)])))))
(defn tasks-card [request]
(html-response
(html-response
(com/card {:class "w-full h-full p-4"}
[:h1.text-2xl.font-bold.text-gray-700
"Tasks"]
@@ -237,7 +235,7 @@
[(:valid-trimmed-client-ids request)
(coerce/to-date (time/with-time-at-start-of-day (time/plus (time/now) (time/years -1))))
nil]))
[uncategorized-transaction-count uncategorized-transaction-amount]
(first (dc/q '[:find (count ?e) (sum ?am)
:in $ [?clients ?start-date ?end-date]
@@ -248,25 +246,23 @@
[(:valid-trimmed-client-ids request)
(coerce/to-date (time/with-time-at-start-of-day (time/plus (time/now) (time/years -1))))
nil]))]
(list
(list
(when (not= 0 (or unpaid-invoice-count 0))
[:div.bg-gray-50.rounded.p-4
[:span "You have " (str unpaid-invoice-count) " unpaid invoices with an outstanding balance of " (format "$%,.2f" unpaid-invoice-amount) ". " ]
[:span "You have " (str unpaid-invoice-count) " unpaid invoices with an outstanding balance of " (format "$%,.2f" unpaid-invoice-amount) ". "]
(com/link {:href (hu/url (bidi.bidi/path-for ssr-routes/only-routes ::i-routes/unpaid-page)
{:date-range "year"})
}
"Pay now")
])
{:date-range "year"})}
"Pay now")])
(when (not= 0 (or uncategorized-transaction-count 0))
[:div.bg-gray-50.rounded.p-4
[:span "You have " (str uncategorized-transaction-count) " transactions needing your feedback. " ]
[:span "You have " (str uncategorized-transaction-count) " transactions needing your feedback. "]
(com/link {:href (str (bidi.bidi/path-for ssr-routes/only-routes ::transaction-routes/requires-feedback-page)
"?date-range="
(url/url-encode (pr-str {:start (atime/unparse-local (time/plus (time/now) (time/years -1)) atime/iso-date) :end (atime/unparse-local (time/now) atime/iso-date)}))) }
(url/url-encode (pr-str {:start (atime/unparse-local (time/plus (time/now) (time/years -1)) atime/iso-date) :end (atime/unparse-local (time/now) atime/iso-date)})))}
"Review now")])))])))
(defn stub-card [params & children]
@@ -280,35 +276,33 @@
[:div.htmx-indicator (svg/spinner {:class "inline w-32 h-32 text-green-500"})]]))
(defn- page-contents [request]
[:div.mb-8
[:div {:class "grid grid-cols-1 lg:grid-cols-2 2xl:grid-cols-3 gap-4 mb-8"}
[:div.h-96 (stub-card {:title "Expenses"
:hx-get (bidi.bidi/path-for ssr-routes/only-routes ::d-routes/expense-card)
:hx-trigger "load"} )]
[:div.h-96
(stub-card {:title "Tasks"
:hx-get (bidi.bidi/path-for ssr-routes/only-routes ::d-routes/tasks-card)
:hx-trigger "load"} )]
[:div {:class " row-span-2 h-[49rem]"}
(stub-card {:title "Bank Accounts"
:hx-get (bidi.bidi/path-for ssr-routes/only-routes ::d-routes/bank-accounts-card)
:hx-trigger "load"} )
]
[:div.h-96
(stub-card {:title "Gross Sales, last 14 days"
:hx-get (bidi.bidi/path-for ssr-routes/only-routes ::d-routes/sales-card)
:hx-trigger "load"})
]
[:div.h-96
(stub-card {:title "Profit and Loss, last month"
:hx-get (bidi.bidi/path-for ssr-routes/only-routes ::d-routes/pnl-card)
:hx-trigger "load"}) ]
[:div.col-span-2.h-96
(stub-card {:title "Expense breakdown"
:hx-get (bidi.bidi/path-for ssr-routes/only-routes :company-expense-report-breakdown-card)
:hx-trigger "load"} )]
[:div]] ])
[:div.mb-8
[:div {:class "grid grid-cols-1 lg:grid-cols-2 2xl:grid-cols-3 gap-4 mb-8"}
[:div.h-96 (stub-card {:title "Expenses"
:hx-get (bidi.bidi/path-for ssr-routes/only-routes ::d-routes/expense-card)
:hx-trigger "load"})]
[:div.h-96
(stub-card {:title "Tasks"
:hx-get (bidi.bidi/path-for ssr-routes/only-routes ::d-routes/tasks-card)
:hx-trigger "load"})]
[:div {:class " row-span-2 h-[49rem]"}
(stub-card {:title "Bank Accounts"
:hx-get (bidi.bidi/path-for ssr-routes/only-routes ::d-routes/bank-accounts-card)
:hx-trigger "load"})]
[:div.h-96
(stub-card {:title "Gross Sales, last 14 days"
:hx-get (bidi.bidi/path-for ssr-routes/only-routes ::d-routes/sales-card)
:hx-trigger "load"})]
[:div.h-96
(stub-card {:title "Profit and Loss, last month"
:hx-get (bidi.bidi/path-for ssr-routes/only-routes ::d-routes/pnl-card)
:hx-trigger "load"})]
[:div.col-span-2.h-96
(stub-card {:title "Expense breakdown"
:hx-get (bidi.bidi/path-for ssr-routes/only-routes :company-expense-report-breakdown-card)
:hx-trigger "load"})]
[:div]]])
(defn page [request]
(base-page
@@ -334,12 +328,12 @@
"Dashboard"))
(def key->handler
( apply-middleware-to-all-handlers
{::d-routes/page page
::d-routes/expense-card expense-pie-card
::d-routes/pnl-card pnl-card
::d-routes/sales-card sales-chart-card
::d-routes/bank-accounts-card bank-accounts-card
::d-routes/tasks-card tasks-card }
(fn [h]
(wrap-client-redirect-unauthenticated (wrap-admin h)))))
(apply-middleware-to-all-handlers
{::d-routes/page page
::d-routes/expense-card expense-pie-card
::d-routes/pnl-card pnl-card
::d-routes/sales-card sales-chart-card
::d-routes/bank-accounts-card bank-accounts-card
::d-routes/tasks-card tasks-card}
(fn [h]
(wrap-client-redirect-unauthenticated (wrap-admin h)))))