Compare commits
1 Commits
test-plan-
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| a78c818270 |
@@ -55,7 +55,7 @@ Every mutating operation checks:
|
|||||||
| 1.4 | It should show "Voided" status as a red pill | UI | [ ] |
|
| 1.4 | It should show "Voided" status as a red pill | UI | [ ] |
|
||||||
| 1.5 | It should show "Scheduled" status as a yellow pill when a scheduled payment exists | UI | [ ] |
|
| 1.5 | It should show "Scheduled" status as a yellow pill when a scheduled payment exists | UI | [ ] |
|
||||||
| 1.6 | It should show "Unpaid" status as a secondary-colored pill | UI | [ ] |
|
| 1.6 | It should show "Unpaid" status as a secondary-colored pill | UI | [ ] |
|
||||||
| 1.7 | It should display due dates relative to today: "today", "in X days", or "X days ago" with appropriate color coding | Unit + UI | [x] |
|
| 1.7 | It should display due dates relative to today: "today", "in X days", or "X days ago" with appropriate color coding | Unit + UI | [ ] |
|
||||||
| 1.8 | It should show a partial payment indicator "of $X.XX" when outstanding balance differs from total | UI | [ ] |
|
| 1.8 | It should show a partial payment indicator "of $X.XX" when outstanding balance differs from total | UI | [ ] |
|
||||||
| 1.9 | It should display a links dropdown showing payments, transactions, ledger entries, and source files for each invoice | UI | [ ] |
|
| 1.9 | It should display a links dropdown showing payments, transactions, ledger entries, and source files for each invoice | UI | [ ] |
|
||||||
| 1.10 | It should group table rows by vendor name when sorted by vendor | Integration | [ ] |
|
| 1.10 | It should group table rows by vendor name when sorted by vendor | Integration | [ ] |
|
||||||
@@ -141,9 +141,9 @@ Every mutating operation checks:
|
|||||||
| # | Behavior | Test Strategy | Status |
|
| # | Behavior | Test Strategy | Status |
|
||||||
|---|----------|---------------|--------|
|
|---|----------|---------------|--------|
|
||||||
| 8.1 | It should require client, vendor, date, invoice number, and total | Integration | [ ] |
|
| 8.1 | It should require client, vendor, date, invoice number, and total | Integration | [ ] |
|
||||||
| 8.2 | It should auto-calculate the due date from vendor terms when client, date, and vendor are selected | Unit | [x] |
|
| 8.2 | It should auto-calculate the due date from vendor terms when client, date, and vendor are selected | Unit | [ ] |
|
||||||
| 8.3 | It should auto-calculate the scheduled payment date from vendor autopay settings | Unit | [x] |
|
| 8.3 | It should auto-calculate the scheduled payment date from vendor autopay settings | Unit | [ ] |
|
||||||
| 8.4 | It should suggest the vendor's default expense account | Unit | [x] |
|
| 8.4 | It should suggest the vendor's default expense account | Unit | [ ] |
|
||||||
| 8.5 | It should prevent duplicate invoice numbers for the same vendor and client | Unit + Integration | [ ] |
|
| 8.5 | It should prevent duplicate invoice numbers for the same vendor and client | Unit + Integration | [ ] |
|
||||||
| 8.6 | It should allow editing all fields when creating a new invoice | UI | [ ] |
|
| 8.6 | It should allow editing all fields when creating a new invoice | UI | [ ] |
|
||||||
|
|
||||||
@@ -153,8 +153,8 @@ Every mutating operation checks:
|
|||||||
|---|----------|---------------|--------|
|
|---|----------|---------------|--------|
|
||||||
| 9.1 | It should allow adding multiple expense account rows | UI | [ ] |
|
| 9.1 | It should allow adding multiple expense account rows | UI | [ ] |
|
||||||
| 9.2 | It should allow selecting an account, location, and amount per row | UI | [ ] |
|
| 9.2 | It should allow selecting an account, location, and amount per row | UI | [ ] |
|
||||||
| 9.3 | It should auto-populate the location from the account's configured location, or default to "Shared" | Unit | [x] |
|
| 9.3 | It should auto-populate the location from the account's configured location, or default to "Shared" | Unit | [ ] |
|
||||||
| 9.4 | It should validate that expense account amounts sum to the invoice total | Unit + Integration | [x] |
|
| 9.4 | It should validate that expense account amounts sum to the invoice total | Unit + Integration | [ ] |
|
||||||
| 9.5 | Given a "Shared" location, when the invoice is saved, then the amount should be spread equally across all client locations | Unit | [ ] |
|
| 9.5 | Given a "Shared" location, when the invoice is saved, then the amount should be spread equally across all client locations | Unit | [ ] |
|
||||||
| 9.6 | Given a "Shared" location with an odd total, when spread across N locations, then the remainder should be distributed 1 cent at a time to the first locations | Unit | [x] |
|
| 9.6 | Given a "Shared" location with an odd total, when spread across N locations, then the remainder should be distributed 1 cent at a time to the first locations | Unit | [x] |
|
||||||
| 9.7 | Given a negative total, when spread across locations, then negative amounts should be distributed correctly | Unit | [x] |
|
| 9.7 | Given a negative total, when spread across locations, then negative amounts should be distributed correctly | Unit | [x] |
|
||||||
@@ -177,7 +177,7 @@ Every mutating operation checks:
|
|||||||
| 11.1 | It should allow editing unpaid and paid invoices | Integration | [ ] |
|
| 11.1 | It should allow editing unpaid and paid invoices | Integration | [ ] |
|
||||||
| 11.2 | It should disable the vendor field when editing | UI | [ ] |
|
| 11.2 | It should disable the vendor field when editing | UI | [ ] |
|
||||||
| 11.3 | It should allow modifying expense account amounts, adding/removing accounts | Integration | [ ] |
|
| 11.3 | It should allow modifying expense account amounts, adding/removing accounts | Integration | [ ] |
|
||||||
| 11.4 | It should validate that modified amounts still sum to the total | Unit + Integration | [x] |
|
| 11.4 | It should validate that modified amounts still sum to the total | Unit + Integration | [ ] |
|
||||||
| 11.5 | Given the user saves changes, then the invoice row should update in place without a full page reload | UI | [ ] |
|
| 11.5 | Given the user saves changes, then the invoice row should update in place without a full page reload | UI | [ ] |
|
||||||
| 11.6 | It should block editing invoices with dates before the client's locked-until date | Integration | [ ] |
|
| 11.6 | It should block editing invoices with dates before the client's locked-until date | Integration | [ ] |
|
||||||
|
|
||||||
@@ -202,7 +202,7 @@ Every mutating operation checks:
|
|||||||
| 13.1 | It should display a grid of selected invoices with vendor, number, total, and pay amount | UI | [ ] |
|
| 13.1 | It should display a grid of selected invoices with vendor, number, total, and pay amount | UI | [ ] |
|
||||||
| 13.2 | It should default to "Pay in full" mode, paying the outstanding balance of each invoice | Integration | [ ] |
|
| 13.2 | It should default to "Pay in full" mode, paying the outstanding balance of each invoice | Integration | [ ] |
|
||||||
| 13.3 | It should allow switching to "Customize payments" mode to set individual pay amounts | UI | [ ] |
|
| 13.3 | It should allow switching to "Customize payments" mode to set individual pay amounts | UI | [ ] |
|
||||||
| 13.4 | It should validate that custom payment amounts do not exceed the outstanding balance | Unit + Integration | [x] |
|
| 13.4 | It should validate that custom payment amounts do not exceed the outstanding balance | Unit + Integration | [ ] |
|
||||||
| 13.5 | It should require a check number for handwritten checks | Integration | [ ] |
|
| 13.5 | It should require a check number for handwritten checks | Integration | [ ] |
|
||||||
| 13.6 | It should block payment if the invoice date is before the client's locked-until date | Integration | [ ] |
|
| 13.6 | It should block payment if the invoice date is before the client's locked-until date | Integration | [ ] |
|
||||||
| 13.7 | Given the user submits a check payment, when successful, then a PDF download link should be provided | Integration | [ ] |
|
| 13.7 | Given the user submits a check payment, when successful, then a PDF download link should be provided | Integration | [ ] |
|
||||||
@@ -223,10 +223,10 @@ Every mutating operation checks:
|
|||||||
|---|----------|---------------|--------|
|
|---|----------|---------------|--------|
|
||||||
| 15.1 | It should allow selecting multiple invoices and opening the bulk edit wizard | UI | [ ] |
|
| 15.1 | It should allow selecting multiple invoices and opening the bulk edit wizard | UI | [ ] |
|
||||||
| 15.2 | It should allow adding expense account rows with account, location, and percentage | UI | [ ] |
|
| 15.2 | It should allow adding expense account rows with account, location, and percentage | UI | [ ] |
|
||||||
| 15.3 | It should validate that percentages sum to 100% | Unit + Integration | [x] |
|
| 15.3 | It should validate that percentages sum to 100% | Unit + Integration | [ ] |
|
||||||
| 15.4 | Given valid percentages, when submitted, then all selected invoices should be coded with the new expense accounts | Integration | [ ] |
|
| 15.4 | Given valid percentages, when submitted, then all selected invoices should be coded with the new expense accounts | Integration | [ ] |
|
||||||
| 15.5 | It should exclude invoices with dates before the client's locked-until date | Integration | [ ] |
|
| 15.5 | It should exclude invoices with dates before the client's locked-until date | Integration | [ ] |
|
||||||
| 15.6 | It should spread "Shared" locations across all client locations, rounding cents correctly | Unit | [x] |
|
| 15.6 | It should spread "Shared" locations across all client locations, rounding cents correctly | Unit | [ ] |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -271,9 +271,9 @@ Every mutating operation checks:
|
|||||||
| # | Behavior | Test Strategy | Status |
|
| # | Behavior | Test Strategy | Status |
|
||||||
|---|----------|---------------|--------|
|
|---|----------|---------------|--------|
|
||||||
| 19.1 | Given a paid invoice with a scheduled payment and no linked payments, when the user undoes autopay, then the status should reset to unpaid and outstanding should equal total | Integration | [ ] |
|
| 19.1 | Given a paid invoice with a scheduled payment and no linked payments, when the user undoes autopay, then the status should reset to unpaid and outstanding should equal total | Integration | [ ] |
|
||||||
| 19.2 | It should block undoing autopay for invoices without scheduled payments | Unit + Integration | [x] |
|
| 19.2 | It should block undoing autopay for invoices without scheduled payments | Unit + Integration | [ ] |
|
||||||
| 19.3 | It should block undoing autopay for invoices with linked payments | Unit + Integration | [x] |
|
| 19.3 | It should block undoing autopay for invoices with linked payments | Unit + Integration | [ ] |
|
||||||
| 19.4 | It should block undoing autopay for invoices that are not paid | Unit + Integration | [x] |
|
| 19.4 | It should block undoing autopay for invoices that are not paid | Unit + Integration | [ ] |
|
||||||
| 19.5 | It should block undoing autopay for invoices with dates before the client's locked-until date | Integration | [ ] |
|
| 19.5 | It should block undoing autopay for invoices with dates before the client's locked-until date | Integration | [ ] |
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -324,11 +324,11 @@ Every mutating operation checks:
|
|||||||
|
|
||||||
| # | Behavior | Test Strategy | Status |
|
| # | Behavior | Test Strategy | Status |
|
||||||
|---|----------|---------------|--------|
|
|---|----------|---------------|--------|
|
||||||
| 24.1 | It should extract total from AMOUNT_DUE or TOTAL fields | Unit | [x] |
|
| 24.1 | It should extract total from AMOUNT_DUE or TOTAL fields | Unit | [ ] |
|
||||||
| 24.2 | It should extract customer from CUSTOMER_NUMBER or RECEIVER_NAME, falling back to Solr search | Unit + Integration | [ ] |
|
| 24.2 | It should extract customer from CUSTOMER_NUMBER or RECEIVER_NAME, falling back to Solr search | Unit + Integration | [ ] |
|
||||||
| 24.3 | It should extract vendor from VENDOR_NAME, falling back to Solr search | Unit + Integration | [ ] |
|
| 24.3 | It should extract vendor from VENDOR_NAME, falling back to Solr search | Unit + Integration | [ ] |
|
||||||
| 24.4 | It should extract date from INVOICE_RECEIPT_DATE, ORDER_DATE, or DELIVERY_DATE | Unit | [x] |
|
| 24.4 | It should extract date from INVOICE_RECEIPT_DATE, ORDER_DATE, or DELIVERY_DATE | Unit | [ ] |
|
||||||
| 24.5 | It should extract invoice number from INVOICE_RECEIPT_ID or PO_NUMBER | Unit | [x] |
|
| 24.5 | It should extract invoice number from INVOICE_RECEIPT_ID or PO_NUMBER | Unit | [ ] |
|
||||||
|
|
||||||
### Form Behaviors
|
### Form Behaviors
|
||||||
|
|
||||||
|
|||||||
@@ -108,11 +108,7 @@
|
|||||||
"url": "https://mcp.context7.com/mcp",
|
"url": "https://mcp.context7.com/mcp",
|
||||||
"enabled": true
|
"enabled": true
|
||||||
},
|
},
|
||||||
"clojure-mcp": {
|
|
||||||
"type": "local",
|
|
||||||
"command": ["clojure", "-Tmcp", "start", ":config-profile", ":cli-assist"],
|
|
||||||
"enabled": true
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"permission": {
|
"permission": {
|
||||||
"read": "allow",
|
"read": "allow",
|
||||||
|
|||||||
@@ -19,9 +19,8 @@
|
|||||||
[com.brunobonacci.mulog.buffer :as rb]
|
[com.brunobonacci.mulog.buffer :as rb]
|
||||||
[config.core :refer [env]]
|
[config.core :refer [env]]
|
||||||
[datomic.api :as dc]
|
[datomic.api :as dc]
|
||||||
[datomic.api :as d]
|
|
||||||
[puget.printer :as puget]
|
[puget.printer :as puget]
|
||||||
|
[datomic.api :as d]
|
||||||
[figwheel.main.api]
|
[figwheel.main.api]
|
||||||
[hawk.core]
|
[hawk.core]
|
||||||
[mount.core :as mount]
|
[mount.core :as mount]
|
||||||
@@ -45,7 +44,8 @@
|
|||||||
item
|
item
|
||||||
:user)))
|
:user)))
|
||||||
(when (= :auto-ap.logging/peek (:mulog/event-name item))
|
(when (= :auto-ap.logging/peek (:mulog/event-name item))
|
||||||
(println "\u001B[31mTEST"))
|
(println "\u001B[31mTEST")
|
||||||
|
)
|
||||||
(when (:error item)
|
(when (:error item)
|
||||||
(println (:error item)))
|
(println (:error item)))
|
||||||
(puget/cprint (reduce
|
(puget/cprint (reduce
|
||||||
@@ -58,15 +58,18 @@
|
|||||||
{:seq-limit 10})
|
{:seq-limit 10})
|
||||||
(println))
|
(println))
|
||||||
|
|
||||||
|
|
||||||
(deftype DevPublisher [config buffer transform]
|
(deftype DevPublisher [config buffer transform]
|
||||||
|
|
||||||
com.brunobonacci.mulog.publisher.PPublisher
|
com.brunobonacci.mulog.publisher.PPublisher
|
||||||
(agent-buffer [_]
|
(agent-buffer [_]
|
||||||
buffer)
|
buffer)
|
||||||
|
|
||||||
|
|
||||||
(publish-delay [_]
|
(publish-delay [_]
|
||||||
200)
|
200)
|
||||||
|
|
||||||
|
|
||||||
(publish [_ buffer]
|
(publish [_ buffer]
|
||||||
;; items are pairs [offset <item>]
|
;; items are pairs [offset <item>]
|
||||||
(doseq [item (transform (map second (rb/items buffer)))]
|
(doseq [item (transform (map second (rb/items buffer)))]
|
||||||
@@ -74,6 +77,8 @@
|
|||||||
(flush)
|
(flush)
|
||||||
(rb/clear buffer)))
|
(rb/clear buffer)))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(defn dev-publisher
|
(defn dev-publisher
|
||||||
[{:keys [transform pretty?] :as config}]
|
[{:keys [transform pretty?] :as config}]
|
||||||
(DevPublisher. config (rb/agent-buffer 10000) (or transform identity)))
|
(DevPublisher. config (rb/agent-buffer 10000) (or transform identity)))
|
||||||
@@ -82,6 +87,8 @@
|
|||||||
[config]
|
[config]
|
||||||
(dev-publisher config))
|
(dev-publisher config))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||||
(defn load-accounts [conn]
|
(defn load-accounts [conn]
|
||||||
(let [[header & rows] (-> "master-account-list.csv" (io/resource) io/input-stream (BOMInputStream.) (io/reader) csv/read-csv)
|
(let [[header & rows] (-> "master-account-list.csv" (io/resource) io/input-stream (BOMInputStream.) (io/reader) csv/read-csv)
|
||||||
@@ -154,6 +161,7 @@
|
|||||||
(also-merge-txes also-merge old-account-id))
|
(also-merge-txes also-merge old-account-id))
|
||||||
tx)))))
|
tx)))))
|
||||||
|
|
||||||
|
|
||||||
conj
|
conj
|
||||||
[]
|
[]
|
||||||
rows)]
|
rows)]
|
||||||
@@ -184,6 +192,7 @@
|
|||||||
'[(<= ?z 9999)]]}
|
'[(<= ?z 9999)]]}
|
||||||
(dc/db conn)))))
|
(dc/db conn)))))
|
||||||
|
|
||||||
|
|
||||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||||
(defn find-conflicting-accounts []
|
(defn find-conflicting-accounts []
|
||||||
(filter
|
(filter
|
||||||
@@ -217,6 +226,8 @@
|
|||||||
:where [['?e :account-client-override/client '?client-id]]}
|
:where [['?e :account-client-override/client '?client-id]]}
|
||||||
(dc/db conn) client-id)
|
(dc/db conn) client-id)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
_ (when-let [bad-rows (seq (->> rows
|
_ (when-let [bad-rows (seq (->> rows
|
||||||
(group-by (fn [[_ account]]
|
(group-by (fn [[_ account]]
|
||||||
account))
|
account))
|
||||||
@@ -274,6 +285,7 @@
|
|||||||
txes
|
txes
|
||||||
#_@(d/transact conn txes)))
|
#_@(d/transact conn txes)))
|
||||||
|
|
||||||
|
|
||||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||||
(defn fix-transactions-without-locations [client-code location]
|
(defn fix-transactions-without-locations [client-code location]
|
||||||
(->>
|
(->>
|
||||||
@@ -295,6 +307,7 @@
|
|||||||
accounts)))
|
accounts)))
|
||||||
vec))
|
vec))
|
||||||
|
|
||||||
|
|
||||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||||
(defn entity-history [i]
|
(defn entity-history [i]
|
||||||
(vec (sort-by first (dc/q
|
(vec (sort-by first (dc/q
|
||||||
@@ -329,11 +342,13 @@
|
|||||||
{:start (- i 100)
|
{:start (- i 100)
|
||||||
:end (+ i 100)}))))
|
:end (+ i 100)}))))
|
||||||
|
|
||||||
|
|
||||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||||
(defn start-db []
|
(defn start-db []
|
||||||
(mu/start-publisher! {:type :dev})
|
(mu/start-publisher! {:type :dev})
|
||||||
(mount.core/start (mount.core/only #{#'auto-ap.datomic/conn})))
|
(mount.core/start (mount.core/only #{#'auto-ap.datomic/conn})))
|
||||||
|
|
||||||
|
|
||||||
(defn- auto-reset-handler [ctx event]
|
(defn- auto-reset-handler [ctx event]
|
||||||
(require 'figwheel.main.api)
|
(require 'figwheel.main.api)
|
||||||
(binding [*ns* *ns*]
|
(binding [*ns* *ns*]
|
||||||
@@ -348,9 +363,11 @@
|
|||||||
(hawk.core/watch! [{:paths ["src/" "test/"]
|
(hawk.core/watch! [{:paths ["src/" "test/"]
|
||||||
:handler auto-reset-handler}]))
|
:handler auto-reset-handler}]))
|
||||||
|
|
||||||
|
|
||||||
(defn start-http []
|
(defn start-http []
|
||||||
(mount.core/start (mount.core/only #{#'auto-ap.server/port #'auto-ap.server/jetty})))
|
(mount.core/start (mount.core/only #{#'auto-ap.server/port #'auto-ap.server/jetty})))
|
||||||
|
|
||||||
|
|
||||||
(defn start-dev []
|
(defn start-dev []
|
||||||
(set-refresh-dirs "src")
|
(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.server))
|
||||||
@@ -375,6 +392,7 @@
|
|||||||
(for [r data]
|
(for [r data]
|
||||||
((apply juxt columns) r)))))
|
((apply juxt columns) r)))))
|
||||||
|
|
||||||
|
|
||||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||||
(defn find-queries [words]
|
(defn find-queries [words]
|
||||||
(let [obj (s3/list-objects-v2 :bucket-name (:data-bucket env)
|
(let [obj (s3/list-objects-v2 :bucket-name (:data-bucket env)
|
||||||
@@ -400,6 +418,7 @@
|
|||||||
(println "failed " e)))
|
(println "failed " e)))
|
||||||
(async/<!! (async/into [] output-chan))))
|
(async/<!! (async/into [] output-chan))))
|
||||||
|
|
||||||
|
|
||||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||||
(defn upsert-invoice-amounts [tsv]
|
(defn upsert-invoice-amounts [tsv]
|
||||||
(let [data (with-open [reader (io/reader (char-array tsv))]
|
(let [data (with-open [reader (io/reader (char-array tsv))]
|
||||||
@@ -441,12 +460,15 @@
|
|||||||
target-date (clj-time.coerce/to-date (atime/parse target-date atime/normal-date))
|
target-date (clj-time.coerce/to-date (atime/parse target-date atime/normal-date))
|
||||||
current-date (:invoice/date invoice)
|
current-date (:invoice/date invoice)
|
||||||
|
|
||||||
|
|
||||||
current-expense-account-amount (:invoice-expense-account/amount invoice-expense-account 0.0)
|
current-expense-account-amount (:invoice-expense-account/amount invoice-expense-account 0.0)
|
||||||
target-expense-account-amount (- (Double/parseDouble amount))
|
target-expense-account-amount (- (Double/parseDouble amount))
|
||||||
|
|
||||||
|
|
||||||
current-expense-account-location (:invoice-expense-account/location invoice-expense-account)
|
current-expense-account-location (:invoice-expense-account/location invoice-expense-account)
|
||||||
target-expense-account-location location
|
target-expense-account-location location
|
||||||
|
|
||||||
|
|
||||||
[[_ _ invoice-payment]] (vec (dc/q
|
[[_ _ invoice-payment]] (vec (dc/q
|
||||||
'[:find ?p ?a ?ip
|
'[:find ?p ?a ?ip
|
||||||
:in $ ?i
|
:in $ ?i
|
||||||
@@ -490,6 +512,7 @@
|
|||||||
(filter identity)
|
(filter identity)
|
||||||
vec)))
|
vec)))
|
||||||
|
|
||||||
|
|
||||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||||
(defn get-schema [prefix]
|
(defn get-schema [prefix]
|
||||||
(->> (dc/q '[:find ?i
|
(->> (dc/q '[:find ?i
|
||||||
@@ -514,6 +537,7 @@
|
|||||||
(defn init-repl []
|
(defn init-repl []
|
||||||
(set! nrepl.middleware.print/*print-fn* clojure.pprint/pprint))
|
(set! nrepl.middleware.print/*print-fn* clojure.pprint/pprint))
|
||||||
|
|
||||||
|
|
||||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||||
(defn sample-ledger-import
|
(defn sample-ledger-import
|
||||||
([client-code]
|
([client-code]
|
||||||
@@ -540,6 +564,7 @@
|
|||||||
a)
|
a)
|
||||||
:separator \tab))))
|
:separator \tab))))
|
||||||
|
|
||||||
|
|
||||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||||
(defn sample-manual-yodlee
|
(defn sample-manual-yodlee
|
||||||
([client-code]
|
([client-code]
|
||||||
@@ -557,6 +582,8 @@
|
|||||||
["posted" d (str "Random Description - " id) "Travel" nil nil (- amount) nil nil nil nil nil (rand-nth bank-accounts) client-code])
|
["posted" d (str "Random Description - " id) "Travel" nil nil (- amount) nil nil nil nil nil (rand-nth bank-accounts) client-code])
|
||||||
:separator \tab))))
|
:separator \tab))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(defn index-solr
|
(defn index-solr
|
||||||
[]
|
[]
|
||||||
(println "invoice")
|
(println "invoice")
|
||||||
@@ -616,3 +643,4 @@
|
|||||||
(print ".")
|
(print ".")
|
||||||
@(dc/transact auto-ap.datomic/conn n)))
|
@(dc/transact auto-ap.datomic/conn n)))
|
||||||
|
|
||||||
|
|
||||||
@@ -1,408 +0,0 @@
|
|||||||
(ns auto-ap.ssr.invoice.invoice-unit-test
|
|
||||||
(:require [clojure.test :refer [deftest testing is]]
|
|
||||||
[auto-ap.ssr.invoice.new-invoice-wizard :as sut]
|
|
||||||
[auto-ap.ssr.invoices :as invoices]
|
|
||||||
[auto-ap.ssr.invoice.glimpse :as glimpse]
|
|
||||||
[slingshot.slingshot :refer [try+]]
|
|
||||||
[clj-time.core :as time]))
|
|
||||||
|
|
||||||
(deftest assert-invoice-amounts-add-up-test
|
|
||||||
(testing "Valid when expense accounts sum equals invoice total"
|
|
||||||
(is (nil? (sut/assert-invoice-amounts-add-up
|
|
||||||
{:invoice/expense-accounts [{:invoice-expense-account/amount 50.0}
|
|
||||||
{:invoice-expense-account/amount 50.0}]
|
|
||||||
:invoice/total 100.0}))))
|
|
||||||
|
|
||||||
(testing "Valid with single expense account matching total"
|
|
||||||
(is (nil? (sut/assert-invoice-amounts-add-up
|
|
||||||
{:invoice/expense-accounts [{:invoice-expense-account/amount 100.0}]
|
|
||||||
:invoice/total 100.0}))))
|
|
||||||
|
|
||||||
(testing "Valid with floating point amounts within tolerance"
|
|
||||||
(is (nil? (sut/assert-invoice-amounts-add-up
|
|
||||||
{:invoice/expense-accounts [{:invoice-expense-account/amount 33.33}
|
|
||||||
{:invoice-expense-account/amount 33.33}
|
|
||||||
{:invoice-expense-account/amount 33.34}]
|
|
||||||
:invoice/total 100.0}))))
|
|
||||||
|
|
||||||
(testing "Throws when expense accounts sum does not equal total"
|
|
||||||
(is (thrown? clojure.lang.ExceptionInfo
|
|
||||||
(sut/assert-invoice-amounts-add-up
|
|
||||||
{:invoice/expense-accounts [{:invoice-expense-account/amount 40.0}]
|
|
||||||
:invoice/total 100.0}))))
|
|
||||||
|
|
||||||
(testing "Throws when expense accounts sum is greater than total"
|
|
||||||
(is (thrown? clojure.lang.ExceptionInfo
|
|
||||||
(sut/assert-invoice-amounts-add-up
|
|
||||||
{:invoice/expense-accounts [{:invoice-expense-account/amount 150.0}]
|
|
||||||
:invoice/total 100.0})))))
|
|
||||||
|
|
||||||
(deftest does-amount-exceed-outstanding-test
|
|
||||||
(testing "Valid when amount equals positive outstanding balance"
|
|
||||||
(is (not (invoices/does-amount-exceed-outstanding? 100.0 100.0))))
|
|
||||||
|
|
||||||
(testing "Valid when amount is less than positive outstanding balance"
|
|
||||||
(is (not (invoices/does-amount-exceed-outstanding? 50.0 100.0))))
|
|
||||||
|
|
||||||
(testing "Invalid when amount exceeds positive outstanding balance"
|
|
||||||
(is (invoices/does-amount-exceed-outstanding? 150.0 100.0)))
|
|
||||||
|
|
||||||
(testing "Invalid when amount is zero or negative for positive outstanding"
|
|
||||||
(is (invoices/does-amount-exceed-outstanding? 0.0 100.0))
|
|
||||||
(is (invoices/does-amount-exceed-outstanding? -10.0 100.0)))
|
|
||||||
|
|
||||||
(testing "Valid when amount equals negative outstanding balance"
|
|
||||||
(is (not (invoices/does-amount-exceed-outstanding? -100.0 -100.0))))
|
|
||||||
|
|
||||||
(testing "Valid when amount is greater than negative outstanding balance"
|
|
||||||
(is (not (invoices/does-amount-exceed-outstanding? -50.0 -100.0))))
|
|
||||||
|
|
||||||
(testing "Invalid when amount is less than negative outstanding balance"
|
|
||||||
(is (invoices/does-amount-exceed-outstanding? -150.0 -100.0)))
|
|
||||||
|
|
||||||
(testing "Invalid when amount is zero or positive for negative outstanding"
|
|
||||||
(is (invoices/does-amount-exceed-outstanding? 0.0 -100.0))
|
|
||||||
(is (invoices/does-amount-exceed-outstanding? 10.0 -100.0)))
|
|
||||||
|
|
||||||
(testing "Invalid when amount is non-zero for zero outstanding"
|
|
||||||
(is (invoices/does-amount-exceed-outstanding? 10.0 0.0))
|
|
||||||
(is (invoices/does-amount-exceed-outstanding? -10.0 0.0)))
|
|
||||||
|
|
||||||
(testing "Valid when amount is zero for zero outstanding"
|
|
||||||
(is (not (invoices/does-amount-exceed-outstanding? 0.0 0.0)))))
|
|
||||||
|
|
||||||
(deftest assert-percentages-add-up-test
|
|
||||||
(testing "Valid when percentages sum to 100%"
|
|
||||||
(is (nil? (invoices/assert-percentages-add-up
|
|
||||||
{:expense-accounts [{:percentage 0.5}
|
|
||||||
{:percentage 0.5}]}))))
|
|
||||||
|
|
||||||
(testing "Valid with single account at 100%"
|
|
||||||
(is (nil? (invoices/assert-percentages-add-up
|
|
||||||
{:expense-accounts [{:percentage 1.0}]}))))
|
|
||||||
|
|
||||||
(testing "Valid with floating point within tolerance"
|
|
||||||
(is (nil? (invoices/assert-percentages-add-up
|
|
||||||
{:expense-accounts [{:percentage 0.333}
|
|
||||||
{:percentage 0.333}
|
|
||||||
{:percentage 0.334}]}))))
|
|
||||||
|
|
||||||
(testing "Throws when percentages sum to less than 100%"
|
|
||||||
(is (thrown? clojure.lang.ExceptionInfo
|
|
||||||
(invoices/assert-percentages-add-up
|
|
||||||
{:expense-accounts [{:percentage 0.5}]}))))
|
|
||||||
|
|
||||||
(testing "Throws when percentages sum to more than 100%"
|
|
||||||
(is (thrown? clojure.lang.ExceptionInfo
|
|
||||||
(invoices/assert-percentages-add-up
|
|
||||||
{:expense-accounts [{:percentage 0.8}
|
|
||||||
{:percentage 0.8}]})))))
|
|
||||||
|
|
||||||
(deftest stack-rank-test
|
|
||||||
(testing "Ranks fields by confidence and returns text values"
|
|
||||||
(let [fields [{:type {:text "AMOUNT_DUE" :confidence 0.9}
|
|
||||||
:value-detection {:text "$123.45" :confidence 0.95}}
|
|
||||||
{:type {:text "AMOUNT_DUE" :confidence 0.8}
|
|
||||||
:value-detection {:text "$100.00" :confidence 0.9}}
|
|
||||||
{:type {:text "TOTAL" :confidence 0.9}
|
|
||||||
:value-detection {:text "$150.00" :confidence 0.85}}]]
|
|
||||||
(is (= ["$123.45" "$150.00" "$100.00"]
|
|
||||||
(glimpse/stack-rank #{"AMOUNT_DUE" "TOTAL"} fields)))))
|
|
||||||
|
|
||||||
(testing "Filters out fields not in valid-values set"
|
|
||||||
(let [fields [{:type {:text "AMOUNT_DUE" :confidence 0.9}
|
|
||||||
:value-detection {:text "$123.45" :confidence 0.95}}
|
|
||||||
{:type {:text "OTHER" :confidence 0.9}
|
|
||||||
:value-detection {:text "$999.00" :confidence 0.99}}]]
|
|
||||||
(is (= ["$123.45"]
|
|
||||||
(glimpse/stack-rank #{"AMOUNT_DUE"} fields)))))
|
|
||||||
|
|
||||||
(testing "Returns empty when no fields match"
|
|
||||||
(is (empty? (glimpse/stack-rank #{"TOTAL"} []))))
|
|
||||||
|
|
||||||
(testing "Filters blank values"
|
|
||||||
(let [fields [{:type {:text "TOTAL" :confidence 0.9}
|
|
||||||
:value-detection {:text "" :confidence 0.95}}
|
|
||||||
{:type {:text "TOTAL" :confidence 0.8}
|
|
||||||
:value-detection {:text " " :confidence 0.9}}]]
|
|
||||||
(is (empty? (glimpse/stack-rank #{"TOTAL"} fields))))))
|
|
||||||
|
|
||||||
(deftest deduplicate-test
|
|
||||||
(testing "Removes duplicate parsed values keeping first occurrence"
|
|
||||||
(let [data [["$123.45" 123.45]
|
|
||||||
["123.45" 123.45]
|
|
||||||
["$100.00" 100.0]
|
|
||||||
["100" 100.0]]]
|
|
||||||
(is (= [["$123.45" 123.45] ["$100.00" 100.0]]
|
|
||||||
(glimpse/deduplicate data)))))
|
|
||||||
|
|
||||||
(testing "Returns empty for empty input"
|
|
||||||
(is (empty? (glimpse/deduplicate []))))
|
|
||||||
|
|
||||||
(testing "Preserves all unique values"
|
|
||||||
(let [data [["A" 1] ["B" 2] ["C" 3]]]
|
|
||||||
(is (= [["A" 1] ["B" 2] ["C" 3]]
|
|
||||||
(glimpse/deduplicate data)))))
|
|
||||||
|
|
||||||
(testing "Handles nil parsed values (nil is not deduplicated due to set semantics)"
|
|
||||||
(let [data [["A" nil] ["B" nil] ["C" 3]]]
|
|
||||||
(is (= [["A" nil] ["B" nil] ["C" 3]]
|
|
||||||
(glimpse/deduplicate data))))))
|
|
||||||
|
|
||||||
(deftest clientize-vendor-test
|
|
||||||
(testing "Returns nil when vendor is nil"
|
|
||||||
(is (nil? (sut/clientize-vendor nil 123))))
|
|
||||||
|
|
||||||
(testing "Applies terms override for matching client"
|
|
||||||
(let [vendor {:vendor/terms 30
|
|
||||||
:vendor/terms-overrides [{:vendor-terms-override/client {:db/id 123}
|
|
||||||
:vendor-terms-override/terms 15}]
|
|
||||||
:vendor/automatically-paid-when-due []
|
|
||||||
:vendor/default-account {:db/id 1 :account/name "Food"}}]
|
|
||||||
(is (= 15 (:vendor/terms (sut/clientize-vendor vendor 123))))))
|
|
||||||
|
|
||||||
(testing "Keeps default terms when no override for client"
|
|
||||||
(let [vendor {:vendor/terms 30
|
|
||||||
:vendor/terms-overrides [{:vendor-terms-override/client {:db/id 999}
|
|
||||||
:vendor-terms-override/terms 15}]
|
|
||||||
:vendor/automatically-paid-when-due []
|
|
||||||
:vendor/default-account {:db/id 1 :account/name "Food"}}]
|
|
||||||
(is (= 30 (:vendor/terms (sut/clientize-vendor vendor 123))))))
|
|
||||||
|
|
||||||
(testing "Applies account override for matching client"
|
|
||||||
(let [vendor {:vendor/terms 30
|
|
||||||
:vendor/account-overrides [{:vendor-account-override/client {:db/id 123}
|
|
||||||
:vendor-account-override/account {:db/id 2 :account/name "Override"}}]
|
|
||||||
:vendor/automatically-paid-when-due []
|
|
||||||
:vendor/default-account {:db/id 1 :account/name "Food"}}]
|
|
||||||
(is (= "Override" (:account/name (:vendor/default-account (sut/clientize-vendor vendor 123)))))))
|
|
||||||
|
|
||||||
(testing "Uses default account when no account override for client"
|
|
||||||
(let [vendor {:vendor/terms 30
|
|
||||||
:vendor/account-overrides [{:vendor-account-override/client {:db/id 999}
|
|
||||||
:vendor-account-override/account {:db/id 2 :account/name "Override"}}]
|
|
||||||
:vendor/automatically-paid-when-due []
|
|
||||||
:vendor/default-account {:db/id 1 :account/name "Food"}}]
|
|
||||||
(is (= "Food" (:account/name (:vendor/default-account (sut/clientize-vendor vendor 123)))))))
|
|
||||||
|
|
||||||
(testing "Sets automatically-paid-when-due when client is in the list"
|
|
||||||
(let [vendor {:vendor/terms 30
|
|
||||||
:vendor/automatically-paid-when-due [{:db/id 123}]
|
|
||||||
:vendor/default-account {:db/id 1 :account/name "Food"}}]
|
|
||||||
(is (true? (:vendor/automatically-paid-when-due (sut/clientize-vendor vendor 123))))))
|
|
||||||
|
|
||||||
(testing "Clears automatically-paid-when-due when client is not in the list"
|
|
||||||
(let [vendor {:vendor/terms 30
|
|
||||||
:vendor/automatically-paid-when-due [{:db/id 999}]
|
|
||||||
:vendor/default-account {:db/id 1 :account/name "Food"}}]
|
|
||||||
(is (false? (:vendor/automatically-paid-when-due (sut/clientize-vendor vendor 123))))))
|
|
||||||
|
|
||||||
(testing "Removes override fields from result"
|
|
||||||
(let [vendor {:vendor/terms 30
|
|
||||||
:vendor/terms-overrides [{:vendor-terms-override/client {:db/id 123}
|
|
||||||
:vendor-terms-override/terms 15}]
|
|
||||||
:vendor/account-overrides [{:vendor-account-override/client {:db/id 123}
|
|
||||||
:vendor-account-override/account {:db/id 2 :account/name "Override"}}]
|
|
||||||
:vendor/automatically-paid-when-due []
|
|
||||||
:vendor/default-account {:db/id 1 :account/name "Food"}}
|
|
||||||
result (sut/clientize-vendor vendor 123)]
|
|
||||||
(is (nil? (:vendor/terms-overrides result)))
|
|
||||||
(is (nil? (:vendor/account-overrides result))))))
|
|
||||||
|
|
||||||
(deftest location-select-test
|
|
||||||
(testing "Uses account location when provided"
|
|
||||||
(let [result (sut/location-select* {:name "loc"
|
|
||||||
:account-location "DT"
|
|
||||||
:client-locations ["MH" "DE"]
|
|
||||||
:value nil})]
|
|
||||||
(is (= :select (first result)))
|
|
||||||
(is (some #(= "DT" %) (flatten result)))))
|
|
||||||
|
|
||||||
(testing "Defaults to Shared when no account location but client locations exist"
|
|
||||||
(let [result (sut/location-select* {:name "loc"
|
|
||||||
:account-location nil
|
|
||||||
:client-locations ["MH" "DE"]
|
|
||||||
:value nil})]
|
|
||||||
(is (= :select (first result)))
|
|
||||||
(is (some #(= "Shared" %) (flatten result)))
|
|
||||||
(is (some #(= "MH" %) (flatten result)))
|
|
||||||
(is (some #(= "DE" %) (flatten result)))))
|
|
||||||
|
|
||||||
(testing "Defaults to Shared when no locations provided"
|
|
||||||
(let [result (sut/location-select* {:name "loc"
|
|
||||||
:account-location nil
|
|
||||||
:client-locations nil
|
|
||||||
:value nil})]
|
|
||||||
(is (= :select (first result)))
|
|
||||||
(is (some #(= "Shared" %) (flatten result))))))
|
|
||||||
|
|
||||||
(deftest maybe-code-accounts-test
|
|
||||||
(testing "Creates single account with specified location"
|
|
||||||
(let [invoice {:invoice/total 100.0}
|
|
||||||
rules [{:percentage 1.0 :account "acc-1" :location "DT"}]
|
|
||||||
result (invoices/maybe-code-accounts invoice rules ["MH" "DE"])]
|
|
||||||
(is (= 1 (count result)))
|
|
||||||
(is (= "acc-1" (:invoice-expense-account/account (first result))))
|
|
||||||
(is (= "DT" (:invoice-expense-account/location (first result))))
|
|
||||||
(is (= 100.0 (:invoice-expense-account/amount (first result))))))
|
|
||||||
|
|
||||||
(testing "Spreads Shared location across all valid locations"
|
|
||||||
(let [invoice {:invoice/total 100.0}
|
|
||||||
rules [{:percentage 1.0 :account "acc-1" :location "Shared"}]
|
|
||||||
result (invoices/maybe-code-accounts invoice rules ["MH" "DE"])]
|
|
||||||
(is (= 2 (count result)))
|
|
||||||
(is (= #{"MH" "DE"} (set (map :invoice-expense-account/location result))))
|
|
||||||
(is (= 100.0 (reduce + 0.0 (map :invoice-expense-account/amount result))))))
|
|
||||||
|
|
||||||
(testing "Handles odd totals with correct rounding for Shared locations"
|
|
||||||
(let [invoice {:invoice/total 100.0}
|
|
||||||
rules [{:percentage 1.0 :account "acc-1" :location "Shared"}]
|
|
||||||
result (invoices/maybe-code-accounts invoice rules ["MH" "DE" "DT"])]
|
|
||||||
(is (= 3 (count result)))
|
|
||||||
(is (= 100.0 (reduce + 0.0 (map :invoice-expense-account/amount result))))
|
|
||||||
(is (every? #(<= (count (re-find #"\.\d+" (str %))) 3) (map :invoice-expense-account/amount result)))))
|
|
||||||
|
|
||||||
(testing "Handles multiple account rules"
|
|
||||||
(let [invoice {:invoice/total 100.0}
|
|
||||||
rules [{:percentage 0.5 :account "acc-1" :location "DT"}
|
|
||||||
{:percentage 0.5 :account "acc-2" :location "Shared"}]
|
|
||||||
result (invoices/maybe-code-accounts invoice rules ["MH" "DE"])]
|
|
||||||
(is (= 3 (count result)))
|
|
||||||
(is (= 100.0 (reduce + 0.0 (map :invoice-expense-account/amount result))))))
|
|
||||||
|
|
||||||
(testing "Uses absolute value for negative totals (produces positive amounts)"
|
|
||||||
(let [invoice {:invoice/total -100.0}
|
|
||||||
rules [{:percentage 1.0 :account "acc-1" :location "Shared"}]
|
|
||||||
result (invoices/maybe-code-accounts invoice rules ["MH" "DE"])]
|
|
||||||
(is (= 2 (count result)))
|
|
||||||
(is (= 100.0 (reduce + 0.0 (map :invoice-expense-account/amount result)))))))
|
|
||||||
|
|
||||||
(deftest can-undo-autopayment-test
|
|
||||||
(testing "Returns true for paid invoice with scheduled payment and no linked payments"
|
|
||||||
(with-redefs [auto-ap.graphql.utils/assert-not-locked-ssr (fn [& _] nil)]
|
|
||||||
(is (true? (invoices/can-undo-autopayment
|
|
||||||
{:invoice/status :invoice-status/paid
|
|
||||||
:invoice/scheduled-payment #inst "2024-01-01"
|
|
||||||
:invoice/payments nil
|
|
||||||
:invoice/client {:db/id 1}
|
|
||||||
:invoice/date #inst "2024-01-01"})))))
|
|
||||||
|
|
||||||
(testing "Returns false for invoice without scheduled payment (behavior 19.2)"
|
|
||||||
(with-redefs [auto-ap.graphql.utils/assert-not-locked-ssr (fn [& _] nil)]
|
|
||||||
(is (false? (invoices/can-undo-autopayment
|
|
||||||
{:invoice/status :invoice-status/paid
|
|
||||||
:invoice/scheduled-payment nil
|
|
||||||
:invoice/payments nil
|
|
||||||
:invoice/client {:db/id 1}
|
|
||||||
:invoice/date #inst "2024-01-01"})))))
|
|
||||||
|
|
||||||
(testing "Returns false for invoice with linked payments (behavior 19.3)"
|
|
||||||
(with-redefs [auto-ap.graphql.utils/assert-not-locked-ssr (fn [& _] nil)]
|
|
||||||
(is (false? (invoices/can-undo-autopayment
|
|
||||||
{:invoice/status :invoice-status/paid
|
|
||||||
:invoice/scheduled-payment #inst "2024-01-01"
|
|
||||||
:invoice/payments [{:db/id 1}]
|
|
||||||
:invoice/client {:db/id 1}
|
|
||||||
:invoice/date #inst "2024-01-01"})))))
|
|
||||||
|
|
||||||
(testing "Returns false for invoice that is not paid (behavior 19.4)"
|
|
||||||
(with-redefs [auto-ap.graphql.utils/assert-not-locked-ssr (fn [& _] nil)]
|
|
||||||
(is (false? (invoices/can-undo-autopayment
|
|
||||||
{:invoice/status :invoice-status/unpaid
|
|
||||||
:invoice/scheduled-payment #inst "2024-01-01"
|
|
||||||
:invoice/payments nil
|
|
||||||
:invoice/client {:db/id 1}
|
|
||||||
:invoice/date #inst "2024-01-01"})))))
|
|
||||||
|
|
||||||
(testing "Returns false for voided invoice"
|
|
||||||
(with-redefs [auto-ap.graphql.utils/assert-not-locked-ssr (fn [& _] nil)]
|
|
||||||
(is (false? (invoices/can-undo-autopayment
|
|
||||||
{:invoice/status :invoice-status/voided
|
|
||||||
:invoice/scheduled-payment #inst "2024-01-01"
|
|
||||||
:invoice/payments nil
|
|
||||||
:invoice/client {:db/id 1}
|
|
||||||
:invoice/date #inst "2024-01-01"}))))))
|
|
||||||
|
|
||||||
(deftest due-date-calculation-test
|
|
||||||
(testing "Calculates due date from vendor terms (behavior 8.2)"
|
|
||||||
(let [invoice-date (time/date-time 2024 1 1)
|
|
||||||
vendor-terms 30
|
|
||||||
expected-due (time/plus invoice-date (time/days vendor-terms))]
|
|
||||||
(is (= expected-due
|
|
||||||
(time/plus invoice-date (time/days vendor-terms))))))
|
|
||||||
|
|
||||||
(testing "Due date is date plus terms days"
|
|
||||||
(let [date (time/date-time 2024 6 15)
|
|
||||||
terms 15]
|
|
||||||
(is (= (time/date-time 2024 6 30)
|
|
||||||
(time/plus date (time/days terms)))))))
|
|
||||||
|
|
||||||
(deftest scheduled-payment-calculation-test
|
|
||||||
(testing "Scheduled payment equals due date when autopay is enabled (behavior 8.3)"
|
|
||||||
(let [due-date (time/date-time 2024 1 31)
|
|
||||||
vendor {:vendor/automatically-paid-when-due true}]
|
|
||||||
(is (= due-date
|
|
||||||
(when (:vendor/automatically-paid-when-due vendor)
|
|
||||||
due-date)))))
|
|
||||||
|
|
||||||
(testing "No scheduled payment when autopay is disabled"
|
|
||||||
(let [due-date (time/date-time 2024 1 31)
|
|
||||||
vendor {:vendor/automatically-paid-when-due false}]
|
|
||||||
(is (nil?
|
|
||||||
(when (:vendor/automatically-paid-when-due vendor)
|
|
||||||
due-date)))))
|
|
||||||
|
|
||||||
(testing "No scheduled payment when no due date"
|
|
||||||
(let [vendor {:vendor/automatically-paid-when-due true}]
|
|
||||||
(is (nil?
|
|
||||||
(when nil
|
|
||||||
(:vendor/automatically-paid-when-due vendor)))))))
|
|
||||||
|
|
||||||
(deftest due-date-display-test
|
|
||||||
(testing "Displays 'today' when due date is today (behavior 1.7)"
|
|
||||||
(let [today (time/now)
|
|
||||||
days 0]
|
|
||||||
(is (= 0 days))
|
|
||||||
(is (= "today"
|
|
||||||
(cond (= 0 days) "today"
|
|
||||||
(> days 0) (format "in %d days" days)
|
|
||||||
:else (format "%d days ago" (- days))))))))
|
|
||||||
|
|
||||||
(deftest can-handwrite-test
|
|
||||||
(testing "Returns true for single vendor with positive balance"
|
|
||||||
(is (true? (invoices/can-handwrite?
|
|
||||||
[{:invoice/vendor {:db/id 1}
|
|
||||||
:invoice/outstanding-balance 100.0}]))))
|
|
||||||
|
|
||||||
(testing "Returns false for multiple vendors"
|
|
||||||
(is (false? (invoices/can-handwrite?
|
|
||||||
[{:invoice/vendor {:db/id 1}
|
|
||||||
:invoice/outstanding-balance 100.0}
|
|
||||||
{:invoice/vendor {:db/id 2}
|
|
||||||
:invoice/outstanding-balance 50.0}]))))
|
|
||||||
|
|
||||||
(testing "Returns false for zero or negative total balance"
|
|
||||||
(is (false? (invoices/can-handwrite?
|
|
||||||
[{:invoice/vendor {:db/id 1}
|
|
||||||
:invoice/outstanding-balance 0.0}])))
|
|
||||||
(is (false? (invoices/can-handwrite?
|
|
||||||
[{:invoice/vendor {:db/id 1}
|
|
||||||
:invoice/outstanding-balance -50.0}])))))
|
|
||||||
|
|
||||||
(deftest credit-only-test
|
|
||||||
(testing "Returns true when all vendor totals are zero or negative"
|
|
||||||
(is (true? (invoices/credit-only?
|
|
||||||
[{:invoice/vendor {:db/id 1}
|
|
||||||
:invoice/outstanding-balance -100.0}
|
|
||||||
{:invoice/vendor {:db/id 1}
|
|
||||||
:invoice/outstanding-balance -50.0}]))))
|
|
||||||
|
|
||||||
(testing "Returns false when any vendor total is positive"
|
|
||||||
(is (false? (invoices/credit-only?
|
|
||||||
[{:invoice/vendor {:db/id 1}
|
|
||||||
:invoice/outstanding-balance -100.0}
|
|
||||||
{:invoice/vendor {:db/id 2}
|
|
||||||
:invoice/outstanding-balance 50.0}]))))
|
|
||||||
|
|
||||||
(testing "Returns true for empty invoice list"
|
|
||||||
(is (true? (invoices/credit-only? [])))))
|
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
(:require [clojure.test :refer [deftest testing is]]
|
(:require [clojure.test :refer [deftest testing is]]
|
||||||
[auto-ap.ssr.invoice.new-invoice-wizard :as sut9]))
|
[auto-ap.ssr.invoice.new-invoice-wizard :as sut9]))
|
||||||
|
|
||||||
|
|
||||||
(deftest maybe-spread-locations-test
|
(deftest maybe-spread-locations-test
|
||||||
(testing "Shared amount correctly spread across multiple locations"
|
(testing "Shared amount correctly spread across multiple locations"
|
||||||
(let [invoice {:invoice/expense-accounts [{:invoice-expense-account/amount 100.0
|
(let [invoice {:invoice/expense-accounts [{:invoice-expense-account/amount 100.0
|
||||||
@@ -29,6 +30,8 @@
|
|||||||
:invoice-expense-account/location "Location 2"}]
|
:invoice-expense-account/location "Location 2"}]
|
||||||
(map #(select-keys % #{:invoice-expense-account/amount :invoice-expense-account/location}) (:invoice/expense-accounts result))))))
|
(map #(select-keys % #{:invoice-expense-account/amount :invoice-expense-account/location}) (:invoice/expense-accounts result))))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(testing "Shared amount correctly spread with leftovers"
|
(testing "Shared amount correctly spread with leftovers"
|
||||||
(let [invoice {:invoice/expense-accounts [{:invoice-expense-account/amount 100.0
|
(let [invoice {:invoice/expense-accounts [{:invoice-expense-account/amount 100.0
|
||||||
:invoice-expense-account/location "Shared"}]
|
:invoice-expense-account/location "Shared"}]
|
||||||
@@ -81,7 +84,7 @@
|
|||||||
{:invoice-expense-account/amount -5
|
{:invoice-expense-account/amount -5
|
||||||
:invoice-expense-account/location "Shared"}]
|
:invoice-expense-account/location "Shared"}]
|
||||||
:invoice/total -101}
|
:invoice/total -101}
|
||||||
result (sut9/maybe-spread-locations invoice ["Location 1"])]
|
result (sut8/maybe-spread-locations invoice ["Location 1" ])]
|
||||||
(is (=
|
(is (=
|
||||||
[{:invoice-expense-account/amount -100.0
|
[{:invoice-expense-account/amount -100.0
|
||||||
:invoice-expense-account/location "Location 1"}
|
:invoice-expense-account/location "Location 1"}
|
||||||
|
|||||||
Reference in New Issue
Block a user