toga-ai 1.0.215 → 1.0.217

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,6 +4,7 @@
4
4
  |-----|---------|-------|
5
5
  | [_underscore Framework Architecture](architecture.md) | `_underscore` is the shared PHP backend framework for **all 2.0 applications**. | _underscore/_underscore.php, _underscore/Loader.php, _underscore/Framework.php, _underscore/Model.php, _underscore/Database.php, _underscore/Query.php, _underscore/Route.php, _underscore/Component.php |
6
6
  | [ACL Permission Chain (Record & Field Authorization)](features/acl-permission-chain.md) | Authorization in the 2.0 API is **metadata-driven**: whether a role may Create/Read/Update/Delete a record is decided by rows across **four linked tables**, not | api2/Component/Api/V2/V2.php, _underscore/Model/Core/Page.php, dbchanges2/Client/2026-06-03- BLANK_CLIENT_DATABASE.sql, dbchanges2/Client/2026-06-23b - ItemTranslationsAcl.sql |
7
+ | [Assortment Name Translation (AssortmentTranslations sidecar)](features/assortment-name-translation.md) | Serves Assortment (product-grouping) **names** in multiple languages by adding a per-language **sidecar** table `AssortmentTranslations`, reusing the platform's | _underscore/Model/Client/AssortmentTranslation.php, dbchanges2/Client/2026-06-26a - AssortmentTranslations.sql, dbchanges2/Core/2026-06-26a - AssortmentTranslationsRecord.sql, dbchanges2/Client/2026-06-26b - AssortmentTranslationsAcl.sql |
7
8
  | [Carrier Shipping Labels (UPS/FedEx) & NetSuite Item Fulfillment](features/carrier-shipping-labels.md) | Backend mechanics behind TOGa Supply's Fulfill & Ship: buying a carrier label (UPS/FedEx), persisting it, and creating the NetSuite Item Fulfillment with tracki | _underscore/Model/Client/ItemFulfillment.php, _underscore/Model/Client/ItemFulfillments/TrackingNumber.php, _underscore/Component/Library/LabelPdf/LabelPdf.php, _underscore/Component/Library/Carriers/Ups/Ups.php, _underscore/Component/Library/Carriers/Fedex/Fedex.php, _underscore/Trait/Netsuite/ItemFulfillment.php, _underscore/Trait/Netsuite/SalesOrder.php, _underscore/Component/Library/NetSuite/NetSuite.php, _underscore/Model/Client/TrackingNumber.php, _underscore/Model/Client/ShippingMethod.php, _underscore/Model.php, _underscore/Cloud.php |
8
9
  | [Client Email Template Sending](features/email-template-sending.md) | `_Model_Client_EmailTemplate` sends a stored, client-defined email template by UUID. | _underscore/Model/Client/EmailTemplate.php, _underscore/Model/Client/EmailTemplateOutgoingEmailAddress.php, _underscore/Email.php |
9
10
  | [Forecast.Sales NetSuite import engine (real-time webhook)](features/forecast-sale-import.md) | Real-time importer that takes a NetSuite **sale** record and writes its lines into `Forecast.Sales` (the Forecast2 revenue table). | _underscore/Component/Forecast/SaleImport/SaleImport.php, _underscore/Component/Forecast/Db/Db.php, worker2/Worker/Netsuite/Invoice.php, worker2/Worker/Netsuite/CashSale.php, worker2/Worker/Netsuite/CreditMemo.php, worker2/Worker/Netsuite/CashRefund.php, worker2/Worker/Netsuite/JournalEntry.php, worker2/Worker/Netsuite/Opportunity.php, worker2/Worker/Netsuite/SalesOrder.php, dbchanges2/Forecast/2026-06-26a - Add journalEntry to Sales transaction type enum.sql, test/@dave/test_invoice_lifecycle.php, test/@dave/test_je_lifecycle.php, test/@dave/test_creditmemo_lifecycle.php, test/@dave/test_cashsale_lifecycle.php, test/@dave/test_cashrefund_lifecycle.php, test/@dave/NetSuite/api-message-queue/ue_api_msg_queue_enqueue.js, test/@dave/NetSuite/api-message-queue/dev_ue_api_msg_queue_enqueue.js |
@@ -0,0 +1,86 @@
1
+ ---
2
+ title: Assortment Name Translation (AssortmentTranslations sidecar)
3
+ framework: "2.0"
4
+ repo: _underscore
5
+ project: _Underscore
6
+ client: shared
7
+ type: feature
8
+ status: active
9
+ updated: 2026-06-26
10
+ owners: ["bala"]
11
+ files:
12
+ - _underscore/Model/Client/AssortmentTranslation.php
13
+ - dbchanges2/Client/2026-06-26a - AssortmentTranslations.sql
14
+ - dbchanges2/Core/2026-06-26a - AssortmentTranslationsRecord.sql
15
+ - dbchanges2/Client/2026-06-26b - AssortmentTranslationsAcl.sql
16
+ related:
17
+ - ../../api2/features/language-translation-layer.md
18
+ - ./acl-permission-chain.md
19
+ ---
20
+
21
+ ## Summary
22
+
23
+ Serves Assortment (product-grouping) **names** in multiple languages by adding a per-language
24
+ **sidecar** table `AssortmentTranslations`, reusing the platform's existing translation mechanism.
25
+ English stays in `Assortments.name`; the sidecar holds only non-English overlays. Because the
26
+ translation layer is fully metadata-driven (see
27
+ [Language Translation Layer](../../api2/features/language-translation-layer.md)), wiring this up
28
+ required **no api2 code change** — only a new table, model, Core record registration, ACL grant,
29
+ and a single field link. Compass Canada's French (fr-CA) assortment names are seeded as the first
30
+ consumer.
31
+
32
+ ## Key files / entry points
33
+
34
+ - `_underscore/Model/Client/AssortmentTranslation.php` — the sidecar model (`_Model_Client_AssortmentTranslation`).
35
+ - `dbchanges2/Client/2026-06-26a - AssortmentTranslations.sql` — the sidecar table (every client DB).
36
+ - `dbchanges2/Core/2026-06-26a - AssortmentTranslationsRecord.sql` — Core record + fields + the field link.
37
+ - `dbchanges2/Client/2026-06-26b - AssortmentTranslationsAcl.sql` — Base-role ACL grant (every client DB).
38
+
39
+ ## How it works
40
+
41
+ The mechanism itself is the data-driven translation layer documented in
42
+ [Language Translation Layer](../../api2/features/language-translation-layer.md) — this feature only
43
+ **registers a new translatable field** into it:
44
+
45
+ 1. **Sidecar table.** `Client.AssortmentTranslations` holds non-English overlays for assortment names.
46
+ English remains in `Assortments.name`; a null/absent sidecar row falls back to English.
47
+ 2. **Model.** `_Model_Client_AssortmentTranslation` is the ORM model for the sidecar.
48
+ 3. **Core record registration.** `Core.Records` registers route `assortment-translations` →
49
+ model `\_Model_Client_AssortmentTranslation` (record id **332**, `aclDatabase = 'CLIENT'`), with
50
+ `Core.RecordFields` rows for `id`, `uuid`, `assortmentId`, `languageId`, and `name`.
51
+ 4. **The field link (what makes it translate automatically).** The translatable `Assortments.name`
52
+ RecordField gets its `Core.RecordFields.translationRecordFieldId` set to the sidecar's `name`
53
+ RecordField. The api2 V2 layer reads `translationRecordFieldId` for every field and automatically
54
+ returns the sidecar value for the caller's resolved language (falling back to English when absent) —
55
+ so assortment names now translate **without any api2 code change**.
56
+ 5. **ACL.** A full ACL chain grants the **Base** role read/write on the new record. Because the
57
+ record's `aclDatabase = 'CLIENT'`, the grant lives in each client DB — see
58
+ [ACL Permission Chain](./acl-permission-chain.md).
59
+
60
+ ## Data model
61
+
62
+ - `Client.AssortmentTranslations` — `id, uuid, dtCreated, dtUpdated, assortmentId (FK Assortments),
63
+ languageId (FK Languages), name`; `UNIQUE(assortmentId, languageId)`. English stays in `Assortments`;
64
+ sidecar holds only non-English overlays (null → English fallback).
65
+ - `Core.Records` **332** = `assortment-translations` (`aclDatabase = 'CLIENT'`); `Core.RecordFields`
66
+ for id/uuid/assortmentId/languageId/name.
67
+ - `Assortments.name` RecordField → `translationRecordFieldId` points at the sidecar `name` RecordField.
68
+
69
+ ## Client variations
70
+
71
+ - **Compass Canada** seeds fr-CA assortment names (16 rows) into `AssortmentTranslations`. The French
72
+ text was extracted from the existing bilingual `Assortments.name` values (stored as `"English/French"`),
73
+ and a companion migration cleans `Assortments.name` back to English-only so the base value is pure
74
+ English and the French overlay lives only in the sidecar. See the Compass Canada profile.
75
+
76
+ ## Change history
77
+
78
+ - 2026-06-26 — Initial build: `AssortmentTranslations` sidecar table + `_Model_Client_AssortmentTranslation`
79
+ model + `Core.Records` 332 registration + `translationRecordFieldId` link on `Assortments.name` + Base-role
80
+ ACL chain. Reuses the existing metadata-driven translation layer, so no api2 code change was needed.
81
+ Compass Canada French (fr-CA) assortment names seeded, with `Assortments.name` cleaned to English-only. (bala)
82
+
83
+ ## Related docs
84
+
85
+ - [Language Translation Layer](../../api2/features/language-translation-layer.md)
86
+ - [ACL Permission Chain](./acl-permission-chain.md)
@@ -8,6 +8,8 @@
8
8
  | [ClickUp Work Type Automation (Committed / Conditional / Stretch)](features/clickup-work-type-automation.md) | The ClickUp webhook handler (`_Worker_Clickup`) automatically maintains each task's **Work Type** custom field — `Committed`, `Conditional`, or `Stretch` — base | worker2/Worker/Clickup.php, worker2/Tests/Worker/ClickupWorkTypeTest.php |
9
9
  | [Creating Worker Actions](features/creating-worker-actions.md) | How to add a new callable Worker action — a PHP class whose `public static` methods are invoked as background jobs (via webhook, cron, or `_Worker::runTask()`). | worker2/Worker/, worker2/Controller/Index.php, _underscore/Worker.php |
10
10
  | [Elite Freshservice Sync (worker2)](features/elite-freshservice-sync.md) | `_Worker_Elite` processes Freshservice webhook events and syncs them into TOGA 2. | worker2/Worker/Elite.php, worker2/Config/dev-kmaramreddy-laptop.ini |
11
+ | [Etilize Catalog Item Import & Refresh](features/etilize-catalog-item-import.md) | Client-generic catalog onboarding from an S3 CSV plus an Etilize re-pull. | worker2/Worker/Etilize/Items.php |
12
+ | [Etilize Item Translation Import](features/etilize-item-translation-import.md) | The abstract worker class `_Worker_Etilize_ItemTranslations` imports **non-English** item text from Etilize into the client's `ItemTranslations` table. | worker2/Worker/Etilize/ItemTranslations.php |
11
13
  | [Monitoring Framework (Orchestrator + Child Monitors)](features/monitoring-framework.md) | A unified, DB-driven monitoring framework for business-critical data flows (Compass POs, Prudential asset imports, AIG closed claims, …). | worker2/Worker/Monitor.php, worker2/Worker/Monitors/, worker2/Worker/Notification/Email.php, dbchanges2/Core/2026-05-21 - Monitors.sql |
12
14
  | [NetSuite → TOGA Opportunity Sync (API Message Queue + worker2 webhook)](features/netsuite-opportunity-sync.md) | Outbound sync from NetSuite to TOGA for the record types the Forecast2 importer pulls (opportunities first; sales/items/etc. | worker2/Worker/Netsuite.php, worker2/Worker/Netsuite/Opportunity.php, worker2/Controller/Index.php, _underscore/Worker.php, test/@dave/NetSuite/api-message-queue/lib_amq_queue.js, test/@dave/NetSuite/api-message-queue/ue_api_msg_queue_enqueue.js, test/@dave/NetSuite/api-message-queue/ue_amq_drain.js, test/@dave/NetSuite/api-message-queue/ss_amq_drain.js, test/@dave/NetSuite/api-message-queue/DEPLOY_RUNBOOK.md, test/@dave/clickup/backfill_opportunity_numbers.php, test/@dave/clickup/probe_opportunity_fields.php, test/@dave/probe_clickup_desc_match.php, test/@dave/test_model_load_behavior.php, dbchanges2/Forecast/2026-06-25a - Add unique index on Opportunities netsuiteOpportunityInternalId.sql, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php |
13
15
  | [NetSuite → Forecast Open-Orders Sync (salesOrder webhook → OpenOrderItems)](features/netsuite-salesorder-open-orders-sync.md) | Webhook-driven, single-record port of the legacy open-orders importer (TRUE-79142). | worker2/Worker/Netsuite/SalesOrder.php, worker2/Worker/Netsuite.php, test/@dave/probe_salesorder_rest_shape.php, test/@dave/probe_open_order_lines.php, test/@dave/check_so_status.php, test/@dave/check_so_history.php, test/@dave/probe_so_rest_lines.php, test/@dave/probe_missing_oo_timing.php, test/@dave/probe_missing_oo_createdby.php, test/@dave/probe_drift_so_dates.php, test/@dave/probe_open_order_gating.php, worker/crons/toga2/forecast2/import_open_orders.php, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php |
@@ -0,0 +1,105 @@
1
+ ---
2
+ title: Etilize Catalog Item Import & Refresh
3
+ framework: "2.0"
4
+ repo: worker2
5
+ project: Worker
6
+ client: shared
7
+ type: feature
8
+ status: active
9
+ updated: 2026-06-26
10
+ owners: [bala]
11
+ files:
12
+ - worker2/Worker/Etilize/Items.php
13
+ related:
14
+ - ./creating-worker-actions.md
15
+ - ./etilize-item-translation-import.md
16
+ ---
17
+
18
+ ## Summary
19
+
20
+ Client-generic catalog onboarding from an S3 CSV plus an Etilize re-pull. The
21
+ abstract worker class `_Worker_Etilize_Items` ingests an item catalog (items, vendor
22
+ items, costs, prices, personas) and enriches each item with features and images from
23
+ the Etilize datasheet. All three actions write **directly to `DB_CLIENT`** via raw
24
+ `_Query` — there is **no api2 round-trip**. Used today by **Compass Canada** (single
25
+ price) and **Quad** (multi-currency), but nothing in the code is client-specific.
26
+
27
+ ## Public actions
28
+
29
+ Routes are `Etilize/Items/<Method>` (see [creating-worker-actions](./creating-worker-actions.md)).
30
+
31
+ 1. `ImportItemsFromCsvFile(string $client, string $s3Url, string $catalogUuid)`
32
+ — **normal import**: one `ItemPrice` + `VendorItemCost` per item with **NULL
33
+ currency**, using the Cost / Lease Price columns (e.g. Compass).
34
+ 2. `ImportItemsWithCurrencyPricesFromCsvFile(string $client, string $s3Url, string $catalogUuid)`
35
+ — **per-currency import**: each currency-coded column becomes its own
36
+ `ItemPrice` + `VendorItemCost` (e.g. Quad).
37
+ 3. `RefreshItemsFromEtilize(string $client, array $items, ?string $catalog = null)`
38
+ where `items = [{partNumber, etilizeId}]` — looks each item up by `partNumber`
39
+ and, only if its stored `c_etilizeProductId` differs from the supplied `etilizeId`,
40
+ updates it and re-pulls the item's features + images from the Etilize datasheet.
41
+ Returns a per-item changed/unchanged summary.
42
+
43
+ ## How the import works
44
+
45
+ Both import actions delegate to the shared private
46
+ `runItemCsvImport(..., bool $useCurrencyPrices)` — `useCurrencyPrices` is the only
47
+ difference between the two public entry points.
48
+
49
+ 1. **Setup** — resolve `clientId` from `Core.Clients` by `clientIdentifier`, register
50
+ the client DB, and resolve `catalogId` from `catalogUuid` against the `Catalogs` table.
51
+ 2. **Download & parse CSV** — pulled from S3 with `_Cloud::getFileFromS3`. **Row 1 =
52
+ headers, row 2 = a field-description row that is skipped, data starts at row 3.**
53
+ Columns are mapped **by header name** via case-insensitive regex patterns, so clients
54
+ whose columns are in a **different order** both work (Compass vs. Quad) — order is never
55
+ assumed.
56
+ 3. **Per data row, find-or-create:**
57
+ - the **Item** — resolving `manufacturerId` / `itemCategoryId` / `itemTypeId` /
58
+ `assetTypeId` by name via a generic find-or-create over `Manufacturers`,
59
+ `ItemCategories`, `ItemTypes`, `AssetTypes`; set in the catalog with flags.
60
+ - the **VendorItem** — vendor resolved by name; `overridePoSubmissionIntegrationId`
61
+ left NULL.
62
+ - **pricing** — either a single NULL-currency row, or one row per currency column
63
+ (see Multi-currency below).
64
+ - **personas** — each CSV persona token resolved by numeric id **or** by name against
65
+ `Personas`, linked through the `Personas_Items` bridge table.
66
+ 4. **Etilize enrichment** (from the `getProduct` datasheet):
67
+ - **features** → `Features` (find-or-create by name, Text measure) + `ItemFeatures` +
68
+ `ItemCategoryFeatureGroups` + `ItemCategoryFeatures`.
69
+ - **images** → `Images` (find-or-create by `sourceUrl`) + a single `ItemImages` row
70
+ holding the four size-slot FKs: thumbnail / low / medium / high mapped to Etilize
71
+ sizes **160 / 225 / 650 / 900**.
72
+
73
+ ## Multi-currency
74
+
75
+ - The set of **valid currency codes is read per-client at runtime** from that client's own
76
+ `Currencies` table via `loadClientCurrencyCodes()` — **never hardcoded** — so each client
77
+ only uses the currencies it actually has configured.
78
+ - A small **alias map** normalizes CSV header spellings to the real `Currencies.code`
79
+ (e.g. a `PER` header → `PEN`).
80
+ - A CSV currency column is used **only if** its normalized code matches one of the client's
81
+ configured currencies. For each match the import creates an `ItemPrice` + `VendorItemCost`
82
+ with that `currencyId` (`priceType` `each`).
83
+
84
+ ## Etilize re-pull (`RefreshItemsFromEtilize`)
85
+
86
+ - Item located by `partNumber`. The Etilize id is updated **only when** the supplied
87
+ `etilizeId` differs from the stored `c_etilizeProductId`.
88
+ - The feature/image re-pull is **find-or-create** and **does not delete stale features**
89
+ — this is intentional.
90
+
91
+ ## Gotchas
92
+
93
+ - CSV layout is fixed at the top: header row, a **skipped** description row, then data
94
+ from row 3. Feeding a two-row (header+data) file would treat the first data row as the
95
+ description row.
96
+ - Columns are matched by header regex, not position — a renamed/missing header silently
97
+ drops that column rather than mis-mapping by index.
98
+ - Currency columns not present in the client's `Currencies` table are ignored, so a
99
+ catalog that ships an unconfigured currency simply won't price it.
100
+
101
+ ## Change history
102
+ - 2026-06-26 — Documented `_Worker_Etilize_Items`: header-mapped S3 CSV import with
103
+ shared `runItemCsvImport`, normal (NULL-currency) vs. per-currency pricing, per-client
104
+ runtime currency resolution, persona linking, Etilize feature/image enrichment, and the
105
+ id-diff-only `RefreshItemsFromEtilize` re-pull. (bala)
@@ -0,0 +1,69 @@
1
+ ---
2
+ title: Etilize Item Translation Import
3
+ framework: "2.0"
4
+ repo: worker2
5
+ project: Worker
6
+ client: shared
7
+ type: feature
8
+ status: active
9
+ updated: 2026-06-26
10
+ owners: [bala]
11
+ files:
12
+ - worker2/Worker/Etilize/ItemTranslations.php
13
+ related:
14
+ - ./creating-worker-actions.md
15
+ - ./etilize-catalog-item-import.md
16
+ ---
17
+
18
+ ## Summary
19
+
20
+ The abstract worker class `_Worker_Etilize_ItemTranslations` imports **non-English**
21
+ item text from Etilize into the client's `ItemTranslations` table. English is never
22
+ written — it stays in the `Items` table. The class writes **directly to `DB_CLIENT`**
23
+ via raw `_Query` (**no api2**). Used today to give **Compass Canada** French (fr-CA)
24
+ catalog text without duplicating English, but the code is client-generic.
25
+
26
+ `ItemTranslations` columns: `itemId`, `languageId`, `title`, `description`,
27
+ `longDescription`, with `UNIQUE(itemId, languageId)`.
28
+
29
+ ## Public actions
30
+
31
+ Routes are `Etilize/ItemTranslations/<Method>` (see
32
+ [creating-worker-actions](./creating-worker-actions.md)).
33
+
34
+ 1. `ImportAllItemsForLanguage(string $client, string $languageCode, ?string $languageName = null, ?string $catalog = null)`
35
+ — translates **all** items that have a `c_etilizeProductId`.
36
+ 2. `ImportItemsLanguageByPartNumbers(string $client, string $languageCode, array $partNumbers, ?string $languageName = null, ?string $catalog = null)`
37
+ — translates only the given part numbers (uses a `partNumber IN (...)` filter).
38
+
39
+ ## How it works
40
+
41
+ 1. Resolve `clientId` from `Core.Clients` by `clientIdentifier` and register the client DB.
42
+ 2. **Find-or-create the Language** by `code` (e.g. `fr-CA`).
43
+ 3. **Derive the Etilize locale** from the language code: `fr-CA` → `fr_ca` (lowercased,
44
+ hyphen → underscore).
45
+ 4. Load the client's `Items` that have a **non-empty `c_etilizeProductId`** (scoped to the
46
+ given part numbers for the by-part-numbers action).
47
+ 5. For each item, call the Etilize **Spex Live `getProduct`** API (`appId` 231136, catalog
48
+ default `na`) for that locale.
49
+ 6. Extract the translated **`title`** (Etilize description **type 2**) and
50
+ **`longDescription`** (Etilize description **type 1**).
51
+ 7. **Upsert** into `ItemTranslations` via `INSERT ... ON DUPLICATE KEY UPDATE` using
52
+ `COALESCE(VALUES(col), col)` so a blank new value never overwrites existing text.
53
+
54
+ Items **not published in the requested locale are skipped**.
55
+
56
+ ## Gotchas
57
+
58
+ - The unique key is `(itemId, languageId)`; re-running an import upserts rather than
59
+ duplicating, and the `COALESCE` guard means a blank Etilize value will not clear an
60
+ existing translation.
61
+ - Only items with a `c_etilizeProductId` are translatable — items without an Etilize id
62
+ are silently skipped.
63
+ - English is intentionally never written here; it lives in the `Items` table.
64
+
65
+ ## Change history
66
+ - 2026-06-26 — Documented `_Worker_Etilize_ItemTranslations`: per-language Etilize
67
+ `getProduct` import into `ItemTranslations` (title=desc type 2, longDescription=type 1),
68
+ locale derived from language code, COALESCE upsert that never overwrites with blanks,
69
+ all-items vs. by-part-numbers actions. (bala)
@@ -16,8 +16,8 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
16
16
 
17
17
  ## 2.0 framework
18
18
 
19
- - **_underscore** (_Underscore) _(framework core)_ — 16 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
20
- - **worker2** (Worker) — 15 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
19
+ - **_underscore** (_Underscore) _(framework core)_ — 17 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
20
+ - **worker2** (Worker) — 17 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
21
21
  - **api2** (API) — 7 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
22
22
  - **dbchanges2** (Database Changes) _(framework core)_ — 3 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
23
23
  - **toga2-supply** (TOGa Supply) — 3 doc(s) → [2.0/apps/toga2-supply/INDEX.md](2.0/apps/toga2-supply/INDEX.md)
@@ -7,12 +7,13 @@ apps:
7
7
  - toga2-supply
8
8
  - toga2-commerce
9
9
  - worker
10
+ - worker2
10
11
  - dbchanges2
11
12
  project: _Underscore
12
13
  client: compass-canada
13
14
  type: profile
14
15
  status: active
15
- updated: 2026-06-19
16
+ updated: 2026-06-26
16
17
  owners: [jcardinal, bala]
17
18
  files: []
18
19
  related:
@@ -43,5 +44,9 @@ to but distinct from Compass USA. Like Compass USA it spans the **2.0** commerce
43
44
  ## Notes
44
45
  - Customer language preference: `UserGlobalSettings.settingId = 2` (`en` / `fr-CA`); customer-
45
46
  facing emails are sent in EN or FR accordingly.
47
+ - Assortment (product-grouping) names are served in fr-CA via the `AssortmentTranslations` sidecar
48
+ (16 rows seeded). French was extracted from the old bilingual `"English/French"` `Assortments.name`
49
+ values, which were then cleaned to English-only. See
50
+ [Assortment Name Translation](../../2.0/apps/_underscore/features/assortment-name-translation.md).
46
51
  - The 2026-06-08 ASN → ItemFulfillment work was for **Compass USA**, not Compass Canada.
47
52
  - Related: [Compass USA](../compass-usa/profile.md).
@@ -6,13 +6,14 @@ apps:
6
6
  - api2
7
7
  - toga2-supply
8
8
  - toga2-commerce
9
+ - worker2
9
10
  - dbchanges2
10
11
  project: _Underscore
11
12
  client: quad
12
13
  type: profile
13
14
  status: active
14
- updated: 2026-06-19
15
- owners: ["jcardinal"]
15
+ updated: 2026-06-26
16
+ owners: ["jcardinal", "bala"]
16
17
  files: []
17
18
  related:
18
19
  - ../../2.0/apps/_underscore/features/tracking-number-bridges.md
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.215",
3
+ "version": "1.0.217",
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",