toga-ai 1.0.31 → 1.0.33
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.
- package/knowledge/2.0/apps/_underscore/INDEX.md +1 -0
- package/knowledge/2.0/apps/_underscore/features/carrier-shipping-labels.md +113 -0
- package/knowledge/2.0/apps/toga2-supply/INDEX.md +6 -0
- package/knowledge/2.0/apps/toga2-supply/architecture.md +73 -0
- package/knowledge/2.0/apps/toga2-supply/features/fulfill-and-ship.md +112 -0
- package/knowledge/INDEX.md +2 -1
- package/knowledge/registry.json +2 -1
- package/package.json +1 -1
- package/scripts/install.js +40 -5
- package/skills/kickoff/SKILL.md +33 -6
|
@@ -3,4 +3,5 @@
|
|
|
3
3
|
| Doc | Summary | Files |
|
|
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
|
+
| [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/Component/Library/Carriers/Ups/Ups.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 |
|
|
6
7
|
| [Recursive Item Fulfillments (upstream mirroring)](features/recursive-item-fulfillments.md) | In a multi-tier supply chain a sales order (SO) spawns a purchase order (PO) that becomes another SO downstream, and so on. | _underscore/Model/Client/ItemFulfillment.php, _underscore/Model/Client/ItemFulfillmentItem.php, _underscore/Model/Client/ItemFulfillmentItemUnit.php, _underscore/Model/Client/ItemFulfillmentPackage.php, _underscore/Model/Compass/AdvanceShippingNotice.php, dbchanges2/Core/2026-02-13 - 75601 - RecursiveItemFulfillmentCreation.sql, dbchanges2/Core/2026-06-04 - RecursiveItemFulfillmentPut.sql |
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Carrier Shipping Labels (UPS/FedEx) & NetSuite Item Fulfillment
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: _underscore
|
|
5
|
+
project: _Underscore
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-10
|
|
10
|
+
owners: [mhammontree]
|
|
11
|
+
files:
|
|
12
|
+
- _underscore/Model/Client/ItemFulfillment.php
|
|
13
|
+
- _underscore/Component/Library/Carriers/Ups/Ups.php
|
|
14
|
+
- _underscore/Trait/Netsuite/ItemFulfillment.php
|
|
15
|
+
- _underscore/Trait/Netsuite/SalesOrder.php
|
|
16
|
+
- _underscore/Component/Library/NetSuite/NetSuite.php
|
|
17
|
+
- _underscore/Model/Client/TrackingNumber.php
|
|
18
|
+
- _underscore/Model/Client/ShippingMethod.php
|
|
19
|
+
- _underscore/Model.php
|
|
20
|
+
- _underscore/Cloud.php
|
|
21
|
+
related:
|
|
22
|
+
- ../architecture.md
|
|
23
|
+
- recursive-item-fulfillments.md
|
|
24
|
+
- ../../toga2-supply/features/fulfill-and-ship.md
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Summary
|
|
28
|
+
|
|
29
|
+
Backend mechanics behind TOGa Supply's Fulfill & Ship: buying a carrier label
|
|
30
|
+
(UPS/FedEx), persisting it, and creating the NetSuite Item Fulfillment with tracking
|
|
31
|
+
number and label attached. Includes the **authoritative label-storage architecture
|
|
32
|
+
decision** (dev lead Jeff, June 2026) that supersedes what the code currently does.
|
|
33
|
+
|
|
34
|
+
## Key files / entry points
|
|
35
|
+
|
|
36
|
+
- `upsShipmentApi` / `fedexShipmentApi` (scripted APIs on
|
|
37
|
+
`Model/Client/ItemFulfillment.php`): build the carrier request, call the carrier,
|
|
38
|
+
(currently) convert ZPL→PDF via Labelary, save the PDF to NetSuite File Cabinet +
|
|
39
|
+
the DB storage field, return `{ trackingNumber, pdfLabel, fileInternalId }` or
|
|
40
|
+
`{ error, trackingNumber }`. UPS path now checks `$shipmentResponse->success` and
|
|
41
|
+
fast-fails on an empty service code (it previously swallowed UPS errors and surfaced
|
|
42
|
+
a misleading "Failed to save PDF to NetSuite").
|
|
43
|
+
- `_Component_Library_Carriers_Ups::submitShipmentRequest`
|
|
44
|
+
(`Component/Library/Carriers/Ups/Ups.php`): returns `{ success, errorMessage,
|
|
45
|
+
trackingNumber, encodedLabel }`.
|
|
46
|
+
- `createNetsuiteItemFulfillment` (`Trait/Netsuite/ItemFulfillment.php`): builds the NS
|
|
47
|
+
IF from the SO, assigns serialized inventory, sets packages/tracking numbers.
|
|
48
|
+
**Returns the numeric NS internalId on success, an error string on failure**
|
|
49
|
+
(ambiguous return — callers must check `/^\d+$/`). Takes
|
|
50
|
+
`$labelFileInternalId = null` and attaches the label to the IF after creation
|
|
51
|
+
(`attachFileToRecord` was widened `private` → `protected` so the trait can call it
|
|
52
|
+
from client subclasses).
|
|
53
|
+
- The trait is composed into **client-specific** subclasses
|
|
54
|
+
(`_Model_Growrk_ItemFulfillment extends _Model_Client_ItemFulfillment`), not the base
|
|
55
|
+
model.
|
|
56
|
+
|
|
57
|
+
## Label-storage architecture (AUTHORITATIVE — dev lead Jeff, 2026-06)
|
|
58
|
+
|
|
59
|
+
Supersedes the Labelary-PDF + manual-S3 approach:
|
|
60
|
+
|
|
61
|
+
1. **Do NOT touch S3 by hand.** The model's `FIELD_STORAGE` abstraction, accessed via
|
|
62
|
+
the 2.0 API, already handles label fetch + storage.
|
|
63
|
+
2. **Store the raw carrier response as a GIF**, not a Labelary-converted PDF.
|
|
64
|
+
3. **Generate the printable PDF on demand**, combining the shipping label AND the
|
|
65
|
+
return label into one PDF (the browser print dialog handles one file at a time).
|
|
66
|
+
Leaning **backend** generation — `pdf-lib` (client-side) cannot embed GIF.
|
|
67
|
+
|
|
68
|
+
Not yet implemented. Open questions before building: backend vs frontend for the
|
|
69
|
+
GIF→PDF step; which existing 2.0 label-fetch/storage calls to build on. Combining
|
|
70
|
+
shipping + return also depends on the return-label flow existing — outbound-only can
|
|
71
|
+
come first.
|
|
72
|
+
|
|
73
|
+
## `FIELD_STORAGE` mechanics (reference)
|
|
74
|
+
|
|
75
|
+
`Model.php` handles storage fields separately from regular columns (write loop ~line
|
|
76
|
+
393; read ~line 730). Config keys in `[_underscore]`: `model_storage_folder` → local
|
|
77
|
+
folder mode (if set, S3 is never used); `model_storage_s3_bucket` +
|
|
78
|
+
`model_storage_s3_folder` → S3 mode. Storage key/path (both modes):
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
<folder-or-s3-prefix>/<env (_Environment::$name)>/<database>/<TABLE>/<field>/<primaryKeyId>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
e.g. `<prefix>/beta/Client_Growrk/TrackingNumbers/labelPdfFile/<id>` — named by numeric
|
|
85
|
+
PK, **no extension**. Reads return the raw bytes verbatim (no base64/data-uri); writes
|
|
86
|
+
store whatever value was assigned. `_Cloud::copyFileToS3` **re-throws** on AWS errors —
|
|
87
|
+
if no exception reached the caller and execution continued, the write was never
|
|
88
|
+
attempted.
|
|
89
|
+
|
|
90
|
+
## Gotchas / known issues
|
|
91
|
+
|
|
92
|
+
- UPS **test** endpoint (`wwwcie.ups.com`, debug mode) returns a constant tracking
|
|
93
|
+
number `1ZXXXXXXXXXXXXXXXX` + SAMPLE labels → `TrackingNumbers.number` UNIQUE
|
|
94
|
+
collision and File Cabinet duplicate-filename failures on repeat tests (see the
|
|
95
|
+
fulfill-and-ship doc for the cleanup SQL). Never in prod.
|
|
96
|
+
- `upsShipmentApi` reads ship-to from the **Sales Order** (`shipToAddressId`), not the
|
|
97
|
+
Item Fulfillment — form-edited addresses never reach UPS.
|
|
98
|
+
- UPS `AddressLine` max 35 chars/line; send an array of trimmed lines (FedEx already
|
|
99
|
+
does), not `line1 . ' ' . line2`.
|
|
100
|
+
- `ShippingMethods.code` is carrier-scoped: UPS numeric (`03` Ground, `01` Next Day
|
|
101
|
+
Air), FedEx strings (`FEDEX_GROUND`). Empty code → UPS error 120500.
|
|
102
|
+
- Serialized-inventory assignment fails ("Invalid issueinventorynumber reference key")
|
|
103
|
+
when the lookup isn't location-scoped and the SO has no Warehouse Location —
|
|
104
|
+
recommended: always scope `getInventoryNumberFromSerialNumber` by location.
|
|
105
|
+
- `createNetsuiteItemFulfillment` doesn't set carrier/method on the NS IF — it defaults
|
|
106
|
+
(can show FedEx for a UPS shipment). Open item.
|
|
107
|
+
|
|
108
|
+
## Related docs
|
|
109
|
+
|
|
110
|
+
- [Fulfill & Ship (toga2-supply)](../../toga2-supply/features/fulfill-and-ship.md) —
|
|
111
|
+
frontend flow and success gating.
|
|
112
|
+
- [Recursive Item Fulfillments](recursive-item-fulfillments.md) — interceptor-driven
|
|
113
|
+
upstream mirroring on the same models.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# toga2-supply (TOGa Supply) — 2.0 knowledge
|
|
2
|
+
|
|
3
|
+
| Doc | Summary | Files |
|
|
4
|
+
|-----|---------|-------|
|
|
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/api/UpdateShipmentApi.ts, toga2-supply/src/pages/Shipments/view/components/ShipmentsCardTableForm/ShipmentsCardTableForm.tsx, toga2-supply/src/pages/Shipments/api/ShipmentsApi.ts, _underscore/Model/Client/ItemFulfillment.php, _underscore/Trait/Netsuite/ItemFulfillment.php, _underscore/Component/Library/Carriers/Ups/Ups.php |
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: TOGa Supply (toga2-supply) Architecture
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: toga2-supply
|
|
5
|
+
project: TOGa Supply
|
|
6
|
+
client: shared
|
|
7
|
+
type: architecture
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-10
|
|
10
|
+
owners: [mhammontree]
|
|
11
|
+
files:
|
|
12
|
+
- toga2-supply/src/api/toga.ts
|
|
13
|
+
- toga2-supply/src/pages/ShipmentItems/view/ShipmentItemsPage.tsx
|
|
14
|
+
- toga2-supply/src/pages/EditShipment/view/EditShipmentPage.tsx
|
|
15
|
+
- toga2-supply/src/pages/EditShipment/api/UpdateShipmentApi.ts
|
|
16
|
+
- toga2-supply/src/pages/Shipments/view/components/ShipmentsCardTableForm/ShipmentsCardTableForm.tsx
|
|
17
|
+
related:
|
|
18
|
+
- ../_underscore/architecture.md
|
|
19
|
+
- ../api2/architecture.md
|
|
20
|
+
- features/fulfill-and-ship.md
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Summary
|
|
24
|
+
|
|
25
|
+
`toga2-supply` is the **React + Vite frontend** for TOGa Supply — warehouse fulfillment
|
|
26
|
+
tooling (shipment selection, fulfill & ship against carrier APIs, NetSuite Item
|
|
27
|
+
Fulfillment creation, label printing/reprinting). It is a pure frontend: all business
|
|
28
|
+
logic lives in the 2.0 API (`api2` engine over `_underscore` models). It has **no
|
|
29
|
+
backend code of its own** — `dependsOn: [api2]`.
|
|
30
|
+
|
|
31
|
+
## Topology (dev/test setup)
|
|
32
|
+
|
|
33
|
+
- The frontend runs **locally** on the developer machine (e.g.
|
|
34
|
+
`http://growrk.togasupply:5173`); it is not deployed to beta in this setup. Local
|
|
35
|
+
code edits take effect on reload.
|
|
36
|
+
- It calls the **beta** API at `https://api.beta.togahub.com`. Backend (`_underscore`)
|
|
37
|
+
changes therefore require a **deploy to beta** before they are observable — a symptom
|
|
38
|
+
can look "unfixed" simply because the backend fix isn't deployed yet.
|
|
39
|
+
- Client subdomain selects the tenant (e.g. `growrk.togasupply` → client GroWrk,
|
|
40
|
+
DB `Client_Growrk`).
|
|
41
|
+
|
|
42
|
+
## Shared API client — `src/api/toga.ts`
|
|
43
|
+
|
|
44
|
+
`togaApiRequest` resolves the API base from the hostname, auto-bootstraps auth
|
|
45
|
+
(`/auth/public` / `/auth/refresh`), and **retries once on 401**. The recurring 401s
|
|
46
|
+
seen at page load are this retry/refresh pattern (usually transient) — the real failure
|
|
47
|
+
mode is a non-401 status (400/business error) carrying a message.
|
|
48
|
+
|
|
49
|
+
Response envelope (2.0 API): `{ isSuccess, status, error, messages, data: { <route>:
|
|
50
|
+
{ <method>: ... } } }`. Scripted-API results nest as `data.<routeCamel>.<methodName>`.
|
|
51
|
+
|
|
52
|
+
## Route map
|
|
53
|
+
|
|
54
|
+
| Route | Page | Purpose |
|
|
55
|
+
|---|---|---|
|
|
56
|
+
| `/shipment-items?internalId=<NS SO id>` | `ShipmentItemsPage` | select SO line items; the **only** place the SO syncs from NetSuite into Toga; stores selection in `localStorage("selectedItems")` |
|
|
57
|
+
| `/edit-shipment?internalId=<id>` | `EditShipmentPage` → `EditShipmentForm` | serials, carrier, method → **Fulfill & Ship** |
|
|
58
|
+
| `/shipments`, `/fulfilled-shipments` | `ShipmentsCardTableForm` | pending vs fulfilled views; reprint lives here |
|
|
59
|
+
|
|
60
|
+
## Key decisions
|
|
61
|
+
|
|
62
|
+
- Carrier dropdown filters to FEDEX/UPS carriers that have ≥1 account number.
|
|
63
|
+
- Success/failure gating: a fulfillment is only "green" when the carrier returned a real
|
|
64
|
+
tracking number AND NetSuite returned a numeric internalId (`/^\d+$/`) — see the
|
|
65
|
+
fulfill-and-ship feature doc.
|
|
66
|
+
- Browser print: the print dialog handles one file at a time and `window.print()` fires
|
|
67
|
+
once per session — hence the combined-PDF reprint design. Popups must be allowed
|
|
68
|
+
(label window is `window.open`).
|
|
69
|
+
|
|
70
|
+
## Related docs
|
|
71
|
+
|
|
72
|
+
- [Fulfill & Ship feature](features/fulfill-and-ship.md) — the end-to-end flow, bugs, gotchas.
|
|
73
|
+
- [Carrier shipping labels (_underscore)](../_underscore/features/carrier-shipping-labels.md) — backend label + NetSuite mechanics.
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Fulfill & Ship
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: toga2-supply
|
|
5
|
+
project: TOGa Supply
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-10
|
|
10
|
+
owners: [mhammontree]
|
|
11
|
+
files:
|
|
12
|
+
- toga2-supply/src/pages/ShipmentItems/view/ShipmentItemsPage.tsx
|
|
13
|
+
- toga2-supply/src/pages/EditShipment/view/EditShipmentPage.tsx
|
|
14
|
+
- toga2-supply/src/pages/EditShipment/view/components/forms/EditShipmentForm.tsx
|
|
15
|
+
- toga2-supply/src/pages/EditShipment/api/UpdateShipmentApi.ts
|
|
16
|
+
- toga2-supply/src/pages/Shipments/view/components/ShipmentsCardTableForm/ShipmentsCardTableForm.tsx
|
|
17
|
+
- toga2-supply/src/pages/Shipments/api/ShipmentsApi.ts
|
|
18
|
+
- _underscore/Model/Client/ItemFulfillment.php
|
|
19
|
+
- _underscore/Trait/Netsuite/ItemFulfillment.php
|
|
20
|
+
- _underscore/Component/Library/Carriers/Ups/Ups.php
|
|
21
|
+
related:
|
|
22
|
+
- ../architecture.md
|
|
23
|
+
- ../../_underscore/features/carrier-shipping-labels.md
|
|
24
|
+
- ../../_underscore/features/recursive-item-fulfillments.md
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Summary
|
|
28
|
+
|
|
29
|
+
Fulfill & Ship lets a warehouse user select sales-order line items, enter serials,
|
|
30
|
+
pick a carrier/method, and in one action: create the Item Fulfillment records in Toga,
|
|
31
|
+
buy a carrier label (UPS/FedEx), and create the NetSuite Item Fulfillment with the
|
|
32
|
+
tracking number and label attached. Driven green end-to-end on beta (UPS, client
|
|
33
|
+
GroWrk, June 2026); FedEx + UPS both need verification before prod.
|
|
34
|
+
|
|
35
|
+
## How it works
|
|
36
|
+
|
|
37
|
+
1. `/shipment-items` load: `checkIfSalesOrderExists(internalId)` (GET `/sales-orders`
|
|
38
|
+
by `c_netsuiteInternalSalesOrderId`); if missing →
|
|
39
|
+
`GET /sales-orders/syncNetsuiteSalesOrder` then `syncNetsuiteItemFulfillmentWithToga`.
|
|
40
|
+
**This is the only place the SO syncs from NetSuite into Toga.**
|
|
41
|
+
2. `/edit-shipment` submit (`onFormSubmit("fulfillAndShip")` in `EditShipmentForm.tsx`):
|
|
42
|
+
- `saveShipment` → POST `/item-fulfillments`, `/tracking-numbers`,
|
|
43
|
+
`/item-fulfillment-packages`.
|
|
44
|
+
- `fulfillShipment` → GET `/item-fulfillments/upsShipmentApi` (or `fedexShipmentApi`)
|
|
45
|
+
for the label, then `saveShipmentToNetsuite` →
|
|
46
|
+
GET `/item-fulfillments/createNetsuiteItemFulfillment`.
|
|
47
|
+
- `handleShipmentDownload` opens the label PDF and triggers print.
|
|
48
|
+
3. **Success gating**: `fulfillShipment` returns `{ success, error, data }` and requires
|
|
49
|
+
BOTH a real tracking number (no `error`) AND a numeric NetSuite internalId
|
|
50
|
+
(`/^\d+$/` — `createNetsuiteItemFulfillment` returns the numeric internalId on
|
|
51
|
+
success but an **error string** on failure). Both callers gate the success toaster
|
|
52
|
+
on `success`.
|
|
53
|
+
4. The label `fileInternalId` is passed through `fulfillShipment` →
|
|
54
|
+
`saveShipmentToNetsuite` → `createNetsuiteItemFulfillment`, which attaches the label
|
|
55
|
+
to the IF **after** creating it (the IF doesn't exist yet when the label is bought).
|
|
56
|
+
|
|
57
|
+
## Reprint (first pass — being reworked)
|
|
58
|
+
|
|
59
|
+
Fulfilled-shipments view: multi-select shipments, merge each stored label into one
|
|
60
|
+
multi-page PDF with `pdf-lib`, print once. **Superseded by the label-storage decision**
|
|
61
|
+
(see the carrier-shipping-labels doc): labels will be stored as raw carrier GIFs and
|
|
62
|
+
the combined PDF generated on demand — note `pdf-lib` cannot embed GIF, so generation
|
|
63
|
+
likely moves to the backend.
|
|
64
|
+
|
|
65
|
+
## Gotchas / known issues
|
|
66
|
+
|
|
67
|
+
- **UPS test endpoint** (`wwwcie.ups.com`, debug mode) returns a constant tracking
|
|
68
|
+
number `1ZXXXXXXXXXXXXXXXX` + SAMPLE labels. On 2nd+ test runs this collides with the
|
|
69
|
+
`TrackingNumbers.number` UNIQUE index (surfaces mislabeled as "Labelary API error")
|
|
70
|
+
AND NetSuite File Cabinet's unique filename (`ShippingLabel_<tracking>.pdf`). Free
|
|
71
|
+
both between runs:
|
|
72
|
+
`UPDATE TrackingNumbers SET number = CONCAT(number,'-',id) WHERE number='1ZXXXXXXXXXXXXXXXX'`
|
|
73
|
+
and delete/rename the prior File Cabinet file. Never happens in prod.
|
|
74
|
+
- **Address source mismatch**: `upsShipmentApi` reads ship-to from the **Sales Order**
|
|
75
|
+
(`SalesOrder.shipToAddressId`), NOT the address edited in the form (the IF's) — form
|
|
76
|
+
address edits never reach the carrier. Open decision: read the IF address instead.
|
|
77
|
+
- **UPS address limits**: `AddressLine` max 35 chars/line; code currently sends
|
|
78
|
+
`line1 + ' ' + line2` as one line — should send a trimmed array (FedEx path already
|
|
79
|
+
does).
|
|
80
|
+
- **Service codes are carrier-scoped**: UPS numeric (`03`=Ground, `01`=Next Day Air),
|
|
81
|
+
FedEx strings (`FEDEX_GROUND`), in `ShippingMethods.code`. Empty code → UPS 120500;
|
|
82
|
+
there is now a fast-fail guard.
|
|
83
|
+
- **Serialized inventory needs a location**: a SO with no Warehouse Location makes
|
|
84
|
+
NetSuite reject the serial issue ("Invalid issueinventorynumber reference key").
|
|
85
|
+
Recommended hardening: always scope `getInventoryNumberFromSerialNumber` by location.
|
|
86
|
+
- **EV-12 on POST /item-fulfillments** means the SO isn't synced into Toga yet — fire
|
|
87
|
+
`GET /sales-orders/syncNetsuiteSalesOrder?netsuiteInternalSalesOrderId=<id>`.
|
|
88
|
+
- **Carrier on the NS IF defaults wrong**: `createNetsuiteItemFulfillment` doesn't set
|
|
89
|
+
carrier/method, so the IF can show FedEx while the tracking number is UPS. Open item.
|
|
90
|
+
- Real carrier errors live in the client-logs `Api` table (DIRECTION `OUT`,
|
|
91
|
+
`hostname LIKE '%ups.com%'`, `responsePayload`).
|
|
92
|
+
- React warning: `setState`-in-render in `ShipmentItemsTable.tsx` (~line 149,
|
|
93
|
+
`clearErrors` inside a `setRowsClicked` updater) — move out of the updater.
|
|
94
|
+
|
|
95
|
+
## Remaining work
|
|
96
|
+
|
|
97
|
+
- Label storage + reprint rework per the carrier-shipping-labels doc (raw GIF +
|
|
98
|
+
on-demand combined PDF).
|
|
99
|
+
- Return-label flow: `returnTrackingNumberId` (nullable) on `ItemFulfillmentItemUnits`;
|
|
100
|
+
"needs return label" checkbox; return-address editable combo ("Rolodex pattern" of
|
|
101
|
+
client locations, overridable, validated); "return label" text on return pages of the
|
|
102
|
+
combined PDF.
|
|
103
|
+
- Address-source decision, UPS 35-char hardening, location-scoped inventory lookup,
|
|
104
|
+
carrier/method on the NS IF, responsiveness per Figma, FedEx + UPS end-to-end
|
|
105
|
+
verification before prod.
|
|
106
|
+
|
|
107
|
+
## Client variations
|
|
108
|
+
|
|
109
|
+
None in the flow itself — but the NetSuite trait is composed into **client-specific**
|
|
110
|
+
model subclasses (e.g. `_Model_Growrk_ItemFulfillment uses _Trait_Netsuite_ItemFulfillment`),
|
|
111
|
+
not the base `_Model_Client_ItemFulfillment`. Tested with GroWrk; UPS support was built
|
|
112
|
+
for Compass and is not yet in prod.
|
package/knowledge/INDEX.md
CHANGED
|
@@ -9,10 +9,11 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
9
9
|
|
|
10
10
|
## 2.0 framework
|
|
11
11
|
|
|
12
|
-
- **_underscore** (_Underscore) _(framework core)_ —
|
|
12
|
+
- **_underscore** (_Underscore) _(framework core)_ — 3 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
|
|
13
13
|
- **worker2** (Worker) — 3 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
|
|
14
14
|
- **api2** (API) — 1 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
|
|
15
15
|
- **dbchanges2** (Database Changes) _(framework core)_ — 1 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
|
|
16
|
+
- **toga2-supply** (TOGa Supply) — 2 doc(s) → [2.0/apps/toga2-supply/INDEX.md](2.0/apps/toga2-supply/INDEX.md)
|
|
16
17
|
|
|
17
18
|
## Clients
|
|
18
19
|
|
package/knowledge/registry.json
CHANGED
|
@@ -4,5 +4,6 @@
|
|
|
4
4
|
{ "repo": "api2", "project": "API", "framework": "2.0", "role": "app", "dependsOn": [] },
|
|
5
5
|
{ "repo": "dbchanges2", "project": "Database Changes", "framework": "2.0", "role": "core", "dependsOn": [] },
|
|
6
6
|
{ "repo": "library", "project": "Library", "framework": "1.0", "role": "core", "dependsOn": [] },
|
|
7
|
-
{ "repo": "worker", "project": "Worker", "framework": "1.0", "role": "app", "dependsOn": [] }
|
|
7
|
+
{ "repo": "worker", "project": "Worker", "framework": "1.0", "role": "app", "dependsOn": [] },
|
|
8
|
+
{ "repo": "toga2-supply", "project": "TOGa Supply", "framework": "2.0", "role": "app", "dependsOn": ["api2"] }
|
|
8
9
|
]
|
package/package.json
CHANGED
package/scripts/install.js
CHANGED
|
@@ -661,13 +661,28 @@ function main() {
|
|
|
661
661
|
const ok = mergeSettings(claudeDir, harnessDir);
|
|
662
662
|
console.log(' ' + (ok ? '✓' : '✗') + ' Hooks (' + hooksCount + ' scripts in .claude/hooks/toga/, settings.json merged)');
|
|
663
663
|
|
|
664
|
-
//
|
|
664
|
+
// Contexts — mode context files (dev/research/review), update if changed
|
|
665
|
+
const contextsSrc = path.join(harnessDir, 'contexts');
|
|
666
|
+
const contextsDest = path.join(claudeDir, 'contexts', 'toga');
|
|
667
|
+
if (fs.existsSync(contextsSrc)) {
|
|
668
|
+
let contextsStats = { added: 0, updated: 0, unchanged: 0 };
|
|
669
|
+
try { contextsStats = copyDir(contextsSrc, contextsDest, { updateIfChanged: true }); }
|
|
670
|
+
catch (e) { errors.push('contexts: ' + e.message); }
|
|
671
|
+
console.log(' ✓ Contexts (' + countMd(contextsDest) + '): ' + fmtStats(contextsStats));
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// MCP example — update if changed so teammates get new server configs
|
|
665
675
|
const mcpSrc = path.join(harnessDir, 'mcp-configs', 'mcp-servers.json');
|
|
666
676
|
const mcpDest = path.join(claudeDir, 'mcp-servers.example.json');
|
|
667
|
-
if (fs.existsSync(mcpSrc)
|
|
668
|
-
try {
|
|
669
|
-
|
|
670
|
-
|
|
677
|
+
if (fs.existsSync(mcpSrc)) {
|
|
678
|
+
try {
|
|
679
|
+
const firstInstall = !fs.existsSync(mcpDest);
|
|
680
|
+
const incoming = fs.readFileSync(mcpSrc);
|
|
681
|
+
if (firstInstall || !incoming.equals(fs.readFileSync(mcpDest))) {
|
|
682
|
+
fs.copyFileSync(mcpSrc, mcpDest);
|
|
683
|
+
console.log(' ✓ MCP (.claude/mcp-servers.example.json ' + (firstInstall ? 'created' : 'updated') + ' — merge into .mcp.json to enable)');
|
|
684
|
+
}
|
|
685
|
+
} catch (e) { errors.push('mcp: ' + e.message); }
|
|
671
686
|
}
|
|
672
687
|
|
|
673
688
|
// Knowledge — git repo if available (has team-captured docs), else npm bundle
|
|
@@ -697,6 +712,26 @@ function main() {
|
|
|
697
712
|
}
|
|
698
713
|
console.log(' ✓ Knowledge (' + countMd(knowledgeDest) + ' docs): ' + fmtStats(knowledgeStats));
|
|
699
714
|
|
|
715
|
+
// registry.json — the repo↔project↔framework map every skill depends on.
|
|
716
|
+
// Force-sync explicitly from the newest source (git if pulled, else bundle) so a
|
|
717
|
+
// partial knowledge copy can never leave a machine without/with a stale registry.
|
|
718
|
+
const registrySrc = path.join(knowledgeDir, 'knowledge', 'registry.json');
|
|
719
|
+
const registryDest = path.join(knowledgeDest, 'registry.json');
|
|
720
|
+
if (fs.existsSync(registrySrc)) {
|
|
721
|
+
try {
|
|
722
|
+
fs.copyFileSync(registrySrc, registryDest);
|
|
723
|
+
let repoCount = 0;
|
|
724
|
+
try {
|
|
725
|
+
const reg = JSON.parse(fs.readFileSync(registryDest, 'utf8'));
|
|
726
|
+
repoCount = Array.isArray(reg) ? reg.length : 0;
|
|
727
|
+
} catch (e) {}
|
|
728
|
+
console.log(' ✓ Registry (' + repoCount + ' repos): synced from ' + (knowledgeDir !== PACKAGE_ROOT ? 'git repo' : 'npm bundle'));
|
|
729
|
+
} catch (e) { errors.push('registry.json: ' + e.message); }
|
|
730
|
+
} else {
|
|
731
|
+
errors.push('registry.json: missing from source ' + registrySrc);
|
|
732
|
+
console.log(' ✗ Registry MISSING from source — kickoff will not work. Re-run: npm cache clean --force && npx toga-ai@latest');
|
|
733
|
+
}
|
|
734
|
+
|
|
700
735
|
// CLAUDE.md
|
|
701
736
|
const action = updateClaudeMd(projectRoot, skillNames, harnessDir, 'npm bundle v' + bundleVersion);
|
|
702
737
|
console.log(' ✓ CLAUDE.md ' + action);
|
package/skills/kickoff/SKILL.md
CHANGED
|
@@ -5,7 +5,18 @@ description: Start-of-session context loader for TOGA Technology projects. Run t
|
|
|
5
5
|
|
|
6
6
|
# Kickoff — prime a coding session from the team knowledge base
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## Arguments — text passed after `/kickoff` never skips any step
|
|
9
|
+
|
|
10
|
+
`/kickoff` may be invoked with trailing text (e.g. `/kickoff worker2 backend fix for Compass`).
|
|
11
|
+
That text is the developer's description of today's work — it is **not** permission to
|
|
12
|
+
shortcut the flow.
|
|
13
|
+
|
|
14
|
+
- **Step 0 (auto-update check) ALWAYS runs first**, with or without arguments.
|
|
15
|
+
- Use the argument text to **pre-fill answers** to the Step 2 interview (framework, layer,
|
|
16
|
+
repo, client, task). Only ask about whatever is still missing or ambiguous.
|
|
17
|
+
- Never treat the argument as an instruction to start coding before Steps 0–5 complete.
|
|
18
|
+
|
|
19
|
+
## Step 0 — Auto-update check (runs before anything else, even with arguments)
|
|
9
20
|
|
|
10
21
|
Before loading any context, check whether the installed harness is up to date:
|
|
11
22
|
|
|
@@ -20,17 +31,33 @@ LATEST=$(node -e "const {execSync}=require('child_process');try{process.stdout.w
|
|
|
20
31
|
|
|
21
32
|
**If `INSTALLED` equals `LATEST`** — already up to date. Continue to Step 1.
|
|
22
33
|
|
|
23
|
-
**If `INSTALLED` differs from `LATEST`** — auto-upgrade now, then
|
|
34
|
+
**If `INSTALLED` differs from `LATEST`** — auto-upgrade now, then **verify** before continuing:
|
|
24
35
|
|
|
25
36
|
```bash
|
|
26
37
|
npx toga-ai@latest
|
|
27
38
|
```
|
|
28
39
|
|
|
29
|
-
|
|
30
|
-
|
|
40
|
+
After the upgrade, re-read the version file to confirm it actually updated — npx can
|
|
41
|
+
serve a stale cached package (common on Windows), in which case the installer ran but
|
|
42
|
+
installed the old version again:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
NOW=$(cat .claude/toga-ai.version 2>/dev/null || echo "unknown")
|
|
46
|
+
```
|
|
31
47
|
|
|
32
|
-
If
|
|
33
|
-
> "
|
|
48
|
+
- **If `NOW` equals `LATEST`** — success. Tell the developer:
|
|
49
|
+
> "Updated toga-ai from v`INSTALLED` → v`LATEST`. Skills, agents, knowledge, and registry refreshed."
|
|
50
|
+
- **If `NOW` still differs from `LATEST`** — stale npx cache. Retry once with a clean cache:
|
|
51
|
+
```bash
|
|
52
|
+
npm cache clean --force && npx toga-ai@latest
|
|
53
|
+
```
|
|
54
|
+
Re-read the version file again. If it now equals `LATEST`, report success as above.
|
|
55
|
+
- **If still stale after the cache-clean retry, or the upgrade command failed** — warn and
|
|
56
|
+
continue; a stale install beats a blocked session:
|
|
57
|
+
> "⚠ Auto-update to v`LATEST` failed (still on v`NOW`). Run `npm cache clean --force && npx toga-ai@latest` manually when convenient."
|
|
58
|
+
|
|
59
|
+
Never report the update as done based on the command exiting cleanly — only the version
|
|
60
|
+
file equaling `LATEST` counts as success.
|
|
34
61
|
|
|
35
62
|
|
|
36
63
|
|