Adds ability to find expected deposits and link them
This commit is contained in:
@@ -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]]}
|
||||
|
||||
@@ -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}]]}})
|
||||
|
||||
|
||||
@@ -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))]
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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))))))
|
||||
|
||||
|
||||
|
||||
109
src/clj/user.clj
109
src/clj/user.clj
@@ -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)
|
||||
#{}))
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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])}]]])]]))
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
:date
|
||||
[:yodlee_merchant [:name :yodlee-id :id]]
|
||||
:post_date
|
||||
[:expected-deposit [:id :date]]
|
||||
[:forecast-match [:id :identifier]]
|
||||
:status
|
||||
:description_original
|
||||
|
||||
@@ -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)}))}]]])]]]]])]]])]]]))
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user