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

@@ -6,7 +6,7 @@
[auto-ap.logging :as alog]
[auto-ap.routes.admin.excel-invoices :as route]
[auto-ap.routes.utils
:refer [wrap-admin wrap-client-redirect-unauthenticated]]
:refer [wrap-admin wrap-client-redirect-unauthenticated]]
[auto-ap.ssr-routes :as ssr-routes]
[auto-ap.ssr.components :as com]
[auto-ap.ssr.components.inputs :as inputs]
@@ -16,8 +16,8 @@
[auto-ap.ssr.nested-form-params :refer [wrap-nested-form-params]]
[auto-ap.ssr.ui :refer [base-page]]
[auto-ap.ssr.utils
:refer [apply-middleware-to-all-handlers html-response wrap-form-4xx-2
wrap-schema-enforce]]
:refer [apply-middleware-to-all-handlers html-response wrap-form-4xx-2
wrap-schema-enforce]]
[auto-ap.utils :refer [by]]
[bidi.bidi :as bidi]
[clj-time.coerce :as coerce]
@@ -38,7 +38,6 @@
invoice)
(defn reset-id [i]
(update i :invoice-number
(fn [n] (if (re-matches #"#+" n)
@@ -85,7 +84,6 @@
(get (by (comp :db/id :vendor-schedule-payment-dom/client) :vendor-schedule-payment-dom/dom (:vendor/schedule-payment-dom vendor))
client-id))
(defn invoice-rows->transaction [rows user]
(->> rows
(mapcat (fn [{:keys [vendor-id total client-id date invoice-number default-location check automatically-paid-when-due account-id]}]
@@ -121,8 +119,7 @@
(let [[[bank-account]] (seq (dc/q '[:find ?ba
:in $ ?c
:where [?c :client/bank-accounts ?ba]
[?ba :bank-account/type :bank-account-type/cash]
]
[?ba :bank-account/type :bank-account-type/cash]]
(dc/db conn)
client-id))]
[:upsert-transaction #:transaction {:amount (- (:invoice/total invoice))
@@ -130,18 +127,17 @@
:client (:invoice/client invoice)
:status "POSTED"
:bank-account bank-account
:db/id #_ {:clj-kondo/ignore [:unresolved-var]} (digest/sha-256 transaction-id)
:id #_ {:clj-kondo/ignore [:unresolved-var]} (digest/sha-256 transaction-id)
:db/id #_{:clj-kondo/ignore [:unresolved-var]} (digest/sha-256 transaction-id)
:id #_{:clj-kondo/ignore [:unresolved-var]} (digest/sha-256 transaction-id)
:raw-id transaction-id
:vendor (:invoice/vendor invoice)
:description-original "Cash payment"
:date (coerce/to-date date)
:approval-status :transaction-approval-status/approved
:accounts [{:db/id (str #_ {:clj-kondo/ignore [:unresolved-var]} (digest/sha-256 transaction-id) "-account")
:accounts [{:db/id (str #_{:clj-kondo/ignore [:unresolved-var]} (digest/sha-256 transaction-id) "-account")
:transaction-account/account (:db/id (a/get-account-by-numeric-code-and-sets 21000 ["default"]))
:transaction-account/location "A"
:transaction-account/amount (Math/abs (:invoice/total invoice))}]}]))
]
:transaction-account/amount (Math/abs (:invoice/total invoice))}]}]))]
[[:propose-invoice (d-invoices/code-invoice (validate-invoice (remove-nils invoice))
account-id)]
(some-> payment remove-nils)
@@ -154,17 +150,16 @@
(map #(str/split % #"\t"))
(map #(into {} (map (fn [c k] [k c]) % columns))))
vendor-name->vendor (->>
(set (map :vendor-name tabulated))
(dc/q '[:find ?n ?v
:in $ [?n ...]
:where [?v :vendor/name ?n]]
(dc/db conn)
)
(into {}))
all-clients (merge (into {}(dc/q '[:find ?n (pull ?v [:db/id :client/locations])
:in $
:where [?v :client/name ?n]]
(dc/db conn)))
(set (map :vendor-name tabulated))
(dc/q '[:find ?n ?v
:in $ [?n ...]
:where [?v :vendor/name ?n]]
(dc/db conn))
(into {}))
all-clients (merge (into {} (dc/q '[:find ?n (pull ?v [:db/id :client/locations])
:in $
:where [?v :client/name ?n]]
(dc/db conn)))
(into {}
(dc/q '[:find ?n (pull ?v [:db/id :client/locations])
:in $
@@ -190,16 +185,16 @@
(let [parsed-invoice-rows (parse-invoice-rows excel-rows)
existing-rows (set (d-invoices/get-existing-set))
grouped-rows (group-by
(fn [i]
(cond (seq (:errors i))
:error
(fn [i]
(cond (seq (:errors i))
:error
(existing-rows [(:vendor-id i) (:client-id i) (:invoice-number i)])
:exists
(existing-rows [(:vendor-id i) (:client-id i) (:invoice-number i)])
:exists
:else
:new))
parsed-invoice-rows)
:else
:new))
parsed-invoice-rows)
vendors-not-found (->> parsed-invoice-rows
(filter #(and (nil? (:vendor-id %))
(not= "Cash" (:check %))))
@@ -208,9 +203,9 @@
(audit-transact (invoice-rows->transaction (:new grouped-rows) user)
user)
{:imported (count (:new grouped-rows))
:already-imported (count (:exists grouped-rows))
:vendors-not-found vendors-not-found
:errors (map #(dissoc % :date) (:error grouped-rows))}))
:already-imported (count (:exists grouped-rows))
:vendors-not-found vendors-not-found
:errors (map #(dissoc % :date) (:error grouped-rows))}))
(def sample "6/16/17 Acme Bread NMKT-CB 3/26/56 12:00 AM $54.00 Naschmarkt X 7/31/17 8:26 AM 8/1/17 3:57 PM 31000
6/20/17 Acme Bread NMKT-CB 3/19/58 12:00 AM $54.00 Naschmarkt X 7/31/17 8:26 AM 8/1/17 3:57 PM
@@ -218,100 +213,98 @@
(defn form* [{:keys [form-params form-errors]} & children]
(fc/start-form form-params form-errors
[:form {:hx-post (bidi/path-for ssr-routes/only-routes ::route/import) :hx-swap "outerHTML"}
[:div {:class "flex flex-col px-4 py-3 space-y-3 w-full"}
[:h1.text-2xl.mb-3.font-bold "Import invoices from excel"]
[:form {:hx-post (bidi/path-for ssr-routes/only-routes ::route/import) :hx-swap "outerHTML"}
[:div {:class "flex flex-col px-4 py-3 space-y-3 w-full"}
[:h1.text-2xl.mb-3.font-bold "Import invoices from excel"]
(fc/with-field :tsv
(com/validated-field {:label "Tab-separated invoices"
:errors (fc/field-errors)}
[:textarea {:class (hh/add-class "w-full h-96" inputs/default-input-classes) :placeholder (hiccup/raw sample)
:name (fc/field-name)
}
(fc/field-value)]))
(com/form-errors {:errors (:errors fc/*form-errors*)})
(com/validated-save-button {:color :primary
:class "place-self-end w-32"
:errors (seq form-errors)}
"Import")
children]]))
(fc/with-field :tsv
(com/validated-field {:label "Tab-separated invoices"
:errors (fc/field-errors)}
[:textarea {:class (hh/add-class "w-full h-96" inputs/default-input-classes) :placeholder (hiccup/raw sample)
:name (fc/field-name)}
(fc/field-value)]))
(com/form-errors {:errors (:errors fc/*form-errors*)})
(com/validated-save-button {:color :primary
:class "place-self-end w-32"
:errors (seq form-errors)}
"Import")
children]]))
(defn page [{:keys [form-params form-errors] :as request}]
(base-page
request
(com/page {:nav com/admin-aside-nav
:client-selection (:client-selection request)
:clients (:clients request)
:client (:client request)
:identity (:identity request)
:request request}
(com/breadcrumbs {} [:a {:href (bidi/path-for ssr-routes/only-routes ::route/page)}
"Admin"])
[:div.flex.space-x-4
(com/content-card
{:class "w-3/4"}
(form* {:form-params {}
:form-errors []}))])
"Admin"))
(base-page
request
(com/page {:nav com/admin-aside-nav
:client-selection (:client-selection request)
:clients (:clients request)
:client (:client request)
:identity (:identity request)
:request request}
(com/breadcrumbs {} [:a {:href (bidi/path-for ssr-routes/only-routes ::route/page)}
"Admin"])
[:div.flex.space-x-4
(com/content-card
{:class "w-3/4"}
(form* {:form-params {}
:form-errors []}))])
"Admin"))
(defn form [{:keys [form-params form-errors] :as request}]
(html-response
(form* {:form-params (or form-params {})
:form-errors (or form-errors [])})))
(html-response
(form* {:form-params (or form-params {})
:form-errors (or form-errors [])})))
(defn import [{:keys [form-params form-errors] :as request}]
(html-response
(let [result (bulk-upload-invoices (:tsv form-params) (:identity request))]
(form* {:form-params form-params
:form-errors form-errors}
[:div.flex.space-x-4
(com/pill {:color :primary}
(format "%d imported" (:imported result)))
(com/pill {:color :secondary}
(format "%d extant" (:already-imported result)))
(when (seq (:vendors-not-found result))
(list
(com/pill {:color :yellow
"@mouseover" "show=true"
"@mouseout" "show=false"
"x-tooltip" "{content: ()=>$refs.tooltip.innerHTML ,
allowHTML: true}" }
(html-response
(let [result (bulk-upload-invoices (:tsv form-params) (:identity request))]
(form* {:form-params form-params
:form-errors form-errors}
[:div.flex.space-x-4
(com/pill {:color :primary}
(format "%d imported" (:imported result)))
(com/pill {:color :secondary}
(format "%d extant" (:already-imported result)))
(when (seq (:vendors-not-found result))
(list
(com/pill {:color :yellow
"@mouseover" "show=true"
"@mouseout" "show=false"
"x-tooltip" "{content: ()=>$refs.tooltip.innerHTML ,
allowHTML: true}"}
(format "%d vendors not found" (count (:vendors-not-found result))))
[:template {:x-ref "tooltip"}
[:ul
(for [n (take 5 (:vendors-not-found result))]
[:li n])]]))]
(format "%d vendors not found" (count (:vendors-not-found result))))
[:template {:x-ref "tooltip"}
[:ul
(for [n (take 5 (:vendors-not-found result))]
[:li n])]]))]
(when (seq (:errors result))
(com/field {:label "Errors"}
(com/data-grid
{:headers [(com/data-grid-header {} "Date")
(com/data-grid-header {} "Invoice #")
(com/data-grid-header {} "Client")
(com/data-grid-header {} "Vendor")
(com/data-grid-header {} "Amount")
(com/data-grid-header {} "Errors")]}
(for [r (:errors result)]
(com/data-grid-row
{}
(com/data-grid-cell {} (:raw-date r))
(com/data-grid-cell {} (:invoice-number r))
(com/data-grid-cell {} (:client-name r))
(com/data-grid-cell {} (:vendor-name r))
(com/data-grid-cell {} (:amount r))
(com/data-grid-cell {} (str/join ", " (map :info (:errors r)))))))))))))
(when (seq (:errors result))
(com/field {:label "Errors"}
(com/data-grid
{:headers [(com/data-grid-header {} "Date")
(com/data-grid-header {} "Invoice #")
(com/data-grid-header {} "Client")
(com/data-grid-header {} "Vendor")
(com/data-grid-header {} "Amount")
(com/data-grid-header {} "Errors")]}
(for [r (:errors result)]
(com/data-grid-row
{}
(com/data-grid-cell {} (:raw-date r))
(com/data-grid-cell {} (:invoice-number r))
(com/data-grid-cell {} (:client-name r))
(com/data-grid-cell {} (:vendor-name r))
(com/data-grid-cell {} (:amount r))
(com/data-grid-cell {} (str/join ", " (map :info (:errors r)))))))))))))
(def key->handler
(apply-middleware-to-all-handlers
(->>
{::route/page page
::route/import (-> import
(wrap-schema-enforce :form-schema [:map [:tsv :string]])
(wrap-nested-form-params)
(wrap-form-4xx-2 form))
})
(fn [h]
(-> h
(wrap-admin)
(wrap-client-redirect-unauthenticated)))))
(apply-middleware-to-all-handlers
(->>
{::route/page page
::route/import (-> import
(wrap-schema-enforce :form-schema [:map [:tsv :string]])
(wrap-nested-form-params)
(wrap-form-4xx-2 form))})
(fn [h]
(-> h
(wrap-admin)
(wrap-client-redirect-unauthenticated)))))