toga-ai 1.0.349 → 1.0.351

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,6 +9,7 @@
9
9
  | [Nested-relationship writes & child matching (link vs. create)](features/nested-relationship-writes.md) | When a 2.0 API write payload (`POST`/`PUT`) contains a **nested related object** (e.g. | api2/Component/Api/V2/V2.php |
10
10
  | [POST + JSON-body args for scripted APIs](features/scripted-api-post-body-args.md) | The V2 engine can run a Record Script (scripted API) for a **POST** request, and a scripted API can receive its arguments from the **JSON request body** instead | api2/Component/Api/V2/V2.php |
11
11
  | [Surface action-state via the surface=<slug> request option (M2M-safe)](features/surface-meta-option.md) | An opt-in V2 engine request option, `surface=<slug>`, that attaches per-record UI action state (`isVisible`/`isEnabled`) to a GET response **under `meta.surface | api2/Component/Api/V2/V2.php, _underscore/Model/Core/Surface.php |
12
+ | [TableView row-filtering via apiWhereClause (options.where grammar, end to end)](features/tableview-apiwhereclause-row-filtering.md) | `TableViews.apiWhereClause` (TEXT, nullable) is the sanctioned, code-free way to restrict or exclude rows from a 2.0 table view. | api2/Component/Api/V2/V2.php, _underscore/Model/Client/TableView.php, toga2-supply/src/api/toga.ts |
12
13
  | [Tickets API (/v2/tickets)](features/tickets-api.md) | The generic ticket endpoint of the 2.0 REST API. | Component/Api/V2/V2.php |
13
14
  | [AWS CodePipeline Deployment via CodeConnections (GitHub → Elastic Beanstalk)](workflows/codepipeline-codeconnections-deploy.md) | 2.0 apps (`api2`, `_underscore`) are deployed through **AWS CodePipeline**. | |
14
15
  | [New Environment Configuration & Provisioning (api2)](workflows/environment-configuration-and-provisioning.md) | What it takes for a 2.0 API environment (e.g. | api2/Config/<environment>.ini, api2/Controller/Index.php, dbchanges2/Core/2026-06-16a - DatabaseHosts for new QA QC stage demo environments.sql, _underscore/Route.php |
@@ -0,0 +1,115 @@
1
+ ---
2
+ title: TableView row-filtering via apiWhereClause (options.where grammar, end to end)
3
+ framework: "2.0"
4
+ repo: api2
5
+ project: API
6
+ client: shared
7
+ type: feature
8
+ status: active
9
+ updated: 2026-07-15
10
+ owners: ["bala"]
11
+ files:
12
+ - api2/Component/Api/V2/V2.php
13
+ - _underscore/Model/Client/TableView.php
14
+ - toga2-supply/src/api/toga.ts
15
+ related:
16
+ - ../../../clients/compass-usa/features/item-fulfillment-tracking-tableview.md
17
+ ---
18
+
19
+ ## Summary
20
+
21
+ `TableViews.apiWhereClause` (TEXT, nullable) is the sanctioned, code-free way to restrict or
22
+ exclude rows from a 2.0 table view. The clause is stored on the view, emitted verbatim in the
23
+ view meta by `_underscore`, pushed into the data request's `options.where.and` by the frontend,
24
+ and finally parsed into SQL by api2's `parseOptionsWhere`. Because api2 parses it for *every*
25
+ data request, the same `field:operator:value` grammar governs any `options.where` filter — table
26
+ views are just one producer of it. Use this to add a "restrict/exclude these rows" rule to a
27
+ view with a migration and no application code.
28
+
29
+ ## Key files / entry points
30
+
31
+ - **Emission** — `_underscore/Model/Client/TableView.php`, the `meta()` scripted API (~line 51):
32
+ emits `TableViews.apiWhereClause` verbatim in the view meta, and builds `join`/`ojoin` entries
33
+ from `TableViewJoins` (~line 631-641 of the consumer) so every table joined in the view is
34
+ present on the data request.
35
+ - **Consumption** — `toga2-supply/src/api/toga.ts` (~line 943-951): pushes the `apiWhereClause`
36
+ literal string into `options.where.and`, and (~line 631-641) builds `options.join` / `ojoin`
37
+ from the view's `TableViewJoins`.
38
+ - **Parsing** — `api2/Component/Api/V2/V2.php::parseOptionsWhere` (~line 7574-7773): turns the
39
+ clause string into SQL.
40
+
41
+ ## How it works
42
+
43
+ Clause syntax: `field:operator:value`. Conditions are comma-separated, grouped with parens, and
44
+ joined with `,AND,` / `,OR,` between conditions. The parser tracks parenthesis depth and only
45
+ splits conditions on commas at the **top** depth.
46
+
47
+ Operator tokens map to SQL as:
48
+
49
+ | token | SQL |
50
+ |-------|-----|
51
+ | `eq` | `=` |
52
+ | `ne` | `<>` |
53
+ | `gt` | `>` |
54
+ | `ge` | `>=` |
55
+ | `lt` | `<` |
56
+ | `le` | `<=` |
57
+ | `like` | `LIKE` |
58
+ | `contains` / `starts` / `ends` | wildcarded `LIKE` |
59
+ | `excludes` | `NOT LIKE` |
60
+ | `in` | `IN` |
61
+ | `notin` | `NOT IN` |
62
+ | `between` | `BETWEEN` |
63
+ | `not` | `NOT` |
64
+
65
+ The field slot accepts a **raw SQL expression**, not just a plain column — e.g.
66
+ `YEAR(SalesOrders.dateOrder)` or `IFNULL(Items.assetTypeId,0)`. Real-world example (the FEE
67
+ exclusion on the Compass fulfillment view): `(IFNULL(Items.assetTypeId,0):ne:<FEE id>)` — the
68
+ `IFNULL(...,0)` keeps rows with a NULL FK from being silently dropped.
69
+
70
+ ### Join alias rule (which table-qualified name to use)
71
+
72
+ In `_underscore/Model/Client/TableView.php` (~line 89-96), the **first** occurrence of a joined
73
+ table in a view gets the bare table name as its SQL alias (e.g. `Items`); each subsequent join to
74
+ the same table gets a `_B`, `_C`, … suffix (counter starts at ASCII 65). So an `apiWhereClause`
75
+ referencing `Items.<field>` only binds correctly when the view joins `Items` **exactly once**.
76
+ Before writing a clause, confirm the target table is joined once, or qualify against the correct
77
+ suffixed alias.
78
+
79
+ ## Data model
80
+
81
+ - `TableViews.apiWhereClause` (TEXT, nullable) — the view-level row filter.
82
+ - `TableViewJoins` — the view's joins; drive `options.join` / `ojoin` so any table referenced in
83
+ the clause is present on the data request.
84
+
85
+ ## Client variations
86
+
87
+ None — engine behavior. A specific client's view may carry its own `apiWhereClause` value.
88
+
89
+ ## Gotchas / known issues
90
+
91
+ - **Comma inside a function is safe.** The parser only splits conditions on top-depth commas, so
92
+ a comma inside `IFNULL(x,0)` or `IN(...)` does not break the clause.
93
+ - **An expression field must NOT start with `(`.** A condition that starts with `(` is treated by
94
+ the parser as a nested group and recursed into — so wrap an expression field in a function that
95
+ starts with a letter (e.g. `IFNULL(...)`), never a bare parenthesized expression.
96
+ - **NULL FKs drop silently.** A plain `field:ne:x` excludes rows where `field IS NULL` too. Wrap
97
+ nullable columns in `IFNULL(col,<sentinel>)` when the intent is "exclude only these values."
98
+ - **Operator allowlist.** `parseOptionsWhere` whitelists the operators above and rejects unknown
99
+ ones (e.g. `regexp` returns a 500) — see the Compass cost-centers doc. Numeric/regex filtering
100
+ that the grammar can't express must be done client-side or by adding an operator to api2.
101
+
102
+ ## Change history
103
+
104
+ - 2026-07-15 — Documented the end-to-end `apiWhereClause` row-filter mechanism (emission →
105
+ consumption → `parseOptionsWhere` grammar), the operator→SQL table, the raw-expression field
106
+ slot, the leading-paren / top-depth-comma parser behavior, and the `_underscore` join-alias
107
+ (`_B`/`_C`) rule. No code change — mechanism discovered while building the Compass FEE-exclusion
108
+ migration. (bala)
109
+
110
+ ## Related docs
111
+
112
+ - `clients/compass-usa/features/item-fulfillment-tracking-tableview.md` — the Compass fulfillment
113
+ view (id 12) that first used this to exclude FEE-asset-type items.
114
+ - `clients/compass-usa/features/cost-centers.md` — notes `parseOptionsWhere` rejecting `regexp`
115
+ with a 500 (operator allowlist).
@@ -3,5 +3,5 @@
3
3
  | Doc | Summary | Files |
4
4
  |-----|---------|-------|
5
5
  | [TOGa Supply (toga2-supply) Architecture](architecture.md) | `toga2-supply` is the **React + Vite frontend** for TOGa Supply — warehouse fulfillment tooling (shipment selection, fulfill & ship against carrier APIs, NetSui | toga2-supply/src/api/toga.ts, toga2-supply/src/pages/ShipmentItems/view/ShipmentItemsPage.tsx, toga2-supply/src/pages/EditShipment/view/EditShipmentPage.tsx, toga2-supply/src/pages/EditShipment/api/UpdateShipmentApi.ts, toga2-supply/src/pages/Shipments/view/components/ShipmentsCardTableForm/ShipmentsCardTableForm.tsx |
6
- | [Fulfill & Ship](features/fulfill-and-ship.md) | Fulfill & Ship lets a warehouse user select sales-order line items, enter serials, pick a carrier/method, and in one action: create the Item Fulfillment records | toga2-supply/src/pages/ShipmentItems/view/ShipmentItemsPage.tsx, toga2-supply/src/pages/EditShipment/view/EditShipmentPage.tsx, toga2-supply/src/pages/EditShipment/view/components/forms/EditShipmentForm.tsx, toga2-supply/src/pages/EditShipment/view/components/SelectedShipmentItemsTable.tsx, toga2-supply/src/pages/EditShipment/view/helpers/ShipmentDetailsForm/renderEditShipmentFormInput.tsx, toga2-supply/src/pages/EditShipment/viewModel/FIELDS/DUMMYUPDATESHIPMENTFIELDS.json, toga2-supply/src/pages/EditShipment/helpers/ShipmentDetailsForm/renderEditShipmentFormInput.tsx, toga2-supply/tailwind.config.cjs, toga2-supply/src/pages/EditShipment/view/modals/ReturnShippingModal.tsx, toga2-supply/src/components/ui/BaseInput/BaseInput.tsx, toga2-supply/src/pages/EditShipment/api/UpdateShipmentApi.ts, toga2-supply/src/pages/EditShipment/helpers/ShipmentDetailsForm/formatShipmentData.ts, toga2-supply/src/pages/EditShipment/types.ts, toga2-supply/src/pages/Shipments/view/ShipmentsPage.tsx, toga2-supply/src/pages/Shipments/view/components/ShipmentsCardTableForm/ShipmentsCardTableForm.tsx, toga2-supply/src/pages/Shipments/api/ShipmentsApi.ts, toga2-supply/src/pages/Shipments/types.ts, toga2-supply/src/pages/FulfilledShipments/view/FulfilledShipmentsPage.tsx, toga2-supply/src/components/ui/CardTable/CardTable.tsx, toga2-supply/src/components/ui/CardTable/types.ts, toga2-supply/src/assets/pen-line.svg, _underscore/Model/Client/ItemFulfillment.php, _underscore/Trait/Netsuite/ItemFulfillment.php, _underscore/Component/Library/Carriers/Ups/Ups.php |
6
+ | [Fulfill & Ship](features/fulfill-and-ship.md) | Fulfill & Ship lets a warehouse user select sales-order line items, enter serials, pick a carrier/method, and in one action: create the Item Fulfillment records | toga2-supply/src/pages/ShipmentItems/view/ShipmentItemsPage.tsx, toga2-supply/src/pages/EditShipment/view/EditShipmentPage.tsx, toga2-supply/src/pages/EditShipment/view/components/forms/EditShipmentForm.tsx, toga2-supply/src/pages/EditShipment/view/components/SelectedShipmentItemsTable.tsx, toga2-supply/src/pages/EditShipment/view/helpers/ShipmentDetailsForm/renderEditShipmentFormInput.tsx, toga2-supply/src/pages/EditShipment/viewModel/FIELDS/DUMMYUPDATESHIPMENTFIELDS.json, toga2-supply/src/pages/EditShipment/helpers/ShipmentDetailsForm/renderEditShipmentFormInput.tsx, toga2-supply/tailwind.config.cjs, toga2-supply/src/pages/EditShipment/view/modals/ReturnShippingModal.tsx, toga2-supply/src/styles/index.scss, toga2-supply/src/components/ui/BaseInput/BaseInput.tsx, toga2-supply/src/pages/EditShipment/api/UpdateShipmentApi.ts, toga2-supply/src/pages/EditShipment/helpers/ShipmentDetailsForm/formatShipmentData.ts, toga2-supply/src/pages/EditShipment/types.ts, toga2-supply/src/pages/Shipments/view/ShipmentsPage.tsx, toga2-supply/src/pages/Shipments/view/components/ShipmentsCardTableForm/ShipmentsCardTableForm.tsx, toga2-supply/src/pages/Shipments/api/ShipmentsApi.ts, toga2-supply/src/pages/Shipments/types.ts, toga2-supply/src/pages/FulfilledShipments/view/FulfilledShipmentsPage.tsx, toga2-supply/src/components/ui/CardTable/CardTable.tsx, toga2-supply/src/components/ui/CardTable/types.ts, toga2-supply/src/assets/pen-line.svg, _underscore/Model/Client/ItemFulfillment.php, _underscore/Trait/Netsuite/ItemFulfillment.php, _underscore/Component/Library/Carriers/Ups/Ups.php |
7
7
  | [AWS Amplify Build & Deploy (non-prod environments)](workflows/amplify-build-and-deploy.md) | How `toga2-supply` (React + Vite) builds and deploys on **AWS Amplify**. | toga2-supply/amplify.yml, toga2-supply/.gitattributes, toga2-supply/.github/workflows/sync-stage-environments.yml, toga2-supply/.env.qc-security |
@@ -18,6 +18,7 @@ files:
18
18
  - toga2-supply/src/pages/EditShipment/helpers/ShipmentDetailsForm/renderEditShipmentFormInput.tsx
19
19
  - toga2-supply/tailwind.config.cjs
20
20
  - toga2-supply/src/pages/EditShipment/view/modals/ReturnShippingModal.tsx
21
+ - toga2-supply/src/styles/index.scss
21
22
  - toga2-supply/src/components/ui/BaseInput/BaseInput.tsx
22
23
  - toga2-supply/src/pages/EditShipment/api/UpdateShipmentApi.ts
23
24
  - toga2-supply/src/pages/EditShipment/helpers/ShipmentDetailsForm/formatShipmentData.ts
@@ -187,6 +188,27 @@ bare string** — see the gotcha below before wiring a new select field.
187
188
  (`onBlur` when `field.isCurrency`). Return modal (`ReturnShippingModal.tsx`): compact selects
188
189
  vertically centered; Carrier Account # widened to `w-[172px]` to match Carrier.
189
190
 
191
+ ### Edit Return Label modal — searchable Addressee combobox + discard guard (2026-07-15)
192
+
193
+ `ReturnShippingModal.tsx`:
194
+ - **Addressee is a searchable combobox built on a native `<input list>` + `<datalist>`** of
195
+ warehouse/location options — typing filters the list (Chromium does a substring "contains" match;
196
+ Firefox only prefix-matches) and **custom free text is still allowed**; selecting a known option
197
+ prefills the address via `applyWarehouse`. Prefer this native pattern over react-select when free
198
+ text plus suggestions are both wanted — but mind the two gotchas below.
199
+ - **Backdrop-click discard guard.** `BaseModal` calls its `onClose` prop on backdrop click, which
200
+ would silently discard unsaved edits. Pass a **guarded** handler to `BaseModal`'s `onClose`
201
+ (`if (formMethods.formState.isDirty) return; onClose();`) so an accidental outside click can't
202
+ drop a dirty form, while **Cancel and the header X still call the real `onClose`** to close/discard
203
+ explicitly. Reuse this pattern for any BaseModal wrapping an editable form.
204
+
205
+ ### Shared checkbox restyled to the Figma design-system spec (2026-07-15)
206
+
207
+ `BaseInput.tsx`'s checkbox now matches the Figma checkbox component: **16px** (was 20px),
208
+ `rounded-sm`, unchecked border `navy-300`, checked fill `supply-blue-400` (`#3F5DCA`, the primary),
209
+ 12px check glyph. This is the **shared** checkbox, so the change applies **app-wide**, not just to
210
+ the shipment forms — audit other consumers when touching it.
211
+
190
212
  ## Planned direction — "Fulfill & Ship" from an existing Item Fulfillment (future, Eric)
191
213
 
192
214
  A **third entry mode** is planned (distinct from Select-Items-create and the pencil-edit): a
@@ -221,7 +243,27 @@ the call 403s and returns no label.
221
243
 
222
244
  ## Gotchas / known issues
223
245
 
224
- - **⚠ Tailwind does NOT scan the field-config JSON — a class used ONLY in `viewModel/FIELDS/*.json`
246
+ - **⚠ Native `<datalist>` renders its OWN dropdown arrow, un-hideable by Tailwind.** An
247
+ `<input list>` combobox (the Edit Return Label Addressee) shows a **second dark arrow** (the
248
+ webkit calendar-picker indicator) that a Tailwind arbitrary variant can't reliably hide. Kill it
249
+ with a **real global CSS rule** in the source stylesheet
250
+ `src/styles/index.scss` (imported by `main.tsx`):
251
+ `input[list]::-webkit-calendar-picker-indicator { display: none !important; }`.
252
+ **Edit `index.scss`, NOT `index.css`** — `index.css` is compiled output and will be overwritten.
253
+ Also set **`autoComplete="off"`** on the input or the browser's address-autofill popup
254
+ ("Saved data / Manage personal info") overlaps and fights the datalist.
255
+ - **⚠ A `className` template literal must keep the trailing space before `${...}`.** `border ${cond
256
+ ? a : b}` is correct; `border${cond ? a : b}` concatenates into an invalid single class
257
+ (e.g. `borderborder-navy-300`) and the utility silently no-ops. Concrete failure: the `BaseInput`
258
+ checkbox's unchecked border disappeared because the space was dropped. Watch this whenever
259
+ conditional classes are appended to a static base class.
260
+ - **⚠ `justify-between` + `clamp()` gaps are frozen when the container is a FIXED width.** The Update
261
+ Info field row uses `gap-x-[clamp(2px,1vw,50px)]` with `justify-between`, but the card was a fixed
262
+ `w-[1820px]`, so `justify-between` distributed columns across a constant width and the gaps never
263
+ changed on resize. Fix: make the card `w-full max-w-[1820px]` and the form section `w-full` so the
264
+ width tracks the viewport and the clamp'd gaps grow/shrink with it. Note `justify-between` does
265
+ **not** cap the max gap at 50px on very wide screens — the `clamp` only *floors* at 2px; the max
266
+ applies until columns are pushed to the edges. (`EditShipmentPage.tsx`.) — a class used ONLY in `viewModel/FIELDS/*.json`
225
267
  is silently inert.** The config-driven forms author Tailwind utility classes inside the field JSON
226
268
  (e.g. `DUMMYUPDATESHIPMENTFIELDS.json`), but `tailwind.config.cjs` `content` globs only scan
227
269
  `./src/**/*.{js,ts,jsx,tsx}` (+ `index.html` + toga-blox dist) — **not `.json`**. So a class used
@@ -417,6 +459,18 @@ not the base `_Model_Client_ItemFulfillment`. Tested with GroWrk; UPS support wa
417
459
  for Compass and is not yet in prod.
418
460
 
419
461
  ## Change history
462
+ - 2026-07-15 — TRUE-79191: made the Edit Return Label **Addressee a searchable combobox** (native
463
+ `<input list>` + `<datalist>`, filters + allows custom text, prefills via `applyWarehouse`); added a
464
+ **backdrop-click discard guard** (guarded `BaseModal onClose` on `formState.isDirty`); restyled the
465
+ **shared `BaseInput` checkbox** to the Figma spec (16px, navy-300 border, `supply-blue-400` fill,
466
+ app-wide). Documented four durable gotchas: native `<datalist>`'s un-hideable arrow (hide via a
467
+ real global rule in `src/styles/index.scss`, not the compiled `index.css`) + `autoComplete="off"`
468
+ to stop autofill overlap; the `className` **trailing-space-before-`${...}`** footgun (dropped space
469
+ → invalid concatenated class → silent no-op, killed the checkbox border); and `justify-between` +
470
+ `clamp()` **gaps frozen by a fixed-width card** (fix: `w-full max-w-[1820px]` so width tracks the
471
+ viewport). Remaining Figma polish (5-col address grid, Signature default object, declared-cost
472
+ 2-decimal rehydrate, Clear All wiring, residential alignment, footer/divider spacing) was cosmetic
473
+ — no doc change. (mhammontree)
420
474
  - 2026-07-15 — TRUE-79191: unified the **dropdown chevron glyph** across the compact selects — added a
421
475
  `CompactDropdownIndicator` (FA `chevronDown`) to `BaseInput` and wired it into both react-select
422
476
  `components` blocks **only when `compact` is true**, so the react-select caret matches the native
@@ -19,7 +19,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
19
19
 
20
20
  - **_underscore** (_Underscore) _(framework core)_ — 32 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
21
21
  - **worker2** (Worker) — 30 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
22
- - **api2** (API) — 10 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
22
+ - **api2** (API) — 11 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
23
23
  - **dbchanges2** (Database Changes) _(framework core)_ — 3 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
24
24
  - **toga2-supply** (TOGa Supply) — 3 doc(s) → [2.0/apps/toga2-supply/INDEX.md](2.0/apps/toga2-supply/INDEX.md)
25
25
  - **saml** (SAML SSO Gateway) — 3 doc(s) → [2.0/apps/saml/INDEX.md](2.0/apps/saml/INDEX.md)
@@ -4,7 +4,7 @@
4
4
  |-----|-----------|---------|-------|
5
5
  | [Compass ASN → ItemFulfillment Auto-Creation](features/asn-to-item-fulfillment.md) | 2.0 | For Compass USA, posting an AdvanceShippingNotice (ASN) auto-creates the ItemFulfillment (IF) on the upstream SalesOrder. | _underscore/Model/Compass/AdvanceShippingNotice.php, _underscore/Model/Compass/PurchaseOrder.php, api2/Component/Api/Cxml/Cxml.php, dbchanges2/Client_Compass/2026-06-11 - AsnItemTrackingNumberAcl.sql, dbchanges2/Client_Compass/2026-06-15b - BackfillSA132781ItemFulfillmentTracking.sql, dbchanges2/Client_Compass/2026-06-16 - CleanupSA132763CrossLineTracking.sql, dbchanges2/Client_Compass/2026-06-16b - CleanupSA132743CrossLineTracking.sql, dbchanges2/Client_Compass/2026-06-16c - BackfillSA132763C40QYUCTracking.sql, dbchanges2/Client_Compass/2026-06-18a - CleanupSA132898DuplicateTracking.sql, dbchanges2/Client_Compass/2026-06-18b - CleanupSA132881DuplicateTracking.sql, dbchanges2/Client_Compass/2026-07-02a - FixSA133377TrackingSerialAndDuplicateIF.sql |
6
6
  | [Cost Centers — Unit Locations, numeric-only policy](features/cost-centers.md) | 2.0 | A Compass "cost center" — the value a user picks in commerce and that lands on an order — is **not** a `CostCenters` row. | toga2-commerce/src/pages/Cart/api/CartApi.ts, worker1.5/crons/toga2/compass/import_locations.php, _underscore/Model/Compass/SalesOrder.php, api2/Component/Api/V2/V2.php, dbchanges2/Client_Compass/2026-07-06 - RemoveNonNumericCostCenters.sql |
7
- | [Compass: Item-Fulfillment TableViews (for-sales-order-items & for-sales-orders, tracking via bridge)](features/item-fulfillment-tracking-tableview.md) | 2.0 | Two sibling Compass TableViews in `Client_Compass` display fulfilled items in toga2-supply, both driven by `TableViews` / `TableViewJoins` / `TableViewFields` c | dbchanges2/Client_Compass/2026-06-10 - ItemFulfillmentsForSalesOrderItemsTableView.sql, dbchanges2/Client_Compass/2026-06-11 - ItemFulfillmentsForSalesOrdersTableView.sql, dbchanges2/Client_Compass/2026-06-15a - FixItemFulfillmentTrackingNumberJoins.sql |
7
+ | [Compass: Item-Fulfillment TableViews (for-sales-order-items & for-sales-orders, tracking via bridge)](features/item-fulfillment-tracking-tableview.md) | 2.0 | Two sibling Compass TableViews in `Client_Compass` display fulfilled items in toga2-supply, both driven by `TableViews` / `TableViewJoins` / `TableViewFields` c | dbchanges2/Client_Compass/2026-06-10 - ItemFulfillmentsForSalesOrderItemsTableView.sql, dbchanges2/Client_Compass/2026-06-11 - ItemFulfillmentsForSalesOrdersTableView.sql, dbchanges2/Client_Compass/2026-06-15a - FixItemFulfillmentTrackingNumberJoins.sql, dbchanges2/Client/2026-07-15a - ExcludeFeeItemsFromItemFulfillmentsForSalesOrdersView.sql |
8
8
  | [Compass MITS PO → SO Item Linking](features/mits-po-to-so-item-linking.md) | 2.0 | MITS sends Compass inbound Purchase Orders (`POST /v2/purchase-orders`) against a Sales Order (`mitsSalesOrder`). | _underscore/Model/Compass/PurchaseOrder.php, worker/crons/toga2/compass/workflow/3a_import_office_depot_purchase_orders.php |
9
9
  | [Compass MITS PO Transmission to Vendors](features/mits-po-transmission-to-vendors.md) | 2.0 | The 1.0 worker cron `2_transmit_mits_purchase_orders_to_vendors.php` transmits Compass PurchaseOrders to their vendors (Office Depot, Strategic Systems, Compass | worker/crons/toga2/compass/workflow/2_transmit_mits_purchase_orders_to_vendors.php, worker/crons/toga2/compasscanada/workflow/2_transmit_mits_purchase_orders_to_vendors.php, library/app/client/compass.php |
10
10
  | [Compass MR/MA Order Auto-Approval & Status Gate](features/mr-ma-order-approval-and-status.md) | 2.0 | Compass **MR** and **MA** sales orders are system-generated from the MITS / Office Depot EDI pipeline (they do not originate as user-entered SA orders) and must | _underscore/Model/Compass/SalesOrder.php, _underscore/Model/Compass/PurchaseOrder.php, worker/crons/toga2/compass/workflow/3a_import_office_depot_purchase_orders.php |
@@ -5,14 +5,16 @@ project: _Underscore
5
5
  client: compass-usa
6
6
  type: client-feature
7
7
  status: active
8
- updated: 2026-06-15
9
- owners: ["jcardinal"]
8
+ updated: 2026-07-15
9
+ owners: ["jcardinal", "bala"]
10
10
  files:
11
11
  - dbchanges2/Client_Compass/2026-06-10 - ItemFulfillmentsForSalesOrderItemsTableView.sql
12
12
  - dbchanges2/Client_Compass/2026-06-11 - ItemFulfillmentsForSalesOrdersTableView.sql
13
13
  - dbchanges2/Client_Compass/2026-06-15a - FixItemFulfillmentTrackingNumberJoins.sql
14
+ - dbchanges2/Client/2026-07-15a - ExcludeFeeItemsFromItemFulfillmentsForSalesOrdersView.sql
14
15
  related:
15
16
  - ../../../2.0/apps/_underscore/features/tracking-number-bridges.md
17
+ - ../../../2.0/apps/api2/features/tableview-apiwhereclause-row-filtering.md
16
18
  ---
17
19
 
18
20
  ## Summary
@@ -93,14 +95,30 @@ returnTrackingNumberId. Superseded unit-level keys: 2183 IFIU-bridge.itemFulfill
93
95
  - With null `joinOnTableViewJoinId`, the engine resolves a join's parent table by the `recordId` of
94
96
  `parentRecordFieldId` — this only works because each record appears once in the graph. Reusing a
95
97
  record twice in one view would require an explicit `joinOnTableViewJoinId`.
98
+ - **FEE-item exclusion (view 12) is filtered via `apiWhereClause`, and "asset type" is a nullable
99
+ FK — not a string.** `Items.assetTypeId` is a nullable FK to `AssetTypes(id, uuid, name)`; the
100
+ id for a given name (e.g. `FEE`) **differs per client** (Compass: `FEE` = id 3, 62 items). About
101
+ 30% of `Items` rows have a **NULL** `assetTypeId` (Compass: 227,685 of ~269,980 fulfilled-item
102
+ rows in this view), so any exclusion **must be NULL-safe** — a plain `assetTypeId:ne:<id>` would
103
+ silently drop every NULL row. The migration uses `(IFNULL(Items.assetTypeId,0):ne:<FEE id>)` with
104
+ the FEE id resolved per client DB via `(SELECT id FROM AssetTypes WHERE name='FEE' ...)`. See the
105
+ shared `apiWhereClause` mechanism doc. The `apiWhereClause` binds `Items.assetTypeId` correctly
106
+ only because view 12 joins `Items` exactly once (the join-alias rule).
107
+ - **The FEE-exclusion migration is team-wide, not Compass-only.** It lives in the `dbchanges2/Client/`
108
+ fan-out folder, so it runs against **every** client DB, guarded to fire only where the view exists,
109
+ a `FEE` asset type exists, and `Items` is joined exactly once. It composes idempotently onto any
110
+ existing `apiWhereClause` (a CASE skips if `%assetTypeId%` is already present). Verified read-only
111
+ on prod + dev-sandbox that it excludes only FEE rows and keeps all NULL-assetType rows.
96
112
 
97
113
  ## Client variations
98
114
  Compass-only. The underlying bridge model is shared (see the _underscore feature doc).
99
115
 
100
116
  ## Change history
117
+ - 2026-07-15 — Excluded FEE-asset-type items from view 12 (`item-fulfillments-for-sales-orders`) via a NULL-safe `apiWhereClause` `(IFNULL(Items.assetTypeId,0):ne:<FEE id>)`, FEE id resolved per client DB. Migration `2026-07-15a` in `dbchanges2/Client/` (fans out to all clients, idempotent, guarded). Verified read-only on prod + dev-sandbox. (bala)
101
118
  - 2026-06-15 — Re-pointed tracking from the unit-level bridge (319) to the item-level bridge `ItemFulfillmentItems_TrackingNumbers` (318) in views 12 & 13; fixes blank item-level tracking on non-serialized lines (e.g. SO MR243437). Migration `2026-06-15a`. (jcardinal)
102
119
  - 2026-06-11 — Re-rooted `item-fulfillments-for-sales-orders` (id 12) from Units to ItemFulfillmentItems with OUTER unit/tracking chain; fixes 0-records on orders lacking unit breakdown (e.g. SA132740). (jcardinal)
103
120
  - 2026-06-10 — Re-rooted `item-fulfillments-for-sales-order-items` (id 13) and moved tracking to the bridge join. (jcardinal)
104
121
 
105
122
  ## Related docs
106
123
  - 2.0 _underscore: Tracking-Number Bridge Migration.
124
+ - 2.0 api2: TableView row-filtering via apiWhereClause (the mechanism used for the FEE exclusion).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.349",
3
+ "version": "1.0.351",
4
4
  "description": "TOGA Technology Team Claude Knowledge System — shared AI coding harness with skills, knowledge base CLI, and project installer for Claude Code.",
5
5
  "keywords": [
6
6
  "claude",