diff --git a/test/clj/auto_ap/ssr/transaction/edit_simple_advanced_mode_test.clj b/test/clj/auto_ap/ssr/transaction/edit_simple_advanced_mode_test.clj index fd5b9e7a..6a17a6fd 100644 --- a/test/clj/auto_ap/ssr/transaction/edit_simple_advanced_mode_test.clj +++ b/test/clj/auto_ap/ssr/transaction/edit_simple_advanced_mode_test.clj @@ -8,6 +8,7 @@ [auto-ap.ssr.transaction.edit :refer [clientize-vendor edit-vendor-changed-handler edit-wizard-toggle-mode-handler + location-select* manual-coding-section* vendor-default-account]] [clojure.test :refer [deftest is testing use-fixtures]] @@ -18,6 +19,10 @@ (def ^:private save-handler #'auto-ap.ssr.transaction.edit/save-handler) +;; Private simple-mode-fields* accessible via var reference +(def ^:private simple-mode-fields*-fn + #'auto-ap.ssr.transaction.edit/simple-mode-fields*) + (use-fixtures :each wrap-setup) ;;; --------------------------------------------------------------------------- @@ -30,7 +35,7 @@ (get (:tempids result) temp-id)) ;;; --------------------------------------------------------------------------- -;;; AC 1-3: manual-mode-initial — mode selection on open +;;; AC1-3: manual-mode-initial — mode selection on open ;;; --------------------------------------------------------------------------- ;; manual-mode-initial is private; access via var reference @@ -38,17 +43,17 @@ #'auto-ap.ssr.transaction.edit/manual-mode-initial) (deftest manual-mode-initial-test - (testing "AC1: no accounts → simple mode" + (testing "AC1: uncoded transaction (no accounts) opens in simple mode" (is (= :simple (manual-mode-initial {:db/id 123}))) (is (= :simple (manual-mode-initial {:db/id 123 :transaction/accounts []})))) - (testing "AC2: exactly one account → simple mode" + (testing "AC2: single-account transaction opens in simple mode" (is (= :simple (manual-mode-initial {:db/id 123 :transaction/accounts [{:transaction-account/account 456 :transaction-account/location "Shared" :transaction-account/amount 100.0}]})))) - (testing "AC3: two or more accounts → advanced mode" + (testing "AC3: multi-account (2+) transaction opens in advanced mode" (is (= :advanced (manual-mode-initial {:db/id 123 :transaction/accounts [{:transaction-account/account 1} {:transaction-account/account 2}]}))) @@ -56,11 +61,11 @@ :transaction/accounts [{} {} {}]}))))) ;;; --------------------------------------------------------------------------- -;;; AC 4-5: edit-vendor-changed-handler — vendor selection in simple mode +;;; AC4-5: edit-vendor-changed-handler — vendor selection in simple mode ;;; --------------------------------------------------------------------------- (deftest edit-vendor-changed-simple-mode-test - (testing "AC4: selecting vendor with no existing account populates account/location from vendor default" + (testing "AC4: vendor selection in simple mode populates account/location from vendor default" ;; Set up vendor with a default account (let [result @(dc/transact conn [{:db/id "vendor-id" :vendor/name "Test Vendor"} @@ -105,7 +110,7 @@ (is (re-find #"Test Account" body) "Response should contain the vendor's default account name"))) - (testing "AC5: selecting vendor when account already set does NOT overwrite existing account" + (testing "AC5: vendor selection in simple mode does NOT overwrite already-set account" (let [result @(dc/transact conn [{:db/id "vendor-id" :vendor/name "Test Vendor"} {:db/id "account-id" @@ -158,11 +163,11 @@ "Response should NOT contain the vendor's default account ID when existing account is set")))) ;;; --------------------------------------------------------------------------- -;;; AC 6: save round-trip — manual mode saves vendor + account to DB +;;; AC6: save round-trip — manual mode saves vendor + account to DB ;;; --------------------------------------------------------------------------- (deftest save-manual-round-trip-test - (testing "AC6: submitting simple-mode form saves vendor and single account/location to DB" + (testing "AC6: save in simple mode persists vendor/account/location — re-opening shows same values" (let [result @(dc/transact conn [{:db/id "vendor-id" :vendor/name "Save Vendor"} {:db/id "account-id" @@ -218,11 +223,86 @@ "The amount should be saved"))))) ;;; --------------------------------------------------------------------------- -;;; AC 7-9: edit-wizard-toggle-mode-handler — mode toggling +;;; AC7: simple mode account typeahead respects :account/default-allowance rules ;;; --------------------------------------------------------------------------- -(deftest toggle-mode-test - (testing "AC7: simple → advanced toggle re-renders in advanced mode with accounts preserved" +(deftest simple-mode-account-typeahead-allowance-test + (testing "AC7: simple mode account typeahead URL includes purpose=transaction" + ;; The account-typeahead* always passes purpose=transaction to the search URL, + ;; and the schema validates accounts using check-allowance with :account/default-allowance. + ;; We test the rendering side: the typeahead URL in simple-mode-fields* must contain purpose=transaction. + (let [result @(dc/transact conn [{:db/id "client-id" + :client/code "ALLOWCL" + :client/locations ["DT"]} + {:db/id "account-id" + :account/name "Allowed Account" + :account/type :account-type/expense} + {:db/id "transaction-id" + :transaction/amount 100.0 + :transaction/date #inst "2023-01-01" + :transaction/id (str (java.util.UUID/randomUUID)) + :transaction/client "client-id"}]) + tx-id (tempid->id result "transaction-id") + account-id (tempid->id result "account-id") + client-id (tempid->id result "client-id") + one-account [{:db/id "row-1" + :transaction-account/account account-id + :transaction-account/location "DT" + :transaction-account/amount 100.0}] + request {:multi-form-state (mm/->MultiStepFormState + {:db/id tx-id + :transaction/client client-id + :transaction/accounts one-account} + [] + {:mode "simple" + :transaction/accounts one-account}) + :entity {:db/id tx-id + :transaction/client {:db/id client-id} + :transaction/amount 100.0}} + html (hiccup/html + (fc/start-form (:multi-form-state request) nil + (fc/with-field :step-params + (manual-coding-section* :simple request))))] + (is (re-find #"purpose=transaction" html) + "Simple mode account typeahead URL must include purpose=transaction to enforce allowance rules")))) + +;;; --------------------------------------------------------------------------- +;;; AC8: location dropdown — fixed location vs full client list +;;; --------------------------------------------------------------------------- + +(deftest location-select-options-test + (testing "AC8: when account has a fixed location, dropdown shows only that location" + (let [html (hiccup/html + (location-select* {:name "location" + :account-location "HQ" + :client-locations ["DT" "NY"] + :value "HQ"}))] + (is (re-find #"HQ" html) + "Fixed account location 'HQ' should appear in dropdown") + (is (not (re-find #"DT" html)) + "Client location 'DT' should NOT appear when account has fixed location") + (is (not (re-find #"Shared" html)) + "'Shared' option should not appear when account has fixed location"))) + + (testing "AC8: when account has no fixed location, dropdown shows full client location list" + (let [html (hiccup/html + (location-select* {:name "location" + :account-location nil + :client-locations ["DT" "NY"] + :value "Shared"}))] + (is (re-find #"Shared" html) + "'Shared' option should appear when no fixed account location") + (is (re-find #"DT" html) + "Client location 'DT' should appear when no fixed account location") + (is (re-find #"NY" html) + "Client location 'NY' should appear when no fixed account location")))) + +;;; --------------------------------------------------------------------------- +;;; AC9: simple → advanced toggle with pre-populated row +;;; --------------------------------------------------------------------------- + +(deftest toggle-simple-to-advanced-test + (testing "AC9: clicking 'Switch to advanced mode' from simple mode re-renders in advanced with one row pre-populated" (let [result @(dc/transact conn [{:db/id "client-id" :client/code "TOGGLECL" :client/locations ["DT"]} @@ -267,14 +347,58 @@ "Advanced mode response should contain the account ID from the simple-mode row") ;; The account name should appear in the advanced table HTML (is (re-find #"Toggle Account" body) - "Advanced mode response should contain the account name from the simple-mode row"))) + "Advanced mode response should contain the account name from the simple-mode row")))) - (testing "AC8: advanced → simple (1 row) re-renders in simple mode" +;;; --------------------------------------------------------------------------- +;;; AC10: blank simple → advanced gives empty table +;;; --------------------------------------------------------------------------- + +(deftest toggle-blank-simple-to-advanced-test + (testing "AC10: 'Switch to advanced mode' from blank simple mode gives advanced with empty table" (let [result @(dc/transact conn [{:db/id "client-id" - :client/code "TOGSIMCL" + :client/code "BLNKTOGLCL" + :client/locations ["DT"]} + {:db/id "transaction-id" + :transaction/amount 100.0 + :transaction/date #inst "2023-01-01" + :transaction/id (str (java.util.UUID/randomUUID)) + :transaction/client "client-id"}]) + tx-id (tempid->id result "transaction-id") + client-id (tempid->id result "client-id") + request {:multi-form-state (mm/->MultiStepFormState + {:db/id tx-id + :transaction/client client-id + :transaction/accounts []} + [] + {:mode "simple" + :transaction/accounts []}) + :entity {:db/id tx-id + :transaction/client {:db/id client-id} + :transaction/amount 100.0}} + response (edit-wizard-toggle-mode-handler request) + body (:body response)] + (is (string? body)) + ;; Should render in advanced mode + (is (re-find #"account-grid-body" body) + "Blank simple → advanced should produce account-grid-body") + ;; Should NOT have any account-row elements (empty grid) + (is (not (re-find #"account-row" body)) + "Blank simple → advanced should have NO account rows") + ;; Should show 'Switch to simple mode' because 0 rows <= 1 + (is (re-find #"Switch to simple mode" body) + "Advanced mode with 0 rows should show 'Switch to simple mode' link")))) + +;;; --------------------------------------------------------------------------- +;;; AC11-12: "Switch to simple mode" visibility based on row count +;;; --------------------------------------------------------------------------- + +(deftest advanced-mode-switch-link-visibility-test + (testing "AC11: 'Switch to simple mode' link is visible in advanced mode with 0 or 1 rows" + (let [result @(dc/transact conn [{:db/id "client-id" + :client/code "VISLINKCL" :client/locations ["DT"]} {:db/id "account-id" - :account/name "Toggle Account" + :account/name "Visibility Account" :account/type :account-type/expense} {:db/id "transaction-id" :transaction/amount 100.0 @@ -284,28 +408,36 @@ tx-id (tempid->id result "transaction-id") account-id (tempid->id result "account-id") client-id (tempid->id result "client-id") + zero-rows [] one-row [{:db/id "row-1" :transaction-account/account account-id :transaction-account/location "DT" :transaction-account/amount 100.0}] - request {:multi-form-state (mm/->MultiStepFormState - {:db/id tx-id - :transaction/client client-id - :transaction/accounts one-row} - [] - {:mode "advanced" - :transaction/accounts one-row}) - :entity {:db/id tx-id - :transaction/client {:db/id client-id} - :transaction/amount 100.0}} - response (edit-wizard-toggle-mode-handler request) - body (:body response)] - (is (string? body)) - ;; After toggling from advanced to simple, show "Switch to advanced mode" link - (is (re-find #"Switch to advanced mode" body) - "After toggling to simple mode, 'Switch to advanced mode' should be visible"))) + make-req (fn [accounts] + {:multi-form-state (mm/->MultiStepFormState + {:db/id tx-id + :transaction/client client-id + :transaction/accounts accounts} + [] + {:mode "advanced" + :transaction/accounts accounts}) + :entity {:db/id tx-id + :transaction/client {:db/id client-id} + :transaction/amount 100.0}}) + html-zero-rows (hiccup/html + (fc/start-form (:multi-form-state (make-req zero-rows)) nil + (fc/with-field :step-params + (manual-coding-section* :advanced (make-req zero-rows))))) + html-one-row (hiccup/html + (fc/start-form (:multi-form-state (make-req one-row)) nil + (fc/with-field :step-params + (manual-coding-section* :advanced (make-req one-row)))))] + (is (re-find #"Switch to simple mode" html-zero-rows) + "In advanced mode with 0 rows, 'Switch to simple mode' should be visible") + (is (re-find #"Switch to simple mode" html-one-row) + "In advanced mode with 1 row, 'Switch to simple mode' should be visible"))) - (testing "AC9: In advanced mode with 2+ rows, 'Switch to simple mode' should be absent" + (testing "AC12: 'Switch to simple mode' link is absent in advanced mode with 2+ rows" (let [result @(dc/transact conn [{:db/id "client-id" :client/code "TWOROWCL" :client/locations ["DT"]} @@ -342,7 +474,6 @@ :entity {:db/id tx-id :transaction/client {:db/id client-id} :transaction/amount 100.0}} - ;; Render advanced mode directly with 2 rows using manual-coding-section* html (hiccup/html (fc/start-form (:multi-form-state request) nil (fc/with-field :step-params @@ -351,175 +482,16 @@ "In advanced mode with 2+ rows, 'Switch to simple mode' link should be absent")))) ;;; --------------------------------------------------------------------------- -;;; AC 9 (direct): "Switch to simple mode" hidden/visible based on row count +;;; AC13: advanced → simple (1 row) re-renders in simple mode ;;; --------------------------------------------------------------------------- -(deftest advanced-mode-switch-link-visibility-test - (testing "AC9a: 'Switch to simple mode' link visible when 1 row in advanced mode" +(deftest toggle-advanced-to-simple-test + (testing "AC13: 'Switch to simple mode' from advanced (1 row) gives simple mode with that row's values" (let [result @(dc/transact conn [{:db/id "client-id" - :client/code "VISLINKCL" + :client/code "TOGSIMCL" :client/locations ["DT"]} {:db/id "account-id" - :account/name "Visibility Account" - :account/type :account-type/expense} - {:db/id "transaction-id" - :transaction/amount 100.0 - :transaction/date #inst "2023-01-01" - :transaction/id (str (java.util.UUID/randomUUID)) - :transaction/client "client-id"}]) - tx-id (tempid->id result "transaction-id") - account-id (tempid->id result "account-id") - client-id (tempid->id result "client-id") - one-row [{:db/id "row-1" - :transaction-account/account account-id - :transaction-account/location "DT" - :transaction-account/amount 100.0}] - two-rows [{:db/id "row-1" - :transaction-account/account account-id - :transaction-account/location "DT" - :transaction-account/amount 50.0} - {:db/id "row-2" - :transaction-account/account account-id - :transaction-account/location "DT" - :transaction-account/amount 50.0}] - make-req (fn [accounts] - {:multi-form-state (mm/->MultiStepFormState - {:db/id tx-id - :transaction/client client-id - :transaction/accounts accounts} - [] - {:mode "advanced" - :transaction/accounts accounts}) - :entity {:db/id tx-id - :transaction/client {:db/id client-id} - :transaction/amount 100.0}}) - ;; Render advanced mode with 1 row - html-one-row (hiccup/html - (fc/start-form (:multi-form-state (make-req one-row)) nil - (fc/with-field :step-params - (manual-coding-section* :advanced (make-req one-row))))) - ;; Render advanced mode with 2 rows - html-two-rows (hiccup/html - (fc/start-form (:multi-form-state (make-req two-rows)) nil - (fc/with-field :step-params - (manual-coding-section* :advanced (make-req two-rows)))))] - (is (re-find #"Switch to simple mode" html-one-row) - "In advanced mode with 1 row, 'Switch to simple mode' should be visible") - (is (not (re-find #"Switch to simple mode" html-two-rows)) - "In advanced mode with 2+ rows, 'Switch to simple mode' should be absent")))) - -;;; --------------------------------------------------------------------------- -;;; AC 10: vendor-default-account uses clientized vendor logic -;;; --------------------------------------------------------------------------- - -(deftest vendor-default-account-clientize-test - (testing "AC10: client-specific account override takes precedence over global vendor default" - (let [result @(dc/transact conn [{:db/id "global-account-id" - :account/name "Global Default Account" - :account/type :account-type/expense} - {:db/id "client-specific-account-id" - :account/name "Client Specific Account" - :account/type :account-type/expense} - {:db/id "client-id" - :client/code "CLACCTEST" - :client/locations ["DT"]} - {:db/id "vendor-id" - :vendor/name "Clientized Vendor" - :vendor/default-account "global-account-id" - :vendor/account-overrides [{:vendor-account-override/client "client-id" - :vendor-account-override/account "client-specific-account-id"}]}]) - vendor-id (tempid->id result "vendor-id") - client-id (tempid->id result "client-id") - client-specific-account-id (tempid->id result "client-specific-account-id") - result-account (vendor-default-account vendor-id client-id)] - (is (some? result-account) - "Should return a default account") - (is (= client-specific-account-id (:db/id result-account)) - "Client-specific account override should take precedence over global vendor default"))) - - (testing "AC10: falls back to global vendor default when no client override" - (let [result @(dc/transact conn [{:db/id "global-account-id" - :account/name "Global Default Account" - :account/type :account-type/expense} - {:db/id "vendor-id" - :vendor/name "Vendor No Override" - :vendor/default-account "global-account-id"} - {:db/id "client-id" - :client/code "NOOVCL" - :client/locations ["DT"]}]) - vendor-id (tempid->id result "vendor-id") - client-id (tempid->id result "client-id") - global-account-id (tempid->id result "global-account-id") - result-account (vendor-default-account vendor-id client-id)] - (is (= global-account-id (:db/id result-account)) - "Should use global vendor default when no client-specific override exists"))) - - (testing "AC10: clientize-vendor applies both terms and account overrides" - (let [vendor {:db/id "vendor-id" - :vendor/name "Test Vendor" - :vendor/default-account {:db/id "global-account-id"} - :vendor/terms "NET30" - :vendor/automatically-paid-when-due [] - :vendor/terms-overrides [{:vendor-terms-override/client {:db/id "client-id"} - :vendor-terms-override/terms "NET60"}] - :vendor/account-overrides [{:vendor-account-override/client {:db/id "client-id"} - :vendor-account-override/account {:db/id "client-specific-account-id"}}]} - clientized (clientize-vendor vendor "client-id")] - (is (= "NET60" (:vendor/terms clientized)) - "Terms override should be applied") - (is (= "client-specific-account-id" (-> clientized :vendor/default-account :db/id)) - "Account override should be applied")))) - -;;; --------------------------------------------------------------------------- -;;; AC 1-2 (rendering): manual-coding-section* renders correct mode -;;; --------------------------------------------------------------------------- - -(deftest manual-coding-section-renders-correct-mode-test - (testing "AC1/AC2: simple mode renders with 'Switch to advanced mode' link (with one account present)" - (let [result @(dc/transact conn [{:db/id "client-id" - :client/code "SIMMODECL" - :client/locations ["DT"]} - {:db/id "account-id" - :account/name "Simple Mode Account" - :account/type :account-type/expense} - {:db/id "transaction-id" - :transaction/amount 100.0 - :transaction/date #inst "2023-01-01" - :transaction/id (str (java.util.UUID/randomUUID)) - :transaction/client "client-id"}]) - tx-id (tempid->id result "transaction-id") - account-id (tempid->id result "account-id") - client-id (tempid->id result "client-id") - ;; Simple mode: one account (renders correctly with one row cursor available) - one-account [{:db/id "row-1" - :transaction-account/account account-id - :transaction-account/location "DT" - :transaction-account/amount 100.0}] - request {:multi-form-state (mm/->MultiStepFormState - {:db/id tx-id - :transaction/client client-id - :transaction/accounts one-account} - [] - {:mode "simple" - :transaction/accounts one-account}) - :entity {:db/id tx-id - :transaction/client {:db/id client-id} - :transaction/amount 100.0}} - html (hiccup/html - (fc/start-form (:multi-form-state request) nil - (fc/with-field :step-params - (manual-coding-section* :simple request))))] - (is (re-find #"Switch to advanced mode" html) - "Simple mode should show 'Switch to advanced mode' link") - (is (not (re-find #"Switch to simple mode" html)) - "Simple mode should NOT show 'Switch to simple mode' link"))) - - (testing "AC3: advanced mode renders with 'Switch to simple mode' link when 1 row" - (let [result @(dc/transact conn [{:db/id "client-id" - :client/code "ADVMODECL" - :client/locations ["DT"]} - {:db/id "account-id" - :account/name "Advanced Mode Account" + :account/name "Toggle Account" :account/type :account-type/expense} {:db/id "transaction-id" :transaction/amount 100.0 @@ -543,11 +515,425 @@ :entity {:db/id tx-id :transaction/client {:db/id client-id} :transaction/amount 100.0}} + response (edit-wizard-toggle-mode-handler request) + body (:body response)] + (is (string? body)) + ;; After toggling from advanced to simple, show "Switch to advanced mode" link + (is (re-find #"Switch to advanced mode" body) + "After toggling to simple mode, 'Switch to advanced mode' should be visible") + ;; The row's account ID should appear in the simple mode form + (is (re-find (re-pattern (str account-id)) body) + "Simple mode should display the account from the advanced mode row")))) + +;;; --------------------------------------------------------------------------- +;;; AC14: vendor change in advanced mode with no rows creates one row +;;; --------------------------------------------------------------------------- + +(deftest edit-vendor-changed-advanced-mode-no-rows-test + (testing "AC14: vendor change in advanced mode with no rows creates one row with vendor's default account" + (let [result @(dc/transact conn [{:db/id "vendor-id" + :vendor/name "Advanced Vendor"} + {:db/id "account-id" + :account/name "Vendor Default Account" + :account/type :account-type/expense} + {:db/id "vendor-id" + :vendor/default-account "account-id"} + {:db/id "client-id" + :client/code "ADVVCL" + :client/locations ["DT"]} + {:db/id "transaction-id" + :transaction/amount 200.0 + :transaction/date #inst "2023-01-01" + :transaction/id (str (java.util.UUID/randomUUID)) + :transaction/client "client-id"}]) + tx-id (tempid->id result "transaction-id") + vendor-id (tempid->id result "vendor-id") + account-id (tempid->id result "account-id") + client-id (tempid->id result "client-id") + ;; Advanced mode with no accounts + request {:multi-form-state (mm/->MultiStepFormState + {:db/id tx-id + :transaction/client client-id + :transaction/accounts []} + [] + {:mode "advanced" + :transaction/vendor vendor-id + :transaction/accounts []}) + :entity {:db/id tx-id + :transaction/client {:db/id client-id} + :transaction/amount 200.0}} + response (edit-vendor-changed-handler request) + body (:body response)] + (is (string? body)) + (is (re-find #"manual-coding-section" body)) + ;; Should have account-grid-body (advanced mode) + (is (re-find #"account-grid-body" body) + "Advanced mode vendor change should show account-grid-body") + ;; The vendor's default account ID should appear in the new row + (is (re-find (re-pattern (str account-id)) body) + "Advanced mode: vendor default account should appear in the newly created row") + (is (re-find #"Vendor Default Account" body) + "Advanced mode: vendor default account name should appear in the new row")))) + +;;; --------------------------------------------------------------------------- +;;; AC15: vendor change in advanced mode with existing rows does NOT modify them +;;; --------------------------------------------------------------------------- + +(deftest edit-vendor-changed-advanced-mode-existing-rows-test + (testing "AC15: vendor change in advanced mode with existing rows does NOT modify existing rows" + (let [result @(dc/transact conn [{:db/id "vendor-id" + :vendor/name "New Vendor"} + {:db/id "vendor-account-id" + :account/name "Vendor Default Account" + :account/type :account-type/expense} + {:db/id "vendor-id" + :vendor/default-account "vendor-account-id"} + {:db/id "existing-account-id" + :account/name "Existing Account" + :account/type :account-type/expense} + {:db/id "client-id" + :client/code "ADVEXCL" + :client/locations ["DT"]} + {:db/id "transaction-id" + :transaction/amount 100.0 + :transaction/date #inst "2023-01-01" + :transaction/id (str (java.util.UUID/randomUUID)) + :transaction/client "client-id"}]) + tx-id (tempid->id result "transaction-id") + vendor-id (tempid->id result "vendor-id") + vendor-account-id (tempid->id result "vendor-account-id") + existing-account-id (tempid->id result "existing-account-id") + client-id (tempid->id result "client-id") + existing-rows [{:db/id "row-1" + :transaction-account/account existing-account-id + :transaction-account/location "DT" + :transaction-account/amount 100.0}] + ;; Advanced mode with existing accounts — vendor change should NOT touch them + request {:multi-form-state (mm/->MultiStepFormState + {:db/id tx-id + :transaction/client client-id + :transaction/accounts existing-rows} + [] + {:mode "advanced" + :transaction/vendor vendor-id + :transaction/accounts existing-rows}) + :entity {:db/id tx-id + :transaction/client {:db/id client-id} + :transaction/amount 100.0}} + response (edit-vendor-changed-handler request) + body (:body response)] + (is (string? body)) + ;; The original (existing) account should still be shown + (is (re-find (re-pattern (str existing-account-id)) body) + "Existing row account should remain unchanged when vendor changes in advanced mode") + ;; The vendor's default account should NOT appear — existing rows are untouched + (is (not (re-find (re-pattern (str vendor-account-id)) body)) + "Vendor default account should NOT replace existing rows in advanced mode")))) + +;;; --------------------------------------------------------------------------- +;;; AC16: vendor-default-account uses clientized vendor logic +;;; --------------------------------------------------------------------------- + +(deftest vendor-default-account-clientize-test + (testing "AC16: client-specific account override takes precedence over global vendor default" + (let [result @(dc/transact conn [{:db/id "global-account-id" + :account/name "Global Default Account" + :account/type :account-type/expense} + {:db/id "client-specific-account-id" + :account/name "Client Specific Account" + :account/type :account-type/expense} + {:db/id "client-id" + :client/code "CLACCTEST" + :client/locations ["DT"]} + {:db/id "vendor-id" + :vendor/name "Clientized Vendor" + :vendor/default-account "global-account-id" + :vendor/account-overrides [{:vendor-account-override/client "client-id" + :vendor-account-override/account "client-specific-account-id"}]}]) + vendor-id (tempid->id result "vendor-id") + client-id (tempid->id result "client-id") + client-specific-account-id (tempid->id result "client-specific-account-id") + result-account (vendor-default-account vendor-id client-id)] + (is (some? result-account) + "Should return a default account") + (is (= client-specific-account-id (:db/id result-account)) + "Client-specific account override should take precedence over global vendor default"))) + + (testing "AC16: falls back to global vendor default when no client override" + (let [result @(dc/transact conn [{:db/id "global-account-id" + :account/name "Global Default Account" + :account/type :account-type/expense} + {:db/id "vendor-id" + :vendor/name "Vendor No Override" + :vendor/default-account "global-account-id"} + {:db/id "client-id" + :client/code "NOOVCL" + :client/locations ["DT"]}]) + vendor-id (tempid->id result "vendor-id") + client-id (tempid->id result "client-id") + global-account-id (tempid->id result "global-account-id") + result-account (vendor-default-account vendor-id client-id)] + (is (= global-account-id (:db/id result-account)) + "Should use global vendor default when no client-specific override exists"))) + + (testing "AC16: clientize-vendor applies both terms and account overrides" + (let [vendor {:db/id "vendor-id" + :vendor/name "Test Vendor" + :vendor/default-account {:db/id "global-account-id"} + :vendor/terms "NET30" + :vendor/automatically-paid-when-due [] + :vendor/terms-overrides [{:vendor-terms-override/client {:db/id "client-id"} + :vendor-terms-override/terms "NET60"}] + :vendor/account-overrides [{:vendor-account-override/client {:db/id "client-id"} + :vendor-account-override/account {:db/id "client-specific-account-id"}}]} + clientized (clientize-vendor vendor "client-id")] + (is (= "NET60" (:vendor/terms clientized)) + "Terms override should be applied") + (is (= "client-specific-account-id" (-> clientized :vendor/default-account :db/id)) + "Account override should be applied")))) + +;;; --------------------------------------------------------------------------- +;;; AC17: approval status, memo, vendor fields present in both modes +;;; --------------------------------------------------------------------------- + +(deftest fields-present-in-both-modes-test + (testing "AC17: vendor field is present in simple mode" + (let [result @(dc/transact conn [{:db/id "client-id" + :client/code "FLDTESTCL" + :client/locations ["DT"]} + {:db/id "account-id" + :account/name "Fields Account" + :account/type :account-type/expense} + {:db/id "transaction-id" + :transaction/amount 100.0 + :transaction/date #inst "2023-01-01" + :transaction/id (str (java.util.UUID/randomUUID)) + :transaction/client "client-id"}]) + tx-id (tempid->id result "transaction-id") + account-id (tempid->id result "account-id") + client-id (tempid->id result "client-id") + one-account [{:db/id "row-1" + :transaction-account/account account-id + :transaction-account/location "DT" + :transaction-account/amount 100.0}] + make-req (fn [mode accounts] + {:multi-form-state (mm/->MultiStepFormState + {:db/id tx-id + :transaction/client client-id + :transaction/accounts accounts} + [] + {:mode (name mode) + :transaction/accounts accounts}) + :entity {:db/id tx-id + :transaction/client {:db/id client-id} + :transaction/amount 100.0}}) + html-simple (hiccup/html + (fc/start-form (:multi-form-state (make-req :simple one-account)) nil + (fc/with-field :step-params + (manual-coding-section* :simple (make-req :simple one-account))))) + html-advanced (hiccup/html + (fc/start-form (:multi-form-state (make-req :advanced one-account)) nil + (fc/with-field :step-params + (manual-coding-section* :advanced (make-req :advanced one-account)))))] + (is (re-find #"Vendor" html-simple) + "AC17: Vendor field label must appear in simple mode") + (is (re-find #"vendor/search" html-simple) + "AC17: Vendor typeahead/search URL must appear in simple mode") + (is (re-find #"transaction/vendor" html-simple) + "AC17: Vendor field name must appear in simple mode") + (is (re-find #"Vendor" html-advanced) + "AC17: Vendor field label must appear in advanced mode") + (is (re-find #"vendor/search" html-advanced) + "AC17: Vendor typeahead/search URL must appear in advanced mode") + (is (re-find #"transaction/vendor" html-advanced) + "AC17: Vendor field name must appear in advanced mode")))) + +;;; --------------------------------------------------------------------------- +;;; AC18: switching modes mid-edit then saving produces a valid transaction +;;; --------------------------------------------------------------------------- + +(deftest switch-modes-then-save-test + (testing "AC18: switching simple→advanced→save produces a single valid transaction row in DB" + (let [result @(dc/transact conn [{:db/id "vendor-id" + :vendor/name "Switch Vendor"} + {:db/id "account-id" + :account/name "Switch Account" + :account/type :account-type/expense} + {:db/id "client-id" + :client/code "SWITCHCL" + :client/locations ["DT"]} + {:db/id "transaction-id" + :transaction/amount 100.0 + :transaction/date #inst "2023-01-01" + :transaction/id (str (java.util.UUID/randomUUID)) + :transaction/client "client-id"}]) + tx-id (tempid->id result "transaction-id") + vendor-id (tempid->id result "vendor-id") + account-id (tempid->id result "account-id") + client-id (tempid->id result "client-id") + simple-row [{:db/id "row-1" + :transaction-account/account account-id + :transaction-account/location "DT" + :transaction-account/amount 100.0}] + ;; Step 1: Start in simple mode + simple-request {:multi-form-state (mm/->MultiStepFormState + {:db/id tx-id + :transaction/client client-id + :transaction/accounts simple-row} + [] + {:mode "simple" + :transaction/accounts simple-row}) + :entity {:db/id tx-id + :transaction/client {:db/id client-id} + :transaction/amount 100.0}} + ;; Step 2: Toggle to advanced mode + toggle-response (edit-wizard-toggle-mode-handler simple-request) + ;; After toggle, build a save request in advanced mode with one row + advanced-snapshot {:db/id tx-id + :transaction/client client-id + :action :manual + :transaction/vendor vendor-id + :transaction/amount 100.0 + :transaction/accounts [{:db/id "row-1" + :transaction-account/account account-id + :transaction-account/location "DT" + :transaction-account/amount 100.0}]} + save-request {:multi-form-state (mm/->MultiStepFormState advanced-snapshot [] advanced-snapshot) + :entity {:db/id tx-id + :transaction/client {:db/id client-id} + :transaction/amount 100.0} + :identity {:user/role "admin"}}] + ;; Verify the toggle worked + (is (string? (:body toggle-response))) + (is (re-find #"account-grid-body" (:body toggle-response)) + "After toggle, response should be in advanced mode") + ;; Now save + (with-redefs [auto-ap.solr/impl (auto-ap.solr/->InMemSolrClient (atom {}))] + (save-handler save-request)) + ;; Verify exactly one account row was saved (no orphaned rows) + (let [saved (dc/pull (dc/db conn) + '[:db/id + {:transaction/accounts [{:transaction-account/account [:db/id]} + :transaction-account/location + :transaction-account/amount]}] + tx-id)] + (is (= 1 (count (:transaction/accounts saved))) + "AC18: Exactly one account row should be saved after mode-switch + save (no orphaned rows)") + (is (= account-id (-> saved :transaction/accounts first :transaction-account/account :db/id)) + "AC18: The correct account should be saved"))))) + +;;; --------------------------------------------------------------------------- +;;; AC19: split transaction re-opens in advanced mode with all splits +;;; --------------------------------------------------------------------------- + +(deftest split-transaction-reopens-advanced-test + (testing "AC19: split transaction (2+ accounts) re-opens in advanced mode with all splits intact" + ;; Test manual-mode-initial with 2-row snapshot + (is (= :advanced (manual-mode-initial {:db/id 123 + :transaction/accounts [{:transaction-account/account 1 + :transaction-account/amount 50.0} + {:transaction-account/account 2 + :transaction-account/amount 50.0}]})) + "AC19: 2-account snapshot should return :advanced mode") + (let [result @(dc/transact conn [{:db/id "client-id" + :client/code "SPLITCL" + :client/locations ["DT"]} + {:db/id "account-id-1" + :account/name "Split Account 1" + :account/type :account-type/expense} + {:db/id "account-id-2" + :account/name "Split Account 2" + :account/type :account-type/expense} + {:db/id "transaction-id" + :transaction/amount 100.0 + :transaction/date #inst "2023-01-01" + :transaction/id (str (java.util.UUID/randomUUID)) + :transaction/client "client-id"}]) + tx-id (tempid->id result "transaction-id") + account-id-1 (tempid->id result "account-id-1") + account-id-2 (tempid->id result "account-id-2") + client-id (tempid->id result "client-id") + two-rows [{:db/id "row-1" + :transaction-account/account account-id-1 + :transaction-account/location "DT" + :transaction-account/amount 50.0} + {:db/id "row-2" + :transaction-account/account account-id-2 + :transaction-account/location "DT" + :transaction-account/amount 50.0}] + request {:multi-form-state (mm/->MultiStepFormState + {:db/id tx-id + :transaction/client client-id + :transaction/accounts two-rows} + [] + {:mode "advanced" + :transaction/accounts two-rows}) + :entity {:db/id tx-id + :transaction/client {:db/id client-id} + :transaction/amount 100.0}} html (hiccup/html (fc/start-form (:multi-form-state request) nil (fc/with-field :step-params (manual-coding-section* :advanced request))))] - (is (re-find #"Switch to simple mode" html) - "Advanced mode with 1 row should show 'Switch to simple mode' link") - (is (not (re-find #"Switch to advanced mode" html)) - "Advanced mode should NOT show 'Switch to advanced mode' link")))) + ;; Should show both account IDs + (is (re-find (re-pattern (str account-id-1)) html) + "AC19: First split account should appear in advanced mode rendering") + (is (re-find (re-pattern (str account-id-2)) html) + "AC19: Second split account should appear in advanced mode rendering") + ;; Should NOT show 'Switch to simple mode' with 2 rows + (is (not (re-find #"Switch to simple mode" html)) + "AC19: Split transaction (2 rows) in advanced mode should NOT show 'Switch to simple mode'")))) + +;;; --------------------------------------------------------------------------- +;;; AC20: single-account transaction re-opens in simple mode +;;; --------------------------------------------------------------------------- + +(deftest single-account-reopens-simple-test + (testing "AC20: single-account transaction re-opens in simple mode with account/location pre-populated" + ;; Test manual-mode-initial returns :simple for single-account snapshot + (is (= :simple (manual-mode-initial {:db/id 123 + :transaction/accounts [{:transaction-account/account 456 + :transaction-account/location "DT" + :transaction-account/amount 100.0}]})) + "AC20: Single-account snapshot should return :simple mode") + (let [result @(dc/transact conn [{:db/id "client-id" + :client/code "SINGCL" + :client/locations ["DT"]} + {:db/id "account-id" + :account/name "Single Account" + :account/type :account-type/expense} + {:db/id "transaction-id" + :transaction/amount 100.0 + :transaction/date #inst "2023-01-01" + :transaction/id (str (java.util.UUID/randomUUID)) + :transaction/client "client-id"}]) + tx-id (tempid->id result "transaction-id") + account-id (tempid->id result "account-id") + client-id (tempid->id result "client-id") + one-account [{:db/id "row-1" + :transaction-account/account account-id + :transaction-account/location "DT" + :transaction-account/amount 100.0}] + request {:multi-form-state (mm/->MultiStepFormState + {:db/id tx-id + :transaction/client client-id + :transaction/accounts one-account} + [] + {:mode "simple" + :transaction/accounts one-account}) + :entity {:db/id tx-id + :transaction/client {:db/id client-id} + :transaction/amount 100.0}} + html (hiccup/html + (fc/start-form (:multi-form-state request) nil + (fc/with-field :step-params + (manual-coding-section* :simple request))))] + ;; Simple mode should show the account typeahead pre-populated with the account + (is (re-find (re-pattern (str account-id)) html) + "AC20: Simple mode should show the account pre-populated") + ;; Should show 'Switch to advanced mode' (confirming it's in simple mode) + (is (re-find #"Switch to advanced mode" html) + "AC20: Simple mode should show 'Switch to advanced mode' link") + ;; Should NOT show 'Switch to simple mode' + (is (not (re-find #"Switch to simple mode" html)) + "AC20: Simple mode should NOT show 'Switch to simple mode' link"))))