Fix bulk code vendor pre-population for single vs multi-client contexts
- vendor-default-account now uses raw vendor default account (not client-specific override) - Account name is clientized via d-accounts/clientize only for single-client contexts - Added single-client-id helper that returns client ID only when user has exactly one client - Added multi-client e2e test verifying no pre-population across multiple clients - Updated test server to support multi-client mode switching via /test-set-client-mode - Test server now seeds a second client for multi-client scenarios
This commit is contained in:
@@ -8,7 +8,10 @@ async function getTestInfo(page: any) {
|
|||||||
return testInfoCache;
|
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.goto('/transaction2');
|
||||||
await page.waitForSelector('table tbody tr');
|
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.describe('Bulk Code Transactions - Vendor Pre-population', () => {
|
||||||
test('should pre-populate default account when vendor is selected', async ({ page }) => {
|
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 navigateToTransactions(page);
|
||||||
await selectTransactionByIndex(page, 0);
|
await selectTransactionByIndex(page, 0);
|
||||||
await openBulkCodeModal(page);
|
await openBulkCodeModal(page);
|
||||||
@@ -440,4 +446,48 @@ test.describe('Bulk Code Transactions - Vendor Pre-population', () => {
|
|||||||
|
|
||||||
await page.waitForSelector('table tbody tr');
|
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');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
(:require
|
(:require
|
||||||
[auto-ap.datomic
|
[auto-ap.datomic
|
||||||
:refer [audit-transact-batch conn pull-attr pull-many]]
|
:refer [audit-transact-batch conn pull-attr pull-many]]
|
||||||
|
[auto-ap.datomic.accounts :as d-accounts]
|
||||||
[auto-ap.logging :as alog]
|
[auto-ap.logging :as alog]
|
||||||
[auto-ap.permissions :refer [wrap-must]]
|
[auto-ap.permissions :refer [wrap-must]]
|
||||||
[auto-ap.query-params :refer [wrap-copy-qp-pqp]]
|
[auto-ap.query-params :refer [wrap-copy-qp-pqp]]
|
||||||
@@ -324,14 +325,16 @@
|
|||||||
[:p (str "Successfully coded " (count all-ids) " transactions.")])
|
[:p (str "Successfully coded " (count all-ids) " transactions.")])
|
||||||
:headers {"hx-trigger" "refreshTable"})))))
|
:headers {"hx-trigger" "refreshTable"})))))
|
||||||
|
|
||||||
(defn- get-client-id [request]
|
|
||||||
(-> request :clients first :db/id))
|
|
||||||
|
|
||||||
(defn- vendor-default-account [vendor-id client-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
|
(when vendor-id
|
||||||
(let [vendor (edit/get-vendor vendor-id)
|
(let [vendor (edit/get-vendor vendor-id)
|
||||||
clientized (edit/clientize-vendor vendor client-id)]
|
account (:vendor/default-account vendor)]
|
||||||
(:vendor/default-account clientized))))
|
(if client-id
|
||||||
|
(d-accounts/clientize account client-id)
|
||||||
|
account))))
|
||||||
|
|
||||||
(defn- build-default-account-row [account]
|
(defn- build-default-account-row [account]
|
||||||
{:db/id (str (java.util.UUID/randomUUID))
|
{:db/id (str (java.util.UUID/randomUUID))
|
||||||
@@ -360,11 +363,17 @@
|
|||||||
:index (count (fc/field-value))}
|
:index (count (fc/field-value))}
|
||||||
"New account")))))])))
|
"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]
|
(defn vendor-changed-handler [request]
|
||||||
(let [snapshot (:snapshot (:multi-form-state request))
|
(let [snapshot (:snapshot (:multi-form-state request))
|
||||||
step-params (:step-params (: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))
|
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))
|
updated-step-params (if (and (empty? (:accounts step-params))
|
||||||
vendor-id
|
vendor-id
|
||||||
client-id)
|
client-id)
|
||||||
|
|||||||
@@ -25,12 +25,26 @@
|
|||||||
[cheshire.core]
|
[cheshire.core]
|
||||||
[config.core :refer [env]]))
|
[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 []
|
(defn admin-identity []
|
||||||
{:user "TEST ADMIN"
|
(case @test-identity-mode
|
||||||
:user/role "admin"
|
:multi-client
|
||||||
:user/name "TEST ADMIN"
|
{:user "TEST ADMIN"
|
||||||
:exp (time/plus (time/now) (time/days 1))
|
:user/role "admin"
|
||||||
:user/clients [{:db/id "client-id" :client/code "TEST" :client/locations ["DT"]}]})
|
: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]
|
(defn wrap-test-auth [handler]
|
||||||
(fn [request]
|
(fn [request]
|
||||||
@@ -48,15 +62,15 @@
|
|||||||
(install-functions)
|
(install-functions)
|
||||||
test-conn)))
|
test-conn)))
|
||||||
|
|
||||||
(def test-transaction-id (atom nil))
|
|
||||||
(def test-account-ids (atom {}))
|
|
||||||
|
|
||||||
(defn seed-test-data [conn]
|
(defn seed-test-data [conn]
|
||||||
(let [tx-result @(dc/transact conn
|
(let [tx-result @(dc/transact conn
|
||||||
[(assoc (test-client :db/id "client-id"
|
[(assoc (test-client :db/id "client-id"
|
||||||
:client/code "TEST"
|
:client/code "TEST"
|
||||||
:client/locations ["DT"])
|
:client/locations ["DT"])
|
||||||
:client/bank-accounts [(test-bank-account :db/id "bank-account-id")])
|
: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"
|
{:db/id "account-id"
|
||||||
:account/name "Test Account"
|
:account/name "Test Account"
|
||||||
:account/type :account-type/expense
|
:account/type :account-type/expense
|
||||||
@@ -113,19 +127,41 @@
|
|||||||
:fixed-location-account (get tempids "account-id-fixed-loc")
|
:fixed-location-account (get tempids "account-id-fixed-loc")
|
||||||
:ap-account (get tempids "ap-account-id")
|
:ap-account (get tempids "ap-account-id")
|
||||||
:vendor (get tempids "vendor-id")})
|
:vendor (get tempids "vendor-id")})
|
||||||
|
(reset! test-client-ids
|
||||||
|
{:test (get tempids "client-id")
|
||||||
|
:test2 (get tempids "client-id-2")})
|
||||||
tx-entity-id))
|
tx-entity-id))
|
||||||
|
|
||||||
(defn test-info-handler [_request]
|
(defn test-info-handler [request]
|
||||||
{:status 200
|
{:status 200
|
||||||
:headers {"Content-Type" "application/json"}
|
:headers {"Content-Type" "application/json"}
|
||||||
:body (cheshire.core/generate-string
|
:body (cheshire.core/generate-string
|
||||||
{:transactionId @test-transaction-id
|
{: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]
|
(defn wrap-test-info [handler]
|
||||||
(fn [request]
|
(fn [request]
|
||||||
(if (= "/test-info" (:uri request))
|
(cond
|
||||||
|
(= "/test-info" (:uri request))
|
||||||
(test-info-handler request)
|
(test-info-handler request)
|
||||||
|
(= "/test-set-client-mode" (:uri request))
|
||||||
|
(test-set-client-mode-handler request)
|
||||||
|
:else
|
||||||
(handler request))))
|
(handler request))))
|
||||||
|
|
||||||
(defn test-app []
|
(defn test-app []
|
||||||
|
|||||||
Reference in New Issue
Block a user