diff --git a/e2e/bulk-code-transactions.spec.ts b/e2e/bulk-code-transactions.spec.ts index 4c3bf9e9..c8fa5273 100644 --- a/e2e/bulk-code-transactions.spec.ts +++ b/e2e/bulk-code-transactions.spec.ts @@ -8,7 +8,10 @@ async function getTestInfo(page: any) { return testInfoCache; } -async function navigateToTransactions(page: any) { +async function navigateToTransactions(page: any, clientMode: string = 'mine') { + await page.setExtraHTTPHeaders({ + 'x-clients': clientMode === 'all' ? '"all"' : '"mine"' + }); await page.goto('/transaction2'); await page.waitForSelector('table tbody tr'); } @@ -387,6 +390,9 @@ test.describe('Bulk Code Transactions - Account Distribution', () => { test.describe('Bulk Code Transactions - Vendor Pre-population', () => { test('should pre-populate default account when vendor is selected', async ({ page }) => { + // Ensure single-client mode + await page.request.get('/test-set-client-mode?mode=single-client'); + await navigateToTransactions(page); await selectTransactionByIndex(page, 0); await openBulkCodeModal(page); @@ -440,4 +446,48 @@ test.describe('Bulk Code Transactions - Vendor Pre-population', () => { await page.waitForSelector('table tbody tr'); }); + + test('should NOT pre-populate default account when user has multiple clients', async ({ page }) => { + // Switch to multi-client mode + await page.request.get('/test-set-client-mode?mode=multi-client'); + + // Use 'all' to see all clients in the database + await navigateToTransactions(page, 'all'); + await selectTransactionByIndex(page, 0); + await openBulkCodeModal(page); + + // Select vendor + const testInfo = await getTestInfo(page); + const vendorId = testInfo.accounts.vendor; + + const vendorContainer = page.locator('div[hx-post*="vendor-changed"]').first(); + const vendorHidden = vendorContainer.locator('input[type="hidden"]').first(); + + await vendorHidden.evaluate((el: HTMLInputElement, value: string) => { + const newInput = document.createElement('input'); + newInput.type = 'hidden'; + newInput.name = el.name; + newInput.value = value; + el.parentNode.replaceChild(newInput, el); + }, vendorId.toString()); + + // Dispatch change on the container to trigger HTMX + await vendorContainer.evaluate((el: HTMLElement) => { + el.dispatchEvent(new Event('change', { bubbles: true })); + }); + + // Wait for HTMX response + await page.waitForResponse(response => response.url().includes('/vendor-changed') && response.status() === 200); + await page.waitForTimeout(500); + + // Should NOT have pre-populated account rows - only the "New account" button row + const accountRows = page.locator('#account-entries tbody tr'); + const rowCount = await accountRows.count(); + + // With multi-client, no pre-population should happen, so only 1 row (the "New account" button) + expect(rowCount).toBe(1); + + // Switch back to single-client mode for other tests + await page.request.get('/test-set-client-mode?mode=single-client'); + }); }); diff --git a/src/clj/auto_ap/ssr/transaction/bulk_code.clj b/src/clj/auto_ap/ssr/transaction/bulk_code.clj index 53d0e802..dbdcc4e9 100644 --- a/src/clj/auto_ap/ssr/transaction/bulk_code.clj +++ b/src/clj/auto_ap/ssr/transaction/bulk_code.clj @@ -2,6 +2,7 @@ (:require [auto-ap.datomic :refer [audit-transact-batch conn pull-attr pull-many]] + [auto-ap.datomic.accounts :as d-accounts] [auto-ap.logging :as alog] [auto-ap.permissions :refer [wrap-must]] [auto-ap.query-params :refer [wrap-copy-qp-pqp]] @@ -324,14 +325,16 @@ [:p (str "Successfully coded " (count all-ids) " transactions.")]) :headers {"hx-trigger" "refreshTable"}))))) -(defn- get-client-id [request] - (-> request :clients first :db/id)) - (defn- vendor-default-account [vendor-id client-id] + "Returns the vendor's standard default account. For single-client contexts, + the account name is clientized (tailored to the customer). For multi-client + contexts, the raw account name is used." (when vendor-id (let [vendor (edit/get-vendor vendor-id) - clientized (edit/clientize-vendor vendor client-id)] - (:vendor/default-account clientized)))) + account (:vendor/default-account vendor)] + (if client-id + (d-accounts/clientize account client-id) + account)))) (defn- build-default-account-row [account] {:db/id (str (java.util.UUID/randomUUID)) @@ -360,11 +363,17 @@ :index (count (fc/field-value))} "New account")))))]))) +(defn- single-client-id [request] + "Returns the client ID if the user has access to exactly one client, nil otherwise." + (when (= 1 (count (:clients request))) + (-> request :clients first :db/id))) + (defn vendor-changed-handler [request] (let [snapshot (:snapshot (:multi-form-state request)) step-params (:step-params (:multi-form-state request)) - client-id (get-client-id request) + client-id (single-client-id request) vendor-id (or (:vendor step-params) (:vendor snapshot)) + _ (println ::VENDOR-CHANGED :client-id client-id :vendor-id vendor-id :accounts-empty (empty? (:accounts step-params))) updated-step-params (if (and (empty? (:accounts step-params)) vendor-id client-id) diff --git a/test/clj/auto_ap/test_server.clj b/test/clj/auto_ap/test_server.clj index 02615533..e7a27371 100644 --- a/test/clj/auto_ap/test_server.clj +++ b/test/clj/auto_ap/test_server.clj @@ -25,12 +25,26 @@ [cheshire.core] [config.core :refer [env]])) +(def test-identity-mode (atom :single-client)) +(def test-transaction-id (atom nil)) +(def test-account-ids (atom {})) +(def test-client-ids (atom {})) + (defn admin-identity [] - {:user "TEST ADMIN" - :user/role "admin" - :user/name "TEST ADMIN" - :exp (time/plus (time/now) (time/days 1)) - :user/clients [{:db/id "client-id" :client/code "TEST" :client/locations ["DT"]}]}) + (case @test-identity-mode + :multi-client + {:user "TEST ADMIN" + :user/role "admin" + :user/name "TEST ADMIN" + :exp (time/plus (time/now) (time/days 1)) + :user/clients [{:db/id (:test @test-client-ids) :client/code "TEST" :client/locations ["DT"]} + {:db/id (:test2 @test-client-ids) :client/code "TEST2" :client/locations ["NY"]}]} + ;; default single-client + {:user "TEST ADMIN" + :user/role "admin" + :user/name "TEST ADMIN" + :exp (time/plus (time/now) (time/days 1)) + :user/clients [{:db/id (:test @test-client-ids) :client/code "TEST" :client/locations ["DT"]}]})) (defn wrap-test-auth [handler] (fn [request] @@ -48,15 +62,15 @@ (install-functions) test-conn))) -(def test-transaction-id (atom nil)) -(def test-account-ids (atom {})) - (defn seed-test-data [conn] (let [tx-result @(dc/transact conn [(assoc (test-client :db/id "client-id" :client/code "TEST" :client/locations ["DT"]) :client/bank-accounts [(test-bank-account :db/id "bank-account-id")]) + (test-client :db/id "client-id-2" + :client/code "TEST2" + :client/locations ["NY"]) {:db/id "account-id" :account/name "Test Account" :account/type :account-type/expense @@ -113,19 +127,41 @@ :fixed-location-account (get tempids "account-id-fixed-loc") :ap-account (get tempids "ap-account-id") :vendor (get tempids "vendor-id")}) + (reset! test-client-ids + {:test (get tempids "client-id") + :test2 (get tempids "client-id-2")}) tx-entity-id)) -(defn test-info-handler [_request] +(defn test-info-handler [request] {:status 200 :headers {"Content-Type" "application/json"} :body (cheshire.core/generate-string {:transactionId @test-transaction-id - :accounts @test-account-ids})}) + :accounts @test-account-ids + :clientMode @test-identity-mode + :clients (mapv :client/code (:clients request))})}) + +(defn test-set-client-mode-handler [request] + (let [query-string (get request :query-string "") + params (when (seq query-string) + (into {} (for [param (clojure.string/split query-string #"&")] + (let [[k v] (clojure.string/split param #"=")] + [(keyword k) (java.net.URLDecoder/decode v "UTF-8")])))) + mode (keyword (:mode params))] + (reset! test-identity-mode mode) + {:status 200 + :headers {"Content-Type" "application/json"} + :body (cheshire.core/generate-string + {:mode mode})})) (defn wrap-test-info [handler] (fn [request] - (if (= "/test-info" (:uri request)) + (cond + (= "/test-info" (:uri request)) (test-info-handler request) + (= "/test-set-client-mode" (:uri request)) + (test-set-client-mode-handler request) + :else (handler request)))) (defn test-app []