fix(ledger): ignore external import rows with no debit or credit

A pasted row with both amount columns empty carries no accounting
information, but it still became a line item: it tripped the "Line item
amount 0.0 must be greater than 0." warning, which demoted the entire
journal entry to "ignored" -- so the good rows around it were dropped
and any existing entry with that external id was retracted.

Drop those rows in table->entries before add-errors runs, so they never
reach the balance check, :amount, or :line-items. An entry whose rows are
all blank disappears completely: not imported, not retracted, not
counted. Only genuinely empty values qualify (nil or a whitespace-only
string, since form input decodes "" to nil); an explicit 0 still warns as
before.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-12 12:59:47 -07:00
parent 785c6b3731
commit bcb1978f44
2 changed files with 100 additions and 3 deletions

View File

@@ -370,6 +370,55 @@
(is (= "TEST" (:client-code (first entries))))
(is (= 2 (count (:line-items (first entries))))))))
(deftest table->entries-blank-amount-rows-test
(let [line (fn [external-id debit credit account-code]
{:source "manual"
:client-code "TEST"
:external-id external-id
:date (coerce/to-date-time #inst "2021-01-01")
:vendor-name "Vendor"
:debit debit
:credit credit
:account-code account-code
:location "HQ"})
entries (fn [table]
(sut/table->entries table
{"Vendor" {:db/id "vendor-1"}}
#{"1100" "1101"}
{"TEST" #inst "2000-01-01"}
{"TEST" #{}}
{"TEST" #{"HQ"}}))]
(testing "Should skip rows with no debit and no credit without flagging them"
(let [_ (setup-test-data [(test-client :db/id "blank-client-1"
:client/code "TEST"
:client/locations ["HQ"])])
result (entries [(line "ext-blank-1" 100.0 nil 1100)
(line "ext-blank-1" nil nil 1101)
(line "ext-blank-1" nil 100.0 1101)])]
(is (= 1 (count result)))
(is (= 2 (count (:line-items (first result)))))
(is (= [0 2] (vec (:indices (first result)))))
(is (= 100.0 (:amount (first result))))
(is (empty? (sut/entry-errors (first result))))))
(testing "Should treat blank strings in the amount columns as no amount"
(let [_ (setup-test-data [(test-client :db/id "blank-client-2"
:client/code "TEST"
:client/locations ["HQ"])])
result (entries [(line "ext-blank-2" 100.0 nil 1100)
(line "ext-blank-2" "" " " 1101)
(line "ext-blank-2" nil 100.0 1101)])]
(is (= 2 (count (:line-items (first result)))))
(is (empty? (sut/entry-errors (first result))))))
(testing "Should drop an entry entirely when every one of its rows is blank"
(let [_ (setup-test-data [(test-client :db/id "blank-client-3"
:client/code "TEST"
:client/locations ["HQ"])])
result (entries [(line "ext-blank-3" nil nil 1100)
(line "ext-blank-3" nil nil 1101)])]
(is (empty? result))))))
(deftest import-ledger-test
(testing "Should upsert hidden vendors and create transactions"
(let [_ (setup-test-data [(test-client :db/id "import-client-1"
@@ -397,7 +446,44 @@
(let [vendor-id (dc/q '[:find ?e .
:where [?e :vendor/name "New Vendor Import Unique"]]
db-after)]
(is vendor-id)))))
(is vendor-id))))
(testing "Should import the entry while ignoring its rows with no debit and no credit"
(let [_ (setup-test-data [(test-client :db/id "import-client-2"
:client/code "IMPORT-BLANK"
:client/locations ["HQ"])
{:db/id "import-blank-account-1100"
:account/numeric-code 1100
:account/account-set "default"
:account/name "Cash"}
{:db/id "import-blank-account-1101"
:account/numeric-code 1101
:account/account-set "default"
:account/name "Other Cash"}])
row (fn [debit credit account-code]
{:source "manual"
:client-code "IMPORT-BLANK"
:external-id "ext-import-blank-1"
:date (coerce/to-date-time #inst "2021-01-01")
:vendor-name "Blank Row Vendor"
:debit debit
:credit credit
:account-code account-code
:location "HQ"})
result (sut/import-ledger {:form-params {:table [(row 100.0 nil 1100)
(row nil nil 1101)
(row nil 100.0 1101)]}
:identity (admin-token)})
entry (dc/pull (dc/db conn)
[:journal-entry/amount
{:journal-entry/line-items [:journal-entry-line/debit
:journal-entry-line/credit]}]
[:journal-entry/external-id "IMPORT-BLANK-manual-ext-import-blank-1"])]
(is (= 1 (:successful result)))
(is (= 0 (:ignored result)))
(is (empty? (:form-errors result)))
(is (= 100.0 (:journal-entry/amount entry)))
(is (= 2 (count (:journal-entry/line-items entry)))))))
(deftest import-ledger-with-errors-test
(testing "Should throw exception when entries have errors - client not found"