Adds ability to find expected deposits and link them

This commit is contained in:
2021-11-24 16:50:13 -08:00
parent 42bd65357f
commit 06ea51a168
11 changed files with 250 additions and 27 deletions

View File

@@ -32,6 +32,11 @@
:where ['[?e :expected-deposit/client ?xx]]}
:args [(set (map :db/id (limited-clients (:id args))))]})
(:exact-match-id args)
(merge-query {:query {:in ['?e]
:where []}
:args [(:exact-match-id args)]})
(:client-id args)
(merge-query {:query {:in ['?client-id]
:where ['[?e :expected-deposit/client ?client-id]]}

View File

@@ -195,10 +195,15 @@
:add-sales-date {:txes [[{:db/ident :expected-deposit/sales-date
:db/doc "The date of sales the deposit was for"
:db/valueType :db.type/instant
:db/cardinality :db.cardinality/one}]]}})
:db/cardinality :db.cardinality/one}]]}
:add-expected-deposit-status {:txes [[{:db/ident :expected-deposit/status
:db/doc "Whether the deposit has been cleared"
:db/valueType :db.type/ref
:db/cardinality :db.cardinality/one}
{:db/ident :expected-deposit-status/pending}
{:db/ident :expected-deposit-status/cleared}
{:db/ident :transaction/expected-deposit
:db/doc "If this transaction is a deposit, the deposit that we anticipated"
:db/valueType :db.type/ref
:db/cardinality :db.cardinality/one}]]}})

View File

@@ -158,6 +158,7 @@
:transaction/vendor [:db/id :vendor/name]
:transaction/matched-rule [:db/id :transaction-rule/note]
:transaction/payment [:db/id :payment/date]
:transaction/expected-deposit [:db/id :expected-deposit/date]
:transaction/accounts [:transaction-account/amount
:db/id
:transaction-account/location
@@ -167,9 +168,10 @@
(map #(update % :transaction/date c/from-date))
(map #(update % :transaction/post-date c/from-date))
(map (fn [transaction]
(if (:transaction/payment transaction)
(update-in transaction [:transaction/payment :payment/date] c/from-date)
transaction)))
(cond-> transaction
(:transaction/payment transaction) (update-in [:transaction/payment :payment/date] c/from-date)
(:transaction/expected-deposit transaction) (update-in [:transaction/expected-deposit :expected-deposit/date] c/from-date))
))
(map #(dissoc % :transaction/id))
(group-by :db/id))]

View File

@@ -339,6 +339,7 @@
:client {:type :client}
:accounts {:type '(list :invoices_expense_accounts)}
:payment {:type :payment}
:expected_deposit {:type :expected_deposit}
:vendor {:type :vendor}
:bank_account {:type :bank_account}
:date {:type 'String}
@@ -651,6 +652,7 @@
:expected_deposit_page {:type :expected_deposit_page
:args {:client_id {:type :id}
:exact_match_id {:type :id}
:date_range {:type :date_range}
:total_lte {:type :money}
:total_gte {:type :money}

View File

@@ -155,9 +155,24 @@
nil))
nil))
(defn find-expected-deposit [client-id amount date]
(when date
(-> (d/q
'[:find ?ed
:in $ ?c ?a ?d-start
:where
[?ed :expected-deposit/client ?c]
(not [?ed :expected-deposit/status :expected-deposit-status/cleared])
[?ed :expected-deposit/date ?d]
[(>= ?d ?d-start)]
[?ed :expected-deposit/total ?a2]
[(auto-ap.utils/dollars= ?a2 ?a)]
]
(d/db conn) client-id amount (coerce/to-date (t/plus date (t/days -10))))
first
first)))
(defn transactions->txs [transactions transaction->bank-account apply-rules existing]
(log/info transactions)
(into []
(for [transaction transactions
@@ -190,13 +205,15 @@
(= "POSTED" status)
(or (not (:start-date bank-account))
(t/after? date (:start-date bank-account)))
)]
(t/after? date (:start-date bank-account))))]
(let [existing-check (transaction->existing-payment transaction check-number client-id bank-account-id amount id)
autopay-invoices-matches (when-not existing-check
(match-transaction-to-unfulfilled-autopayments amount client-id ))
unpaid-invoices-matches (when-not existing-check
(match-transaction-to-unpaid-invoices amount client-id ))]
(match-transaction-to-unpaid-invoices amount client-id ))
expected-deposit (when (and (> amount 0.0)
(not existing-check))
(find-expected-deposit client-id amount date))]
(cond->
[#:transaction
{:post-date (coerce/to-date (time/parse post-date "YYYY-MM-dd"))
@@ -225,10 +242,13 @@
;; temporarily removed to automatically match autopaid invoices
#_(and (not existing-check)
(seq autopay-invoices-matches)) #_(add-new-payment autopay-invoices-matches bank-account-id client-id)
expected-deposit (update 0 #(assoc % :transaction/expected-deposit {:db/id expected-deposit
:expected-deposit/status :expected-deposit-status/cleared}))
(and (not (seq autopay-invoices-matches))
(not (seq unpaid-invoices-matches))) (update 0 #(apply-rules % valid-locations))
(not (seq unpaid-invoices-matches))
(not expected-deposit)) (update 0 #(apply-rules % valid-locations))
true (update 0 remove-nils))))))

View File

@@ -635,4 +635,113 @@
(auto-ap.square.core/upsert-settlements client-code)))
(defn upsert-invoice-amounts [tsv]
(let [data (with-open [reader (io/reader (char-array tsv))]
(doall (csv/read-csv reader :separator \tab)))
db (d/db auto-ap.datomic/conn)
invoice-totals (->> data
(drop 1)
(group-by first)
(map (fn [[k values]]
[(Long/parseLong k)
(reduce + 0.0
(->> values
(map (fn [[_ _ amount]]
(- (Double/parseDouble amount))))))
]))
(into {}))]
(->>
(for [[invoice-id expense-account-id amount expense-account location] (drop 1 data)
:let [
invoice-id (Long/parseLong invoice-id)
invoice (d/entity db invoice-id)
current-total (:invoice/total invoice)
target-total (invoice-totals invoice-id)
expense-account-id (Long/parseLong expense-account-id)
current-expense-account-code (:account/numeric-code (:invoice-expense-account/account (d/entity db expense-account-id)))
target-expense-account-code (Long/parseLong (str/trim expense-account))
[[target-expense-account-id]] (vec (d/q
'[:find ?a
:in $ ?c
:where [?a :account/numeric-code ?c]
]
db target-expense-account-code))
current-expense-account-amount (:invoice-expense-account/amount (d/entity db expense-account-id))
target-expense-account-amount (- (Double/parseDouble amount))
current-expense-account-location (:invoice-expense-account/location (d/entity db expense-account-id))
target-expense-account-location location
[[payment-id payment-amount]] (vec (d/q
'[:find ?p ?a
:in $ ?i
:where [?ip :invoice-payment/invoice ?i]
[?ip :invoice-payment/amount ?a]
[?ip :invoice-payment/payment ?p]
]
db invoice-id))]]
[
(when (not (auto-ap.utils/dollars= current-total target-total))
(if payment-id
(println "Cannot update" invoice-id " of " current-total "to be" target-total "because it has a payment (" payment-id ") of" payment-amount )
{:db/id invoice-id
:invoice/total target-total}))
(when (and (not (auto-ap.utils/dollars= current-expense-account-amount target-expense-account-amount))
(or (auto-ap.utils/dollars= current-total target-total)
(not payment-id)))
{:db/id expense-account-id
:invoice-expense-account/amount target-expense-account-amount})
(when (not= current-expense-account-location
target-expense-account-location)
{:db/id expense-account-id
:invoice-expense-account/location target-expense-account-location})
(when (not= target-expense-account-code current-expense-account-code )
{:db/id expense-account-id
:invoice-expense-account/account target-expense-account-id})]
#_(println (auto-ap.utils/dollars= current-total amount) current-total amount current-expense-account-code expense-account-code)
)
(mapcat identity)
(filter identity)
vec)))
(defn get-schema [prefix]
(->> (d/q '[:find ?i
:in $ ?p
:where [_ :db/ident ?i]
[(namespace ?i) ?p]] (d/db auto-ap.datomic/conn) prefix)
(mapcat identity)
vec
))
(defn manually-add-transaction []
(auto-ap.yodlee.import/transactions->txs [{:postDate "2014-01-04"
:accountId 1234
:date "2021-06-05"
:id 1
:amount {:amount -1743.25}
:description {:original "original-description"
:simple "simple-description"}
:merchant {:id "123"
:name "456"}
:baseType "DEBIT"
:status "POSTED"
:bank-account {:db/id [:bank-account/code "NGAK-1"]
:client/_bank-accounts {:db/id 17592186045456
:client/locations ["MH"]}}}]
:bank-account
(fn noop-rule [transaction locations]
transaction)
#{}))

View File

@@ -22,6 +22,7 @@
{:start (:start params 0)
:sort (:sort params)
:per-page (:per-page params)
:exact-match-id (some-> (:exact-match-id params) str)
:total-gte (:amount-gte (:total-range params))
:total-lte (:amount-lte (:total-range params))
:date-range (:date-range params)

View File

@@ -51,5 +51,12 @@
"Uber Eats"]
[:a.panel-block {:on-click (dispatch-event [::data-page/filter-changed data-page :processor "grubhub"])}
[:span.panel-icon [:img.level-item {:src "/img/grubhub.png"}]]
"Grubhub"]]]])]]))
"Grubhub"]]]])
(when-let [exact-match-id @(re-frame/subscribe [::data-page/filter data-page :exact-match-id])]
[:div
[:p.menu-label "Specific Expected Deposit"]
[:span.tag.is-medium exact-match-id " "
[:button.delete.is-small {:on-click
(dispatch-event [::data-page/filter-changed data-page :exact-match-id nil])}]]])]]))

View File

@@ -11,6 +11,7 @@
:date
[:yodlee_merchant [:name :yodlee-id :id]]
:post_date
[:expected-deposit [:id :date]]
[:forecast-match [:id :identifier]]
:status
:description_original

View File

@@ -92,9 +92,10 @@
[grid/sortable-header-cell {:sort-key "status" :sort-name "Status" :style {:width "7em"}} "Status"]
[grid/header-cell {:style {:width (action-cell-width 3)}}]]]
[grid/body
(for [{:keys [client account vendor approval-status payment status bank-account description-original date amount id yodlee-merchant ] :as i} (:data data)]
(for [{:keys [client account vendor approval-status payment expected-deposit status bank-account description-original date amount id yodlee-merchant ] :as i} (:data data)]
^{:key id}
[grid/row {:class (:class i) :id id :entity i}
(println expected-deposit)
(when-not selected-client
[grid/cell {} (:name client)])
@@ -124,7 +125,7 @@
[buttons/fa-icon {:event [::intend-to-edit i]
:class (status/class-for (get states id))
:icon "fa-pencil"}]
(when payment
(when (some identity [payment expected-deposit])
[drop-down {:id [::links id]
:is-right? true
:header [buttons/fa-icon {:class "badge"
@@ -135,12 +136,23 @@
[:div.dropdown-item
[:table.table.grid.compact
[:tbody
[:tr
[:td
"Payment"]
[:td (date->str (:date payment) pretty)]
[:td
[buttons/fa-icon {:icon "fa-external-link"
:href (str (bidi/path-for routes/routes :payments )
"?"
(url/map->query {:exact-match-id (:id payment)}))}]]]]]]]])]]])]]]))
(when payment
[:tr
[:td
"Payment"]
[:td (date->str (:date payment) pretty)]
[:td
[buttons/fa-icon {:icon "fa-external-link"
:href (str (bidi/path-for routes/routes :payments )
"?"
(url/map->query {:exact-match-id (:id payment)}))}]]])
(when expected-deposit
[:tr
[:td
"Expected Deposit"]
[:td (date->str (:date expected-deposit) pretty)]
[:td
[buttons/fa-icon {:icon "fa-external-link"
:href (str (bidi/path-for routes/routes :expected-deposits )
"?"
(url/map->query {:exact-match-id (:id expected-deposit)}))}]]])]]]]])]]])]]]))

View File

@@ -135,6 +135,65 @@
(t/is (= nil
(:transaction/payment result)))))))
(t/testing "Should match expected-deposits"
(let [{:strs [bank-account-id client-id expected-deposit-id]} (->> [#:expected-deposit {:client "client-id"
:date #inst "2021-07-01T00:00:00-08:00"
:total 100.0
:location "MF"
:status :expected-deposit-status/pending
:db/id "expected-deposit-id"}
#:bank-account {:name "Bank account"
:db/id "bank-account-id"}
#:client {:name "Client"
:db/id "client-id"
:locations ["MF"]
:bank-accounts ["bank-account-id"]}]
(d/transact (d/connect uri))
deref
:tempids)]
(t/testing "Should match within 10 days"
(let [[[transaction-result]] (sut/transactions->txs [(assoc base-transaction
:date "2021-07-03"
:amount {:amount -100.0}
:bank-account {:db/id bank-account-id
:client/_bank-accounts {:db/id client-id
:client/locations ["MF"]}})]
:bank-account
noop-rule
#{})]
(t/is (= expected-deposit-id
(sut/find-expected-deposit client-id 100.0 (clj-time.coerce/to-date-time #inst "2021-07-03T00:00:00-08:00"))))
(t/is (= {:db/id expected-deposit-id
:expected-deposit/status :expected-deposit-status/cleared}
(:transaction/expected-deposit transaction-result)))))
(t/testing "Should not match old expected deposits"
(let [[[transaction-result]] (sut/transactions->txs [(assoc base-transaction
:date "2021-07-13"
:amount {:amount -100.0}
:bank-account {:db/id bank-account-id
:client/_bank-accounts {:db/id client-id
:client/locations ["MF"]}})]
:bank-account
noop-rule
#{})]
(t/is (not (:transaction/expected-deposit transaction-result)))))
(t/testing "Should only match exact."
(let [[[transaction-result]] (sut/transactions->txs [(assoc base-transaction
:date "2021-07-03"
:amount {:amount -100.01}
:bank-account {:db/id bank-account-id
:client/_bank-accounts {:db/id client-id
:client/locations ["MF"]}})]
:bank-account
noop-rule
#{})]
(t/is (not (:transaction/expected-deposit transaction-result)))))))
#_(t/testing "Auto-pay Invoices"
(t/testing "Should match paid invoice that doesn't have a payment yet"
(let [{:strs [bank-account-id client-id invoice1-id invoice2-id vendor-id]} (->> [#:invoice {:status :invoice-status/paid