A 3-section team dashboard for everything currently open in NetSuite: open sales orders, open purchase orders, open work orders. Built as a NetSuite Suitelet (so it lives in NS for the team) with a Pages mirror at /open-orders.html for mobile / offline / fast-render. Both reference the same D1 data, refreshed every 2 minutes.
Mike's words: “I'm also going to probably make an ‘open orders’ Suitelet with sections for sales orders, one section for purchase orders and one section for work orders. This way my team could easily see the system as well I guess it could either be a dashboard or a suitelet either really.” This wiki picks the recommendation: build it as a Suitelet (NS-native, accessible from the NS menu / portlet on Home) with a Pages mirror at /open-orders.html (D1-backed, mobile-friendly, faster). Both surfaces read from the same D1 mirror that already syncs every 2 minutes (R121 tier-2 transactions sync). Three lanes, three audiences: SO for sales/CS, PO for purchasing, WO for production. Inline buttons (approve / hold / escalate) all stage into proposed_actions per ADR-031 — nothing writes to NS directly. The team gets a single pane of glass, Mike keeps the HITL invariant.
A v2 workflow contract (open_orders_suitelet, risk 1) that powers two surfaces:
customscript_gfs_open_orders (script id TBD pending Mike approval). Lives in the NS menu and/or as a Home dashboard portlet.https://gfs-netsuite.pages.dev/open-orders.html. Same D1 data, plain HTML, faster for mobile + offline.It is NOT a write surface in itself. Every action button stages into proposed_actions. The existing ns_push_queue drainer handles the NS write once HITL approves.
It is NOT a replacement for individual SO / PO / WO record drill-down — rows link out to NS records. It's a triage queue, not a replacement for NS.
| Trigger | Source | Condition |
|---|---|---|
| User opens Suitelet | NS menu / Home portlet | GET on customscript_gfs_open_orders |
| User opens Pages mirror | Browser navigation | GET /open-orders.html |
| Cron tick | Cloudflare scheduled() every 2 min | Tier-2 transactions sync refreshes D1 mirror |
| Manual refresh | "Refresh" button on Suitelet header | POST /api/open-orders/refresh · rate-limited 1/15s per user |
| NS user-event on SO/PO/WO save | NS save handler | Triggers incremental sync of the just-saved record (existing R121 path) |
type IN ('SalesOrd','PurchOrd','WorkOrd') AND status not closed.proposed_actions per ADR-031./netsuite/redirect/{type}/{id}.open_orders.dashboard_loaded + per-action open_orders.action_staged events; write workflow_run_log + reflexion_log row.WorkflowDefinition + per-row Order entities so chat can ingest the dashboard schema directly.| Mode | Symptom | Recovery |
|---|---|---|
| D1 sync stale > 5 min | Freshness gate blocks; banner shows “Data is N minutes stale” | Force refresh button invokes /api/open-orders/refresh; if sync still fails, surfaces on admin-dashboard “Needs your attention” |
| SuiteQL timeout | SuiteAPI query exceeds 30s | Fall back to D1 mirror only (last successful sync); banner shows the staleness |
| Action staged but NS push fails | proposed_actions.status = approved but ns_push_queue row keeps retrying | Retry handled by existing ns_push_queue drainer (R121 path); alerts after 3 failures |
| Filter returns empty | User sets filter to a combo with no rows | Show “No open orders match these filters” + "Clear filters" button |
| Manual refresh rate limited | User clicks Refresh too fast | Toast: “wait 15s before next refresh” |
| WO memo missing customer PO# | Section 3 row shows blank memo | Confirm with Mike whether WO carries it; until then, link parent SO via createdfrom |
workflow_type: open_orders_suitelet
risk_level: 1
trigger: cron every 2 min OR manual refresh button OR NS user-event on SO/PO/WO save
inputs_required:
refresh_scope: ['all', 'sales_orders', 'purchase_orders', 'work_orders']
filter:
status: optional
customer: optional (SO section)
vendor: optional (PO section)
item: optional (WO section, assembly item)
location: optional
date_from: optional
date_to: optional
context_to_load:
- sql: SELECT id, type, tranid, entity, status, total, trandate,
otherrefnum, memo, createdby
FROM transactions
WHERE type IN ('SalesOrd','PurchOrd','WorkOrd')
AND status NOT IN ('Closed','Cancelled','Billed',
'Fully Received','Fully Fulfilled','Built and Closed')
- sql: customer_invoices (for SO progress)
- sql: item_receipts (for PO progress)
- sql: assembly_builds (for WO progress)
- sql: transaction_lines (for item_count)
preconditions:
- D1 tables fresh within 5 minutes (else trigger manual sync first)
- source_record_exists for any inline action
fan_out_targets:
- load_open_sales_orders (chat_tool / d1_read)
- load_open_purchase_orders (chat_tool / d1_read)
- load_open_work_orders (chat_tool / d1_read)
- render_three_section_view (chat_tool)
- enable_action_buttons (chat_tool — wires approve/hold/escalate to proposed_actions)
post_actions:
- log_run
- event(open_orders.dashboard_loaded)
- event(open_orders.row_drilled) — per row drill click
- event(open_orders.action_staged) — per action button click
- reflexion (tracks which sections users interact with most)
verify_checks:
- data_age_within_5min (+0s)
- all_three_sections_rendered (+0s)
HITL:
All NS writes (approve / hold / escalate outcomes) flow through proposed_actions per ADR-031.
No direct NS write from the Suitelet itself.
NS Suitelet:
Script id: customscript_gfs_open_orders (TBD pending Mike approval)
Deployed Suitelet record: customdeploy_gfs_open_orders (TBD)
Portlet placement: Home dashboard portlet (TBD: confirm w/ Mike)
Pages mirror:
Path: /open-orders.html
Backed by: D1 via Pages Functions (no caching, live read)
JSON-LD: inline WorkflowDefinition + per-row Order entities
| Date | Round | Change | By |
|---|---|---|---|
2026-05-26 | R595 | Built open_orders_suitelet workflow contract (migration 135). Animated SVG diagram (15 nodes, 3-lane SO/PO/WO swimlanes). Pages mirror at /open-orders.html. Suitelet script id (customscript_gfs_open_orders), deployed Suitelet record, and Home portlet placement flagged TBD pending Mike approval. WO customer PO# threading flagged TBD. | Mike + Claude |
| Section | NS field that carries customer PO# | Notes |
|---|---|---|
| SO | bodyFields.otherrefnum | Standard threading invariant from SO entry; threads to Item Fulfillment + Invoice |
| PO (Path 1 SO-connected) | bodyFields.memo + createdfrom = so.id | Customer PO# copied from parent SO at PO create time |
| PO (Path 2 inventory/build) | internal req# in bodyFields.memo | Does NOT carry a customer PO# — tag row "Path 2 / internal" so the team knows |
| WO TBD | bodyFields.memo (presumed) | Mike to confirm whether WO carries customer PO# directly, or only via the parent SO link on Path 1 |
| Table | Role |
|---|---|
transactions | SO + PO + WO source records (status NOT closed) |
transaction_lines | item_count per row + drill-in |
customer_invoices | SO fulfilled progress |
item_receipts | PO received progress |
assembly_builds | WO built progress |
sync_status | last_success_at[transactions] — freshness gate |
proposed_actions | HITL queue for approve/hold/escalate |
workflow_run_log | per-load audit row |
events | open_orders.* event stream |
reflexion_log | section_interactions analytics |
Apply migration:
npx wrangler d1 execute gfs-netsuite --remote --file migrations/schema/135_open_orders_suitelet.sql
Verify contract:
npx wrangler d1 execute gfs-netsuite --remote --command "SELECT workflow_type, display_name, risk_level, contract_version FROM workflow_definitions WHERE workflow_type = 'open_orders_suitelet';"
Manual refresh (test):
curl -X POST https://api.ai-globalfoodsolutions.co/api/open-orders/refresh \
-H "X-Edit-Token: $TOKEN" \
-d '{"refresh_scope":"all"}'
Open the Pages mirror:
https://gfs-netsuite.pages.dev/open-orders.html
Open the NS Suitelet (once customscript_gfs_open_orders is deployed):
NS → Customization → Scripting → Scripts → "GFS Open Orders" → Deployments → opens at /app/site/hosting/scriptlet.nl?script=customscript_gfs_open_orders&deploy=customdeploy_gfs_open_orders
Roll back (remove contract only — tables stay):
npx wrangler d1 execute gfs-netsuite --remote --command "DELETE FROM workflow_definitions WHERE workflow_type = 'open_orders_suitelet';"
customscript_gfs_open_orders Suitelet script id + deployed Suitelet record/open-orders.html (Pages mirror) — current pass is desktop-leaning