test(invoice): implement unit tests for invoice behaviors
Add comprehensive unit tests for pure invoice business logic: - assert-invoice-amounts-add-up (behaviors 9.4, 11.4) - does-amount-exceed-outstanding? (behavior 13.4) - assert-percentages-add-up (behavior 15.3) - stack-rank and deduplicate (behaviors 24.1, 24.4, 24.5) - clientize-vendor (behavior 8.4) - location-select* (behavior 9.3) - maybe-code-accounts with Shared location spreading (behavior 15.6) - can-undo-autopayment (behaviors 19.2-19.4) - due date / scheduled payment calculations (behaviors 8.2, 8.3) - can-handwrite? and credit-only? (pay wizard behaviors) - due date display logic (behavior 1.7) Also fixes: - user.clj: add missing datomic.api alias (d) used in sample functions - new_invoice_wizard_test.clj: fix sut8 -> sut9 typo Marks completed unit-test behaviors with [x] in invoice.md
This commit is contained in:
188
src/clj/user.clj
188
src/clj/user.clj
@@ -1,32 +1,33 @@
|
||||
(ns user
|
||||
(:require [amazonica.aws.s3 :as s3]
|
||||
[auto-ap.server]
|
||||
[auto-ap.datomic :refer [conn pull-attr random-tempid]]
|
||||
[auto-ap.solr :as solr]
|
||||
[auto-ap.time :as atime]
|
||||
[auto-ap.utils :refer [by]]
|
||||
[clj-time.coerce :as c]
|
||||
[clj-time.core :as t]
|
||||
[clojure.core.async :as async]
|
||||
[auto-ap.handler :refer [app]]
|
||||
[ring.adapter.jetty :refer [run-jetty]]
|
||||
[clojure.data.csv :as csv]
|
||||
[clojure.java.io :as io]
|
||||
[clojure.pprint]
|
||||
[clojure.string :as str]
|
||||
[clojure.tools.namespace.repl :refer [refresh set-refresh-dirs]]
|
||||
[com.brunobonacci.mulog :as mu]
|
||||
[com.brunobonacci.mulog.buffer :as rb]
|
||||
[config.core :refer [env]]
|
||||
[datomic.api :as dc]
|
||||
[puget.printer :as puget]
|
||||
[datomic.api :as d]
|
||||
[figwheel.main.api]
|
||||
[hawk.core]
|
||||
[mount.core :as mount]
|
||||
[nrepl.middleware.print])
|
||||
(:import (org.apache.commons.io.input BOMInputStream)
|
||||
[org.eclipse.jetty.server.handler.gzip GzipHandler]))
|
||||
(:require [amazonica.aws.s3 :as s3]
|
||||
[auto-ap.server]
|
||||
[auto-ap.datomic :refer [conn pull-attr random-tempid]]
|
||||
[auto-ap.solr :as solr]
|
||||
[auto-ap.time :as atime]
|
||||
[auto-ap.utils :refer [by]]
|
||||
[clj-time.coerce :as c]
|
||||
[clj-time.core :as t]
|
||||
[clojure.core.async :as async]
|
||||
[auto-ap.handler :refer [app]]
|
||||
[ring.adapter.jetty :refer [run-jetty]]
|
||||
[clojure.data.csv :as csv]
|
||||
[clojure.java.io :as io]
|
||||
[clojure.pprint]
|
||||
[clojure.string :as str]
|
||||
[clojure.tools.namespace.repl :refer [refresh set-refresh-dirs]]
|
||||
[com.brunobonacci.mulog :as mu]
|
||||
[com.brunobonacci.mulog.buffer :as rb]
|
||||
[config.core :refer [env]]
|
||||
[datomic.api :as dc]
|
||||
[datomic.api :as d]
|
||||
[puget.printer :as puget]
|
||||
|
||||
[figwheel.main.api]
|
||||
[hawk.core]
|
||||
[mount.core :as mount]
|
||||
[nrepl.middleware.print])
|
||||
(:import (org.apache.commons.io.input BOMInputStream)
|
||||
[org.eclipse.jetty.server.handler.gzip GzipHandler]))
|
||||
|
||||
(defn println-event [item]
|
||||
#_(printf "%s: %s - %s:%s by %s\n"
|
||||
@@ -44,8 +45,7 @@
|
||||
item
|
||||
:user)))
|
||||
(when (= :auto-ap.logging/peek (:mulog/event-name item))
|
||||
(println "\u001B[31mTEST")
|
||||
)
|
||||
(println "\u001B[31mTEST"))
|
||||
(when (:error item)
|
||||
(println (:error item)))
|
||||
(puget/cprint (reduce
|
||||
@@ -58,18 +58,15 @@
|
||||
{:seq-limit 10})
|
||||
(println))
|
||||
|
||||
|
||||
(deftype DevPublisher [config buffer transform]
|
||||
|
||||
com.brunobonacci.mulog.publisher.PPublisher
|
||||
(agent-buffer [_]
|
||||
buffer)
|
||||
|
||||
|
||||
(publish-delay [_]
|
||||
200)
|
||||
|
||||
|
||||
(publish [_ buffer]
|
||||
;; items are pairs [offset <item>]
|
||||
(doseq [item (transform (map second (rb/items buffer)))]
|
||||
@@ -77,8 +74,6 @@
|
||||
(flush)
|
||||
(rb/clear buffer)))
|
||||
|
||||
|
||||
|
||||
(defn dev-publisher
|
||||
[{:keys [transform pretty?] :as config}]
|
||||
(DevPublisher. config (rb/agent-buffer 10000) (or transform identity)))
|
||||
@@ -87,29 +82,27 @@
|
||||
[config]
|
||||
(dev-publisher config))
|
||||
|
||||
|
||||
|
||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||
(defn load-accounts [conn]
|
||||
(let [[header & rows] (-> "master-account-list.csv" (io/resource) io/input-stream (BOMInputStream.) (io/reader) csv/read-csv)
|
||||
code->existing-account (by :account/numeric-code (map first (dc/q {:find ['(pull ?e [:account/numeric-code
|
||||
:db/id])]
|
||||
:in ['$]
|
||||
code->existing-account (by :account/numeric-code (map first (dc/q {:find ['(pull ?e [:account/numeric-code
|
||||
:db/id])]
|
||||
:in ['$]
|
||||
:where ['[?e :account/name]]}
|
||||
(dc/db conn))))
|
||||
|
||||
also-merge-txes (fn [also-merge old-account-id]
|
||||
(if old-account-id
|
||||
(let [[sunset-account]
|
||||
(first (dc/q {:find ['?a]
|
||||
:in ['$ '?ac]
|
||||
(first (dc/q {:find ['?a]
|
||||
:in ['$ '?ac]
|
||||
:where ['[?a :account/numeric-code ?ac]]}
|
||||
(dc/db conn) also-merge))]
|
||||
(into (mapv
|
||||
(fn [[entity id _]]
|
||||
[:db/add entity id old-account-id])
|
||||
(dc/q {:find ['?e '?id '?a]
|
||||
:in ['$ '?ac]
|
||||
(dc/q {:find ['?e '?id '?a]
|
||||
:in ['$ '?ac]
|
||||
:where ['[?a :account/numeric-code ?ac]
|
||||
'[?e ?at ?a]
|
||||
'[?at :db/ident ?id]]}
|
||||
@@ -120,7 +113,7 @@
|
||||
|
||||
txes (transduce
|
||||
(comp
|
||||
(map (fn ->map [r]
|
||||
(map (fn ->map [r]
|
||||
(into {} (map vector header r))))
|
||||
(map (fn parse-map [r]
|
||||
{:old-account-id (:db/id (code->existing-account
|
||||
@@ -161,7 +154,6 @@
|
||||
(also-merge-txes also-merge old-account-id))
|
||||
tx)))))
|
||||
|
||||
|
||||
conj
|
||||
[]
|
||||
rows)]
|
||||
@@ -169,8 +161,8 @@
|
||||
|
||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||
(defn find-bad-accounts []
|
||||
(set (map second (dc/q {:find ['(pull ?x [*]) '?z]
|
||||
:in ['$]
|
||||
(set (map second (dc/q {:find ['(pull ?x [*]) '?z]
|
||||
:in ['$]
|
||||
:where ['[?e :account/numeric-code ?z]
|
||||
'[(<= ?z 9999)]
|
||||
'[?x ?a ?e]]}
|
||||
@@ -186,13 +178,12 @@
|
||||
[:db/retractEntity old-account-id])))
|
||||
conj
|
||||
[]
|
||||
(dc/q {:find ['?e]
|
||||
:in ['$]
|
||||
(dc/q {:find ['?e]
|
||||
:in ['$]
|
||||
:where ['[?e :account/numeric-code ?z]
|
||||
'[(<= ?z 9999)]]}
|
||||
(dc/db conn)))))
|
||||
|
||||
|
||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||
(defn find-conflicting-accounts []
|
||||
(filter
|
||||
@@ -202,32 +193,30 @@
|
||||
(fn [acc [e z]]
|
||||
(update acc z conj e))
|
||||
{}
|
||||
(dc/q {:find ['?e '?z]
|
||||
:in ['$]
|
||||
(dc/q {:find ['?e '?z]
|
||||
:in ['$]
|
||||
:where ['[?e :account/numeric-code ?z]]}
|
||||
(dc/db conn)))))
|
||||
|
||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||
(defn customize-accounts [customer filename]
|
||||
(let [[_ & rows] (-> filename (io/resource) io/input-stream (BOMInputStream.) (io/reader) csv/read-csv)
|
||||
[client-id] (first (dc/q (-> {:find ['?e]
|
||||
:in ['$ '?z]
|
||||
[client-id] (first (dc/q (-> {:find ['?e]
|
||||
:in ['$ '?z]
|
||||
:where [['?e :client/code '?z]]}
|
||||
(dc/db conn) customer)))
|
||||
code->existing-account (by :account/numeric-code (map first (dc/q {:find ['(pull ?e [:account/numeric-code
|
||||
{:account/applicability [:db/ident]}
|
||||
:db/id])]
|
||||
:in ['$]
|
||||
code->existing-account (by :account/numeric-code (map first (dc/q {:find ['(pull ?e [:account/numeric-code
|
||||
{:account/applicability [:db/ident]}
|
||||
:db/id])]
|
||||
:in ['$]
|
||||
:where ['[?e :account/name]]}
|
||||
(dc/db conn))))
|
||||
|
||||
existing-account-overrides (dc/q {:find ['?e]
|
||||
:in ['$ '?client-id]
|
||||
existing-account-overrides (dc/q {:find ['?e]
|
||||
:in ['$ '?client-id]
|
||||
:where [['?e :account-client-override/client '?client-id]]}
|
||||
(dc/db conn) client-id)
|
||||
|
||||
|
||||
|
||||
_ (when-let [bad-rows (seq (->> rows
|
||||
(group-by (fn [[_ account]]
|
||||
account))
|
||||
@@ -285,12 +274,11 @@
|
||||
txes
|
||||
#_@(d/transact conn txes)))
|
||||
|
||||
|
||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||
(defn fix-transactions-without-locations [client-code location]
|
||||
(->>
|
||||
(dc/q {:find ['(pull ?e [*])]
|
||||
:in ['$ '?client-code]
|
||||
(dc/q {:find ['(pull ?e [*])]
|
||||
:in ['$ '?client-code]
|
||||
:where ['[?e :transaction/accounts ?ta]
|
||||
'[?e :transaction/matched-rule]
|
||||
'[?e :transaction/approval-status :transaction-approval-status/approved]
|
||||
@@ -307,12 +295,11 @@
|
||||
accounts)))
|
||||
vec))
|
||||
|
||||
|
||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||
(defn entity-history [i]
|
||||
(vec (sort-by first (dc/q
|
||||
{:find ['?tx '?z '?v]
|
||||
:in ['?i '$]
|
||||
{:find ['?tx '?z '?v]
|
||||
:in ['?i '$]
|
||||
:where ['[?i ?a ?v ?tx ?ad]
|
||||
'[?a :db/ident ?z]
|
||||
'[(= ?ad true)]]}
|
||||
@@ -321,8 +308,8 @@
|
||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||
(defn entity-history-with-revert [i]
|
||||
(vec (sort-by first (dc/q
|
||||
{:find ['?tx '?z '?v '?ad]
|
||||
:in ['?i '$]
|
||||
{:find ['?tx '?z '?v '?ad]
|
||||
:in ['?i '$]
|
||||
:where ['[?i ?a ?v ?tx ?ad]
|
||||
'[?a :db/ident ?z]]}
|
||||
i (dc/history (dc/db conn))))))
|
||||
@@ -342,17 +329,15 @@
|
||||
{:start (- i 100)
|
||||
:end (+ i 100)}))))
|
||||
|
||||
|
||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||
(defn start-db []
|
||||
(mu/start-publisher! {:type :dev})
|
||||
(mount.core/start (mount.core/only #{#'auto-ap.datomic/conn})))
|
||||
|
||||
|
||||
(defn- auto-reset-handler [ctx event]
|
||||
(require 'figwheel.main.api)
|
||||
(binding [*ns* *ns*]
|
||||
(clojure.tools.namespace.repl/refresh)
|
||||
(clojure.tools.namespace.repl/refresh)
|
||||
ctx))
|
||||
|
||||
(defn auto-reset
|
||||
@@ -363,15 +348,13 @@
|
||||
(hawk.core/watch! [{:paths ["src/" "test/"]
|
||||
:handler auto-reset-handler}]))
|
||||
|
||||
|
||||
(defn start-http []
|
||||
(defn start-http []
|
||||
(mount.core/start (mount.core/only #{#'auto-ap.server/port #'auto-ap.server/jetty})))
|
||||
|
||||
|
||||
(defn start-dev []
|
||||
(set-refresh-dirs "src")
|
||||
#_(clojure.tools.namespace.repl/disable-reload! (find-ns 'auto-ap.server))
|
||||
#_(clojure.tools.namespace.repl/disable-reload! (find-ns 'auto-ap.time))
|
||||
#_(clojure.tools.namespace.repl/disable-reload! (find-ns 'auto-ap.server))
|
||||
#_(clojure.tools.namespace.repl/disable-reload! (find-ns 'auto-ap.time))
|
||||
(start-db)
|
||||
(start-http)
|
||||
(auto-reset))
|
||||
@@ -392,21 +375,20 @@
|
||||
(for [r data]
|
||||
((apply juxt columns) r)))))
|
||||
|
||||
|
||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||
(defn find-queries [words]
|
||||
(let [obj (s3/list-objects-v2 :bucket-name (:data-bucket env)
|
||||
:prefix (str "queries/"))
|
||||
concurrent 30
|
||||
(let [obj (s3/list-objects-v2 :bucket-name (:data-bucket env)
|
||||
:prefix (str "queries/"))
|
||||
concurrent 30
|
||||
output-chan (async/chan)]
|
||||
(async/pipeline-blocking concurrent
|
||||
output-chan
|
||||
(comp
|
||||
(map #(do
|
||||
[(:key %)
|
||||
(str (slurp (:object-content (s3/get-object
|
||||
:bucket-name (:data-bucket env)
|
||||
:key (:key %)))))]))
|
||||
(str (slurp (:object-content (s3/get-object
|
||||
:bucket-name (:data-bucket env)
|
||||
:key (:key %)))))]))
|
||||
|
||||
(filter #(->> words
|
||||
(every? (fn [w] (str/includes? (second %) w)))))
|
||||
@@ -418,12 +400,11 @@
|
||||
(println "failed " e)))
|
||||
(async/<!! (async/into [] output-chan))))
|
||||
|
||||
|
||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||
(defn upsert-invoice-amounts [tsv]
|
||||
(let [data (with-open [reader (io/reader (char-array tsv))]
|
||||
(doall (csv/read-csv reader :separator \tab)))
|
||||
db (dc/db conn)
|
||||
(let [data (with-open [reader (io/reader (char-array tsv))]
|
||||
(doall (csv/read-csv reader :separator \tab)))
|
||||
db (dc/db conn)
|
||||
i->invoice-id (fn [i]
|
||||
(try (Long/parseLong i)
|
||||
(catch Exception e
|
||||
@@ -460,15 +441,12 @@
|
||||
target-date (clj-time.coerce/to-date (atime/parse target-date atime/normal-date))
|
||||
current-date (:invoice/date invoice)
|
||||
|
||||
|
||||
current-expense-account-amount (:invoice-expense-account/amount invoice-expense-account 0.0)
|
||||
target-expense-account-amount (- (Double/parseDouble amount))
|
||||
|
||||
|
||||
current-expense-account-location (:invoice-expense-account/location invoice-expense-account)
|
||||
target-expense-account-location location
|
||||
|
||||
|
||||
[[_ _ invoice-payment]] (vec (dc/q
|
||||
'[:find ?p ?a ?ip
|
||||
:in $ ?i
|
||||
@@ -479,7 +457,7 @@
|
||||
:when current-total]
|
||||
|
||||
[(when (not (auto-ap.utils/dollars= current-total target-total))
|
||||
{:db/id invoice-id
|
||||
{:db/id invoice-id
|
||||
:invoice/total target-total})
|
||||
|
||||
(when new-account?
|
||||
@@ -512,7 +490,6 @@
|
||||
(filter identity)
|
||||
vec)))
|
||||
|
||||
|
||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||
(defn get-schema [prefix]
|
||||
(->> (dc/q '[:find ?i
|
||||
@@ -537,7 +514,6 @@
|
||||
(defn init-repl []
|
||||
(set! nrepl.middleware.print/*print-fn* clojure.pprint/pprint))
|
||||
|
||||
|
||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||
(defn sample-ledger-import
|
||||
([client-code]
|
||||
@@ -546,7 +522,7 @@
|
||||
(let [client-location (ffirst (d/q '[:find ?l :in $ ?c :where [?c :client/locations ?l]] (dc/db conn) [:client/code client-code]))]
|
||||
(clojure.data.csv/write-csv
|
||||
*out*
|
||||
(for [n (range n)
|
||||
(for [n (range n)
|
||||
:let [v (rand-nth (map first (d/q '[:find ?vn :where [_ :vendor/name ?vn]] (dc/db conn))))
|
||||
[{a-1 :account/numeric-code a-1-location :account/location}
|
||||
{a-2 :account/numeric-code a-2-location :account/location}] (->> (d/q '[:find (pull ?a [:account/numeric-code :account/location]) :where [?a :account/numeric-code]]
|
||||
@@ -559,12 +535,11 @@
|
||||
(t/minus (t/days (rand-int 60)))
|
||||
(atime/unparse atime/normal-date))
|
||||
id (rand-int 100000)]
|
||||
a [[(str id) client-code "synthetic" v d a-1 (or a-1-location client-location) 0 amount]
|
||||
[(str id) client-code "synthetic" v d a-2 (or a-2-location client-location) amount 0]]]
|
||||
a [[(str id) client-code "synthetic" v d a-1 (or a-1-location client-location) 0 amount]
|
||||
[(str id) client-code "synthetic" v d a-2 (or a-2-location client-location) amount 0]]]
|
||||
a)
|
||||
:separator \tab))))
|
||||
|
||||
|
||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||
(defn sample-manual-yodlee
|
||||
([client-code]
|
||||
@@ -573,7 +548,7 @@
|
||||
(let [bank-accounts (map first (d/q '[:find ?bac :in $ ?c :where [?c :client/bank-accounts ?b] [?b :bank-account/code ?bac]] (dc/db conn) [:client/code client-code]))]
|
||||
(clojure.data.csv/write-csv
|
||||
*out*
|
||||
(for [n (range n)
|
||||
(for [n (range n)
|
||||
:let [amount (rand-int 2000)
|
||||
d (-> (t/now)
|
||||
(t/minus (t/days (rand-int 60)))
|
||||
@@ -582,8 +557,6 @@
|
||||
["posted" d (str "Random Description - " id) "Travel" nil nil (- amount) nil nil nil nil nil (rand-nth bank-accounts) client-code])
|
||||
:separator \tab))))
|
||||
|
||||
|
||||
|
||||
(defn index-solr
|
||||
[]
|
||||
(println "invoice")
|
||||
@@ -591,7 +564,7 @@
|
||||
:in $
|
||||
:where [?i :invoice/invoice-number]
|
||||
(not [?i :invoice/status :invoice-status/voided])]
|
||||
:args [(dc/db conn)]})
|
||||
:args [(dc/db conn)]})
|
||||
(map first)
|
||||
(partition-all 500))]
|
||||
(print ".")
|
||||
@@ -604,7 +577,7 @@
|
||||
:in $
|
||||
:where [?i :payment/date]
|
||||
(not [?i :payment/status :payment-status/voided])]
|
||||
:args [(dc/db conn)]})
|
||||
:args [(dc/db conn)]})
|
||||
(map first)
|
||||
(partition-all 500))]
|
||||
(print ".")
|
||||
@@ -617,7 +590,7 @@
|
||||
:in $
|
||||
:where [?i :transaction/description-original]
|
||||
(not [?i :transaction/approval-status :transaction-approval-status/suppressed])]
|
||||
:args [(dc/db conn)]})
|
||||
:args [(dc/db conn)]})
|
||||
(map first)
|
||||
(partition-all 500))]
|
||||
(print ".")
|
||||
@@ -628,7 +601,7 @@
|
||||
(doseq [batch (->> (dc/qseq {:query '[:find ?i
|
||||
:in $
|
||||
:where [?i :journal-entry/date]]
|
||||
:args [(dc/db conn)]})
|
||||
:args [(dc/db conn)]})
|
||||
(map first)
|
||||
(partition-all 500))]
|
||||
(print ".")
|
||||
@@ -643,4 +616,3 @@
|
||||
(print ".")
|
||||
@(dc/transact auto-ap.datomic/conn n)))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user