fixes bugs, makes checks printable for different dates.

This commit is contained in:
2025-10-07 21:52:46 -07:00
parent c528795b1e
commit a462c56595
3 changed files with 127 additions and 117 deletions

View File

@@ -1,29 +1,27 @@
(ns auto-ap.graphql.checks (ns auto-ap.graphql.checks
(:require (:require
[amazonica.aws.s3 :as s3] [amazonica.aws.s3 :as s3]
[auto-ap.datomic :refer [conn remove-nils pull-many audit-transact]] [auto-ap.datomic :refer [audit-transact conn pull-many remove-nils]]
[auto-ap.datomic.accounts :as a] [auto-ap.datomic.accounts :as a]
[auto-ap.datomic.bank-accounts :as d-bank-accounts] [auto-ap.datomic.bank-accounts :as d-bank-accounts]
[auto-ap.datomic.checks :as d-checks] [auto-ap.datomic.checks :as d-checks]
[auto-ap.datomic.clients :as d-clients] [auto-ap.datomic.clients :as d-clients]
[auto-ap.datomic.invoices :as d-invoices] [auto-ap.datomic.invoices :as d-invoices]
[auto-ap.datomic.transactions :as d-transactions] [auto-ap.datomic.transactions :as d-transactions]
[auto-ap.logging :as alog]
[auto-ap.datomic.vendors :as d-vendors] [auto-ap.datomic.vendors :as d-vendors]
[auto-ap.graphql.utils [auto-ap.graphql.utils
:refer [->graphql :refer [->graphql <-graphql assert-admin assert-can-see-client
<-graphql assert-failure assert-not-locked attach-tracing-resolvers
attach-tracing-resolvers
assert-admin
assert-can-see-client
assert-failure
assert-not-locked
enum->keyword]] enum->keyword]]
[auto-ap.logging :as alog]
[auto-ap.numeric :refer [num->words]] [auto-ap.numeric :refer [num->words]]
[auto-ap.time :refer [iso-date local-now parse]] [auto-ap.solr :as solr]
[auto-ap.time :refer [iso-date parse]]
[auto-ap.time :as atime]
[auto-ap.utils :refer [by dollars-0?]] [auto-ap.utils :refer [by dollars-0?]]
[clj-pdf.core :as pdf] [clj-pdf.core :as pdf]
[clj-time.coerce :as c] [clj-time.coerce :as c]
[clj-time.coerce :as coerce]
[clj-time.core :as time] [clj-time.core :as time]
[clj-time.format :as f] [clj-time.format :as f]
[clojure.edn :as edn] [clojure.edn :as edn]
@@ -31,12 +29,9 @@
[clojure.set :as set] [clojure.set :as set]
[clojure.string :as str] [clojure.string :as str]
[com.brunobonacci.mulog :as mu] [com.brunobonacci.mulog :as mu]
[com.walmartlabs.lacinia.util :refer [attach-resolvers]]
[config.core :refer [env]] [config.core :refer [env]]
[clj-time.coerce :as coerce]
[datomic.api :as dc] [datomic.api :as dc]
[digest] [digest])
[auto-ap.solr :as solr])
(:import (:import
(java.io ByteArrayOutputStream) (java.io ByteArrayOutputStream)
(java.text DecimalFormat) (java.text DecimalFormat)
@@ -215,7 +210,7 @@
(str "https://" (:data-bucket env) "/merged-checks/" uuid ".pdf"))) (str "https://" (:data-bucket env) "/merged-checks/" uuid ".pdf")))
#_{:clj-kondo/ignore [:unused-binding]} #_{:clj-kondo/ignore [:unused-binding]}
(defmulti invoices->entities (fn [invoices vendor-id client bank-account type index invoice-amounts] (defmulti invoices->entities (fn [invoices vendor-id client bank-account type index invoice-amounts date]
type)) type))
(defn invoice-payments [invoices invoice-amounts] (defn invoice-payments [invoices invoice-amounts]
(->> (for [invoice invoices (->> (for [invoice invoices
@@ -228,16 +223,16 @@
[:pay (:db/id invoice) invoice-amount]]) [:pay (:db/id invoice) invoice-amount]])
(reduce into []))) (reduce into [])))
(defn base-payment [invoices vendor client bank-account _ _ invoice-amounts] (defn base-payment [invoices vendor client bank-account _ _ invoice-amounts date]
{:db/id (str (:db/id vendor)) {:db/id (str (:db/id vendor))
:payment/bank-account (:db/id bank-account) :payment/bank-account (:db/id bank-account)
:payment/amount (reduce + 0 (map (comp invoice-amounts :db/id) invoices)) :payment/amount (reduce + 0 (map (comp invoice-amounts :db/id) invoices))
:payment/vendor (:db/id vendor) :payment/vendor (:db/id vendor)
:payment/client (:db/id client) :payment/client (:db/id client)
:payment/date (c/to-date (time/now)) :payment/date (c/to-date (or date (time/now)))
:payment/invoices (map :db/id invoices)}) :payment/invoices (map :db/id invoices)})
(defmethod invoices->entities :payment-type/check [invoices vendor client bank-account type index invoice-amounts] (defmethod invoices->entities :payment-type/check [invoices vendor client bank-account type index invoice-amounts date]
(when (<= (->> invoices (when (<= (->> invoices
(map (comp invoice-amounts :db/id)) (map (comp invoice-amounts :db/id))
(reduce + 0.0)) (reduce + 0.0))
@@ -245,51 +240,52 @@
(throw (ex-info "The selected invoices do not have an outstanding balance." (throw (ex-info "The selected invoices do not have an outstanding balance."
{:validation-error "The selected invoices do not have an outstanding balance."}))) {:validation-error "The selected invoices do not have an outstanding balance."})))
(let [uuid (str (UUID/randomUUID)) (let [uuid (str (UUID/randomUUID))
date (or date (time/now))
memo (str "Invoice #'s: " memo (str "Invoice #'s: "
(str/join ", " (str/join ", "
(map (fn [i] (map (fn [i]
(str (:invoice/invoice-number i) "(" (invoice-amounts (:db/id i)) ")")) (str (:invoice/invoice-number i) "(" (invoice-amounts (:db/id i)) ")"))
invoices))) invoices)))
base-payment (base-payment invoices vendor client bank-account type index invoice-amounts) base-payment (base-payment invoices vendor client bank-account type index invoice-amounts date)
payment payment
(remove-nils (remove-nils
(assoc base-payment (cond-> (assoc base-payment
:payment/s3-uuid (when (> (:payment/amount base-payment) 0) uuid) :payment/s3-uuid (when (> (:payment/amount base-payment) 0) uuid)
:payment/s3-key (when (> (:payment/amount base-payment) 0) (str "checks/" uuid ".pdf")) :payment/s3-key (when (> (:payment/amount base-payment) 0) (str "checks/" uuid ".pdf"))
:payment/s3-url (when (> (:payment/amount base-payment) 0) (str "https://" (:data-bucket env) "/checks/" uuid ".pdf")) :payment/s3-url (when (> (:payment/amount base-payment) 0) (str "https://" (:data-bucket env) "/checks/" uuid ".pdf"))
:payment/check-number (+ index (:bank-account/check-number bank-account)) :payment/check-number (+ index (:bank-account/check-number bank-account))
:payment/type :payment-type/check :payment/type :payment-type/check
:payment/memo memo :payment/memo memo
:payment/status :payment-status/pending :payment/status :payment-status/pending
:payment/pdf-data (pr-str {:vendor vendor :payment/pdf-data (pr-str {:vendor vendor
:paid-to (or (:vendor/paid-to vendor) (:vendor/name vendor)) :paid-to (or (:vendor/paid-to vendor) (:vendor/name vendor))
:amount (reduce + 0 (map (comp invoice-amounts :db/id) invoices)) :amount (reduce + 0 (map (comp invoice-amounts :db/id) invoices))
:check (str (+ index (:bank-account/check-number bank-account))) :check (str (+ index (:bank-account/check-number bank-account)))
:memo memo :memo memo
:date (date->str (local-now)) :date (date->str date)
:client (dissoc client :client/bank-accounts :client/square-integration-status :client/ezcater-locations :client/locked-until :client/emails :client/square-auth-token :client/square-locations) :client (dissoc client :client/bank-accounts :client/square-integration-status :client/ezcater-locations :client/locked-until :client/emails :client/square-auth-token :client/square-locations)
:bank-account (dissoc bank-account :bank-account/start-date :bank-account/integration-status) :bank-account (dissoc bank-account :bank-account/start-date :bank-account/integration-status)
#_#_:client {:name (:name client) #_#_:client {:name (:name client)
:address (:address client) :address (:address client)
:signature-file (:signature-file client) :signature-file (:signature-file client)
:bank {:name (:bank-account/bank-name bank-account) :bank {:name (:bank-account/bank-name bank-account)
:acct (:bank-account/bank-code bank-account) :acct (:bank-account/bank-code bank-account)
:routing (:bank-account/routing bank-account) :routing (:bank-account/routing bank-account)
:acct-number (:bank-account/number bank-account)}}})))] :acct-number (:bank-account/number bank-account)}}}))))]
(-> [] (-> []
(conj payment) (conj payment)
(into (invoice-payments invoices invoice-amounts))))) (into (invoice-payments invoices invoice-amounts)))))
(defmethod invoices->entities :payment-type/debit [invoices vendor client bank-account type index invoice-amounts] (defmethod invoices->entities :payment-type/debit [invoices vendor client bank-account type index invoice-amounts date]
(when (<= (->> invoices (when (<= (->> invoices
(map (comp invoice-amounts :db/id)) (map (comp invoice-amounts :db/id))
(reduce + 0.0)) (reduce + 0.0))
0.001) 0.001)
(throw (ex-info "The selected invoices do not have an outstanding balance." (throw (ex-info "The selected invoices do not have an outstanding balance."
{:validation-error "The selected invoices do not have an outstanding balance."}))) {:validation-error "The selected invoices do not have an outstanding balance."})))
(let [payment (assoc (base-payment invoices vendor client bank-account type index invoice-amounts) (let [payment (assoc (base-payment invoices vendor client bank-account type index invoice-amounts date)
:payment/type :payment-type/debit :payment/type :payment-type/debit
:payment/memo (str "Debit Invoice #'s: " :payment/memo (str "Debit Invoice #'s: "
(str/join ", " (str/join ", "
@@ -310,14 +306,14 @@
(throw (ex-info "The selected invoices do not have an outstanding balance." (throw (ex-info "The selected invoices do not have an outstanding balance."
{:validation-error "The selected invoices do not have an outstanding balance."})))) {:validation-error "The selected invoices do not have an outstanding balance."}))))
(defmethod invoices->entities :payment-type/credit [invoices vendor client bank-account type index invoice-amounts] (defmethod invoices->entities :payment-type/credit [invoices vendor client bank-account type index invoice-amounts date]
(when (>= (->> invoices (when (>= (->> invoices
(map (comp invoice-amounts :db/id)) (map (comp invoice-amounts :db/id))
(reduce + 0.0)) (reduce + 0.0))
0.001) 0.001)
(throw (ex-info "The selected invoices do not have an outstanding balance." (throw (ex-info "The selected invoices do not have an outstanding balance."
{:validation-error "The selected invoices do not have an outstanding balance."}))) {:validation-error "The selected invoices do not have an outstanding balance."})))
(let [payment (assoc (base-payment invoices vendor client bank-account type index invoice-amounts) (let [payment (assoc (base-payment invoices vendor client bank-account type index invoice-amounts date)
:payment/type :payment-type/credit :payment/type :payment-type/credit
:payment/memo (str "Debit Invoice #'s: " :payment/memo (str "Debit Invoice #'s: "
(str/join ", " (str/join ", "
@@ -329,14 +325,14 @@
(conj payment) (conj payment)
(into (invoice-payments invoices invoice-amounts))))) (into (invoice-payments invoices invoice-amounts)))))
(defmethod invoices->entities :payment-type/cash [invoices vendor client bank-account type index invoice-amounts] (defmethod invoices->entities :payment-type/cash [invoices vendor client bank-account type index invoice-amounts date]
(when (<= (->> invoices (when (<= (->> invoices
(map (comp invoice-amounts :db/id)) (map (comp invoice-amounts :db/id))
(reduce + 0.0)) (reduce + 0.0))
0.001) 0.001)
(throw (ex-info "The selected invoices do not have an outstanding balance." (throw (ex-info "The selected invoices do not have an outstanding balance."
{:validation-error "The selected invoices do not have an outstanding balance."}))) {:validation-error "The selected invoices do not have an outstanding balance."})))
(let [base-payment (base-payment invoices vendor client bank-account type index invoice-amounts) (let [base-payment (base-payment invoices vendor client bank-account type index invoice-amounts date)
transaction-id (str (UUID/randomUUID)) transaction-id (str (UUID/randomUUID))
memo (str "Cash Invoice #'s: " memo (str "Cash Invoice #'s: "
(str/join ", " (str/join ", "
@@ -381,50 +377,51 @@
:client-id client-id :client-id client-id
:invoices (map :invoice/invoice-number invoices)})))) :invoices (map :invoice/invoice-number invoices)}))))
(defn print-checks-internal [invoice-payments client-id bank-account-id type id] (defn print-checks-internal
(let [type (keyword "payment-type" (name type)) ([invoice-payments client-id bank-account-id type id date]
invoices (d-invoices/get-multi (map :invoice-id invoice-payments)) (let [type (keyword "payment-type" (name type))
client (d-clients/get-by-id client-id) invoices (d-invoices/get-multi (map :invoice-id invoice-payments))
invoice-amounts (by :invoice-id :amount invoice-payments) client (d-clients/get-by-id client-id)
invoices-grouped-by-vendor (group-by (comp :db/id :invoice/vendor) invoices) invoice-amounts (by :invoice-id :amount invoice-payments)
vendors (->> (pull-many (dc/db conn) d-vendors/default-read (keys invoices-grouped-by-vendor)) invoices-grouped-by-vendor (group-by (comp :db/id :invoice/vendor) invoices)
(by :db/id)) vendors (->> (pull-many (dc/db conn) d-vendors/default-read (keys invoices-grouped-by-vendor))
bank-account (d-bank-accounts/get-by-id bank-account-id) (by :db/id))
_ (validate-belonging client-id invoices bank-account) bank-account (d-bank-accounts/get-by-id bank-account-id)
_ (when (and (nil? (:bank-account/check-number bank-account)) _ (validate-belonging client-id invoices bank-account)
(= type :payment-type/check)) _ (when (and (nil? (:bank-account/check-number bank-account))
(let [message (str "The bank account " (:bank-account/name bank-account) " does not have a starting check number. Please ask the integreat staff to initialize it.")] (= type :payment-type/check))
(throw (ex-info message (let [message (str "The bank account " (:bank-account/name bank-account) " does not have a starting check number. Please ask the integreat staff to initialize it.")]
{:validation-error message})))) (throw (ex-info message
checks (mu/trace ::build-checks [] {:validation-error message}))))
(->> (for [[[vendor-id invoices] index] (map vector invoices-grouped-by-vendor (range))] checks (mu/trace ::build-checks []
(invoices->entities invoices (vendors vendor-id) client bank-account type index invoice-amounts)) (->> (for [[[vendor-id invoices] index] (map vector invoices-grouped-by-vendor (range))]
(reduce into []) (invoices->entities invoices (vendors vendor-id) client bank-account type index invoice-amounts date))
doall)) (reduce into [])
checks (if (= type :payment-type/check) doall))
(conj checks [:plus (:db/id bank-account) :bank-account/check-number (count invoices-grouped-by-vendor)]) checks (if (= type :payment-type/check)
checks)] (conj checks [:plus (:db/id bank-account) :bank-account/check-number (count invoices-grouped-by-vendor)])
(when (= type :payment-type/check) checks)]
(mu/trace ::making-pdfs [:checks checks] (when (= type :payment-type/check)
(make-pdfs (filter #(and (= :payment-type/check (:payment/type %)) (mu/trace ::making-pdfs [:checks checks]
(> (:payment/amount %) 0.0)) (make-pdfs (filter #(and (= :payment-type/check (:payment/type %))
checks)))) (> (:payment/amount %) 0.0))
(let [result (audit-transact (map #(if (map? %) checks))))
(dissoc % :payment/pdf-data) (let [result (audit-transact (map #(if (map? %)
%) checks) id)] (dissoc % :payment/pdf-data)
%) checks) id)]
(try (try
(doseq [[_ i] (:tempids result)] (doseq [[_ i] (:tempids result)]
(solr/touch-with-ledger i)) (solr/touch-with-ledger i))
(catch Exception e (catch Exception e
(alog/error ::cant-save-solr (alog/error ::cant-save-solr
:error e)))) :error e))))
{:invoices (d-invoices/get-multi (map :invoice-id invoice-payments)) {:invoices (d-invoices/get-multi (map :invoice-id invoice-payments))
:pdf-url (if (= type :payment-type/check) :pdf-url (if (= type :payment-type/check)
(mu/trace ::merge-pdfs (mu/trace ::merge-pdfs
[] []
(merge-pdfs (filter identity (map :payment/s3-key checks)))) (merge-pdfs (filter identity (map :payment/s3-key checks))))
nil)})) nil)})))
(defn get-payment-page [context args _] (defn get-payment-page [context args _]
(let [[payments checks-count] (d-checks/get-graphql (-> args (let [[payments checks-count] (d-checks/get-graphql (-> args
@@ -474,14 +471,15 @@
bank-account bank-account
:payment-type/check :payment-type/check
0 0
invoice-payment-lookup)] invoice-payment-lookup
(parse (:date args) iso-date))]
(let [result (audit-transact (let [result (audit-transact
(into [(assoc base-payment (into [(assoc base-payment
:payment/type :payment-type/check :payment/type :payment-type/check
:payment/status :payment-status/pending :payment/status :payment-status/pending
:payment/check-number (:check_number args) :payment/check-number (:check_number args)
:payment/date (c/to-date (parse (:date args) iso-date)))] #_#_:payment/date (c/to-date (parse (:date args) iso-date)))]
(invoice-payments invoices invoice-payment-lookup)) (invoice-payments invoices invoice-payment-lookup))
(:id context))] (:id context))]
(doseq [[_ i] (:tempids result)] (doseq [[_ i] (:tempids result)]
@@ -586,7 +584,8 @@
(:client_id args) (:client_id args)
(:bank_account_id args) (:bank_account_id args)
(:type args) (:type args)
(:id context)))) (:id context)
(time/now))))
(defn pay-invoices-from-balance [context {invoices :invoices (defn pay-invoices-from-balance [context {invoices :invoices
client-id :client_id} _] client-id :client_id} _]

View File

@@ -212,7 +212,8 @@
client_id client_id
bank-account-id bank-account-id
type type
(:id context)) (:id context)
(time/now))
u/->graphql)))) u/->graphql))))
(defn edit-invoice [context {{:keys [id due invoice_number vendor_id total date expense_accounts scheduled_payment] :as in} :invoice} _] (defn edit-invoice [context {{:keys [id due invoice_number vendor_id total date expense_accounts scheduled_payment] :as in} :invoice} _]

View File

@@ -1165,7 +1165,7 @@
:name (fc/field-name) :name (fc/field-name)
:error? (fc/field-errors) :error? (fc/field-errors)
:placeholder "10001"})))) :placeholder "10001"}))))
(when (= :handwrite-check (:method (:snapshot (:multi-form-state request)))) (when (#{:handwrite-check :print-check} (:method (:snapshot (:multi-form-state request))))
(fc/with-field :handwritten-date (fc/with-field :handwritten-date
(com/validated-field (com/validated-field
{:errors (fc/field-errors) {:errors (fc/field-errors)
@@ -1186,6 +1186,7 @@
(format "Pay in full ($%,.2f)" total)))} (format "Pay in full ($%,.2f)" total)))}
{:value "advanced" {:value "advanced"
:content "Customize payments"}]}) :content "Customize payments"}]})
[:div.space-y-4 [:div.space-y-4
(fc/with-field :invoices (fc/with-field :invoices
(com/validated-field (com/validated-field
@@ -1244,13 +1245,13 @@
bank-account bank-account
:payment-type/check :payment-type/check
0 0
invoice-payment-lookup)] invoice-payment-lookup
(:handwritten-date snapshot))]
(let [result (audit-transact (let [result (audit-transact
(into [(assoc base-payment (into [(assoc base-payment
:payment/type :payment-type/check :payment/type :payment-type/check
:payment/status :payment-status/pending :payment/status :payment-status/pending
:payment/check-number (:check-number snapshot) :payment/check-number (:check-number snapshot))]
:payment/date (coerce/to-date (:handwritten-date snapshot)))]
(invoice-payments invoices invoice-payment-lookup)) (invoice-payments invoices invoice-payment-lookup))
(:identity request))] (:identity request))]
(try (try
@@ -1344,24 +1345,33 @@
(= "" (:check-number snapshot))) (= "" (:check-number snapshot)))
(throw (Exception. "Check number is required"))) (throw (Exception. "Check number is required")))
true)) true))
result (exception->4xx result (exception->4xx
#(if (= :handwrite-check (:method snapshot)) #(do
(add-handwritten-check request this snapshot) (when (:handwritten-date snapshot)
(print-checks-internal (map (fn [i] {:invoice-id (:invoice-id i) (let [invoices (d-invoices/get-multi (map :invoice-id (:invoices snapshot)))]
:amount (:amount i)}) (assert-not-locked (:db/id (:invoice/client (first invoices))) (:handwritten-date snapshot))))
(:invoices snapshot)) (if (= :handwrite-check (:method snapshot))
(:client snapshot) (add-handwritten-check request this snapshot)
(:bank-account snapshot) (try
(cond (= :print-check (:method snapshot)) (print-checks-internal (map (fn [i] {:invoice-id (:invoice-id i)
:payment-type/check :amount (:amount i)})
(= :debit (:method snapshot)) (:invoices snapshot))
:payment-type/debit (:client snapshot)
(= :cash (:method snapshot)) (:bank-account snapshot)
:payment-type/cash (cond (= :print-check (:method snapshot))
(= :credit (:method snapshot)) :payment-type/check
:payment-type/credit (= :debit (:method snapshot))
:else :payment-type/debit) :payment-type/debit
identity)))] (= :cash (:method snapshot))
:payment-type/cash
(= :credit (:method snapshot))
:payment-type/credit
:else :payment-type/debit)
identity
(:handwritten-date snapshot))
(catch Exception e
(println e))))))]
(modal-response (modal-response
(com/modal {} (com/modal {}
(com/modal-card-advanced (com/modal-card-advanced