Makes beginning of new invoice

This commit is contained in:
Bryce
2024-03-20 22:36:14 -07:00
parent 9ea70005cb
commit 2891503377
5 changed files with 525 additions and 172 deletions

View File

@@ -22,7 +22,7 @@
(defn get-all-graphql [context args _] (defn get-all-graphql [context args _]
(assert-admin (:id context)) (assert-admin (:id context))
(let [args (assoc args :id (:id context)) (let [args (assoc args :id (:id context))
[accounts _ ] (d-accounts/get-graphql (assoc (<-graphql args) :per-page Integer/MAX_VALUE))] [accounts _] (d-accounts/get-graphql (assoc (<-graphql args) :per-page Integer/MAX_VALUE))]
(map ->graphql accounts))) (map ->graphql accounts)))
(defn default-for-vendor [context args _] (defn default-for-vendor [context args _]
@@ -31,34 +31,32 @@
(->graphql (d-accounts/clientize result (:client_id args))))) (->graphql (d-accounts/clientize result (:client_id args)))))
(def search-pattern [:db/id (def search-pattern [:db/id
:account/numeric-code :account/numeric-code
:account/location :account/location
{:account/vendor-allowance [:db/ident] {:account/vendor-allowance [:db/ident]
:account/default-allowance [:db/ident] :account/default-allowance [:db/ident]
:account/invoice-allowance [:db/ident]}]) :account/invoice-allowance [:db/ident]}])
(defn search- [id query client] (defn search- [id query client]
(let [client-part (if (some->> client (can-see-client? id)) (let [client-part (if (some->> client (can-see-client? id))
(format "((applicability:(global OR optional) AND -client_id:*) OR (account_client_override_id:* AND client_id:%s))" client) (format "((applicability:(global OR optional) AND -client_id:*) OR (account_client_override_id:* AND client_id:%s))" client)
"(applicability:(global OR optional) AND -client_id:*)" "(applicability:(global OR optional) AND -client_id:*)")
)
query (format "_text_:(%s) AND %s" (cleanse-query query) client-part)] query (format "_text_:(%s) AND %s" (cleanse-query query) client-part)]
(mu/log ::searching :search-query query) (mu/log ::searching :search-query query)
(for [{:keys [account_id name] :as g} (solr/query solr/impl "accounts" (for [{:keys [account_id name] :as g} (solr/query solr/impl "accounts"
{"query" query {"query" query
"fields" "id, name, client_id, numeric_code, applicability, account_id"})] "fields" "id, name, client_id, numeric_code, applicability, account_id"})]
{:account_id (first account_id) {:account_id (first account_id)
:name (first name)}))) :name (first name)})))
(defn search [context {query :query client :client_id allowance :allowance vendor-id :vendor_id} _] (defn search [context {query :query client :client_id allowance :allowance vendor-id :vendor_id} _]
(when client (when client
(assert-can-see-client (:id context) client)) (assert-can-see-client (:id context) client))
(let [num (some-> (re-find #"([0-9]+)" query) (let [num (some-> (re-find #"([0-9]+)" query)
second second
(not-empty ) (not-empty)
Integer/parseInt) Integer/parseInt)
valid-allowances (cond-> #{:allowance/allowed valid-allowances (cond-> #{:allowance/allowed
:allowance/warn} :allowance/warn}
(is-admin? (:id context)) (conj :allowance/admin-only)) (is-admin? (:id context)) (conj :allowance/admin-only))
@@ -71,77 +69,76 @@
vendor-account (when vendor-id vendor-account (when vendor-id
(-> (dc/q '[:find ?da (-> (dc/q '[:find ?da
:in $ ?v :in $ ?v
:where [?v :vendor/default-account ?da]] :where [?v :vendor/default-account ?da]]
(dc/db conn) (dc/db conn)
vendor-id) vendor-id)
ffirst)) ffirst))
xform (comp xform (comp
(filter (fn [[_ a]] (filter (fn [[_ a]]
(or (or
(valid-allowances (-> a allowance :db/ident)) (valid-allowances (-> a allowance :db/ident))
(= (:db/id a) vendor-account)))) (= (:db/id a) vendor-account))))
(map (fn [[n a]] (map (fn [[n a]]
{:name (str (:account/numeric-code a) " - " n) {:name (str (:account/numeric-code a) " - " n)
:id (:db/id a) :id (:db/id a)
:location (:account/location a) :location (:account/location a)
:warning (when (= :allowance/warn (-> a allowance :db/ident)) :warning (when (= :allowance/warn (-> a allowance :db/ident))
"This account is not typically used for this purpose.")})))] "This account is not typically used for this purpose.")})))]
(if query (if query
(if num (if num
(->> (dc/q '[:find ?n (pull ?i pattern) (->> (dc/q '[:find ?n (pull ?i pattern)
:in $ ?numeric-code ?allowance pattern :in $ ?numeric-code ?allowance pattern
:where [?i :account/numeric-code ?numeric-code] :where [?i :account/numeric-code ?numeric-code]
[?i :account/name ?n] [?i :account/name ?n]
(or [?i :account/applicability :account-applicability/global] (or [?i :account/applicability :account-applicability/global]
[?i :account/applicability :account-applicability/optional] [?i :account/applicability :account-applicability/optional]
[?i :account/applicability :account-applicability/customized])] [?i :account/applicability :account-applicability/customized])]
(dc/db conn) (dc/db conn)
num num
allowance allowance
search-pattern) search-pattern)
(sequence xform)) (sequence xform))
(->> (search- (:id context) query client) (->> (search- (:id context) query client)
(sequence (sequence
(comp (map (fn [i] [(:name i) (dc/pull (dc/db conn) search-pattern (:account_id i))])) (comp (map (fn [i] [(:name i) (dc/pull (dc/db conn) search-pattern (:account_id i))]))
xform)))) xform))))
[]))) [])))
(defn rebuild-search-index [] (defn rebuild-search-index []
(solr/index-documents-raw (solr/index-documents-raw
solr/impl solr/impl
"accounts" "accounts"
(for [result (map first (dc/qseq {:query '[:find (pull ?aco [:account-client-override/search-terms :account-client-override/client :db/id {:account/_client-overrides [:account/numeric-code :account/location :db/id {:account/applicability [:db/ident]}]}]) (for [result (map first (dc/qseq {:query '[:find (pull ?aco [:account-client-override/search-terms :account-client-override/client :db/id {:account/_client-overrides [:account/numeric-code :account/location :db/id {:account/applicability [:db/ident]}]}])
:in $ :in $
:where [?aco :account-client-override/client ] :where [?aco :account-client-override/client]
[?aco :account-client-override/search-terms ] [?aco :account-client-override/search-terms]
[_ :account/client-overrides ?aco]] [_ :account/client-overrides ?aco]]
:args [(dc/db conn)]})) :args [(dc/db conn)]}))
:when (:account/numeric-code (:account/_client-overrides result))] :when (:account/numeric-code (:account/_client-overrides result))]
{"id" (:db/id result) {"id" (:db/id result)
"account_id" (:db/id (:account/_client-overrides result)) "account_id" (:db/id (:account/_client-overrides result))
"account_client_override_id" (str (:db/id result)) "account_client_override_id" (str (:db/id result))
"name" (:account-client-override/search-terms result) "name" (:account-client-override/search-terms result)
"client_id" (str (:db/id (:account-client-override/client result))) "client_id" (str (:db/id (:account-client-override/client result)))
"numeric_code" (:account/numeric-code (:account/_client-overrides result)) "numeric_code" (:account/numeric-code (:account/_client-overrides result))
"location" (:account/location (:account/_client-overrides result)) "location" (:account/location (:account/_client-overrides result))
"applicability" (name (:db/ident (:account/applicability (:account/_client-overrides result))))})) "applicability" (name (:db/ident (:account/applicability (:account/_client-overrides result))))}))
(solr/index-documents-raw (solr/index-documents-raw
solr/impl solr/impl
"accounts" "accounts"
(for [result (map first (dc/qseq {:query '[:find (pull ?a [:account/numeric-code (for [result (map first (dc/qseq {:query '[:find (pull ?a [:account/numeric-code
:account/search-terms :account/search-terms
{:account/applicability [:db/ident]} {:account/applicability [:db/ident]}
:db/id :db/id
:account/location]) :account/location])
:in $ :in $
:where [?a :account/search-terms ]] :where [?a :account/search-terms]]
:args [(dc/db conn)]})) :args [(dc/db conn)]}))
:when (:account/search-terms result) :when (:account/search-terms result)]
] {"id" (:db/id result)
{"id" (:db/id result) "account_id" (:db/id result)
"account_id" (:db/id result) "name" (:account/search-terms result)
"name" (:account/search-terms result) "numeric_code" (:account/numeric-code result)
"numeric_code" (:account/numeric-code result) "location" (:account/location result)
"location" (:account/location result) "applicability" (name (:db/ident (:account/applicability result)))})))
"applicability" (name (:db/ident (:account/applicability result)))})))

View File

@@ -21,18 +21,18 @@
(defn can-user-edit-vendor? [vendor-id id] (defn can-user-edit-vendor? [vendor-id id]
(if (is-admin? id) (if (is-admin? id)
true true
(empty? (empty?
(set/difference (set (->> (dc/q '[:find ?c (set/difference (set (->> (dc/q '[:find ?c
:in $ ?v :in $ ?v
:where [?vu :vendor-usage/vendor ?v] :where [?vu :vendor-usage/vendor ?v]
[?vu :vendor-usage/client ?c] [?vu :vendor-usage/client ?c]
[?vu :vendor-usage/count ?d] [?vu :vendor-usage/count ?d]
[(>= ?d 0)]] [(>= ?d 0)]]
(dc/db conn) (dc/db conn)
vendor-id) vendor-id)
(map first))) (map first)))
(set (map :db/id (:user/clients id))))))) (set (map :db/id (:user/clients id)))))))
(defn upsert-vendor [context {{:keys [id name hidden terms code print_as primary_contact plaid_merchant secondary_contact address default_account_id invoice_reminder_schedule schedule_payment_dom terms_overrides account_overrides] :as in} :vendor} _] (defn upsert-vendor [context {{:keys [id name hidden terms code print_as primary_contact plaid_merchant secondary_contact address default_account_id invoice_reminder_schedule schedule_payment_dom terms_overrides account_overrides] :as in} :vendor} _]
(when (and id (not (can-user-edit-vendor? id (:id context)))) (when (and id (not (can-user-edit-vendor? id (:id context))))
@@ -63,11 +63,11 @@
hidden hidden
false) false)
terms-overrides (mapv terms-overrides (mapv
(fn [to] (fn [to]
#:vendor-terms-override {:client (:client_id to) #:vendor-terms-override {:client (:client_id to)
:terms (:terms to) :terms (:terms to)
:db/id (or (:id to) (random-tempid))}) :db/id (or (:id to) (random-tempid))})
terms_overrides) terms_overrides)
account-overrides (mapv account-overrides (mapv
(fn [ao] (fn [ao]
#:vendor-account-override {:client (:client_id ao) #:vendor-account-override {:client (:client_id ao)
@@ -75,11 +75,11 @@
:db/id (or (:id ao) (random-tempid))}) :db/id (or (:id ao) (random-tempid))})
account_overrides) account_overrides)
schedule-payment-dom (mapv schedule-payment-dom (mapv
(fn [ao] (fn [ao]
#:vendor-schedule-payment-dom {:client (:client_id ao) #:vendor-schedule-payment-dom {:client (:client_id ao)
:dom (:dom ao) :dom (:dom ao)
:db/id (or (:id ao) (random-tempid))}) :db/id (or (:id ao) (random-tempid))})
schedule_payment_dom) schedule_payment_dom)
transaction [:upsert-entity (cond-> #:vendor {:db/id (if id transaction [:upsert-entity (cond-> #:vendor {:db/id (if id
id id
"vendor") "vendor")
@@ -114,41 +114,40 @@
"secondary") "secondary")
:name (:name secondary_contact) :name (:name secondary_contact)
:phone (:phone secondary_contact) :phone (:phone secondary_contact)
:email (:email secondary_contact)}) :email (:email secondary_contact)}))
)
:search-terms [name]} :search-terms [name]}
(is-admin? (:id context)) (assoc (is-admin? (:id context)) (assoc
:vendor/legal-entity-name (:legal_entity_name in) :vendor/legal-entity-name (:legal_entity_name in)
:vendor/legal-entity-first-name (:legal_entity_first_name in) :vendor/legal-entity-first-name (:legal_entity_first_name in)
:vendor/legal-entity-middle-name (:legal_entity_middle_name in) :vendor/legal-entity-middle-name (:legal_entity_middle_name in)
:vendor/legal-entity-last-name (:legal_entity_last_name in) :vendor/legal-entity-last-name (:legal_entity_last_name in)
:vendor/legal-entity-tin (:legal_entity_tin in) :vendor/legal-entity-tin (:legal_entity_tin in)
:vendor/legal-entity-tin-type (enum->keyword (:legal_entity_tin_type in) "legal-entity-tin-type") :vendor/legal-entity-tin-type (enum->keyword (:legal_entity_tin_type in) "legal-entity-tin-type")
:vendor/legal-entity-1099-type (enum->keyword (:legal_entity_1099_type in) "legal-entity-1099-type") :vendor/legal-entity-1099-type (enum->keyword (:legal_entity_1099_type in) "legal-entity-1099-type")
:vendor/plaid-merchant plaid_merchant :vendor/plaid-merchant plaid_merchant
:vendor/account-overrides account-overrides :vendor/account-overrides account-overrides
:vendor/terms-overrides terms-overrides :vendor/terms-overrides terms-overrides
:vendor/schedule-payment-dom schedule-payment-dom :vendor/schedule-payment-dom schedule-payment-dom
:vendor/automatically-paid-when-due (:automatically_paid_when_due in)))] :vendor/automatically-paid-when-due (:automatically_paid_when_due in)))]
transaction-result (audit-transact [transaction] (:id context)) transaction-result (audit-transact [transaction] (:id context))
new-vendor (d-vendors/get-by-id (or (-> transaction-result :tempids (get "vendor")) new-vendor (d-vendors/get-by-id (or (-> transaction-result :tempids (get "vendor"))
id))] id))]
(auto-ap.solr/index-documents-raw (auto-ap.solr/index-documents-raw
auto-ap.solr/impl auto-ap.solr/impl
"vendors" "vendors"
[{"id" (:db/id new-vendor) [{"id" (:db/id new-vendor)
"name" (:vendor/name new-vendor) "name" (:vendor/name new-vendor)
"hidden" (boolean (:vendor/hidden new-vendor))}]) "hidden" (boolean (:vendor/hidden new-vendor))}])
(-> new-vendor (-> new-vendor
(->graphql)))) (->graphql))))
(defn merge-vendors [context {:keys [from to]} _] (defn merge-vendors [context {:keys [from to]} _]
(let [transaction (->> (dc/q {:find '[?x ?a2] (let [transaction (->> (dc/q {:find '[?x ?a2]
:in '[$ ?vendor-from ] :in '[$ ?vendor-from]
:where ['[?x ?a ?vendor-from] :where ['[?x ?a ?vendor-from]
'[?a :db/ident ?a2]]} '[?a :db/ident ?a2]]}
(dc/db conn) (dc/db conn)
@@ -165,13 +164,13 @@
(defn get-graphql [context args _] (defn get-graphql [context args _]
(assert-admin (:id context)) (assert-admin (:id context))
(let [args (assoc args :id (:id context)) (let [args (assoc args :id (:id context))
[vendors vendors-count ] (d-vendors/get-graphql (<-graphql args))] [vendors vendors-count] (d-vendors/get-graphql (<-graphql args))]
(result->page vendors vendors-count :vendors args))) (result->page vendors vendors-count :vendors args)))
(defn get-by-id [context args _] (defn get-by-id [context args _]
(->graphql (->graphql
(d-vendors/get-graphql-by-id (assoc args :id (:id context)) (d-vendors/get-graphql-by-id (assoc args :id (:id context))
(:id args)))) (:id args))))
(defn partial-match-first [query matches] (defn partial-match-first [query matches]
(if-let [best-match (->> matches (if-let [best-match (->> matches
@@ -187,7 +186,7 @@
(defn search [context args _] (defn search [context args _]
(if-let [query (not-empty (cleanse-query (:query args)))] (if-let [query (not-empty (cleanse-query (:query args)))]
(let [search-query (str "name:(" query ")")] (let [search-query (str "name:(" query ")")]
(for [{:keys [id name]} (solr/query solr/impl "vendors" {"query" (cond-> search-query (for [{:keys [id name]} (solr/query solr/impl "vendors" {"query" (cond-> search-query
(not (is-admin? (:id context))) (str " hidden:false")) (not (is-admin? (:id context))) (str " hidden:false"))
@@ -210,4 +209,4 @@
:args [(dc/db conn)]})] :args [(dc/db conn)]})]
{"id" (:db/id result) {"id" (:db/id result)
"name" (:vendor/name result) "name" (:vendor/name result)
"hidden" (boolean (:vendor/hidden result))})))) "hidden" (boolean (:vendor/hidden result))}))))

View File

@@ -3,7 +3,7 @@
[auto-ap.datomic [auto-ap.datomic
:refer [add-sorter-fields apply-pagination apply-sort-3 :refer [add-sorter-fields apply-pagination apply-sort-3
audit-transact conn merge-query observable-query audit-transact conn merge-query observable-query
pull-many]] pull-attr pull-many]]
[auto-ap.datomic.accounts :as d-accounts] [auto-ap.datomic.accounts :as d-accounts]
[auto-ap.datomic.bank-accounts :as d-bank-accounts] [auto-ap.datomic.bank-accounts :as d-bank-accounts]
[auto-ap.datomic.invoices :as d-invoices] [auto-ap.datomic.invoices :as d-invoices]
@@ -23,6 +23,7 @@
:refer [wrap-admin wrap-client-redirect-unauthenticated]] :refer [wrap-admin wrap-client-redirect-unauthenticated]]
[auto-ap.solr :as solr] [auto-ap.solr :as solr]
[auto-ap.ssr-routes :as ssr-routes] [auto-ap.ssr-routes :as ssr-routes]
[auto-ap.ssr.common-handlers :refer [add-new-entity-handler]]
[auto-ap.ssr.components :as com] [auto-ap.ssr.components :as com]
[auto-ap.ssr.components.link-dropdown :refer [link-dropdown]] [auto-ap.ssr.components.link-dropdown :refer [link-dropdown]]
[auto-ap.ssr.components.multi-modal :as mm] [auto-ap.ssr.components.multi-modal :as mm]
@@ -390,6 +391,7 @@
(pay-button* {:ids (selected->ids request (pay-button* {:ids (selected->ids request
(:query-params request))}))) (:query-params request))})))
;; TODO test as a real user
(def grid-page (def grid-page
(helper/build {:id "entity-table" (helper/build {:id "entity-table"
:nav com/main-aside-nav :nav com/main-aside-nav
@@ -412,7 +414,10 @@
"Void selected")) "Void selected"))
(when (can? (:identity request) {:subject :invoice :activity :pay}) (when (can? (:identity request) {:subject :invoice :activity :pay})
(pay-button* {:ids (selected->ids request (pay-button* {:ids (selected->ids request
(:query-params request))}))]) (:query-params request))}))
(when (can? (:identity request) {:subject :invoice :activity :create})
(com/button {:hx-get (bidi/path-for ssr-routes/only-routes ::route/new-wizard)}
"New invoice"))])
:row-buttons (fn [_ entity] :row-buttons (fn [_ entity]
[(when (= :invoice-status/unpaid (:invoice/status entity)) [(when (= :invoice-status/unpaid (:invoice/status entity))
(com/icon-button {:hx-delete (bidi/path-for ssr-routes/only-routes (com/icon-button {:hx-delete (bidi/path-for ssr-routes/only-routes
@@ -923,7 +928,7 @@
:validation-route ::route/pay-wizard-navigate))) :validation-route ::route/pay-wizard-navigate)))
(defn add-handwritten-check [request wizard snapshot] (defn add-handwritten-check [request wizard snapshot]
(let [invoices (d-invoices/get-multi (map :invoice-id (:invoices snapshot))) ;; TODO shouldn't need datomic (let [invoices (d-invoices/get-multi (map :invoice-id (:invoices snapshot)))
bank-account-id (:bank-account snapshot) bank-account-id (:bank-account snapshot)
bank-account (d-bank-accounts/get-by-id bank-account-id) bank-account (d-bank-accounts/get-by-id bank-account-id)
_ (when-not (= 1 (count (set (map (comp :db/id :invoice/vendor) invoices)))) _ (when-not (= 1 (count (set (map (comp :db/id :invoice/vendor) invoices))))
@@ -960,6 +965,7 @@
;; Thought is to put it into the pay function itself ;; Thought is to put it into the pay function itself
;; balance should only go to negative if total was negative ;; balance should only go to negative if total was negative
;; balance should stay positive if total was positive ;; balance should stay positive if total was positive
;; NOTE: payable-ids function could be used.
;; TODO support crediting from balance ;; TODO support crediting from balance
(defrecord PayWizard [form-params current-step invoice-by-id] (defrecord PayWizard [form-params current-step invoice-by-id]
@@ -1057,7 +1063,7 @@
:payment-type/cash :payment-type/cash
(= :credit (:method snapshot)) (= :credit (:method snapshot))
:payment-type/credit :payment-type/credit
:else :payment-type/debit) ;; TODO might not be right :else :payment-type/debit)
identity)))] identity)))]
(modal-response (modal-response
(com/modal {} (com/modal {}
@@ -1080,6 +1086,325 @@
(def pay-wizard (def pay-wizard
(->PayWizard nil nil nil)) (->PayWizard nil nil nil))
(def new-form-schema
[:map
[:invoice/client [:entity-map {:pull [:db/id :client/name :client/accounts]}]]
[:invoice/vendor [:entity-map {:pull [:db/id :vendor/name]}]]])
(defrecord BasicDetailsStep [linear-wizard]
mm/ModalWizardStep
(step-name [_]
"Basic Details")
(step-key [_]
:basic-details)
(edit-path [_ _]
[])
(step-schema [_]
(mut/select-keys (mm/form-schema linear-wizard) #{:invoice/client :invoice/vendor}))
(render-step [this request]
(mm/default-render-step
linear-wizard this
:head [:div.p-2 "New invoice"]
:body (mm/default-step-body
{}
[:div {}
(fc/with-field :invoice/client
(if (:client request)
(com/hidden {:name (fc/field-name)
:value (:db/id (:client request))})
(com/validated-field
{:label "Client"
:errors (fc/field-errors)}
[:div.w-96
(com/typeahead {:name (fc/field-name)
:error? (fc/error?)
:class "w-96"
:placeholder "Search..."
:url (bidi/path-for ssr-routes/only-routes :company-search)
:value (fc/field-value)
:content-fn (fn [c] (pull-attr (dc/db conn) :client/name c))})])))
(fc/with-field :invoice/vendor
(com/validated-field
{:label "Vendor"
:errors (fc/field-errors)}
[:div.w-96
(com/typeahead {:name (fc/field-name)
:error? (fc/error?)
:class "w-96"
:placeholder "Search..."
:url (bidi/path-for ssr-routes/only-routes :vendor-search)
:value (fc/field-value)
:content-fn (fn [c] (pull-attr (dc/db conn) :vendor/name c))})]))
(fc/with-field :invoice/date
(com/validated-field
{:label "Date"
:errors (fc/field-errors)}
[:div {:class "w-24"}
(com/date-input {:value (-> (fc/field-value)
(atime/unparse-local atime/normal-date))
:name (fc/field-name)
:error? (fc/field-errors)
:placeholder "1/1/2024"})]))
(fc/with-field :invoice/due
(com/validated-field
{:label "Due (optional)"
:errors (fc/field-errors)}
[:div {:class "w-24"}
(com/date-input {:value (-> (fc/field-value)
(atime/unparse-local atime/normal-date))
:name (fc/field-name)
:error? (fc/field-errors)
:placeholder "1/1/2024"})]))
(fc/with-field :invoice/scheduled-payment
(com/validated-field
{:label "Scheduled payment (optional)"
:errors (fc/field-errors)}
[:div {:class "w-24"}
(com/date-input {:value (-> (fc/field-value)
(atime/unparse-local atime/normal-date))
:name (fc/field-name)
:error? (fc/field-errors)
:placeholder "1/1/2024"})]))
(fc/with-field :invoice/invoice-number
(com/validated-field
{:label "Invoice Number"
:errors (fc/field-errors)}
[:div {:class "w-24"}
(com/text-input {:value (-> (fc/field-value))
:name (fc/field-name)
:error? (fc/field-errors)
:placeholder "HA-123"})]))
(fc/with-field :invoice/total
(com/validated-field
{:label "Total"
:errors (fc/field-errors)}
[:div {:class "w-16"}
(com/money-input {:value (-> (fc/field-value))
:name (fc/field-name)
:class "w-24"
:error? (fc/field-errors)
:placeholder "212.44"})]))])
:footer
(mm/default-step-footer linear-wizard this :validation-route ::route/new-wizard-navigate)
:validation-route ::route/new-wizard-navigate)))
(defn- location-select*
[{:keys [name account-location client-locations value]}]
(com/select {:options (into [["" ""]]
(cond account-location
[[account-location account-location]]
(seq client-locations)
(into [["Shared" "Shared"]]
(for [cl client-locations]
[cl cl]))
:else
[["Shared" "Shared"]]))
:name name
:value value
:class "w-full"}))
(defn- account-typeahead*
[{:keys [name value client-id x-model]}]
[:div.flex.flex-col
(com/typeahead {:name name
:placeholder "Search..."
:url (str (bidi/path-for ssr-routes/only-routes :account-search) "?client-id=" client-id)
:id name
:x-model x-model
:value value
:content-fn (fn [value]
(:account/name (d-accounts/clientize (dc/pull (dc/db conn) d-accounts/default-read value)
client-id)))})])
(defn- transaction-rule-account-row*
[account client-id client-locations]
(com/data-grid-row
(-> {:x-data (hx/json {:accountId (or (:db/id (fc/field-value (:transaction-rule-account/account account)))
(fc/field-value (:transaction-rule-account/account account)))
:location (fc/field-value (:transaction-rule-account/location account))
:show (boolean (not (fc/field-value (:new? account))))})
:data-key "show"
:x-ref "p"}
hx/alpine-mount-then-appear)
(let [account-name (fc/field-name (:transaction-rule-account/account account))]
(list
(fc/with-field :db/id
(com/hidden {:name (fc/field-name)
:value (fc/field-value)}))
(fc/with-field :transaction-rule-account/account
(com/data-grid-cell
{}
(com/validated-field
{:errors (fc/field-errors)}
[:div {:hx-trigger "changed"
:hx-target "next div"
:hx-vals (format "js:{name: '%s', 'client-id': event.detail.clientId || '', value: event.detail.accountId || ''}" account-name)
:hx-get (str (bidi/path-for ssr-routes/only-routes ::route/account-typeahead))
:x-init "$watch('clientId', cid => $dispatch('changed', $data));"}]
(account-typeahead* {:value (fc/field-value)
:client-id client-id
:name (fc/field-name)
:x-model "accountId"}))))
(fc/with-field :transaction-rule-account/location
(com/data-grid-cell
{}
(com/validated-field
{:errors (fc/field-errors)
:x-data (hx/json {:location (fc/field-value)})}
[:div {:hx-trigger "changed"
:hx-target "next *"
:hx-swap "outerHTML"
:hx-vals (format "js:{name: '%s', 'client-id': event.detail.clientId || '', 'account-id': event.detail.accountId || '', value: event.detail.location || ''}" (fc/field-name))
:hx-get (bidi/path-for ssr-routes/only-routes ::route/location-select)
:x-init "$watch('clientId', cid => $dispatch('changed', $data)); $watch('accountId', cid => $dispatch('changed', $data) )"}]
(location-select* {:name (fc/field-name)
:account-location (:account/location (cond->> (:transaction-rule-account/account @account)
(nat-int? (:transaction-rule-account/account @account)) (dc/pull (dc/db conn)
'[:account/location])))
:client-locations client-locations
:x-model "location"
:value (fc/field-value)}))))
(fc/with-field :transaction-rule-account/percentage
(com/data-grid-cell
{}
(com/validated-field
{:errors (fc/field-errors)}
(com/money-input {:name (fc/field-name)
:class "w-16"
:value (some-> (fc/field-value)
(* 100)
(long))}))))))
(com/data-grid-cell {:class "align-top"}
(com/a-icon-button {"@click.prevent.stop" "show=false; setTimeout(() => $refs.p.remove(), 500)"} svg/x))))
(defrecord AccountsStep [linear-wizard]
mm/ModalWizardStep
(step-name [_]
"Details")
(step-key [_]
:accounts)
(edit-path [_ _]
[])
(step-schema [_]
(mut/select-keys (mm/form-schema linear-wizard) #{}))
(render-step [this {{:keys [snapshot]} :multi-form-state :as request}]
(mm/default-render-step
linear-wizard this
:head [:div.p-2 "Invoice accounts "]
:body (mm/default-step-body
{}
[:div {}
(:client/name (:invoice/client snapshot))
(fc/with-field :invoice/expense-accounts
(com/validated-field
{:errors (fc/field-errors)}
(com/data-grid {:headers [(com/data-grid-header {} "Account")
(com/data-grid-header {:class "w-32"} "Location")
(com/data-grid-header {:class "w-16"} "%")
(com/data-grid-header {:class "w-16"})]}
(fc/cursor-map #(transaction-rule-account-row* %
(some->> snapshot :invoice/client :db/id)
(some->> snapshot :invoice/client :client/locations)))
(com/data-grid-new-row {:colspan 4
:hx-get (bidi/path-for ssr-routes/only-routes
::route/new-wizard-new-account)
:index (count (fc/field-value))
:tr-params (hx/bind-alpine-vals {} {"client-id" "clientId"})}
"New account")))) ])
:footer
(mm/default-step-footer linear-wizard this :validation-route ::route/new-wizard-navigate)
:validation-route ::route/new-wizard-navigate)))
(defrecord NewWizard2 [_ current-step]
mm/LinearModalWizard
(hydrate-from-request
[this request]
this)
(navigate [this step-key]
(assoc this :current-step step-key))
(get-current-step [this]
(if current-step
(mm/get-step this current-step)
(mm/get-step this :basic-details)))
(render-wizard [this {:keys [multi-form-state] :as request}]
(alog/peek ::MFS multi-form-state)
(mm/default-render-wizard
this request
:form-params
(-> mm/default-form-props
(assoc :hx-post
(str (bidi/path-for ssr-routes/only-routes ::route/pay-submit))))))
(steps [_]
[:basic-details
:accounts])
(get-step [this step-key]
(let [step-key-result (mc/parse mm/step-key-schema step-key)
[step-key-type step-key] step-key-result]
(get {:basic-details (->BasicDetailsStep this)
:accounts (->AccountsStep this)}
step-key)))
(form-schema [_] new-form-schema)
(submit [this {:keys [multi-form-state request-method identity] :as request}]
#_(let [snapshot (mc/decode
payment-form-schema
(:snapshot multi-form-state)
mt/strip-extra-keys-transformer)
_ (exception->4xx
#(if (= :handwrite-check (:method snapshot))
(when (or (not (some? (:check-number snapshot)))
(= "" (:check-number snapshot)))
(throw (Exception. "Check number is required")))
true))
result (exception->4xx
#(if (= :handwrite-check (:method snapshot))
(add-handwritten-check request this snapshot)
(print-checks-internal (map (fn [i] {:invoice-id (:invoice-id i)
:amount (:amount i)})
(:invoices snapshot))
(:client snapshot)
(:bank-account snapshot)
(cond (= :print-check (:method snapshot))
:payment-type/check
(= :debit (:method snapshot))
:payment-type/debit
(= :cash (:method snapshot))
:payment-type/cash
(= :credit (:method snapshot))
:payment-type/credit
:else :payment-type/debit)
identity)))]
(modal-response
(com/modal {}
(com/modal-card-advanced
{: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"}
(com/modal-body {}
[:div.flex.flex-col.mt-4.space-y-4.items-center
[:div.w-24.h-24.bg-green-50.rounded-full.p-4.text-green-300.animate-gg
svg/thumbs-up]
(when-not (:pdf-url result)
[:div "That's a wrap. Your payment is complete."])
(when (:pdf-url result)
[:div "Your checks are ready. Click "
(com/link {:href (:pdf-url result) :target "_new"} "here")
" to download and print."])
(when (:pdf-url result)
[:div.text-xs.italic [:em "Remember to turn off all scaling and margins."]])])))
:headers {"hx-trigger" "invalidated"}))))
(def new-wizard (->NewWizard2 nil nil))
(defn wrap-status-from-source [handler] (defn wrap-status-from-source [handler]
@@ -1091,6 +1416,55 @@
(= ::route/all-page matched-current-page-route) (assoc-in [:route-params :status] nil))] (= ::route/all-page matched-current-page-route) (assoc-in [:route-params :status] nil))]
(handler request)))) (handler request))))
(defn payable-ids [ids]
(->> (dc/q '[:find ?i
:in $ [?i ...]
:where [?i :invoice/status :invoice-status/unpaid]
[?i :invoice/client ?c]
[(get-else $ ?c :client/locked-until #inst "2000-01-01") ?lu]
[?i :invoice/date ?d]
[(>= ?d ?lu)]]
(dc/db conn)
ids)
(map first)))
(defn initial-pay-wizard-state [request]
(exception->notification
#(let [selected-ids (selected->ids request (:query-params request))
selected-ids (payable-ids selected-ids)
_ (when (= 0 (count selected-ids))
(throw (ex-info "No selected invoices are applicable for payment" {:type :notification})))
has-warning? (and (:selected (:query-params request))
(not= (count selected-ids)
(count (:selected (:query-params request)))))
invoices (->> (dc/q '[:find (pull ?i [{:invoice/vendor [:vendor/name :db/id]
:invoice/client [:db/id]}
:invoice/outstanding-balance
:invoice/invoice-number
:db/id])
:in $ [?i ...]]
(dc/db conn)
selected-ids)
(map first)
(sort-by (juxt (comp :invoice/vendor :vendor/name)
:invoice/invoice-number)))]
(mm/->MultiStepFormState {:invoices (mapv (fn [i] {:invoice-id (:db/id i)
:amount (:invoice/outstanding-balance i)})
invoices)
:mode :simple
:client (-> invoices first :invoice/client :db/id)
:has-warning? (boolean has-warning?)
:handwritten-date (time/now)}
[]
{:mode :simple
:has-warning? (boolean has-warning?)}))))
(defn initial-new-wizard-state [request]
(mm/->MultiStepFormState {:TODO nil}
[]
{}))
(def key->handler (def key->handler
(apply-middleware-to-all-handlers (apply-middleware-to-all-handlers
{::route/all-page (-> (helper/page-route grid-page) {::route/all-page (-> (helper/page-route grid-page)
@@ -1114,46 +1488,26 @@
::route/pay-wizard (-> mm/open-wizard-handler ::route/pay-wizard (-> mm/open-wizard-handler
(mm/wrap-wizard pay-wizard) (mm/wrap-wizard pay-wizard)
(mm/wrap-init-multi-form-state (fn [request] (mm/wrap-init-multi-form-state initial-pay-wizard-state))
(exception->notification ::route/new-wizard (-> mm/open-wizard-handler
#(let [selected-ids (selected->ids request (:query-params request))
selected-ids (->> (dc/q '[:find ?i
:in $ [?i ...]
:where [?i :invoice/status :invoice-status/unpaid]
[?i :invoice/client ?c]
[(get-else $ ?c :client/locked-until #inst "2000-01-01") ?lu]
[?i :invoice/date ?d]
[(>= ?d ?lu)]]
(dc/db conn)
selected-ids)
(map first))
_ (when (= 0 (count selected-ids))
(throw (ex-info "No selected invoices are applicable for payment" {:type :notification})))
has-warning? (and (:selected (:query-params request)) (mm/wrap-wizard new-wizard)
(not= (count selected-ids) (mm/wrap-init-multi-form-state initial-new-wizard-state))
(count (:selected (:query-params request))))) ::route/new-wizard-navigate (-> mm/next-handler
invoices (->> (dc/q '[:find (pull ?i [{:invoice/vendor [:vendor/name :db/id] (mm/wrap-wizard new-wizard)
:invoice/client [:db/id]} (mm/wrap-decode-multi-form-state))
:invoice/outstanding-balance ::route/new-wizard-new-account (->
:invoice/invoice-number (add-new-entity-handler [:step-params :invoice/expense-accounts]
:db/id]) (fn render [cursor request]
:in $ [?i ...]] (transaction-rule-account-row*
(dc/db conn) cursor
selected-ids) (:client-id (:query-params request))
(map first) (some->> (:client-id (:query-params request)) (pull-attr (dc/db conn) :client/locations))))
(sort-by (juxt (comp :invoice/vendor :vendor/name) (fn build-new-row [base _]
:invoice/invoice-number)))] (assoc base :transaction-rule-account/location "Shared")))
(mm/->MultiStepFormState {:invoices (mapv (fn [i] {:invoice-id (:db/id i) (wrap-schema-enforce :query-schema [:map
:amount (:invoice/outstanding-balance i)}) [:client-id {:optional true}
invoices) [:maybe entity-id]]]))
:mode :simple
:client (-> invoices first :invoice/client :db/id)
:has-warning? (boolean has-warning?)
:handwritten-date (time/now)}
[]
{:mode :simple
:has-warning? (boolean has-warning?)}))))))
::route/pay-submit (-> mm/submit-handler ::route/pay-submit (-> mm/submit-handler
(mm/wrap-wizard pay-wizard) (mm/wrap-wizard pay-wizard)

View File

@@ -312,7 +312,7 @@
(def dissoc-nil-transformer (def dissoc-nil-transformer
(let [e {:map {:compile (fn [schema _] (let [e {:map {:compile (fn [schema _]
(fn [data] (fn [data]
(if (map? data) (if (map? data)
(filter-vals (filter-vals

View File

@@ -2,7 +2,10 @@
(def routes {"" {:get ::all-page (def routes {"" {:get ::all-page
"/unpaid" ::unpaid-page "/unpaid" ::unpaid-page
"/paid" ::paid-page "/paid" ::paid-page
"/voided" ::voided-page } "/voided" ::voided-page}
"/new" {:get ::new-wizard
"/navigate" ::new-wizard-navigate
"/account/new" ::new-wizard-new-account}
"/pay-button" ::pay-button "/pay-button" ::pay-button
"/pay" {:get ::pay-wizard "/pay" {:get ::pay-wizard
"/navigate" ::pay-wizard-navigate "/navigate" ::pay-wizard-navigate