NetSuite purchase order — master

SO-trigger or purchasing need → PO entry → transmit → vendor confirm → branch into 2 path sub-contracts → receive → ★ Finance handoff → Vendor Bill → pay → close

High-altitude master view of the PO process — the vendor side of the transaction. POs originate in one of two ways: Path 1 connected to a Sales Order (drop ship vendor orders or customer-specific fulfillment), or Path 2 for inventory replenishment, assembly build components, packaging, or general purchasing. Master dispatches into one of two sub-contracts — Path 1 SO-connected (drop ship; carries customer PO# threading from the SO chain via PO.memo and VendBill.memo/tranid/initialtranid) or Path 2 inventory/build (no customer PO# threading; memo carries internal req# or vendor invoice#). After Item Receipt, the universal ★ automation alert fires to Finance — receiving happens at the dock but billing/payment is Finance-owned. Finance reviews PO + Item Receipt + supporting docs before processing the Vendor Bill. PO closes once items received + billed + paid + any shortages/damages/substitutions/pricing resolved. See path detail docs for field-level depth.

D1-mirrored from NS HITL outside guardrails Path 1 · SO-connected Path 2 · inventory / build EDI 850 outbound STUB

Pipeline — PO trigger → 2 fulfillment paths → finance convergence

idle
Layers:
NS purchase order lifecycle — trigger (SO-connected OR purchasing need) → PO entry → transmit → vendor confirm → branch by origin into 2 paths (SO-connected dropship / inventory or build) → Item Receipt → Finance alert (key handoff) → vendor bill → pay → close 01 / Trigger origin · SO-connected OR purchasing need 02 / PO entry in NetSuite (capture memo / entity / lineFields) 03 / Transmit · vendor confirm (order / pricing / delivery date) 04 / Branch by origin · dispatch to sub-contract PATH 1 / SO-connected · see ns-purchase-order-path-1-sales-order-connected.html PATH 2 / Inventory or build · see ns-purchase-order-path-2-inventory-or-build.html 05 / Item Receipt (when applicable) · universal step 06 / ★ Automation alert · Finance handoff · STUB (pending NS automation) 07 / Finance review · Vendor Bill · pay per terms · close PO WHAT THIS DOES: When a customer's Sales Order has a drop-ship or special-order line, GFS spins up a Purchase Order to a vendor instead of pulling stock. The PO inherits the customer's PO number from the SO so the thread carries forward to billing. NS RECORD: Vendor TECHNICAL DETAILS: PATH 1 ORIGIN — SO triggers PO creation. Vendor ships to customer (drop ship) or to GFS crossdock. TRIGGER customer SO entered in NetSuite one or more lines: items.dropship = true OR items.special_order = true LINKAGE purchaseorder.createdfrom will be set to so.id THREADING SO.bodyFields.otherrefnum (customer PO#) carries to PO.bodyFields.memo STATUS: REAL PATH 1 origin · SO triggers PO SO has dropship_item / special_order line PO will carry SO.otherrefnum on memo DATABASE · SO-connected origin i WHAT THIS DOES: Sometimes a PO is needed for internal reasons — inventory is low, an assembly build needs components, packaging is running out, or it's a general purchase. These POs are not tied to a customer SO. NS RECORD: InvtryBal TECHNICAL DETAILS: PATH 2 ORIGIN — Purchasing need identified internally. TRIGGER inventory replenishment (reorder point hit) assembly build components needed (BOM consume upcoming) packaging supply general purchasing (services, supplies, freight) LINKAGE purchaseorder.createdfrom is NULL (no SO link) THREADING No customer PO# threading. memo holds internal req# or vendor invoice#. STATUS: REAL PATH 2 origin · purchasing need inventory · assembly components · packaging no SO link · no customer PO# thread CLOUD · internal-need origin i WHAT THIS DOES: The buyer picks the vendor: must be active, payment terms must be known, and the vendor must be able to sell us the item. Examples: Cardinal Foods for drop-ship, Bongards for cheddar, Echo Lake for frozen breakfast. NS RECORD: Vendor TECHNICAL DETAILS: VENDOR SELECTION (universal). CHECKS vendor active (vendors.status = 'active') vendor terms known (Net 30 / Net 45 / Prepay / 2/10 Net 30) item is purchasable from this vendor SAMPLE Cardinal Foods #891 (drop ship) Bongards Creameries (USDA barrel cheddar) Echo Lake Foods (frozen breakfast) STATUS: REAL · operator-driven vendor selection (universal) vendor active · terms known · purchasable e.g. Cardinal #891 / Bongards / Echo Lake EXTERNAL · vendor record i WHAT THIS DOES: The actual Purchase Order record is created in NetSuite — this is the system of record. Within 2 minutes it mirrors to our Cloudflare D1 database for fast reads. Path 1 POs carry the customer PO number on the memo; Path 2 POs carry an internal req number. NS RECORD: PurchOrd TECHNICAL DETAILS: ENTER PO IN NETSUITE — PO posted as system-of-record. ACTION create: NS Purchase Order (PurchOrd) capture: bodyFields.tranid (NS-assigned PO#, e.g. "PO-7833") capture: bodyFields.entity = vendor_id Path 1: bodyFields.memo = customer_po_number (e.g. "72622") Path 1: bodyFields.createdfrom = so.id Path 2: bodyFields.memo = internal req# / vendor invoice# (no customer PO#) mirror: D1 purchase_orders + purchase_order_lines on next hot-tier sync (2min) TABLES write: NS PurchOrd · D1 purchase_orders · purchase_order_lines STATUS: REAL enter PO in NetSuite tranid = "PO-7833" · entity = vendor_id Path 1: memo=customer_po · createdfrom=so.id Path 2: memo=internal req# / vendor invoice# BACKEND · NS + D1 mirror i WHAT THIS DOES: The PO is sent to the vendor. Today this is manual — a buyer emails a PDF. Future-state, we'll auto-transmit via EDI 850 to EDI-enabled vendors. NS RECORD: events TECHNICAL DETAILS: TRANSMIT PO TO VENDOR. TODAY: manual (email PDF to vendor) FUTURE STUB: auto-transmit (EDI 850 outbound) TABLES write: events (po.transmitted) STATUS: REAL (manual) · STUB (auto) transmit PO to vendor manual: email PDF today STUB: EDI 850 outbound CLOUD · vendor channel i WHAT THIS DOES: The vendor replies confirming the order, the locked-in pricing, and an expected ship/delivery date. Any substitutions or shortages get flagged at this point so we can react. NS RECORD: n/a TECHNICAL DETAILS: VENDOR CONFIRMS ORDER / PRICING / DELIVERY DATE. CAPTURED order accepted pricing locked (vendor cost per line) expected ship date · expected delivery date any substitutions / shortages flagged TABLES write: purchase_orders (vendor_confirmed=1, expected_delivery) STATUS: REAL · operator records vendor reply vendor confirms order accepted · pricing locked expected ship / delivery date subs / shortages flagged EXTERNAL · vendor side i WHAT THIS DOES: The platform looks at how the PO was created to decide which playbook applies. If it links back to a Sales Order, it's Path 1; otherwise it's Path 2. Each path has its own sub-contract. NS RECORD: SalesOrd TECHNICAL DETAILS: CLASSIFY PO ORIGIN — dispatch into sub-contract. LOGIC if purchaseorder.createdfrom IS NOT NULL AND that record is type=SalesOrd → Path 1 else → Path 2 TABLES read: purchase_orders.createdfrom STATUS: REAL (operator/system-driven) classify PO origin createdfrom = SO → Path 1 no SO link → Path 2 SECURITY · dispatch · sub-contract i WHAT THIS DOES: Summary box for Path 1 — the SO-connected route. Read the dedicated Path 1 diagram for field-level depth on customer PO# threading. NS RECORD: ItemRcpt TECHNICAL DETAILS: PATH 1 · SO-CONNECTED — Summary. See ns-purchase-order-path-1-sales-order-connected.html for field-level depth. WORKFLOW po_lifecycle_sales_order_connected_path (sub-contract dispatched from master) SUMMARY PO has createdfrom=so.id → vendor ships (direct or crossdock) → Item Receipt (when applicable) → SO fulfilled → Finance alert → invoice customer → VendBill carries thread → pay → close THREADING PO.bodyFields.memo = "72622" (customer PO#, from SO.otherrefnum) PO.lineFields.links[].tranid = "1217 / 72622" (SO + customer PO# combo) VendBill.memo = tranid = initialtranid carry customer PO# STATUS: REAL PATH 1 · PO connected to a Sales Order createdfrom = so.id · memo = customer PO# = "72622" drop ship to customer OR crossdock at GFS VendBill memo + tranid + initialtranid all carry "72622" closes when both customer side + vendor side complete PATH 1 · po_lifecycle_sales_order_connected_path BACKEND · see ns-purchase-order-path-1-sales-order-connected.html i WHAT THIS DOES: Summary box for Path 2 — the inventory-or-build route. No SO link, no customer PO# threading. Read the dedicated Path 2 diagram for details. NS RECORD: ItemRcpt TECHNICAL DETAILS: PATH 2 · INVENTORY OR ASSEMBLY BUILD — Summary. See ns-purchase-order-path-2-inventory-or-build.html for field-level depth. WORKFLOW po_lifecycle_inventory_or_build_path (sub-contract dispatched from master) SUMMARY PO entered for replenishment / assembly components / packaging / general purchasing → vendor confirms → product arrives at facility → loading dock receives → Item Receipt → inventory added at correct location → Finance alert → Finance reviews → VendBill processed → paid → PO closed THREADING No customer PO# threading. memo carries internal req# or vendor invoice#. STATUS: REAL PATH 2 · PO for inventory or assembly build no SO link · memo = internal req# / vendor invoice# replenishment · assembly components · packaging dock receives → inventory_balance += qty at location closes when all items received + billed + paid PATH 2 · po_lifecycle_inventory_or_build_path DATABASE · see ns-purchase-order-path-2-inventory-or-build.html i WHAT THIS DOES: Both paths converge here when the goods physically arrive. Direct drop-ship may skip this step (vendor ships straight to the customer). For everything else, the dock posts an Item Receipt that closes the PO line. NS RECORD: ItemRcpt TECHNICAL DETAILS: ITEM RECEIPT — universal step (when applicable). Direct drop ship may skip warehouse on Path 1. NS RECORD: ItemRcpt FIELDS WRITTEN bodyFields.createdfrom = po.id (e.g. "PO-7833") bodyFields.status = "Received" bodyFields.received_at = datetime bodyFields.location = location_id (Path 2; Path 1 may be N/A for direct dropship) INVENTORY EFFECT Path 1: transient (crossdock case only) Path 2: inventory_balance.quantityavailable += received qty at location STATUS: REAL Item Receipt (universal when applicable) createdfrom = po.id · status = "Received" BACKEND · Path 1 may skip (direct dropship); Path 2 credits inventory i WHAT THIS DOES: Receiving happens at the dock, but billing is Finance's job. As soon as the goods are received, an automation alert fires so Finance can pick up the next steps. NS RECORD: ItemRcpt TECHNICAL DETAILS: ★ AUTOMATION ALERT → FINANCE — universal handoff. TRIGGER Item Receipt completed (or fulfillment on Path 1 direct dropship) WHY Receiving happens at the dock, but billing/payment is Finance-owned. Alert tells Finance: review PO + Item Receipt + supporting docs before processing payment. ACTION fire: NS workflow / scheduled script alert to Finance fire: events row po.finance_alert_fired STATUS: STUB — platform-side event fires (REAL); NS workflow build pending (Mike to deploy) ★ automation alert → Finance STUB · REAL once NS automation deployed FINANCE · emits po.finance_alert_fired event i WHAT THIS DOES: Finance checks that what arrived matches what was ordered — right quantity, right price, packing slip / vendor invoice attached, no flagged issues. NS RECORD: ItemRcpt TECHNICAL DETAILS: FINANCE REVIEW — Finance reviews PO + Item Receipt + supporting docs. CHECKS received qty matches expected vendor pricing matches PO packing slip / vendor invoice attached any shortages / damages / substitutions flagged TABLES read: purchase_orders · item_receipts · vendor terms STATUS: REAL · Finance-driven Finance review qty · pricing · docs · subs CLOUD · Finance role i WHAT THIS DOES: Finance enters the Vendor Bill in NetSuite, linked back to the PO. On Path 1, three fields carry the customer PO# so the thread reaches all the way to AP. NS RECORD: VendBill TECHNICAL DETAILS: VENDOR BILL ENTERED — Finance processes against the PO. NS RECORD: VendBill FIELDS WRITTEN bodyFields.createdfrom = po.id (e.g. "PO-7833") bodyFields.entity = vendor_id Path 1: bodyFields.memo = customer_po_number ("72622") Path 1: bodyFields.tranid = bodyFields.initialtranid = "<so.tranid> / <customer_po>" ("1217 / 72622") Path 2: bodyFields.memo = internal req# / vendor invoice# (no customer PO#) STATUS: REAL Vendor Bill entered VendBill · createdfrom = po.id MESSAGEBUS · threading on Path 1 i WHAT THIS DOES: Finance pays the vendor according to the agreed terms (Net 30, Net 45, prepay, etc.). NetSuite posts a Vendor Payment record. NS RECORD: VendPymt TECHNICAL DETAILS: VENDOR BILL PAID per vendor terms. NS RECORD: VendPymt FIELDS bodyFields.appliedto = vendor_bill.id (e.g. "VB-2244") bodyFields.amount = vendor cost TABLES write: vendor_payments · transactions (VendPymt) STATUS: REAL VendBill paid VendPymt · per terms DATABASE · VendPymt i WHAT THIS DOES: Before the PO can be closed, any short-shipments, damages, substitutions, or pricing variances must be resolved — vendor credits issued, claims filed, operator approvals captured. NS RECORD: n/a TECHNICAL DETAILS: RESOLVE SHORTAGES / DAMAGES / SUBS / PRICING — before close. CHECKS any short-ships → vendor credit / rebill damages → claim filed substitutions → operator approves pricing variance → reconcile vs PO rate TABLES write: proposed_actions (vendor_credit_draft, claim_open, etc.) STATUS: REAL · HITL for any non-clean close resolve issues shortages · damages · subs · pricing SECURITY · HITL i WHAT THIS DOES: The PO's status flips to Closed once items are received, billed, paid, and all issues are resolved. An event fires so downstream systems know. NS RECORD: events TECHNICAL DETAILS: CLOSE PO — once items received + billed + paid + issues resolved. ACTION status: PO → 'Closed' fire: events.po.closed TABLES write: purchase_orders (status) · events STATUS: REAL close PO status = 'Closed' · events fire BACKEND · po.closed i WHAT THIS DOES: On Path 1, the customer-side cycle (invoice, dunning, payment) runs in parallel on the SO chain. The PO only closes after both vendor side and customer side complete. NS RECORD: n/a TECHNICAL DETAILS: PATH 1 SIDECAR — customer-side invoice + payment cycle runs in parallel. DETAIL Path 1 closes the SO once vendor side + customer side both complete. Customer invoice + dunning + payment apply happen on the SO chain, not the PO chain. SEE ns-sales-order-path-3-dropship.html for customer-side detail STATUS: REAL Path 1 sidecar · customer-side cycle SO invoice + dunning + payment in parallel DATABASE · cross-link to SO Path 3 i SO line internal need select vendor create PO send to vendor confirm reply → Path 1 → Path 2 → Path 1 (sub-contract) → Path 2 (sub-contract) converge both paths converge → Item Receipt Item Receipt ★ automation alert notify createdfrom = po.id applylist = bill resolved flow continues LEGEND Path 1 SO-connected Path 2 inventory / build ★ Finance handoff HITL / issue resolve CUSTOMER PO# THREADING (PATH 1 only) · trace one customer PO across SO + PO + VendBill chain (sample: customer PO# 72622, NS SO# 1217) SO · otherrefnum "72622" / tranid "1217" PO · memo + lineFields.links[].tranid "72622" / "1217 / 72622" Item Receipt · createdfrom po.id → inherits memo VendBill · memo + tranid + initialtranid "72622" / "1217 / 72622" / "1217 / 72622" PATH 2 · no customer PO# threading memo = internal req# / vendor invoice# memo createdfrom memo + tranid n/a
Diagram glossary · colors, icons, line styles

Color codes (node fill)

Blue / data · D1 + NetSuite records
Green / backend · HITL / processing
Light blue / cloud · external service
Red / decision · classify / HITL gate
Orange / handoff · message bus / bill
Dark blue / Finance · key handoff

Line styles (arrows)

Path 1 (SO-connected) flow
Path 2 (inventory / build) flow
Finance handoff (heavy)
Optional / dashed link

Icons used (Lucide-style)

Database / NS read or write
Cloud / external service
Decision / close gate
Backend processing
Finance handoff / payment
Freight / shipping
Inventory / receipt
Review / VendBill
Read operation
Write / create record
Cross-record linkage
Flag for review
Send / transmit
Branching / dispatch
Vendor-side step

Tip: click a step number in the navigator above to highlight that node. Toggle HITL gates / Edge labels / Escalation paths with the layer buttons. Hover any node's "i" icon for plain-English popover.

Phase detail — Mike's actual 2-path process

01 Trigger origin — SO-connected OR purchasing need REAL

POs originate one of two ways. Path 1 is triggered by a Sales Order needing drop ship or customer-specific fulfillment. Path 2 is triggered by a purchasing need: inventory replenishment, assembly build components, packaging supply, or general purchasing.
Path 1 trigger
SO has items.dropship = true OR items.special_order = true
Path 2 trigger
inventory reorder point hit · assembly BOM upcoming · packaging restock · general purchasing
Vendor selection
vendors.status = 'active' · terms known · item purchasable

02 Enter PO in NetSuite REAL

PO posted as system-of-record. D1 mirrors on next 2-min hot-tier sync. Path 1 captures customer PO# threading on memo + createdfrom = so.id.
NS record
PurchOrd
Tables write
purchase_orders · purchase_order_lines · transactions (PurchOrd)
Path 1 fields
memo = customer_po_number · createdfrom = so.id · lineFields.links[].tranid = "<so.tranid> / <customer_po>"
Path 2 fields
memo = internal req# / vendor invoice# · createdfrom = NULL

03 Transmit PO · vendor confirms REAL (manual) EDI auto STUB

PO is transmitted to the vendor (today manual via email PDF; EDI 850 outbound is a STUB). Vendor confirms order acceptance, pricing, and expected ship/delivery date.
Channels
email PDF (today) · EDI 850 outbound (STUB)
Captured
order accepted · pricing locked · expected ship/delivery date · subs/shortages flagged

04 Classify origin · dispatch sub-contract REAL

Master inspects PO origin and dispatches into one of two path sub-contracts.
Path 1
createdfrom IS NOT NULL AND parent is a Sales Order → po_lifecycle_sales_order_connected_path
Path 2
no SO link → po_lifecycle_inventory_or_build_path

P1 Option 1 — PO connected to a Sales Order PATH 1 REAL

For drop ship vendor orders or customer-specific fulfillment. Vendor ships product (direct to customer or to GFS for crossdock). Item Receipt closes the PO line (may be skipped on direct drop ship). SO Item Fulfillment ships to customer. Carries customer PO# threading end-to-end.
NS records
PurchOrd · ItemRcpt · ItemFulfill · VendBill · VendPymt
Threading
PO.memo = SO.otherrefnum · VendBill.memo/tranid/initialtranid carry customer PO#
SO sidecar
customer-side invoice + dunning + payment apply runs in parallel on the SO chain

P2 Option 2 — PO for inventory or assembly build PATH 2 REAL

For inventory replenishment, assembly build components, packaging supply, or general purchasing. Product arrives at the facility, loading dock receives, Item Receipt completed, inventory added at the correct location. No customer-side cycle.
NS records
PurchOrd · ItemRcpt · VendBill · VendPymt
Inventory effect
inventory_balance.quantityavailable += received qty at location_id
Threading
none — no customer PO#. memo carries internal req# or vendor invoice#

05 Item Receipt (universal) REAL

Both paths converge here when receiving is applicable. Path 1 may skip on direct drop ship. Path 2 always lands here and credits inventory.
NS record
ItemRcpt (createdfrom = po.id)
Tables write
item_receipts · inventory_balance (Path 2)

06 ★ Automation alert → Finance REAL (concept) NS workflow build pending

Universal handoff. Receiving happens at the dock but billing/payment is Finance-owned. Alert tells Finance to review PO + Item Receipt + supporting docs before processing payment.
Trigger
Item Receipt completed (or fulfillment on Path 1 direct dropship)
Event
events.po.finance_alert_fired
STUB
NS workflow trigger config pending Mike's deploy

07 Finance review · Vendor Bill · pay REAL

Finance reviews PO + Item Receipt + supporting docs (packing slip, vendor invoice). Vendor Bill entered against the PO. Paid per vendor terms.
NS records
VendBill · VendPymt
Path 1 threading
memo = customer_po · tranid = initialtranid = "<so.tranid> / <customer_po>"
Path 2 fields
memo = internal req# / vendor invoice# (no customer PO#)

08 Resolve issues · close PO REAL

PO closes once items received + billed + paid + any shortages/damages/substitutions/pricing issues resolved.
NS status
PurchOrd'Closed'
Event
events.po.closed

Tables, endpoints, code paths

kindnamepurpose
D1 tablepurchase_ordersPO header mirror · createdfrom, memo, entity, status
D1 tablepurchase_order_linesPO line-level mirror · item, qty, rate, lineFields.links[].tranid
D1 tableitem_receiptsvendor receipt closes PO line · createdfrom = po.id
D1 tableitem_fulfillmentsPath 1 sidecar · ships SO line to customer
D1 tablevendor_billsVendBill mirror · Path 1 carries customer PO# on memo/tranid/initialtranid
D1 tablevendor_paymentsVendPymt mirror · appliedto = vendor_bill.id
D1 tableinventory_balancePath 2 credits qty at location_id on Item Receipt
D1 tabletransactionsPurchOrd / ItemRcpt / VendBill / VendPymt rows
D1 tableeventspo.transmitted · po.finance_alert_fired · po.closed
Workflowpurchase_order_lifecyclemaster dispatcher (risk 2)
Workflowpo_lifecycle_sales_order_connected_pathPath 1 sub-contract (risk 3)
Workflowpo_lifecycle_inventory_or_build_pathPath 2 sub-contract (risk 2)
NS recordPurchOrd · ItemRcpt · VendBill · VendPymtsystem-of-record across the 2 paths

Open gaps — honest punch list

Side-step · Freight routing check R594

For PO Path 1 (SO-connected dropship) and any PO where GFS pays freight (vendor freight collect, prepay-and-add), freight_routing_check fires after PO save. It classifies the freight type and, when needed, routes the record to the logistics review queue. Path 1 dropships carry the customer PO# through to the BOL; Path 2 (inventory) POs typically don't trigger this check unless inbound freight is on GFS's account.

Side-step in the PO flow (runs alongside Item Receipt scheduling) · HITL-gated. See ns-freight-routing-check.html + ns-logistics-review-flow.html.

Path detail docs