toga-ai 1.0.203 → 1.0.205

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.
@@ -4,7 +4,7 @@
4
4
  |-----|---------|-------|
5
5
  | [TOGa 2.5 Supply — Architecture](architecture.md) | `toga25-supply` ("TOGa 2.5 Supply") is the **React/TypeScript frontend** for the 2.0 Supply application — an iteration and improvement of `toga2-supply`. | toga25-supply/src/main.tsx, toga25-supply/src/App.tsx, toga25-supply/src/routes.tsx, toga25-supply/src/fieldsConfig/index.ts, toga25-supply/src/fieldsConfig/useClientFields.ts, toga25-supply/src/layout/, toga25-supply/src/pages/, toga25-supply/src/hooks/useTableCellInteractions.ts |
6
6
  | [Action-Button Rule Engine (Flag / Rule grammar)](features/action-button-rule-engine.md) | A declarative, fully config-driven rule engine that resolves the boolean-ish flags (`isEnabled`, `isVisible`, `isComplete`) on SalesOrder action-button options. | toga25-supply/src/pages/SalesOrders/helpers/evaluateEnableRule.ts, toga25-supply/src/pages/SalesOrders/helpers/buildPatchedTenantFields.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/useSalesOrderRecordModalLayoutModel.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderApprovalModalsLayout/viewModel/useApprovalModalViewModel.tsx |
7
- | [Client-Configurable Fields (useClientFields / fieldsConfig)](features/client-configurable-fields.md) | The mechanism for config that **varies by client** (or client × role) — field overrides, filter buttons, group-by options, column pickers, layout toggles — with | toga25-supply/src/fieldsConfig/index.ts, toga25-supply/src/fieldsConfig/useClientFields.ts, toga25-supply/src/pages/SalesOrders/viewModel/FIELDS/, toga25-supply/src/pages/Inventory/viewModel/FIELDS/, toga25-supply/src/pages/Inventory/README.md, toga25-supply/src/layout/ItemRecordModalLayout/viewModel/FIELDS/, toga25-supply/src/layout/VendorItemRecordModalLayout/viewModel/FIELDS/ |
7
+ | [Client-Configurable Fields (useClientFields / fieldsConfig)](features/client-configurable-fields.md) | The mechanism for config that **varies by client** (or client × role) — field overrides, filter buttons, group-by options, column pickers, layout toggles — with | toga25-supply/src/fieldsConfig/index.ts, toga25-supply/src/fieldsConfig/useClientFields.ts, toga25-supply/src/pages/SalesOrders/viewModel/FIELDS/, toga25-supply/src/pages/Inventory/viewModel/FIELDS/, toga25-supply/src/pages/Inventory/README.md, toga25-supply/src/layout/ItemRecordModalLayout/viewModel/FIELDS/, toga25-supply/src/layout/VendorItemRecordModalLayout/viewModel/FIELDS/, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderTopBar.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/FIELDS/ |
8
8
  | [Column Visibility (URL-driven show/hide columns)](features/column-visibility.md) | A "Columns" header button that opens a modal listing every column from the table meta, lets the user show/hide columns, adjusts the table live, and persists the | toga25-supply/src/components/ColumnVisibilityModal/, toga25-supply/src/pages/SalesOrders/SalesOrders.tsx, toga25-supply/src/pages/SalesOrders/viewModel/useSalesOrdersPageViewModel.tsx, toga25-supply/src/pages/SalesOrders/hooks/useSalesOrdersTableData.tsx |
9
9
  | [Meta-Driven Page & Table Setup](features/meta-driven-table-data.md) | A page in this app is **meta-driven end to end**: the page view model fetches *page meta* (labels, sections, ACL) and *table meta* (the columns/fields + table s | toga25-supply/src/pages/SalesOrders/viewModel/useSalesOrdersPageViewModel.tsx, toga25-supply/src/pages/SalesOrders/hooks/useSalesOrdersTableState.ts, toga25-supply/src/hooks/useTablePageMeta.ts, toga-blox-npm/dist/hooks/useFetchPageMeta.d.ts, toga-blox-npm/dist/hooks/useFetchTablePageMeta.d.ts, toga-blox-npm/dist/hooks/useAssignTableFieldLabels.d.ts, toga-blox-npm/dist/components/Table/hooks/useTableData.d.ts |
10
10
  | [Record Modals & Nested Tables](features/record-modals-and-nested-tables.md) | The repo's family of modal + nested-table patterns layered over toga-blox `TableRecordModal` and `PrimaryTable*Layout`. | toga25-supply/src/layout/ItemRecordModalLayout/, toga25-supply/src/layout/SalesOrderRecordModalLayout/, toga25-supply/src/layout/SalesOrderItemsTableLayout/, toga25-supply/src/layout/ItemFulfillmentModal/, toga25-supply/src/layout/GenericNestedTables/, toga25-supply/src/hooks/useTableCellInteractions.ts |
@@ -16,6 +16,8 @@ files:
16
16
  - toga25-supply/src/pages/Inventory/README.md
17
17
  - toga25-supply/src/layout/ItemRecordModalLayout/viewModel/FIELDS/
18
18
  - toga25-supply/src/layout/VendorItemRecordModalLayout/viewModel/FIELDS/
19
+ - toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderTopBar.tsx
20
+ - toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/FIELDS/
19
21
  related:
20
22
  - ../architecture.md
21
23
  - column-visibility.md
@@ -70,6 +72,29 @@ registry that maps enum → real value (e.g. `MODAL_RENDERERS: Record<ModalKey,
70
72
  This is the one place app-owned JSX is grafted back onto client-authored config. Clients never
71
73
  author JSX.
72
74
 
75
+ ### Presentation intent: `kind` / `variant` (SalesOrder action buttons)
76
+
77
+ SalesOrder record-modal action buttons (`recordActionFields.actionOptions[]`, rendered by
78
+ `SalesOrderTopBar.tsx`) split **presentation** from **identity** so rendering is config-driven,
79
+ not branch-on-`valueKey`:
80
+
81
+ - **`kind`** chooses the renderer: `"icon"` (generic icon button), `"text"` (pill), or a
82
+ registry key like `"viewLog"` that maps to a dedicated component.
83
+ - **`variant`** chooses color from a **closed set** — `approve` / `deny` / `approvalWorkflow` /
84
+ `neutral` — via a `variantClass` lookup. Keying on this small vocabulary (not on each business
85
+ key) is what makes it scale.
86
+ - **`valueKey`** is decoupled: it is **only** the click-dispatch identity (`onActionClick(valueKey)`),
87
+ never a rendering switch. A new button reusing an existing `kind` + `variant` is **config-only** —
88
+ no edit to `SalesOrderTopBar.tsx`.
89
+
90
+ A `componentRegistry: Record<kind, (opt) => ReactNode>` in `SalesOrderTopBar` is the hydration
91
+ point for **whole components**: `const renderSpecial = componentRegistry[opt.kind]; if (renderSpecial)
92
+ return renderSpecial(opt);` short-circuits the generic icon/text branches to render a dedicated
93
+ component (e.g. `viewLog` → `<ViewLogButton label onOpenLog={onOpenLog} />`), with the handler wired
94
+ in one place. Same enum→non-serializable boundary as `MODAL_RENDERERS`, applied to components — keep
95
+ runtime-only signals (e.g. PO-complete) gated by config presence (`!!opt.completedLabel`) rather than
96
+ by `valueKey`.
97
+
73
98
  ### Record-modal header & action config
74
99
 
75
100
  Record-modal layouts (`ItemRecordModalLayout`, `VendorItemRecordModalLayout`) now drive the
@@ -108,11 +133,17 @@ modal's chrome from the same per-client JSON, not from the component:
108
133
  `fieldsConfig/index.ts`, derive the default defensively rather than assuming the wrapper exists:
109
134
  `const DEFAULT_ORDER_VIEW_FIELDS = (defaultBundle as any).DEFAULT ?? (defaultBundle as any) ?? {};`
110
135
  — fall through to the bundle itself, then `{}`, so a flat JSON file does not resolve to `undefined`.
136
+ - **`recordActionFields` shape.** A client `orderViewFields.json` can hold **multiple per-persona
137
+ `recordActionFields` blocks**, plus a sibling `recordActionFields.poNumber` object that configures
138
+ the Enter-PO modal fields — its nested `valueKey`s are **not** action buttons. `DEFAULT/orderViewFields.json`
139
+ has no `recordActionFields` at all. Any tooling that walks action buttons must scope strictly to the
140
+ `actionOptions` array.
111
141
  - Worked examples: `salesOrdersPageFields` (client × role) and `inventoryGroupings` (client-only +
112
142
  hydration; full write-up in `src/pages/Inventory/README.md`); `itemRecordViewFields` /
113
143
  `vendorItemRecordViewFields` are record-modal-layout examples living under `src/layout/.../viewModel/FIELDS/`.
114
144
 
115
145
  ## Change history
146
+ - 2026-06-25 — SalesOrder action buttons render by presentation intent: `kind` (icon/text/viewLog) picks the renderer, `variant` a closed color set, `valueKey` is pure click-dispatch; a `componentRegistry` keyed by `kind` hydrates dedicated components (e.g. ViewLogButton). Added the `recordActionFields`-shape gotcha (per-persona blocks + sibling `poNumber` sub-config; walk `actionOptions` only). (apeterson)
116
147
  - 2026-06-25 — Extended to record-modal layouts: per-mode `*ViewFields`/`*EditFields` JSON under `src/layout/.../viewModel/FIELDS/`, JSON-driven modal `header` (modalTag/labelTemplate/statusBadge) + `editItem` button gated via the Flag/Rule engine, and the flat-bundle defensive-default gotcha. (apeterson)
117
148
  - 2026-06-23 — Documented from the `add-client-fields` skill during initial knowledge seed. (apeterson)
118
149
  - 2026-06-25 — Client field JSON also lives under `src/layout/<Modal>/viewModel/FIELDS/<CLIENT>/`
@@ -5,7 +5,7 @@ project: Worker
5
5
  client: tow-foundation
6
6
  type: client-feature
7
7
  status: active
8
- updated: 2026-06-23
8
+ updated: 2026-06-25
9
9
  owners: ["rgirish"]
10
10
  files:
11
11
  - worker2/Worker/Client/TowFoundation.php
@@ -57,8 +57,11 @@ Credit Card Receipts/
57
57
  5. **Pass 1 — Extract** — for each receipt:
58
58
  - Enforce type + size limit: unsupported mime types (`application/octet-stream`) throw immediately; 4 MB cap for images, 10 MB for documents
59
59
  - Download file bytes from SharePoint
60
- - POST to Talos AI `/api/ai/generate` → structured `{vendor_name, invoice_date, total, payment_memo, category, ...}`
60
+ - **`.docx` files bypass Talos** and route to `extractDocxData()` (Talos returns HTTP 500 on a `.docx` MIME type — it only accepts PDF/image). That method opens the docx as a ZIP, extracts `word/document.xml`, strips tags, and regex-parses `MM/DD/YY: description` lines into `line_items[]`
61
+ - All other types POST to Talos AI `/api/ai/generate` → structured `{vendor_name, invoice_date, total, payment_memo, category, ...}`
62
+ - **Year guard on AI dates** — if Talos returns an `invoice_date` whose year is more than 1 year from the current year (AI hallucination on two-digit year inputs, e.g. `5/13/76` → 1976, `5/28/28` → 2028), the year is clamped to the current year while month/day are preserved
61
63
  - Cross-verify amount ± $0.01 against parsed statement; if matched, override `payment_memo` with statement description
64
+ - **Zero-total fallback** — if the extracted/docx total is 0, `lookupStatementTotalByVendor()` derives the amount from the billing statement (see *Statement structure* below)
62
65
  - Build base filename (no suffix yet) and store in `$pendingRenames`
63
66
  - On extract failure → move to `Archive/exception/` immediately
64
67
  6. **Deduplication** — after Pass 1, receipts sharing identical `(personName, invoice_date, vendor_name, total)` are deduplicated; only the first is kept, the rest are logged as `DUPLICATE skipped` and dropped. This catches the same receipt uploaded twice to SharePoint.
@@ -66,6 +69,7 @@ Credit Card Receipts/
66
69
  8. **Pass 2 — Move** — for each pending rename:
67
70
  - If base name appears more than once, assign `_a`, `_b`, `_c`... suffix to **all** colliding files (including the first)
68
71
  - PATCH SharePoint to rename + move to `Archive/` (flat — no person subfolder)
72
+ - **Multi-entry docx expansion** — a `.docx` whose `extractDocxData()` produced multiple `line_items[]` (e.g. a subway-rides document with 14 dated trip entries) is expanded into one Excel row per entry
69
73
  8. **Excel generation** — one `.xlsx` per person (PhpSpreadsheet); columns: Row #, Account Name, QB Vendor, Payment Amount, Date, Payment Method, Payment Memo, QB Description, Class, Category, Payment Account, Ref No.
70
74
  9. **SharePoint upload** — each Excel uploaded to root-level `Credit Card Receipts/3. QB Excel/` via Graph API PUT (shared folder for all persons)
71
75
  10. **Summary email** — sent with Excel files attached; To: Jheanelle, CC: Magdalena, BCC: devteam@togatech.com
@@ -83,6 +87,28 @@ Amex billing cycle runs 3rd-to-3rd. `computeRefNo()` returns the cycle-end date
83
87
  - Charge on or before the 3rd → cycle ends on the 3rd of the same month
84
88
  - Charge after the 3rd → cycle ends on the 3rd of the next month
85
89
 
90
+ ### Billing statement structure (Amex xlsx)
91
+
92
+ The Amex statement Emily submits is a **multi-sheet** xlsx — one sheet per weekly billing
93
+ period (e.g. `"05.04.2026 - 05.11.2026"`, `"05.11.2026 - 05.18.2026"`, …). `loadStatementExcel()`
94
+ must iterate **`getAllSheets()`** and accumulate rows from every sheet — using `getActiveSheet()`
95
+ reads only sheet 0 and silently drops charges that live on later sheets.
96
+
97
+ Each sheet's columns: `Date | Receipt | Notes | Description | Amount | Extended Details |
98
+ Appears On Your Statement As | Address | City/State | Zip Code | Country | Reference | Category`.
99
+
100
+ - The **`Receipt` column (col B)** is a human-readable label Emily fills in to group charges
101
+ (e.g. `"1"`, `"2"`, `"Subway Rides"`). All transit charges (`NYCT PAYGO`) get the label
102
+ `"Subway Rides"` across all sheets. `loadStatementExcel()` must detect and capture this column.
103
+ - `lookupStatementTotalByVendor()` resolves a statement total for a vendor in two passes:
104
+ 1. **Receipt-label sum (first)** — sum the `Amount` of **all** rows whose `Receipt` label
105
+ contains the vendor keyword. Transit is one `$3.00` row per ride (e.g. 14 rows tagged
106
+ `"Subway Rides"`), so the total is the **sum** ($42.00), not a single row.
107
+ 2. **Description-keyword match (fallback)** — for non-transit vendors, match the keyword
108
+ against the `Description` column. (The description for subway is `"AplPay NYCT PAYGO
109
+ NEW YORK"`, which contains no `"subway"` — so the receipt-label pass is what makes
110
+ transit resolve at all.)
111
+
86
112
  ### QB vendor matching
87
113
  `TowFoundationVendors.php` holds 2,994 vendor names exported verbatim from Tow Foundation's
88
114
  QuickBooks vendor list (June 2026). `matchQbVendor()` runs three passes against this list:
@@ -180,8 +206,9 @@ Fatal errors send only to `NOTIFY_EMAIL_DEV` (no CC/BCC).
180
206
  exists at the root of `Credit Card Receipts/` alongside the person folders.
181
207
 
182
208
  - **Person folder name normalization** — SharePoint folders are named `"Brent Peterkin CC receipts"` but `CLASS_MAP` / `PAYMENT_ACCOUNT_MAP` keys are just `"Brent Peterkin"`. The ` CC receipts` suffix is stripped via regex in `walkReceiptsFolder()`. Without this, Class and Payment Account columns are blank for those persons.
209
+ - **Archive paths must use the actual SharePoint folder name, not the normalized name** — the normalized person name (` CC receipts` stripped, spaces replaced) is for **map lookups only**. When building the SharePoint archive path, use the *actual* folder name (e.g. `"Emily Tow CC receipts"`), not the normalized `"Emily Tow"` — otherwise the PATCH move 404s because the path segment does not exist. Keep the normalized name and the real folder name as separate values.
183
210
  - **`billingCycle` filter is suffix-match, not exact** — always pass just the date portion (`"06-03-2026"`), not the full folder name. Passing the full name (`"Amex ending in 06-03-2026"`) also works but would miss Mastercard folders.
184
- - **Ole Mole not in QB vendor list** — as of June 2026 this vendor is not in Tow Foundation's QuickBooks, so `qb_vendor` will be blank for Ole Mole charges until they add it to QB and the vendor list is refreshed.
211
+ - **Vendors not in QB vendor list** — as of June 2026 these vendors are not in Tow Foundation's QuickBooks, so `qb_vendor` is blank for their charges until the client adds them to QB and the vendor list is refreshed: **Ole Mole**, **AMORE PIZZA CAFE**, **Green & Tonic (New Canaan)**, **NCFP (National Center for Philanthropy)**, **Rippling**, **Langan's**. This is a client action, not a code fix.
185
212
  - **Duplicate detection is within-run only** — if the same receipt was processed in a prior run and archived, it won't be caught. The dedup only covers receipts present in the current run's `$pendingRenames`.
186
213
  - **Unmapped AI categories pass through** — if the AI returns a category string not in `TowFoundationCategories.php`, `mapCategory()` logs a warning and writes the raw AI string to the Excel. Check error logs after a run if the Category column looks odd; add the new value to the map file to fix it.
187
214
  - **Short vendor acronyms (< 4 chars) require whole-word match** — the substring pass uses a word-boundary regex for needles under 4 chars to avoid false positives (e.g. `"UPS"` inside `"USPS"`). If a short vendor name is not matching, check that the QB vendor list entry starts or ends with the acronym as a whole word.
@@ -189,6 +216,7 @@ Fatal errors send only to `NOTIFY_EMAIL_DEV` (no CC/BCC).
189
216
 
190
217
  ## Change history
191
218
 
219
+ - 2026-06-25 — Statement parsing hardened for Emily Tow's May 2026 Amex xlsx: `loadStatementExcel()` now iterates `getAllSheets()` (4 weekly-period sheets) instead of `getActiveSheet()` and captures the `Receipt` column; `lookupStatementTotalByVendor()` now **sums** all receipt-label-matched rows (14 × $3.00 "Subway Rides" = $42.00) before falling back to description-keyword match — fixes subway totals reading $0/$9.00. Added `extractDocxData()` so `.docx` receipts bypass Talos (Talos 500s on docx), parsing `MM/DD/YY: description` line items into per-entry Excel rows. Added a year guard clamping AI-hallucinated `invoice_date` years (e.g. 1976, 2028) to the current year. Fixed archive-path bug: SharePoint move now uses the actual folder name (`"Emily Tow CC receipts"`) not the normalized name, fixing 404 on archive moves. 5 more vendors found missing from the QB vendor list (client action). (rgirish)
192
220
  - 2026-06-23 — `TowFoundationCategories.php` added: AI category values now mapped to exact QB account strings via `mapCategory()`; `"Transportation"` → `"6610 Travel expense"` etc. (19 mappings). QB vendor substring match minimum lowered from 4 to 3 chars with whole-word boundary guard for short needles — fixes `"CVS"` → `"CVS Pharmacy"`, `"MTA"` → `"MTA Metro card"`, prevents `"UPS"` → `"USPS"` false positive. (rgirish)
193
221
  - 2026-06-18 — QB vendor matching via local fuzzy match against `TowFoundationVendors.php` (2,994 vendors); AI category prompt updated to return specific types (Dining, Travel, etc.); duplicate receipt detection added (person+date+vendor+amount dedup); `billingCycle` filter parameter added (suffix-match — card-type agnostic); person folder ` CC receipts` suffix stripped so CLASS_MAP/PAYMENT_ACCOUNT_MAP resolve correctly (rgirish)
194
222
  - 2026-06-12 — QB Excel upload moved to root-level `3. QB Excel/`; Archive/exception folders flattened (no person subfolder); unsupported mime types (`application/octet-stream`) now throw ReceiptProcessingException instead of being silently skipped (rgirish)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.203",
3
+ "version": "1.0.205",
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",