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:
@@ -526,12 +526,23 @@
|
||||
ea)))
|
||||
line-items)))))
|
||||
|
||||
(defn blank-amount?
|
||||
"A row with neither a debit nor a credit carries no amount at all, so it is
|
||||
skipped entirely on import rather than flagged."
|
||||
[{:keys [debit credit]}]
|
||||
(letfn [(blank? [v]
|
||||
(or (nil? v)
|
||||
(and (string? v) (str/blank? v))))]
|
||||
(and (blank? debit) (blank? credit))))
|
||||
|
||||
(defn table->entries [table all-vendors all-accounts client-locked-lookup all-client-bank-accounts all-client-locations]
|
||||
(let [lines-with-indexes (for [[i l] (map vector (range) table)]
|
||||
(assoc l :index i))]
|
||||
(into []
|
||||
(for [[_ lines] (group-by line->id lines-with-indexes)
|
||||
:let [{:keys [source client-code date vendor-name note cleared-against] :as line} (first lines)]]
|
||||
(for [[_ grouped-lines] (group-by line->id lines-with-indexes)
|
||||
:let [lines (remove blank-amount? grouped-lines)
|
||||
{:keys [source client-code date vendor-name note cleared-against] :as line} (first lines)]
|
||||
:when (seq lines)]
|
||||
(add-errors {:source source
|
||||
:indices (map :index lines)
|
||||
:external-id (line->id line)
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user