(ns auto-ap.ledger.reports-test (:require [auto-ap.ledger.reports :as sut] [clojure.test :refer [deftest is testing]])) ;; A client's bank account and the financial account it posts to share a ;; numeric code. Reports key their rows off that code, so before the fix the ;; pair rendered as two rows carrying the same code-level total, and the ;; section stopped footing to its own subtotal. (def ^:private period #inst "2026-08-14") (defn- account [numeric-code name amount] {:client-id 1 :location "M" :numeric-code numeric-code :name name :amount amount :debits 0.0 :credits amount :count 1 :account-type :account-type/liability :period period}) (defn- bank-account "Same as `account`, but its name came from a :bank-account rather than the chart of accounts — which is what earns it the row label." [numeric-code name amount] (assoc (account numeric-code name amount) :bank-account-name? true)) (defn- pnl-data [data] (sut/->PNLData {:periods [period]} data {1 "CLIENT"})) (defn- labels [rows] (map (comp :value first) rows)) (deftest used-accounts-collapses-a-shared-numeric-code (testing "two names on one code yield a single entry, named for the bank account" (is (= [{:numeric-code 21010 :name "Capital One CC - 3196"}] (sut/used-accounts [{:data [(bank-account 21010 "Capital One CC - 3196" -100.0) (account 21010 "Accounts Payable 10" -150.0)]}])))) (testing "the bank account wins even when the financial name is more common" (is (= [{:numeric-code 21010 :name "Capital One CC - 3196"}] (sut/used-accounts [{:data [(bank-account 21010 "Capital One CC - 3196" -100.0)]} {:data [(account 21010 "Accounts Payable 10" -150.0)]} {:data [(account 21010 "Accounts Payable 10" -150.0)]}])))) (testing "distinct codes are left alone, ordered by code" (is (= [{:numeric-code 21009 :name "Due to Grand Ventures"} {:numeric-code 21010 :name "Capital One CC - 3196"}] (sut/used-accounts [{:data [(bank-account 21010 "Capital One CC - 3196" -100.0) (account 21009 "Due to Grand Ventures" 25.0)]}])))) (testing "with no bank account in play the most common name wins" (is (= [{:numeric-code 21010 :name "Accounts Payable 10"}] (sut/used-accounts [{:data [(account 21010 "Due to Sandwich Monkey" -100.0)]} {:data [(account 21010 "Accounts Payable 10" -150.0)]} {:data [(account 21010 "Accounts Payable 10" -150.0)]}])))) (testing "an even split breaks alphabetically so runs are reproducible" (is (= [{:numeric-code 21010 :name "Accounts Payable 10"}] (sut/used-accounts [{:data [(account 21010 "Due to Sandwich Monkey" -100.0)]} {:data [(account 21010 "Accounts Payable 10" -150.0)]}]))) (is (= [{:numeric-code 21010 :name "Amex - 41001"}] (sut/used-accounts [{:data [(bank-account 21010 "BofA CC - 8779" -100.0)]} {:data [(bank-account 21010 "Amex - 41001" -150.0)]}])) "two bank accounts still resolve to one, deterministically"))) (deftest balance-sheet-renders-one-row-per-shared-code (let [report (sut/summarize-balance-sheet (pnl-data [(bank-account 21010 "Capital One CC - 3196" -100.0) (account 21010 "Accounts Payable 10" -150.0) (account 21009 "Due to Grand Ventures" 25.0)])) rows (:rows report)] (testing "the shared code appears once, under the bank account's name" (is (= 1 (count (filter #(= "Capital One CC - 3196:21010" %) (labels rows))))) (is (not (some #{"Accounts Payable 10:21010"} (labels rows))))) (testing "its row carries the total for the code, not one account's share" (is (= -250.0 (->> rows (filter #(= "Capital One CC - 3196:21010" (:value (first %)))) first second :value)))) (testing "the detail rows foot to the section subtotal" (let [detail (->> rows (filter #(re-find #":\d+$" (str (:value (first %))))) (map (comp :value second))) subtotal (->> rows (filter #(= "Liabilities" (:value (first %)))) (keep (comp :value second)) last)] (is (= 2 (count detail)) "three accounts across two codes collapse to two rows") (is (= -225.0 (reduce + 0.0 detail) subtotal) "before the fix 21010 printed twice and the rows overshot the subtotal"))))) (deftest balance-sheet-keeps-a-clients-figure-when-another-client-named-the-code (testing "a column is blank only when that client has nothing at the code" (let [rows (:rows (sut/summarize-balance-sheet (sut/->PNLData {:periods [period]} [(assoc (bank-account 21010 "Capital One CC - 3196" -100.0) :client-id 1) (assoc (account 21010 "Accounts Payable 10" -150.0) :client-id 2) (assoc (account 21009 "Due to Grand Ventures" 25.0) :client-id 1)] {1 "ONE" 2 "TWO"}))) shared (->> rows (filter #(re-find #":21010$" (str (:value (first %))))) first) absent (->> rows (filter #(re-find #":21009$" (str (:value (first %))))) first)] (is (= [-100.0 -150.0] (map :value (drop-last (rest shared)))) "client TWO reaches 21010 under a name client ONE won, and still shows its balance") (is (= [25.0 ""] (map :value (drop-last (rest absent)))) "client TWO has nothing at 21009 and stays blank"))))