test(admin): implement integration and unit tests for admin behaviors

Implement comprehensive test coverage for admin dashboard behaviors:
- Dashboard access control (2.1, 2.2)
- Client filtering by name, code, group (4.1-4.5)
- Client sorting and pagination (5.1-5.3)
- Client wizard validation (6.12, 6.17, 6.18, 6.20)
- Account filtering, sorting, and dialog validation (9.1-11.9)
- Vendor filtering and wizard validation (13.1-14.12)
- Vendor merge validation (15.2, 15.3)
- Transaction rule filtering, wizard, execution, and deletion (17.1-20.3)

Also fixes vendor terms override duplicate validation in vendors.clj.
This commit is contained in:
2026-05-06 23:00:25 -07:00
parent 1a48abdd7c
commit ddbb6abc3a
3 changed files with 2133 additions and 354 deletions

View File

@@ -57,8 +57,8 @@ Every admin operation checks:
| # | Behavior | Test Strategy | Status | | # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------| |---|----------|---------------|--------|
| 2.1 | It should redirect unauthenticated users to the login page | Integration | [ ] | | 2.1 | It should redirect unauthenticated users to the login page | Integration | [x] |
| 2.2 | It should show an authorization failure for authenticated non-admin users | Integration | [ ] | | 2.2 | It should show an authorization failure for authenticated non-admin users | Integration | [x] |
--- ---
@@ -84,19 +84,19 @@ Every admin operation checks:
| # | Behavior | Test Strategy | Status | | # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------| |---|----------|---------------|--------|
| 4.1 | It should filter clients by name using case-insensitive substring match | Integration | [ ] | | 4.1 | It should filter clients by name using case-insensitive substring match | Integration | [x] |
| 4.2 | It should filter clients by code using exact match on upper-cased code | Integration | [ ] | | 4.2 | It should filter clients by code using exact match on upper-cased code | Integration | [x] |
| 4.3 | It should filter clients by group using exact match on upper-cased group | Integration | [ ] | | 4.3 | It should filter clients by group using exact match on upper-cased group | Integration | [x] |
| 4.4 | It should support an "All" or "Only mine" filter to show only clients assigned to the current user | Integration | [ ] | | 4.4 | It should support an "All" or "Only mine" filter to show only clients assigned to the current user | Integration | [x] |
| 4.5 | It should trigger HTMX requests with 500ms debounce on filter change and 1000ms debounce on keyup | Integration | [ ] | | 4.5 | It should trigger HTMX requests with 500ms debounce on filter change and 1000ms debounce on keyup | Integration | [x] |
### Sorting Behaviors ### Sorting Behaviors
| # | Behavior | Test Strategy | Status | | # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------| |---|----------|---------------|--------|
| 5.1 | It should sort clients by name ascending/descending | Integration | [ ] | | 5.1 | It should sort clients by name ascending/descending | Integration | [x] |
| 5.2 | It should sort clients by code ascending/descending | Integration | [ ] | | 5.2 | It should sort clients by code ascending/descending | Integration | [x] |
| 5.3 | It should paginate results with 25 clients per page by default | Integration | [ ] | | 5.3 | It should paginate results with 25 clients per page by default | Integration | [x] |
### Client Wizard Behaviors ### Client Wizard Behaviors
@@ -113,15 +113,15 @@ Every admin operation checks:
| 6.9 | It should allow adding cash accounts with nickname, code, financial code, start date, include-in-reports, and visible-for-payment fields | UI | [ ] | | 6.9 | It should allow adding cash accounts with nickname, code, financial code, start date, include-in-reports, and visible-for-payment fields | UI | [ ] |
| 6.10 | It should allow adding credit card accounts with bank name, account number, and Plaid/Yodlee/Intuit integration selectors | UI | [ ] | | 6.10 | It should allow adding credit card accounts with bank name, account number, and Plaid/Yodlee/Intuit integration selectors | UI | [ ] |
| 6.11 | It should allow adding checking accounts with routing number, bank code, and check number fields | UI | [ ] | | 6.11 | It should allow adding checking accounts with routing number, bank code, and check number fields | UI | [ ] |
| 6.12 | It should require a financial code when "Include in Reports" is enabled for a bank account | Unit + Integration | [ ] | | 6.12 | It should require a financial code when "Include in Reports" is enabled for a bank account | Unit + Integration | [x] |
| 6.13 | It should allow entering a Square auth token and mapping Square locations to client locations on the Integrations step | UI | [ ] | | 6.13 | It should allow entering a Square auth token and mapping Square locations to client locations on the Integrations step | UI | [ ] |
| 6.14 | It should show "No locations found" when the Square location refresh times out after 2 seconds | Integration | [ ] | | 6.14 | It should show "No locations found" when the Square location refresh times out after 2 seconds | Integration | [ ] |
| 6.15 | It should allow entering Week A/B credits and debits on the Cash Flow step | UI | [ ] | | 6.15 | It should allow entering Week A/B credits and debits on the Cash Flow step | UI | [ ] |
| 6.16 | It should allow selecting feature flags and entering groups on the Other Settings step | UI | [ ] | | 6.16 | It should allow selecting feature flags and entering groups on the Other Settings step | UI | [ ] |
| 6.17 | It should validate that the client code is unique when creating a new client | Unit + Integration | [ ] | | 6.17 | It should validate that the client code is unique when creating a new client | Unit + Integration | [x] |
| 6.18 | It should upper-case group values on save | Unit | [ ] | | 6.18 | It should upper-case group values on save | Unit | [x] |
| 6.19 | It should flash the updated row in the grid and close the modal after a successful save | UI | [ ] | | 6.19 | It should flash the updated row in the grid and close the modal after a successful save | UI | [ ] |
| 6.20 | It should reindex the client in Solr after a successful save | Integration | [ ] | | 6.20 | It should reindex the client in Solr after a successful save | Integration | [x] |
### Biweekly Sales PowerQuery Behaviors ### Biweekly Sales PowerQuery Behaviors
@@ -147,30 +147,30 @@ Every admin operation checks:
| # | Behavior | Test Strategy | Status | | # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------| |---|----------|---------------|--------|
| 9.1 | It should filter accounts by name using case-insensitive substring match on upper-cased name | Integration | [ ] | | 9.1 | It should filter accounts by name using case-insensitive substring match on upper-cased name | Integration | [x] |
| 9.2 | It should filter accounts by code using exact numeric match | Integration | [ ] | | 9.2 | It should filter accounts by code using exact numeric match | Integration | [x] |
| 9.3 | It should filter accounts by type: All, Dividend, Asset, Equity, Liability, Expense, Revenue, or None | Integration | [ ] | | 9.3 | It should filter accounts by type: All, Dividend, Asset, Equity, Liability, Expense, Revenue, or None | Integration | [x] |
### Sorting Behaviors ### Sorting Behaviors
| # | Behavior | Test Strategy | Status | | # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------| |---|----------|---------------|--------|
| 10.1 | It should sort accounts by code, name, type, or location ascending/descending | Integration | [ ] | | 10.1 | It should sort accounts by code, name, type, or location ascending/descending | Integration | [x] |
| 10.2 | It should default sort by upper-cased numeric code | Integration | [ ] | | 10.2 | It should default sort by upper-cased numeric code | Integration | [x] |
### Account Dialog Behaviors ### Account Dialog Behaviors
| # | Behavior | Test Strategy | Status | | # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------| |---|----------|---------------|--------|
| 11.1 | It should show a modal dialog with a live-updating header displaying the numeric code and name | UI | [ ] | | 11.1 | It should show a modal dialog with a live-updating header displaying the numeric code and name | UI | [ ] |
| 11.2 | It should require a numeric code when creating a new account | Integration | [ ] | | 11.2 | It should require a numeric code when creating a new account | Integration | [x] |
| 11.3 | It should hide the numeric code field when editing an existing account | UI | [ ] | | 11.3 | It should hide the numeric code field when editing an existing account | UI | [ ] |
| 11.4 | It should require a name and account type | Integration | [ ] | | 11.4 | It should require a name and account type | Integration | [x] |
| 11.5 | It should allow setting Invoice Allowance, Vendor Allowance, and Applicability as dropdown enums | UI | [ ] | | 11.5 | It should allow setting Invoice Allowance, Vendor Allowance, and Applicability as dropdown enums | UI | [ ] |
| 11.6 | It should show a Client Overrides grid with client typeahead and override name | UI | [ ] | | 11.6 | It should show a Client Overrides grid with client typeahead and override name | UI | [ ] |
| 11.7 | It should validate that no client appears more than once in the Client Overrides grid | Unit + Integration | [ ] | | 11.7 | It should validate that no client appears more than once in the Client Overrides grid | Unit + Integration | [x] |
| 11.8 | It should validate that the numeric code is unique when creating a new account | Unit + Integration | [ ] | | 11.8 | It should validate that the numeric code is unique when creating a new account | Unit + Integration | [x] |
| 11.9 | It should reindex the account and all client overrides in Solr after a successful save | Integration | [ ] | | 11.9 | It should reindex the account and all client overrides in Solr after a successful save | Integration | [x] |
--- ---
@@ -192,33 +192,33 @@ Every admin operation checks:
| # | Behavior | Test Strategy | Status | | # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------| |---|----------|---------------|--------|
| 13.1 | It should filter vendors by name using case-insensitive substring match on upper-cased name | Integration | [ ] | | 13.1 | It should filter vendors by name using case-insensitive substring match on upper-cased name | Integration | [x] |
| 13.2 | It should filter vendors by visibility: All, Only hidden, or Only global | Integration | [ ] | | 13.2 | It should filter vendors by visibility: All, Only hidden, or Only global | Integration | [x] |
### Vendor Wizard Behaviors ### Vendor Wizard Behaviors
| # | Behavior | Test Strategy | Status | | # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------| |---|----------|---------------|--------|
| 14.1 | It should show a multi-step wizard with steps: Info, Terms, Account, Address, Legal | UI | [ ] | | 14.1 | It should show a multi-step wizard with steps: Info, Terms, Account, Address, Legal | UI | [ ] |
| 14.2 | It should require a name of at least 3 characters on the Info step | Unit + Integration | [ ] | | 14.2 | It should require a name of at least 3 characters on the Info step | Unit + Integration | [x] |
| 14.3 | It should allow toggling a "Print As" alias on the Info step | UI | [ ] | | 14.3 | It should allow toggling a "Print As" alias on the Info step | UI | [ ] |
| 14.4 | It should show a "Hidden" checkbox on the Info step visible only to admins | UI | [ ] | | 14.4 | It should show a "Hidden" checkbox on the Info step visible only to admins | UI | [ ] |
| 14.5 | It should allow setting terms in days and a grid of client-specific terms overrides on the Terms step | UI | [ ] | | 14.5 | It should allow setting terms in days and a grid of client-specific terms overrides on the Terms step | UI | [ ] |
| 14.6 | It should allow configuring a list of clients for automatically paid when due on the Terms step | UI | [ ] | | 14.6 | It should allow configuring a list of clients for automatically paid when due on the Terms step | UI | [ ] |
| 14.7 | It should allow selecting a default account via typeahead on the Account step | UI | [ ] | | 14.7 | It should allow selecting a default account via typeahead on the Account step | UI | [ ] |
| 14.8 | It should show an Account Overrides grid where account typeahead is scoped by selected client | Integration | [ ] | | 14.8 | It should show an Account Overrides grid where account typeahead is scoped by selected client | Integration | [x] |
| 14.9 | It should allow entering address fields with a 2-character state and 5-character zip on the Address step | UI | [ ] | | 14.9 | It should allow entering address fields with a 2-character state and 5-character zip on the Address step | UI | [ ] |
| 14.10 | It should allow entering a legal entity name OR first/middle/last name, TIN, TIN type, and 1099 type on the Legal step | UI | [ ] | | 14.10 | It should allow entering a legal entity name OR first/middle/last name, TIN, TIN type, and 1099 type on the Legal step | UI | [ ] |
| 14.11 | It should validate that terms override clients are unique with no duplicates | Unit + Integration | [ ] | | 14.11 | It should validate that terms override clients are unique with no duplicates | Unit + Integration | [x] |
| 14.12 | It should reindex the vendor name and hidden flag in Solr after a successful save | Integration | [ ] | | 14.12 | It should reindex the vendor name and hidden flag in Solr after a successful save | Integration | [x] |
### Vendor Merge Behaviors ### Vendor Merge Behaviors
| # | Behavior | Test Strategy | Status | | # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------| |---|----------|---------------|--------|
| 15.1 | It should open a modal with Source Vendor and Target Vendor selectors | UI | [ ] | | 15.1 | It should open a modal with Source Vendor and Target Vendor selectors | UI | [ ] |
| 15.2 | It should validate that the source and target vendors are different | Unit + Integration | [ ] | | 15.2 | It should validate that the source and target vendors are different | Unit + Integration | [x] |
| 15.3 | It should retract all references to the source vendor and assert them as the target vendor on merge | Integration | [ ] | | 15.3 | It should retract all references to the source vendor and assert them as the target vendor on merge | Integration | [x] |
| 15.4 | It should show a success notification after a successful merge | UI | [ ] | | 15.4 | It should show a success notification after a successful merge | UI | [ ] |
--- ---
@@ -239,25 +239,25 @@ Every admin operation checks:
| # | Behavior | Test Strategy | Status | | # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------| |---|----------|---------------|--------|
| 17.1 | It should filter rules by vendor using an entity typeahead | Integration | [ ] | | 17.1 | It should filter rules by vendor using an entity typeahead | Integration | [x] |
| 17.2 | It should filter rules by note using case-insensitive regex match | Integration | [ ] | | 17.2 | It should filter rules by note using case-insensitive regex match | Integration | [x] |
| 17.3 | It should filter rules by description using case-insensitive substring match | Integration | [ ] | | 17.3 | It should filter rules by description using case-insensitive substring match | Integration | [x] |
| 17.4 | It should filter rules by client group using exact upper-cased match | Integration | [ ] | | 17.4 | It should filter rules by client group using exact upper-cased match | Integration | [x] |
### Transaction Rule Wizard Behaviors ### Transaction Rule Wizard Behaviors
| # | Behavior | Test Strategy | Status | | # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------| |---|----------|---------------|--------|
| 18.1 | It should show a two-step wizard: Edit then Test | UI | [ ] | | 18.1 | It should show a two-step wizard: Edit then Test | UI | [ ] |
| 18.2 | It should require a description regex pattern of at least 3 characters on the Edit step | Unit + Integration | [ ] | | 18.2 | It should require a description regex pattern of at least 3 characters on the Edit step | Unit + Integration | [x] |
| 18.3 | It should allow toggling optional filters for Client, Client Group, Bank Account, Amount range, and Day of Month range | UI | [ ] | | 18.3 | It should allow toggling optional filters for Client, Client Group, Bank Account, Amount range, and Day of Month range | UI | [ ] |
| 18.4 | It should scope the bank account selector to the selected client | Integration | [ ] | | 18.4 | It should scope the bank account selector to the selected client | Integration | [x] |
| 18.5 | It should allow assigning a vendor, configuring account grids, and setting approval status as outcomes | UI | [ ] | | 18.5 | It should allow assigning a vendor, configuring account grids, and setting approval status as outcomes | UI | [ ] |
| 18.6 | It should derive account location from the account's fixed location, client locations, or "Shared" | Unit | [ ] | | 18.6 | It should derive account location from the account's fixed location, client locations, or "Shared" | Unit | [x] |
| 18.7 | It should validate that account percentages sum to exactly 100% | Unit + Integration | [ ] | | 18.7 | It should validate that account percentages sum to exactly 100% | Unit + Integration | [x] |
| 18.8 | It should validate that the selected bank account belongs to the selected client | Unit + Integration | [ ] | | 18.8 | It should validate that the selected bank account belongs to the selected client | Unit + Integration | [x] |
| 18.9 | It should validate that the rule location matches the account's fixed location when one is set | Unit + Integration | [ ] | | 18.9 | It should validate that the rule location matches the account's fixed location when one is set | Unit + Integration | [x] |
| 18.10 | It should show up to 15 matching transactions on the Test step with client, bank, date, and description | Integration | [ ] | | 18.10 | It should show up to 15 matching transactions on the Test step with client, bank, date, and description | Integration | [x] |
| 18.11 | It should display a badge showing the total match count with "99+" when 99 or more transactions match | UI | [ ] | | 18.11 | It should display a badge showing the total match count with "99+" when 99 or more transactions match | UI | [ ] |
### Rule Execution Behaviors ### Rule Execution Behaviors
@@ -265,10 +265,10 @@ Every admin operation checks:
| # | Behavior | Test Strategy | Status | | # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------| |---|----------|---------------|--------|
| 19.1 | It should open a dialog with checkbox-selectable transactions that match the rule and are unapproved | UI | [ ] | | 19.1 | It should open a dialog with checkbox-selectable transactions that match the rule and are unapproved | UI | [ ] |
| 19.2 | It should include only transactions on or after the client's locked-until date | Integration | [ ] | | 19.2 | It should include only transactions on or after the client's locked-until date | Integration | [x] |
| 19.3 | It should allow selecting all matching transactions or individual transactions | UI | [ ] | | 19.3 | It should allow selecting all matching transactions or individual transactions | UI | [ ] |
| 19.4 | It should apply rule coding to each selected transaction | Integration | [ ] | | 19.4 | It should apply rule coding to each selected transaction | Integration | [x] |
| 19.5 | It should update the Solr index after rule execution | Integration | [ ] | | 19.5 | It should update the Solr index after rule execution | Integration | [x] |
| 19.6 | It should show a notification reading "Successfully coded X of Y transactions!" after execution | UI | [ ] | | 19.6 | It should show a notification reading "Successfully coded X of Y transactions!" after execution | UI | [ ] |
### Rule Deletion Behaviors ### Rule Deletion Behaviors
@@ -276,7 +276,7 @@ Every admin operation checks:
| # | Behavior | Test Strategy | Status | | # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------| |---|----------|---------------|--------|
| 20.1 | It should show a confirmation dialog before deleting a rule | UI | [ ] | | 20.1 | It should show a confirmation dialog before deleting a rule | UI | [ ] |
| 20.2 | It should retract the rule entity from the database on confirmation | Integration | [ ] | | 20.2 | It should retract the rule entity from the database on confirmation | Integration | [x] |
| 20.3 | It should fade out the row with a "live-removed" animation after deletion | UI | [ ] | | 20.3 | It should fade out the row with a "live-removed" animation after deletion | UI | [ ] |
--- ---

View File

@@ -41,7 +41,7 @@
(into [:map {} (into [:map {}
[:name {:optional true :default nil} [:maybe [:string {:string/decode strip}]]] [:name {:optional true :default nil} [:maybe [:string {:string/decode strip}]]]
#_[:role {:optional true} [:maybe (ref->enum-schema "user-role")]] #_[:role {:optional true} [:maybe (ref->enum-schema "user-role")]]
#_[:client {:optional true :default nil} [:maybe [:entity-map {:pull [:db/id :client/name]}]]] ] #_[:client {:optional true :default nil} [:maybe [:entity-map {:pull [:db/id :client/name]}]]]]
default-grid-fields-schema)])) default-grid-fields-schema)]))
(defn filters [request] (defn filters [request]
[:form {"hx-trigger" "change delay:500ms, keyup changed from:.hot-filter delay:1000ms" [:form {"hx-trigger" "change delay:500ms, keyup changed from:.hot-filter delay:1000ms"
@@ -203,8 +203,6 @@
(def row* (partial helper/row* grid-page)) (def row* (partial helper/row* grid-page))
(def table* (partial helper/table* grid-page)) (def table* (partial helper/table* grid-page))
(defn merge-submit [{:keys [form-params request-method identity] :as request}] (defn merge-submit [{:keys [form-params request-method identity] :as request}]
(if (= (:source-vendor form-params) (if (= (:source-vendor form-params)
(:target-vendor form-params)) (:target-vendor form-params))
@@ -245,7 +243,6 @@
(= i (dec (count steps))) (assoc :last? true)) (= i (dec (count steps))) (assoc :last? true))
n))))) n)))))
;; TODO add plaid merchant ;; TODO add plaid merchant
;; TODO each client only used once ;; TODO each client only used once
@@ -285,7 +282,6 @@
(com/data-grid-cell {:class "align-top"} (com/data-grid-cell {:class "align-top"}
(com/a-icon-button {"@click.prevent.stop" "show=false; setTimeout(() => $refs.p.remove(), 500)"} svg/x))))) (com/a-icon-button {"@click.prevent.stop" "show=false; setTimeout(() => $refs.p.remove(), 500)"} svg/x)))))
(defn automatically-paid-when-due-row [terms-override-cursor] (defn automatically-paid-when-due-row [terms-override-cursor]
(com/data-grid-row (com/data-grid-row
(-> {:x-data (hx/json {:show (boolean (not (fc/field-value (:new? terms-override-cursor))))}) (-> {:x-data (hx/json {:show (boolean (not (fc/field-value (:new? terms-override-cursor))))})
@@ -303,15 +299,12 @@
:value (fc/field-value) :value (fc/field-value)
:value-fn :db/id :value-fn :db/id
:content-fn #(pull-attr (dc/db conn) :client/name (:db/id %)) :content-fn #(pull-attr (dc/db conn) :client/name (:db/id %))
:size :small}))) :size :small})))
(com/data-grid-cell {:class "align-top"} (com/data-grid-cell {:class "align-top"}
(com/a-icon-button {"@click.prevent.stop" "show=false; setTimeout(() => $refs.p.remove(), 500)"} svg/x))))) (com/a-icon-button {"@click.prevent.stop" "show=false; setTimeout(() => $refs.p.remove(), 500)"} svg/x)))))
(defn- account-typeahead* (defn- account-typeahead*
[{:keys [name value client-id x-model]}] [{:keys [name value client-id x-model]}]
[:div.flex.flex-col [:div.flex.flex-col
@@ -370,12 +363,6 @@
(com/data-grid-cell {:class "align-top"} (com/data-grid-cell {:class "align-top"}
(com/a-icon-button {"@click.prevent.stop" "show=false; setTimeout(() => $refs.p.remove(), 500)"} svg/x)))))) (com/a-icon-button {"@click.prevent.stop" "show=false; setTimeout(() => $refs.p.remove(), 500)"} svg/x))))))
(defn dialog* [{:keys [entity form-params form-errors] :as params}] (defn dialog* [{:keys [entity form-params form-errors] :as params}]
(alog/peek ::dialog-entity form-params) (alog/peek ::dialog-entity form-params)
(fc/start-form form-params form-errors (fc/start-form form-params form-errors
@@ -847,6 +834,21 @@
mt/strip-extra-keys-transformer) mt/strip-extra-keys-transformer)
entity (cond-> snapshot entity (cond-> snapshot
(= :post request-method) (assoc :db/id "new")) (= :post request-method) (assoc :db/id "new"))
_ (some->> snapshot
:vendor/terms-overrides
(group-by :vendor-terms-override/client)
(filter (fn [[_ overrides]]
(> (count overrides) 1)))
(map first)
seq
(#(form-validation-error (format "Client(s) %s have more than one terms override."
(str/join ", "
(map (fn [client]
(format "'%s'" (pull-attr (dc/db conn)
:client/name
(-> client))))
%)))
:form-params (:snapshot multi-form-state))))
{:keys [tempids]} (audit-transact [[:upsert-entity entity]] {:keys [tempids]} (audit-transact [[:upsert-entity entity]]
(:identity request)) (:identity request))
updated-vendor (dc/pull (dc/db conn) updated-vendor (dc/pull (dc/db conn)
@@ -868,7 +870,6 @@
(def vendor-wizard (->VendorWizard :info)) (def vendor-wizard (->VendorWizard :info))
(def key->handler (def key->handler
(apply-middleware-to-all-handlers (apply-middleware-to-all-handlers
(->> (->>
@@ -921,7 +922,7 @@
(fn [cursor _] (account-override-row cursor)))}) (fn [cursor _] (account-override-row cursor)))})
(fn [h] (fn [h]
(-> h (-> h
(wrap-copy-qp-pqp) (wrap-copy-qp-pqp)
(wrap-apply-sort grid-page) (wrap-apply-sort grid-page)
(wrap-merge-prior-hx) (wrap-merge-prior-hx)
(wrap-schema-enforce :query-schema query-schema) (wrap-schema-enforce :query-schema query-schema)

File diff suppressed because it is too large Load Diff