From 49a0684d63ede55c59b308183aea3ff12df46142 Mon Sep 17 00:00:00 2001 From: Bryce Covert Date: Tue, 31 May 2022 13:21:49 -0700 Subject: [PATCH] You can now pay invoices with credits from the same vendor. --- src/clj/auto_ap/datomic/migrate.clj | 3 + src/clj/auto_ap/graphql/checks.clj | 94 ++++++++++++++++++- src/clj/auto_ap/graphql/ledger.clj | 8 +- .../auto_ap/views/pages/unpaid_invoices.cljs | 40 +++++++- 4 files changed, 131 insertions(+), 14 deletions(-) diff --git a/src/clj/auto_ap/datomic/migrate.clj b/src/clj/auto_ap/datomic/migrate.clj index fb2ff577..451317d0 100644 --- a/src/clj/auto_ap/datomic/migrate.clj +++ b/src/clj/auto_ap/datomic/migrate.clj @@ -508,6 +508,9 @@ :db/cardinality :db.cardinality/one}]]} :auto-ap/add-payment-type-credit {:txes [[{:db/ident :payment-type/credit :db/doc "Credit for negative invoices"}]]} + + :auto-ap/add-payment-type-balance-credit {:txes [[{:db/ident :payment-type/balance-credit + :db/doc "Used for paying invoices from statement credits."}]]} :auto-ap/fulltext-accounts {:txes [[{:db/ident :account/search-terms :db/valueType :db.type/string :db/cardinality :db.cardinality/one diff --git a/src/clj/auto_ap/graphql/checks.clj b/src/clj/auto_ap/graphql/checks.clj index b01e092d..07d94927 100644 --- a/src/clj/auto_ap/graphql/checks.clj +++ b/src/clj/auto_ap/graphql/checks.clj @@ -10,7 +10,7 @@ [auto-ap.datomic.transactions :as d-transactions] [auto-ap.datomic.vendors :as d-vendors] [auto-ap.graphql.utils - :refer [->graphql <-graphql assert-admin assert-can-see-client enum->keyword assert-not-locked assert-none-locked]] + :refer [->graphql <-graphql assert-admin assert-failure assert-can-see-client enum->keyword assert-not-locked assert-none-locked]] [auto-ap.numeric :refer [num->words]] [auto-ap.time :refer [iso-date local-now parse]] [auto-ap.utils :refer [by dollars-0?]] @@ -212,7 +212,8 @@ type)) (defn invoice-payments [invoices invoice-amounts] (->> (for [invoice invoices - :let [invoice-amount (invoice-amounts (:db/id invoice))]] + :let [invoice-amount (invoice-amounts (:db/id invoice))] + :when invoice-amount] [{:invoice-payment/payment (-> invoice :invoice/vendor :db/id str) :invoice-payment/amount invoice-amount :invoice-payment/invoice (:db/id invoice)} @@ -292,6 +293,16 @@ (conj payment) (into (invoice-payments invoices invoice-amounts))))) + +(defmethod invoices->entities :payment-type/balance-credit [invoices vendor client bank-account type index invoice-amounts] + (when (<= (->> invoices + (map (comp invoice-amounts :db/id)) + (reduce + 0.0)) + 0.001) + (throw (ex-info "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] (when (>= (->> invoices (map (comp invoice-amounts :db/id)) @@ -552,6 +563,76 @@ (:type args) (:id context)))) +(defn pay-invoices-from-balance [context {invoices :invoices + client-id :client_id} _] + (assert-can-see-client (:id context) client-id) + (let [invoices (d-invoices/get-multi invoices) + client (d-clients/get-by-id client-id) + _ (when (> (count (set (map :invoice/vendor invoices))) 1) + (assert-failure "Balance payments can only be done on one vendor at a time.")) + _ (when (> (reduce + 0 (map :invoice/outstanding-balance invoices)) 0.001) + (assert-failure "There isn't a positive balance to pay from.")) + 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) + (assert-failure "You must select invoices that need to be paid.")) + + 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}] + + + + + (audit-transact (-> [] + (conj payment) + (into (invoice-payments invoices invoice-amounts))) (:id context)) + (->graphql {:invoices (d-invoices/get-multi (map :db/id invoices))}))) + (def objects {:payment {:fields {:id {:type :id} :type {:type :payment_type} @@ -601,6 +682,11 @@ :type {:type :payment_type} :client_id {:type :id}} :resolve :mutation/print-checks} + :pay_invoices_from_balance {:type :check_result + :args {:invoices {:type '(list :id)} + :client_id {:type :id}} + :resolve :mutation/pay-invoices-from-balance} + :add_handwritten_check {:type :check_result :args {:invoice_payments {:type '(list :invoice_payment_amount)} :date {:type 'String} @@ -637,7 +723,8 @@ {:payment_type {:values [{:enum-value :check} {:enum-value :cash} {:enum-value :debit} - {:enum-value :credit}]} + {:enum-value :credit} + {:enum-value :balance_credit}]} :payment_status {:values [{:enum-value :voided} {:enum-value :pending} {:enum-value :cleared}]}}) @@ -650,6 +737,7 @@ :mutation/void-payment void-payment :mutation/void-payments void-payments :mutation/print-checks print-checks + :mutation/pay-invoices-from-balance pay-invoices-from-balance :mutation/add-handwritten-check add-handwritten-check }) diff --git a/src/clj/auto_ap/graphql/ledger.clj b/src/clj/auto_ap/graphql/ledger.clj index dd2bbdf5..b9ab53a4 100644 --- a/src/clj/auto_ap/graphql/ledger.clj +++ b/src/clj/auto_ap/graphql/ledger.clj @@ -689,13 +689,7 @@ }) (def enums - {:payment_type {:values [{:enum-value :check} - {:enum-value :cash} - {:enum-value :debit} - {:enum-value :credit}]} - :payment_status {:values [{:enum-value :voided} - {:enum-value :pending} - {:enum-value :cleared}]}}) + {}) (def resolvers diff --git a/src/cljs/auto_ap/views/pages/unpaid_invoices.cljs b/src/cljs/auto_ap/views/pages/unpaid_invoices.cljs index 03b40e9f..691e92ea 100644 --- a/src/cljs/auto_ap/views/pages/unpaid_invoices.cljs +++ b/src/cljs/auto_ap/views/pages/unpaid_invoices.cljs @@ -29,7 +29,8 @@ [goog.string :as gstring] [re-frame.core :as re-frame] [reagent.core :as r] - [vimsical.re-frame.fx.track :as track])) + [vimsical.re-frame.fx.track :as track] + [vimsical.re-frame.cofx.inject :as inject])) (re-frame/reg-event-fx ::params-change @@ -105,6 +106,30 @@ (:invoices (:print-checks result)) (:pdf-url (:print-checks result))])}})) +(re-frame/reg-event-fx + ::pay-invoices-from-balance + [with-user (re-frame/inject-cofx ::inject/sub [::data-page/checked :invoices])] + (fn [{:keys [db user] ::data-page/keys [checked] :as cofx} _] + {:graphql + {:token user + :owns-state {:single ::print-checks} + + :query-obj {:venia/operation {:operation/type :mutation + :operation/name "PayInvoicesFromBalance"} + :venia/queries [[:pay-invoices-from-balance + {:invoices (->> checked + (vals ) + (filter (fn [{:keys [id outstanding-balance] }] + (and id outstanding-balance))) + (map :id)) + :client_id (:client db)} + [[:invoices invoice-read] + :pdf_url]]]} + :on-success (fn [result] + [::checks-printed + (:invoices (:pay-invoices-from-balance result)) + nil])}})) + (re-frame/reg-event-fx ::checks-printed @@ -229,9 +254,16 @@ :disabled (status/disabled-for print-checks-status)} "Debit from " name]) (list ^{:key (str id "-credit")} [:a.dropdown-item {:on-click (dispatch-event-with-propagation [::print-checks id :credit]) - :disabled (status/disabled-for print-checks-status)} "Credit from " name])))) - (when (> balance 0.001) - ^{:key "advanced-divider"} [:hr.dropdown-divider]) + :disabled (status/disabled-for print-checks-status)} "Credit from " name] + )))) + + ^{:key "advanced-divider"} [:hr.dropdown-divider] + + (when (and (> (count checked-invoices) 1) + (= 1 (count (set (map (comp :id :vendor) (vals checked-invoices))))) + (< balance 0.001)) + ^{:key (str "balance-credit")} [:a.dropdown-item {:on-click (dispatch-event-with-propagation [::pay-invoices-from-balance]) + :disabled (status/disabled-for print-checks-status)} "Pay invoices using balance "]) (when (and (= 1 (count (set (map (comp :id :vendor) (vals checked-invoices))))) (> balance 0.001))