Fixes two types of bugs.
This commit is contained in:
@@ -6,18 +6,27 @@
|
|||||||
(require '[auto-ap.utils :refer [dollars=]])
|
(require '[auto-ap.utils :refer [dollars=]])
|
||||||
|
|
||||||
(defn get-bad-ones []
|
(defn get-bad-ones []
|
||||||
(->> (d/query {:query {:find ['?je '?a '(sum ?debit) '(sum ?credit)]
|
|
||||||
|
|
||||||
|
(->> (->> (d/q '[:find ?c
|
||||||
|
:in $
|
||||||
|
:where [?c :client/code]]
|
||||||
|
(d/db auto-ap.datomic/conn))
|
||||||
|
(map first))
|
||||||
|
(mapcat #(->> (d/query {:query {:find ['?je '?a '(sum ?debit) '(sum ?credit)]
|
||||||
:with ['?jel]
|
:with ['?jel]
|
||||||
:in '[$]
|
:in '[$ ?c]
|
||||||
:where ['[?je :journal-entry/amount ?a]
|
:where ['[?je :journal-entry/client ?c]
|
||||||
|
'[?je :journal-entry/amount ?a]
|
||||||
'[?je :journal-entry/line-items ?jel]
|
'[?je :journal-entry/line-items ?jel]
|
||||||
'[(get-else $ ?jel :journal-entry-line/debit 0.0) ?debit]
|
'[(get-else $ ?jel :journal-entry-line/debit 0.0) ?debit]
|
||||||
'[(get-else $ ?jel :journal-entry-line/credit 0.0) ?credit]]
|
'[(get-else $ ?jel :journal-entry-line/credit 0.0) ?credit]]
|
||||||
}
|
}
|
||||||
:args [(d/db auto-ap.datomic/conn)]})
|
:args [(d/db auto-ap.datomic/conn) %]})
|
||||||
(filter (fn [[_ a d c]]
|
(filter (fn [[_ a d c]]
|
||||||
(or (not (dollars= a d))
|
(or (not (dollars= a d))
|
||||||
(not (dollars= a c)))))
|
(not (dollars= a c)))))))
|
||||||
|
|
||||||
#_(count)
|
#_(count)
|
||||||
#_(take 3)))
|
#_(take 3)))
|
||||||
|
|
||||||
@@ -32,7 +41,7 @@
|
|||||||
je))))
|
je))))
|
||||||
(filter (fn [invoice?]
|
(filter (fn [invoice?]
|
||||||
(:invoice/total invoice?)))
|
(:invoice/total invoice?)))
|
||||||
(map (fn [invoice]
|
#_(map (fn [invoice]
|
||||||
{:total (:invoice/total invoice)
|
{:total (:invoice/total invoice)
|
||||||
:client-code (:client/code (:invoice/client invoice))
|
:client-code (:client/code (:invoice/client invoice))
|
||||||
:invoice-number (:invoice/invoice-number invoice)
|
:invoice-number (:invoice/invoice-number invoice)
|
||||||
|
|||||||
@@ -265,6 +265,7 @@
|
|||||||
(assert-can-see-client (:id context) (:db/id (:invoice/client (d-invoices/get-by-id (:invoice_id args)))))
|
(assert-can-see-client (:id context) (:db/id (:invoice/client (d-invoices/get-by-id (:invoice_id args)))))
|
||||||
(let [invoice-id (:invoice_id args)
|
(let [invoice-id (:invoice_id args)
|
||||||
invoice (d-invoices/get-by-id invoice-id)
|
invoice (d-invoices/get-by-id invoice-id)
|
||||||
|
_ (assert-valid-expense-accounts (:expense_accounts args))
|
||||||
deleted (deleted-expense-accounts invoice (:expense_accounts args))
|
deleted (deleted-expense-accounts invoice (:expense_accounts args))
|
||||||
updated {:db/id invoice-id
|
updated {:db/id invoice-id
|
||||||
:invoice/expense-accounts (map
|
:invoice/expense-accounts (map
|
||||||
|
|||||||
@@ -338,7 +338,8 @@
|
|||||||
:args [(d/db auto-ap.datomic/conn)]}))
|
:args [(d/db auto-ap.datomic/conn)]}))
|
||||||
]
|
]
|
||||||
(filter
|
(filter
|
||||||
(fn [[e accounts]] (not= accounts (get jel-accounts e)))
|
(fn [[e accounts]]
|
||||||
|
(not= accounts (get jel-accounts e)))
|
||||||
invoice-accounts)))
|
invoice-accounts)))
|
||||||
|
|
||||||
(defn touch-broken-ledger []
|
(defn touch-broken-ledger []
|
||||||
|
|||||||
@@ -100,25 +100,36 @@
|
|||||||
(map (fn [r] (update r :transaction-rule/description #(some-> % ->pattern))))
|
(map (fn [r] (update r :transaction-rule/description #(some-> % ->pattern))))
|
||||||
(filter #(rule-applies? transaction %))))
|
(filter #(rule-applies? transaction %))))
|
||||||
|
|
||||||
|
(defn spread-cents [cents n]
|
||||||
|
(let [default-spread (for [x (range n)]
|
||||||
|
(int (* cents (/ 1.0 n))))
|
||||||
|
short-by (- cents (reduce + 0 default-spread)) ;; amount that was lost in the differenc
|
||||||
|
adjusted-spread (map
|
||||||
|
(fn [cents increments]
|
||||||
|
(+ cents increments))
|
||||||
|
|
||||||
|
default-spread
|
||||||
|
(concat (take short-by (repeat 1)) (repeat 0)))]
|
||||||
|
(filter #(> % 0) adjusted-spread)))
|
||||||
|
|
||||||
(defn apply-rule [transaction rule valid-locations]
|
(defn apply-rule [transaction rule valid-locations]
|
||||||
(with-precision 2
|
(with-precision 2
|
||||||
(let [accounts (vec (mapcat
|
(let [accounts (vec (mapcat
|
||||||
(fn [tra]
|
(fn [tra]
|
||||||
|
(let [cents-to-distribute (int (Math/round (Math/abs (* (:transaction-rule-account/percentage tra)
|
||||||
|
(:transaction/amount transaction)
|
||||||
|
100))))]
|
||||||
(if (= "Shared" (:transaction-rule-account/location tra))
|
(if (= "Shared" (:transaction-rule-account/location tra))
|
||||||
|
(->> valid-locations
|
||||||
(map
|
(map
|
||||||
(fn [location]
|
(fn [cents location]
|
||||||
{:transaction-account/account (:db/id (:transaction-rule-account/account tra))
|
{:transaction-account/account (:db/id (:transaction-rule-account/account tra))
|
||||||
:transaction-account/amount (Math/abs (* (/ 1.0 (count valid-locations))
|
:transaction-account/amount (* 0.01 cents)
|
||||||
(:transaction-rule-account/percentage tra)
|
|
||||||
(:transaction/amount transaction)))
|
|
||||||
:transaction-account/location location})
|
:transaction-account/location location})
|
||||||
|
(spread-cents cents-to-distribute (count valid-locations))))
|
||||||
|
|
||||||
valid-locations)
|
|
||||||
[(cond-> {:transaction-account/account (:db/id (:transaction-rule-account/account tra))
|
[(cond-> {:transaction-account/account (:db/id (:transaction-rule-account/account tra))
|
||||||
:transaction-account/amount (Math/abs (* (:transaction-rule-account/percentage tra)
|
:transaction-account/amount (* 0.01 cents-to-distribute)}
|
||||||
(:transaction/amount transaction)))}
|
(:transaction-rule-account/location tra) (assoc :transaction-account/location (:transaction-rule-account/location tra)))])))
|
||||||
(:transaction-rule-account/location tra) (assoc :transaction-account/location (:transaction-rule-account/location tra)))]))
|
|
||||||
(filter (comp seq :transaction-rule-account/account) (:transaction-rule/accounts rule))))
|
(filter (comp seq :transaction-rule-account/account) (:transaction-rule/accounts rule))))
|
||||||
accounts (mapv
|
accounts (mapv
|
||||||
(fn [a]
|
(fn [a]
|
||||||
|
|||||||
@@ -173,7 +173,6 @@
|
|||||||
first)))
|
first)))
|
||||||
|
|
||||||
(defn transactions->txs [transactions transaction->bank-account apply-rules existing]
|
(defn transactions->txs [transactions transaction->bank-account apply-rules existing]
|
||||||
(doto
|
|
||||||
(into []
|
(into []
|
||||||
|
|
||||||
(for [transaction transactions
|
(for [transaction transactions
|
||||||
@@ -251,8 +250,7 @@
|
|||||||
(and (not (seq autopay-invoices-matches))
|
(and (not (seq autopay-invoices-matches))
|
||||||
(not (seq unpaid-invoices-matches))
|
(not (seq unpaid-invoices-matches))
|
||||||
(not expected-deposit)) (update 0 #(apply-rules % valid-locations))
|
(not expected-deposit)) (update 0 #(apply-rules % valid-locations))
|
||||||
true (update 0 remove-nils)))))
|
true (update 0 remove-nils))))))
|
||||||
println))
|
|
||||||
|
|
||||||
|
|
||||||
(defn batch-transact [transactions]
|
(defn batch-transact [transactions]
|
||||||
|
|||||||
99
test/clj/auto_ap/integration/rule_matching.clj
Normal file
99
test/clj/auto_ap/integration/rule_matching.clj
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
(ns auto-ap.integration.rule-matching
|
||||||
|
(:require [auto-ap.rule-matching :as sut]
|
||||||
|
[datomic.api :as d]
|
||||||
|
[auto-ap.datomic :refer [uri]]
|
||||||
|
[auto-ap.datomic.migrate :as m]
|
||||||
|
[clojure.test :as t]
|
||||||
|
[clojure.tools.logging :as log]
|
||||||
|
[clojure.set :as set]))
|
||||||
|
|
||||||
|
(defn wrap-setup
|
||||||
|
[f]
|
||||||
|
(with-redefs [auto-ap.datomic/uri "datomic:mem://datomic-transactor:4334/invoice"]
|
||||||
|
(d/create-database uri)
|
||||||
|
(with-redefs [auto-ap.datomic/conn (d/connect uri)]
|
||||||
|
(m/migrate auto-ap.datomic/conn)
|
||||||
|
(f)
|
||||||
|
(d/release auto-ap.datomic/conn)
|
||||||
|
(d/delete-database uri))))
|
||||||
|
|
||||||
|
(t/use-fixtures :each wrap-setup)
|
||||||
|
|
||||||
|
(defn noop-rule [transaction locations]
|
||||||
|
transaction)
|
||||||
|
|
||||||
|
(def base-transaction #:transaction {:amount -12.0
|
||||||
|
:date #inst "2014-01-02T08:00:00.000-00:00"
|
||||||
|
:bank-account 456
|
||||||
|
:client 123
|
||||||
|
:post-date #inst "2014-01-04T08:00:00.000-00:00"
|
||||||
|
:account-id 1234
|
||||||
|
:description-original "original-description"
|
||||||
|
:status "POSTED"
|
||||||
|
:id "6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b"
|
||||||
|
:raw-id "1"
|
||||||
|
:approval-status :transaction-approval-status/unapproved
|
||||||
|
:description-simple "simple-description"})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(t/deftest rule-applying-fn
|
||||||
|
(t/testing "Should apply if description matches"
|
||||||
|
(t/is (sut/rule-applies?
|
||||||
|
base-transaction
|
||||||
|
{:transaction-rule/description #"original-description"
|
||||||
|
:transaction-rule/transaction-approval-status :transaction-approval-status/approved}))
|
||||||
|
|
||||||
|
(t/is (not (sut/rule-applies?
|
||||||
|
base-transaction
|
||||||
|
{:transaction-rule/description #"xxx"
|
||||||
|
:transaction-rule/transaction-approval-status :transaction-approval-status/approved}))))
|
||||||
|
|
||||||
|
(t/testing "Should do nothing if there are no rules"
|
||||||
|
(let [process (sut/rule-applying-fn [])]
|
||||||
|
(t/is (= base-transaction
|
||||||
|
(process base-transaction ["NG"])))))
|
||||||
|
|
||||||
|
(t/testing "Should approve a simple rule"
|
||||||
|
(let [process (sut/rule-applying-fn [{:transaction-rule/description "simple-description"
|
||||||
|
:transaction-rule/transaction-approval-status :transaction-approval-status/approved}])
|
||||||
|
transaction (assoc base-transaction :transaction/description-original "simple-description")]
|
||||||
|
(t/is (= :transaction-approval-status/approved
|
||||||
|
(:transaction/approval-status (process transaction ["NG"]))))))
|
||||||
|
|
||||||
|
(t/testing "spread cents"
|
||||||
|
(t/testing "Should split evenly"
|
||||||
|
(t/is (= [50 50] (sut/spread-cents 100 2)))
|
||||||
|
(t/is (= [50 49] (sut/spread-cents 99 2)))
|
||||||
|
(t/is (= [34 33 33] (sut/spread-cents 100 3)))
|
||||||
|
(t/is (= [100] (sut/spread-cents 100 1)))
|
||||||
|
(t/is (= [2 2] (sut/spread-cents 4 2)))
|
||||||
|
(t/is (= [2 1 1] (sut/spread-cents 4 3)))
|
||||||
|
(t/is (= [2 2 1] (sut/spread-cents 5 3)))
|
||||||
|
(t/is (= [2 1 1 1] (sut/spread-cents 5 4)))
|
||||||
|
(t/is (= [3 2 2] (sut/spread-cents 7 3)))
|
||||||
|
(t/is (= [2 2 2 1] (sut/spread-cents 7 4)))
|
||||||
|
(t/is (= [2 1] (sut/spread-cents 3 2)))
|
||||||
|
(t/is (= [1 1 1] (sut/spread-cents 3 4)))
|
||||||
|
|
||||||
|
(doseq [x (range 20)]
|
||||||
|
(t/is (= 11323 (reduce + 0 (sut/spread-cents 11323 (inc x))))))))
|
||||||
|
|
||||||
|
(t/testing "Should spread across locations"
|
||||||
|
(let [process (sut/rule-applying-fn [{:transaction-rule/description "simple-description"
|
||||||
|
:transaction-rule/accounts [{:transaction-rule-account/location "Shared"
|
||||||
|
:transaction-rule-account/account {:account/numeric-code 2500}
|
||||||
|
:transaction-rule-account/percentage 1.0}]
|
||||||
|
:transaction-rule/transaction-approval-status :transaction-approval-status/approved}])
|
||||||
|
transaction (assoc base-transaction :transaction/description-original "simple-description" :transaction/amount 1.0)]
|
||||||
|
(t/is (= [1.0]
|
||||||
|
(map :transaction-account/amount (:transaction/accounts (process transaction ["NG"])))))
|
||||||
|
(t/is (= [0.5 0.5]
|
||||||
|
(map :transaction-account/amount (:transaction/accounts (process transaction ["NG" "BT"])))))
|
||||||
|
(t/is (= [0.34 0.33 0.33]
|
||||||
|
(map :transaction-account/amount (:transaction/accounts (process transaction ["NG" "BT" "DE"])))))
|
||||||
|
(t/is (= [0.01 0.01]
|
||||||
|
(map :transaction-account/amount (:transaction/accounts (process (assoc transaction :transaction/amount 0.02) ["NG" "BT" "DE"])))))
|
||||||
|
(t/is (= [0.02 0.01 0.01]
|
||||||
|
(map :transaction-account/amount (:transaction/accounts (process (assoc transaction :transaction/amount 0.04) ["NG" "BT" "DE"])))))
|
||||||
|
)))
|
||||||
@@ -55,6 +55,7 @@
|
|||||||
:description-original "original-description"
|
:description-original "original-description"
|
||||||
:status "POSTED"
|
:status "POSTED"
|
||||||
:id "6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b"
|
:id "6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b"
|
||||||
|
:raw-id "1"
|
||||||
:approval-status :transaction-approval-status/unapproved
|
:approval-status :transaction-approval-status/unapproved
|
||||||
:description-simple "simple-description"}]
|
:description-simple "simple-description"}]
|
||||||
result))))
|
result))))
|
||||||
|
|||||||
Reference in New Issue
Block a user