Allows paying from credit

This commit is contained in:
2025-01-17 23:10:42 -08:00
parent ca7a08452c
commit 084df59149
4 changed files with 187 additions and 71 deletions

View File

@@ -212,3 +212,4 @@
"hidden" (boolean (:vendor/hidden result))}))))
#_(rebuild-search-index)

View File

@@ -1,58 +1,60 @@
(ns auto-ap.ssr.invoices
(:require [auto-ap.client-routes :as client-routes]
[auto-ap.datomic
(:require
[auto-ap.client-routes :as client-routes]
[auto-ap.datomic
:refer [add-sorter-fields apply-pagination apply-sort-3
audit-transact conn merge-query observable-query
pull-many]]
[auto-ap.datomic.accounts :as d-accounts]
[auto-ap.datomic.bank-accounts :as d-bank-accounts]
[auto-ap.datomic.invoices :as d-invoices]
[auto-ap.query-params :refer [wrap-copy-qp-pqp]]
[auto-ap.graphql.checks :as gq-checks :refer [base-payment
invoice-payments
print-checks-internal
validate-belonging]]
[auto-ap.graphql.utils :refer [assert-can-see-client
assert-not-locked exception->4xx
exception->notification
extract-client-ids notify-if-locked]]
[auto-ap.logging :as alog]
[auto-ap.permissions :refer [can?]]
[auto-ap.routes.invoice :as route]
[auto-ap.routes.payments :as payment-route]
[auto-ap.routes.utils
[auto-ap.datomic.accounts :as d-accounts]
[auto-ap.datomic.bank-accounts :as d-bank-accounts]
[auto-ap.datomic.clients :as d-clients]
[auto-ap.datomic.invoices :as d-invoices]
[auto-ap.graphql.checks :as gq-checks :refer [base-payment invoice-payments
print-checks-internal
validate-belonging]]
[auto-ap.graphql.utils :refer [assert-can-see-client assert-not-locked
exception->4xx exception->notification
extract-client-ids notify-if-locked]]
[auto-ap.logging :as alog]
[auto-ap.permissions :refer [can?]]
[auto-ap.query-params :refer [wrap-copy-qp-pqp]]
[auto-ap.routes.invoice :as route]
[auto-ap.routes.payments :as payment-route]
[auto-ap.routes.utils
:refer [wrap-admin wrap-client-redirect-unauthenticated]]
[auto-ap.solr :as solr]
[auto-ap.ssr-routes :as ssr-routes]
[auto-ap.ssr.components :as com]
[auto-ap.ssr.components.link-dropdown :refer [link-dropdown]]
[auto-ap.ssr.components.multi-modal :as mm]
[auto-ap.ssr.form-cursor :as fc]
[auto-ap.ssr.grid-page-helper :as helper :refer [wrap-apply-sort]]
[auto-ap.ssr.hiccup-helper :as hh]
[auto-ap.ssr.hx :as hx]
[auto-ap.ssr.invoice.common :refer [default-read]]
[auto-ap.ssr.invoice.new-invoice-wizard :as new-invoice-wizard]
[auto-ap.ssr.invoice.import :as invoice-import]
[auto-ap.ssr.pos.common :refer [date-range-field*]]
[auto-ap.ssr.svg :as svg]
[auto-ap.ssr.utils
:refer [apply-middleware-to-all-handlers clj-date-schema
dissoc-nil-transformer entity-id html-response
main-transformer modal-response money ref->enum-schema
round-money strip wrap-entity wrap-implied-route-param
wrap-merge-prior-hx wrap-schema-enforce form-validation-error assert-schema]]
[auto-ap.time :as atime]
[auto-ap.utils :refer [by dollars=]]
[bidi.bidi :as bidi]
[clj-time.coerce :as coerce]
[clj-time.core :as time]
[clojure.string :as str]
[datomic.api :as dc]
[hiccup.util :as hu]
[malli.core :as mc]
[malli.transform :as mt]
[malli.util :as mut]))
[auto-ap.solr :as solr]
[auto-ap.ssr-routes :as ssr-routes]
[auto-ap.ssr.components :as com]
[auto-ap.ssr.components.link-dropdown :refer [link-dropdown]]
[auto-ap.ssr.components.multi-modal :as mm]
[auto-ap.ssr.form-cursor :as fc]
[auto-ap.ssr.grid-page-helper :as helper :refer [wrap-apply-sort]]
[auto-ap.ssr.hiccup-helper :as hh]
[auto-ap.ssr.hx :as hx]
[auto-ap.ssr.invoice.common :refer [default-read]]
[auto-ap.ssr.invoice.import :as invoice-import]
[auto-ap.ssr.invoice.new-invoice-wizard :as new-invoice-wizard]
[auto-ap.ssr.pos.common :refer [date-range-field*]]
[auto-ap.ssr.svg :as svg]
[auto-ap.ssr.utils
:refer [apply-middleware-to-all-handlers assert-schema
clj-date-schema dissoc-nil-transformer entity-id
html-response main-transformer modal-response money
ref->enum-schema round-money strip wrap-entity
wrap-implied-route-param wrap-merge-prior-hx
wrap-schema-enforce]]
[auto-ap.time :as atime]
[auto-ap.utils :refer [by dollars-0? dollars=]]
[bidi.bidi :as bidi]
[clj-time.coerce :as coerce]
[clj-time.coerce :as c]
[clj-time.core :as time]
[clojure.string :as str]
[datomic.api :as dc]
[hiccup.util :as hu]
[malli.core :as mc]
[malli.transform :as mt]
[malli.util :as mut]))
(defn exact-match-id* [request]
@@ -361,29 +363,41 @@
ids))
0)
outstanding-balances (if (seq ids)
(->>
(dc/q '[:find ?i ?v ?ob
:in $ [?i ...]
:where [?i :invoice/vendor ?v]
[?i :invoice/outstanding-balance ?ob]]
(dc/db conn)
ids)))
vendor-totals (if (seq ids)
(->>
(dc/q '[:find ?i ?v ?ob
:in $ [?i ...]
:where [?i :invoice/vendor ?v]
[?i :invoice/outstanding-balance ?ob]]
(dc/db conn)
ids)
outstanding-balances
(reduce (fn [acc [_ v ob]]
(update acc v (fnil + 0) ob))
{})
(vals)))
all-credits-or-debits (or (every? #(<= % 0.0) vendor-totals)
(every? #(>= % 0.0) vendor-totals))
total (reduce + 0.0 vendor-totals)]
at-least-one-positive-payment (some (fn [[_ _ ob]]
(> ob 0.001))
outstanding-balances)
total (reduce + 0.0 vendor-totals)
paying-credit? (and (> (count ids) 1)
(= 1 (count vendor-totals))
at-least-one-positive-payment
(dollars-0? total))]
[:div {:hx-target "this"
:hx-get (bidi/path-for ssr-routes/only-routes
::route/pay-wizard)
:hx-trigger "click from:#pay-button"
:x-tooltip "{allowHTML: true, content: () => $refs.template.innerHTML, appendTo: $root}"
}
[:div (cond-> {:hx-target "this"
:hx-trigger "click from:#pay-button"
:x-tooltip "{allowHTML: true, content: () => $refs.template.innerHTML, appendTo: $root}"}
paying-credit? (assoc :hx-post (bidi/path-for ssr-routes/only-routes ::route/pay-using-credit))
(not paying-credit? ) (assoc :hx-get (bidi/path-for ssr-routes/only-routes
::route/pay-wizard)))
(com/button {:color :primary
:id "pay-button"
:disabled (or (= (count (:ids params)) 0)
@@ -397,15 +411,22 @@
:x-ref "source"
:minimal-loading? true
:class "relative"}
(if (> (count ids) 0)
(cond
paying-credit?
"Pay invoices using credit"
(> (count ids) 0)
(format "Pay %d invoices ($%,.2f)"
(count ids)
(or total 0.0))
"Pay")
(when (or (= 0 (count ids))
(> selected-client-count 1))
(com/badge {} "!")))
(or (= 0 (count ids))
(> selected-client-count 1))
(list "Pay " (com/badge {} "!"))
:else
"Pay"))
[:template {:x-ref "template"}
(cond
(not all-credits-or-debits)
@@ -790,8 +811,95 @@
updated-count
(count ids))})})))
;; TODO
;; Allow for paying balances from set of invoices for one vendor
#_(defn pay-invoices-from-balance [context {invoices :invoices
client-id :client_id} _]
)
(defn pay-using-credit [request]
(alog/peek (:form-params request))
(let [invoices (selected->ids request (:form-params request))
_ (alog/peek invoices)
invoices (d-invoices/get-multi invoices)
client->invoices (group-by (comp :db/id :invoice/client)
invoices)
client-id (first (keys client->invoices))
_ (when (> (count (keys client->invoices)) 1)
(throw (ex-info "Can only pay from one customer's balance at a time" {:type :form-validation})))
_ (when-not (can? (:identity request) {:activity :pay :subject :invoice
:client client-id})
(throw (ex-info "You can't pay these invoices" {:type :form-validation})))
client (d-clients/get-by-id client-id)
_ (when (> (count (set (map :invoice/vendor invoices))) 1)
(throw (ex-info "Balance payments can only be for one vendor at a time." {:type :form-validation})))
_ (when (> (reduce + 0 (map :invoice/outstanding-balance invoices)) 0.001)
(throw (ex-info "There isn't a positive balance to pay from" {:type :form-validation})))
invoices-to-be-paid (filter
(fn [i]
(> (:invoice/outstanding-balance i)
0.001))
invoices)
credit-invoices (filter
(fn [i]
(< (:invoice/outstanding-balance i)
0.001))
invoices)
total-to-pay (reduce + 0 (map :invoice/outstanding-balance invoices-to-be-paid))
_ (when (<= total-to-pay 0.001)
(throw (ex-info "Select some invoices that need to be paid" {:type :form-validation})))
invoice-amounts (->> invoices-to-be-paid
(map (fn [i]
[(:db/id i)
(:invoice/outstanding-balance i)]))
(concat (->> credit-invoices
(reduce
(fn [[remaining-to-pay invoice-amounts] invoice]
(cond (dollars-0? (+ remaining-to-pay (:invoice/outstanding-balance invoice)))
(reduced (conj invoice-amounts
[(:db/id invoice)
(:invoice/outstanding-balance invoice)]))
(< (+ remaining-to-pay (:invoice/outstanding-balance invoice)) 0.0)
(reduced (conj invoice-amounts
[(:db/id invoice)
(- remaining-to-pay)]))
:else
[(+ remaining-to-pay (:invoice/outstanding-balance invoice))
(conj invoice-amounts [(:db/id invoice)
(:invoice/outstanding-balance invoice)])]))
[total-to-pay []])))
(into {}))
vendor-id (:db/id (:invoice/vendor (first invoices)))
payment {:db/id (str vendor-id)
:payment/amount total-to-pay
:payment/vendor vendor-id
:payment/client (:db/id client)
:payment/date (c/to-date (time/now))
:payment/invoices (map :db/id invoices)
:payment/type :payment-type/balance-credit
:payment/status :payment-status/cleared}
result (audit-transact (-> []
(conj payment)
(into (invoice-payments invoices invoice-amounts))) (:identity request))]
(doseq [[_ n] (:tempids result)]
(solr/touch-with-ledger n))
(html-response [:div]
:headers {"hx-trigger" (hx/json {:modalclose ""
:invalidated ""
:notification (format "Successfully paid %d invoices."
(count invoices))})})))
(defn does-amount-exceed-outstanding? [amount outstanding-balance]
(let [outstanding-balance (round-money outstanding-balance)
@@ -1325,6 +1433,9 @@
(wrap-admin))
::route/bulk-delete (-> bulk-delete-dialog
(wrap-admin))
::route/pay-using-credit (-> pay-using-credit
(wrap-schema-enforce :form-schema query-schema))
::route/pay-wizard (-> mm/open-wizard-handler
(mm/wrap-wizard pay-wizard)
@@ -1334,6 +1445,7 @@
(mm/wrap-wizard pay-wizard)
(mm/wrap-decode-multi-form-state))
::route/pay-wizard-navigate
(-> mm/next-handler
(mm/wrap-wizard pay-wizard)

View File

@@ -36,6 +36,8 @@
(def search (wrap-json-response search))
#_(comment
(solr/delete solr/impl "vendors")
(count (let [valid-ids (->> (dc/q '[:find ?v
:in $
:where [?v :vendor/name]]

View File

@@ -25,6 +25,7 @@
"/pay-button" ::pay-button
"/pay" {:get ::pay-wizard
"/using-credit" ::pay-using-credit
"/navigate" ::pay-wizard-navigate
:post ::pay-submit}