NetSuite open orders Suitelet

User opens → cron + manual refresh → SuiteQL pulls open SO/PO/WO → group by status → render 3 sections → inline actions (approve / hold / escalate / drill) → HITL via proposed_actions

Team-facing "open orders" surface. Built as a NS Suitelet (NS-native, customscript_gfs_open_orders, lives in NS menu / portlet on home dashboard) with a Pages mirror at /open-orders.html (D1-backed, faster for mobile / offline). Both reference the same D1 data refreshed every 2 minutes via cron. Three sections render in parallel: open Sales Orders (Pending Fulfillment / Partially Fulfilled / On Hold), open Purchase Orders (Pending Receipt / Partially Received / Pending Bill), open Work Orders (Released / In Progress / Built but Not Closed). Each row shows the customer PO# thread (otherrefnum on SO, memo on PO Path 1, internal req# on PO Path 2, memo on WO — TBD). Inline buttons (approve / hold / escalate) all stage into proposed_actions per ADR-031 — nothing writes to NS directly. Filters across the top: status, customer/vendor, date range, location. LLM-friendly: each section emits JSON-LD WorkflowDefinition markup so chat can ingest the dashboard schema.

D1-mirrored from NS NS Suitelet + Pages mirror section 1 · SO section 2 · PO section 3 · WO customscript_gfs_open_orders id pending Mike

Pipeline — user load → refresh → group → render → act

idle
layers
WHAT THIS DOES: This is the ns open orders suitelet — user load or cron tick → freshness gate → suiteql pull → 3-lane fan-out (open so/po/wo group + render) → inline action buttons stage into proposed_actions → audit event fired step. It happens as part of the work-order / order-integration chain and produces the data, status change, or record described below. NS RECORD: see TECHNICAL DETAILS TECHNICAL DETAILS: NS open orders Suitelet — user load OR cron tick → freshness gate → SuiteQL pull → 3-lane fan-out (open SO/PO/WO group + render) → inline action buttons stage into proposed_actions → audit event fired 01 / Entry · user opens Suitelet OR cron tick (every 2 min) OR manual refresh 02 / Data freshness gate · D1 sync within 5 min 03 / Pull open transactions via SuiteQL + apply filter bar 04a / Open SO group & render 04b / Open PO group & render 04c / Open WO group & render 05 / Inline action buttons · stage into proposed_actions (HITL) 06 / Audit + observability · event(open_orders.dashboard_loaded) + reflexion WHAT THIS DOES: This is the user opens suitelet — trigger source 1 of 3. step. It happens as part of the work-order / order-integration chain and produces the data, status change, or record described below. NS RECORD: see TECHNICAL DETAILS TECHNICAL DETAILS: USER OPENS SUITELET — Trigger source 1 of 3. SOURCE NetSuite menu → "Open Orders" → customscript_gfs_open_orders OR Home dashboard portlet "Open Orders" (TBD: portlet placement) OR Pages mirror: /open-orders.html (D1-backed, mobile-friendly) NS USER-EVENT None — Suitelet is GET on entry; refresh via internal button PARAMS ?scope=all|sales_orders|purchase_orders|work_orders ?status=, ?customer=, ?vendor=, ?location=, ?date_from=, ?date_to= STATUS: REAL (Suitelet script id TBD pending Mike approval) User opens Suitelet customscript_gfs_open_orders (TBD) OR /open-orders.html (Pages mirror) team accesses from NS menu / portlet FRONTEND · Suitelet GET + Pages mirror i WHAT THIS DOES: This is the cron tick — trigger source 2 of 3. refreshes d1 mirror so the suitelet has fresh data. step. It happens as part of the work-order / order-integration chain and produces the data, status change, or record described below. NS RECORD: see TECHNICAL DETAILS TECHNICAL DETAILS: CRON TICK — Trigger source 2 of 3. Refreshes D1 mirror so the Suitelet has fresh data. CRON every 2 minutes (uses existing transactions tier-2 sync) TARGET syncTier('transactions') — SuiteAPI pull of SalesOrd + PurchOrd + WorkOrd open-status filter applied at sync time to keep mirror narrow SIDE EFFECTS Updates transactions table; updates assembly_builds, item_receipts, customer_invoices for progress calculation on each section STATUS: REAL · piggybacks on existing tier-2 sync Cron tick (every 2 min) tier-2 sync (transactions) refreshes SO + PO + WO + progress tables keeps D1 mirror within 5 min freshness MESSAGEBUS · scheduled() handler i WHAT THIS DOES: This is the manual refresh — trigger source 3 of 3. user clicks the "refresh" button on the suitelet header. step. It happens as part of the work-order / order-integration chain and produces the data, status change, or record described below. NS RECORD: see TECHNICAL DETAILS TECHNICAL DETAILS: MANUAL REFRESH — Trigger source 3 of 3. User clicks the "Refresh" button on the Suitelet header. ENDPOINT POST /api/open-orders/refresh → invokes syncTier('transactions') on-demand → returns last_sync_at + row counts per section USE CASE User just keyed a new SO and wants to see it on the queue without waiting for the next cron tick. RATE LIMIT 1 manual refresh per 15s per user (debounce on UI button) STATUS: REAL · piggybacks on /api/sync-tier endpoint Manual refresh button POST /api/open-orders/refresh on-demand sync invoke rate-limited 1/15s · returns row counts EXTERNAL · user-initiated i WHAT THIS DOES: This is the data freshness gate — block precondition. suitelet must have fresh enough d1 data. step. It happens as part of the work-order / order-integration chain and produces the data, status change, or record described below. NS RECORD: see TECHNICAL DETAILS TECHNICAL DETAILS: DATA FRESHNESS GATE — Block precondition. Suitelet must have fresh enough D1 data. LOGIC IF (now() - sync_status.last_success_at[transactions]) < 5 min THEN proceed (data fresh) ELSE force manual sync first → block render until refreshed SOURCE sync_status table (per-tier last_success_at + last_error) DEGRADED MODE If sync has been failing > 15 min, show banner: "Data is N minutes stale" + force refresh button STATUS: REAL · precondition data freshness gate (now - last_success_at[transactions]) < 5 min block render until D1 mirror current degraded: banner + force refresh button PRECONDITION · block-severity i WHAT THIS DOES: This is the suiteql — open transactions pull step. It happens as part of the work-order / order-integration chain and produces the data, status change, or record described below. NS RECORD: see TECHNICAL DETAILS TECHNICAL DETAILS: SuiteQL — open transactions pull SQL SELECT id, type, tranid, entity, status, total, trandate, otherrefnum, memo, custbody_open_qty FROM transactions WHERE type IN ('SalesOrd','PurchOrd','WorkOrd') AND status NOT IN ('Closed','Cancelled','Billed','Fully Received', 'Fully Fulfilled','Built and Closed') ORDER BY trandate DESC TABLES READ transactions, transaction_lines (per row drill-in) NOTE Open-status enum differs by type: SO: Pending Fulfillment / Partially Fulfilled / On Hold PO: Pending Receipt / Partially Received / Pending Bill WO: Released / In Progress / Built (not closed) STATUS: REAL SuiteQL — open transactions type IN (SalesOrd, PurchOrd, WorkOrd) status NOT closed/cancelled otherrefnum + memo threading captured DATABASE · transactions read i WHAT THIS DOES: This is the filter bar — apply user filters step. It happens as part of the work-order / order-integration chain and produces the data, status change, or record described below. NS RECORD: see TECHNICAL DETAILS TECHNICAL DETAILS: Filter bar — apply user filters INPUTS (querystring or form post) status: multi-select (Pending Fulfillment, Pending Receipt, etc.) customer: internal id of customer (SO section only) vendor: internal id of vendor (PO section only) item: assembly item internal id (WO section only) location: warehouse id date_from / date_to: trandate range SIDE EFFECTS Filters trim the result set per-section; counts shown on section headers STATUS: REAL filter bar · user-applied status · customer/vendor/item · location date range (trandate) per-section counts updated on filter change DATABASE · filtered subset i WHAT THIS DOES: This is the per-row enrichment — progress + value step. It happens as part of the work-order / order-integration chain and produces the data, status change, or record described below. NS RECORD: see TECHNICAL DETAILS TECHNICAL DETAILS: Per-row enrichment — progress + value JOINS SO: LEFT JOIN customer_invoices ON createdfrom = so.id (fulfilled %) PO: LEFT JOIN item_receipts ON createdfrom = po.id (received %) WO: LEFT JOIN assembly_builds ON createdfrom = wo.id (built %) COMPUTED COLUMNS days_open = julianday(now()) - julianday(trandate) item_count from transaction_lines dollar_value = total customer_or_vendor name from join SORT by days_open DESC (oldest first) by default; user-overridable STATUS: REAL per-row enrichment SO/PO/WO progress join days_open · item_count · dollar_value default sort: oldest open first DATABASE · LEFT JOINs + compute i WHAT THIS DOES: This is the render section 1 — open sales orders step. It happens as part of the work-order / order-integration chain and produces the data, status change, or record described below. NS RECORD: see TECHNICAL DETAILS TECHNICAL DETAILS: Render Section 1 — Open Sales Orders COLUMNS tranid · customer · otherrefnum (customer PO# thread) · status · item_count · dollar_value · days_open · owner · actions STATUS GROUPING - Pending Fulfillment (most common) - Partially Fulfilled - On Hold SORT DEFAULT days_open DESC (oldest at top — surfaces stalled orders) DRILL-IN Click row → opens NS SO record in new tab via /netsuite/redirect/so/{id} CROSS-LINK Each row links to ns-sales-order-master.html and the matching path detail STATUS: REAL Section 1 · Open Sales Orders tranid · customer · otherrefnum · status item_count · $ · days_open · owner groups: Pending Fulfillment / Partial / On Hold FRONTEND · row table render · no innerHTML drill-in: NS SO record i WHAT THIS DOES: This is the render section 2 — open purchase orders step. It happens as part of the work-order / order-integration chain and produces the data, status change, or record described below. NS RECORD: see TECHNICAL DETAILS TECHNICAL DETAILS: Render Section 2 — Open Purchase Orders COLUMNS tranid · vendor · memo (Path 1 customer PO#, Path 2 internal req#) · status · item_count · dollar_value · days_open · location · actions STATUS GROUPING - Pending Receipt - Partially Received - Pending Bill SORT DEFAULT days_open DESC DRILL-IN Click row → opens NS PO record in new tab via /netsuite/redirect/po/{id} CROSS-LINK Each row links to ns-purchase-order-master.html and path detail (1 SO-connected / 2 inventory or build) PATH HINT memo prefix or createdfrom flag indicates Path 1 (SO-connected) vs Path 2 (inventory/build) — surfaced as a tag STATUS: REAL Section 2 · Open Purchase Orders tranid · vendor · memo · status item_count · $ · days_open · location groups: Pending Receipt / Partial / Pending Bill BACKEND · row table render · Path 1/2 tag drill-in: NS PO record i WHAT THIS DOES: This is the render section 3 — open work orders step. It happens as part of the work-order / order-integration chain and produces the data, status change, or record described below. NS RECORD: see TECHNICAL DETAILS TECHNICAL DETAILS: Render Section 3 — Open Work Orders COLUMNS tranid · assembly_item · memo (TBD: WO customer PO# threading) · status · qty · dollar_value · days_open · location · actions STATUS GROUPING - Released - In Progress - Built but Not Closed SORT DEFAULT days_open DESC DRILL-IN Click row → opens NS WO record in new tab via /netsuite/redirect/wo/{id} CROSS-LINK Each row links to ns-work-order-master.html and path detail (1 from-SO / 2 inventory-build) TBD WO customer PO# threading — Mike to confirm whether memo carries it or if it lives elsewhere STATUS: REAL (memo threading TBD) Section 3 · Open Work Orders tranid · assembly_item · memo (TBD) qty · $ · days_open · location groups: Released / In Progress / Built-open MESSAGEBUS · row table render drill-in: NS WO record i WHAT THIS DOES: This is the approve button — inline row action step. It happens as part of the work-order / order-integration chain and produces the data, status change, or record described below. NS RECORD: see TECHNICAL DETAILS TECHNICAL DETAILS: APPROVE button — inline row action ON CLICK stage_proposed_action(action_type = 'open_orders_approve', source_record_type = 'so'|'po'|'wo', source_record_id = {row.id}, proposed_by = current_user, risk_level = 1) HITL Per ADR-031 — proposed_actions row created, Mike (or owner once delegated) confirms before any NS write NS WRITE Only happens when proposed_actions row transitions to 'approved' → ns_push_queue drains it (existing infrastructure) STATUS: REAL · HITL-gated Approve (inline) stage_proposed_action(open_orders_approve) risk 1 · HITL via proposed_actions ns_push_queue drains on approval SECURITY · HITL · no direct NS write i WHAT THIS DOES: This is the hold button — inline row action step. It happens as part of the work-order / order-integration chain and produces the data, status change, or record described below. NS RECORD: see TECHNICAL DETAILS TECHNICAL DETAILS: HOLD button — inline row action ON CLICK stage_proposed_action(action_type = 'open_orders_hold', source_record_type, source_record_id, hold_reason = (prompted modal), risk_level = 2) HOLD REASON Free-text + tag picker (credit / inventory / customer hold / pricing dispute / other) NS WRITE TARGET bodyFields.status → 'On Hold' (SO) · custom hold flag (PO/WO — TBD) STATUS: REAL · HITL-gated · hold_reason captured every time Hold (inline) stage_proposed_action(open_orders_hold) prompts hold_reason modal (free-text + tag) risk 2 · HITL · status → 'On Hold' on approval SECURITY · HITL · capture hold reason i WHAT THIS DOES: This is the escalate button — inline row action step. It happens as part of the work-order / order-integration chain and produces the data, status change, or record described below. NS RECORD: see TECHNICAL DETAILS TECHNICAL DETAILS: ESCALATE button — inline row action ON CLICK stage_proposed_action(action_type = 'open_orders_escalate', source_record_type, source_record_id, escalate_to = (picker: mike|finance|logistics|production), risk_level = 3) SIDE EFFECTS hitl_email_draft template = 'open_orders_escalation' email is staged → HITL approve before send NOTIFY Mike (default) gets the queue alert; can re-route via UI STATUS: REAL · HITL-gated · drafts email Escalate (inline) stage_proposed_action(open_orders_escalate) picker: mike / finance / logistics / production risk 3 · email draft staged for HITL approve SECURITY · HITL · staged email i WHAT THIS DOES: This is the audit + observability writes step. It happens as part of the work-order / order-integration chain and produces the data, status change, or record described below. NS RECORD: see TECHNICAL DETAILS TECHNICAL DETAILS: Audit + observability writes EVENTS FIRED (events_ledger via fire_event) open_orders.dashboard_loaded (user_id, sections_rendered, row_counts) open_orders.row_drilled (user_id, source_record_type, source_record_id) — per drill click open_orders.action_staged (user_id, action_type, source_record_id) — per action click WORKFLOW LOG workflow_run_log row written per dashboard load (workflow_type = open_orders_suitelet) REFLEXION reflexion_log row written tagged section_interactions — tracks which section users interact with most (drives layout iteration) STATUS: REAL · feeds observability stack audit + observability event(open_orders.dashboard_loaded) + row_drilled + action_staged workflow_run_log row · reflexion section_interactions BACKEND · events_ledger + workflow_run_log + reflexion_log i WHAT THIS DOES: This is the llm-friendly: json-ld workflowdefinition emit step. It happens as part of the work-order / order-integration chain and produces the data, status change, or record described below. NS RECORD: see TECHNICAL DETAILS TECHNICAL DETAILS: LLM-friendly: JSON-LD WorkflowDefinition emit EMIT (inline <script type="application/ld+json"> per section) Schema.org-style WorkflowDefinition w/ workflow_type, inputs_required, fan_out_targets Per row: structured Order entity (orderNumber, customer, orderStatus, itemsPurchased, totalPrice) WHY Chat (council v2) can ingest the dashboard schema directly when a user asks "what's open for Driscoll" — no scraping required PAGES MIRROR /open-orders.html includes the same JSON-LD blocks STATUS: REAL · pattern matches wiki-* JSON-LD injections (separate Agent C task) JSON-LD WorkflowDefinition emit inline <script type="application/ld+json"> per-section + per-row · chat-ingestible CLOUD · LLM context · no scraping i next step next step next step key flow key flow key flow SO ↔ WO step step next step next step next step next step next step next step LEGEND Sales Order Purchase Order Work Order Decision gate HITL action Backend write Frontend render PO# THREADING MAP · how customer PO# threads through each section (sample row) SO row — otherrefnum (customer PO#) SO#1217 · Driscoll PO 72622 (otherrefnum) PO row — memo (Path 1) OR internal req# (Path 2) PO#4521 · memo = "Driscoll PO 72622" (Path 1) WO row — memo (TBD) WO#892 · memo TBD — Mike to confirm
Legend · color codes + icons used in this diagram
Color codes:
backend / NS record
work order / SO link
finance / invoice
TBD / escalation
cloud / worker
external / generic
Icon meanings:

Phase detail — what the Suitelet does

01 Entry points (3) REAL

User opens the Suitelet from the NS menu / portlet, cron tick refreshes D1, OR user clicks manual refresh.
NS Suitelet id
customscript_gfs_open_orders (TBD pending Mike approval)
Pages mirror
/open-orders.html — D1-backed, faster for mobile / offline
Cron cadence
every 2 minutes (piggybacks on tier-2 transactions sync)
Manual refresh
POST /api/open-orders/refresh · rate-limited 1/15s per user

02 Data freshness gate REAL · block precondition

Only render if D1 mirror is within 5 minutes. Else force a sync first.
Logic
(now() - sync_status.last_success_at[transactions]) < 5 min
Degraded mode
Banner: "Data is N minutes stale" + force refresh button

03 SuiteQL pull + filter + enrich REAL

Pull open transactions for all 3 types, apply filter bar, join progress tables, compute days_open + item_count + dollar_value.
SQL
SELECT ... FROM transactions WHERE type IN ('SalesOrd','PurchOrd','WorkOrd') AND status NOT IN ('Closed','Cancelled','Billed','Fully Received','Fully Fulfilled','Built and Closed')
Filters
status · customer/vendor/item · location · trandate range
Joins
SO+customer_invoices · PO+item_receipts · WO+assembly_builds

04a Render Section 1 · open Sales Orders SO REAL

Open SO table. Customer PO# threading via otherrefnum.
Columns
tranid · customer · otherrefnum · status · item_count · $ · days_open · owner · actions
Status groups
Pending Fulfillment · Partially Fulfilled · On Hold
Drill-in
click row → NS SO record in new tab
Cross-links

04b Render Section 2 · open Purchase Orders PO REAL

Open PO table. Customer PO# threading via memo (Path 1 SO-connected) or internal req# (Path 2 inventory/build).
Columns
tranid · vendor · memo · status · item_count · $ · days_open · location · actions
Status groups
Pending Receipt · Partially Received · Pending Bill
Path tag
memo prefix or createdfrom flag surfaces Path 1 vs Path 2 as a row tag
Cross-links

04c Render Section 3 · open Work Orders WO REAL WO PO# threading TBD

Open WO table. Customer PO# threading via memo — TBD pending Mike confirmation on whether WO carries it directly.
Columns
tranid · assembly_item · memo (TBD) · status · qty · $ · days_open · location · actions
Status groups
Released · In Progress · Built but Not Closed
Cross-links

05 Inline action buttons (HITL) REAL ADR-031 HITL invariant

Approve / Hold / Escalate — all stage into proposed_actions first. Nothing writes to NS directly. Per ADR-031.
Approve
stage_proposed_action(open_orders_approve) · risk 1
Hold
stage_proposed_action(open_orders_hold) · risk 2 · prompts hold_reason modal
Escalate
stage_proposed_action(open_orders_escalate) · risk 3 · drafts email via template open_orders_escalation
NS write path
Only fires when proposed_actions.status → approved · via existing ns_push_queue drainer

06 Audit + observability + LLM ingest REAL

Three side effects on every load: fire events, write workflow_run_log row, write reflexion. Also emit JSON-LD per section so chat can ingest the dashboard schema directly.
Events fired
open_orders.dashboard_loaded · open_orders.row_drilled · open_orders.action_staged
Workflow log
workflow_run_log (workflow_type = open_orders_suitelet)
Reflexion
reflexion_log tagged section_interactions — drives layout iteration
JSON-LD
inline <script type="application/ld+json"> WorkflowDefinition + Order entities per section

Tables, endpoints, code paths

kindnamepurpose
D1 tabletransactionsSO + PO + WO source records (status NOT closed)
D1 tabletransaction_linesper-row item_count + line drill-in
D1 tablecustomer_invoicesSO fulfilled % computation
D1 tableitem_receiptsPO received % computation
D1 tableassembly_buildsWO built % computation
D1 tablesync_statuslast_success_at per tier — freshness gate
D1 tableproposed_actionsHITL queue for approve/hold/escalate row actions
D1 tablens_push_queuedrains approved proposed_actions back to NS
D1 tableeventsopen_orders.dashboard_loaded / .row_drilled / .action_staged
D1 tableworkflow_run_logper-load audit row
D1 tablereflexion_logsection interaction analytics
Workflow contractopen_orders_suiteletthis dashboard · risk 1 · contract_version 2 (migration 135)
NS Suiteletcustomscript_gfs_open_ordersscript id TBD · pending Mike approval
Pages mirror/open-orders.htmlD1-backed mobile-friendly mirror
EndpointPOST /api/open-orders/refreshmanual refresh button · rate-limited 1/15s
NS field (read)bodyFields.otherrefnum / memo / status / trandate / total / shipdateSO PO WO standard fields driving rows

Open gaps — honest punch list

Related docs