Build Mock Fulfillments and Fire 856s
The IF/856 leg of the standard test cycle: 850 inject → SO created → 855 fired → **IF created + packed → 856 fired** → invoice + 810. This skill transforms a Sales Order into an Item Fulfillment, packs it the way the SuiteApp's 856 generator expects (carton + shipped-item records, not native NetSuite packages), ships it, and fires the outbound 856.
The output is one outbound customrecord_orderful_transaction row of document type 856, with the corresponding Orderful transaction visible at https://ui.orderful.com/transactions/{ofId}.
It is the sibling of bill-and-fire-810. The most common failure mode — and the reason this skill leads with a pre-flight — is that the SO looks fine in the UI but isn't actually shippable, so the REST transform produces an empty fulfillment and NetSuite rejects it with a misleading error.
When to use this skill
- "Build the Item Fulfillment for SO 11521567 and fire the 856"
- "We finished the 855 — now ship the orders and send the ASN"
- "/build-mock-fulfillments 11521567"
- "Test the 856 mapping for the new customer"
- End of the fulfillment leg of the test cycle (850 → SO → 855 → IF → 856)
Do NOT use this skill when:
- The customer's packing comes from a configured analytics dataset (
custentity_orderful_pkg_data_srcset) rather than carton records — the carton-record steps below don't apply; seealternative-packing-source. - You only want to edit an existing IF — PATCH it directly instead of transforming a new one.
Planning a consolidated multi-PO 856? Check this before cert day, not during: the ECT's consolidation method (Ship To / Parent 850) defers generation to a consolidation MapReduce, and in accounts observed so far nothing triggers it — every consolidation deployment sits NOTSCHEDULED and the poller chain doesn't include it. If the test plan requires one 856 spanning multiple POs, verify a consolidation deployment is actually scheduled in this account; otherwise plan to build the consolidated 856 by hand from the IFs' packing data and POST it via Orderful's /v3/transactions API.
Prerequisites
- Customer has an outbound 856 ECT (
customrecord_orderful_edi_customer_trans, document type "856 Ship Notice/Manifest",custrecord_edi_enab_trans_auto_send_asn = T). If not, route toenable-customer. - Customer's
~/orderful-onboarding/<slug>/.envis set up vianetsuite-setup. - The 850 → SO step is done and (ideally) the 855 has been acked.
Inputs the skill needs
- One or more NS Sales Order IDs — internal numeric IDs.
- Customer slug — for env loading. Ask if not specified; list
~/orderful-onboarding/.
The recipe
Step 0 — Sales Order outbound-readiness pre-flight
Run this BEFORE the transform. A SO can be Pending Fulfillment and still produce an empty Item Fulfillment; the transform then fails with "You must enter at least one line item for this transaction." — which says nothing about the real cause. Verify the readiness checklist in reference/outbound-source-readiness.md. The high-frequency offenders for the 856:
| Check | SuiteQL / field | Why it bites |
|---|---|---|
| Approved & fulfillable | BUILTIN.DF(status) = Pending Fulfillment |
not transformable otherwise |
| Single ship route | isMultiShipTo on the SO record (REST) |
multi-ship-to splits lines across ship groups; the plain transform selects zero lines → empty IF |
| Every shippable line has a location | transactionline.location per item line |
lines with no location aren't fulfillable; also drives the 856 Ship-From |
| Committed / on-hand | transactionline.quantitycommitted > 0; aggregateItemLocation.quantityonhand at the line location |
nothing to ship |
| Native shipping address populated | GET /record/v1/salesOrder/{id}/shippingAddress → addr1/city/state/zip |
empty here → empty N1*ST on the 856 (and a tripwire for the inbound ship-to write bug — see inspect-inbound-diagnostics) |
| Items resolved + unit set | line item, units |
placeholder/unmapped items break the LIN loop |
| Packing source decided | Auto Pack (item field or packing group) vs hand-built carton records (this skill) vs dataset (custentity_orderful_pkg_data_src) |
the 856 generator hard-errors "None of the following item fulfillments have cartons: <ifId>" if none yields cartons |
If isMultiShipTo = true but the order is logically a single shipment (one destination, split only by shipping method — a frequent artifact of the inbound 850→SO mapping), flip it before transforming:
PATCH /services/rest/record/v1/salesOrder/{soId}
{ "isMultiShipTo": false }
If it is genuinely multi-destination, fulfill one ship group per IF (out of scope for the quick test path — handle in the UI or a dedicated script).
Step 1 — Transform the SO into an Item Fulfillment
POST /services/rest/record/v1/salesOrder/{soId}/!transform/itemFulfillment
The REST transform does not auto-select lines to fulfill — an empty body yields the "at least one line item" error even when the SO is fully committed. Mark each shippable line explicitly. Reference lines by the SO transaction line's unique key (SELECT uniquekey FROM transactionline WHERE transaction = {soId} AND itemtype = 'InvtPart'):
{ "item": { "items": [
{ "orderLine": 296500001, "itemreceive": true },
{ "orderLine": 296500002, "itemreceive": true }
] } }
The 204 response carries the new Item Fulfillment ID in the Location header.
Step 2 — Pack it: carton + shipped-item records
Check whether Auto Pack can do this for you first. If the items carry custitem_orderful_units_p_carton or resolve to a packing group, the Auto Pack button builds these records in one click, with the correct sequencing and dimensions:
node ../configure-packing-groups/audit-packing-config.mjs ~/orderful-onboarding/<slug> --item <itemId>
Hand-build the records below when Auto Pack is not an option — no pack config exists, or the test needs a specific carton shape (a deliberate short carton, a mixed-item carton, a particular SSCC per carton) that a uniform units-per-carton rule cannot produce. That is a real and common need during certification; it just should not be the default. See configure-packing-groups and auto-pack-fulfillment.
The SuiteApp's 856 generator builds the pack (P) and item (I) hierarchy levels from its own custom records, not from NetSuite's native Packages sublist. Create:
- One
customrecord_orderful_cartonper carton:custrecord_orderful_carton_fulfillment→ the IF idcustrecord_orderful_carton_is_pallet=false(carton) /true(pallet/tare). Check the partner's ASN hierarchy first: if the guideline is flat Shipment→Order→Pack→Item (BSN050001— e.g. KeHE via SPS), a pallet carton inserts a Tare HL the partner will reject, and Orderful's validator will NOT catch it (it doesn't check HL shape against BSN05). For flat-ASN partners, never create pallet cartons — and if one exists, hard-delete it; inactivating is not enough, the generator still reads inactive cartons.custrecord_orderful_carton_sequence= carton numbercustrecord_orderful_carton_weight- Marks driver:
custrecord_orderful_carton_sscc18(a valid SSCC-18) → emitsMAN*GM/MAN*AA;custrecord_orderful_carton_tracking→ emitsMAN*CP. Set the one the partner guideline allows; clear the other so the generator doesn't emit a second, disallowedMAN.
- One
customrecord_orderful_shipped_itemper item in the carton:custrecord_orderful_shipped_carton→ the carton idcustrecord_orderful_shipped_fulfillment→ the IF idcustrecord_orderful_shipped_item→ the NS item idcustrecord_orderful_shipped_quantity
customrecord_orderful_shipped_item has a required name field on create; so does customrecord_orderful_carton if the install marks it mandatory — pass name/altName to avoid "Please enter value(s) for: Name.".
Step 3 — Ship the Item Fulfillment
PATCH /services/rest/record/v1/itemfulfillment/{ifId}
{ "shipStatus": "C" }
shipStatus C = Shipped (the 856 reflects a shipped IF). You can set status and packages in the same PATCH if you also use native packages, but the carton records above are what the 856 generator reads.
Step 4 — Fire the outbound trigger
PATCH /services/rest/record/v1/itemfulfillment/{ifId}
{ "custbody_orderful_ready_to_process_ful": true }
This is the 856 analog of the 810's custbody_orderful_ready_to_process_inv. The SuiteApp's outbound MapReduce picks it up within ~30–60 seconds. If a prior attempt left a stale state, toggle the flag false then true to re-queue.
Step 5 — Watch for the resulting 856 transaction
The generator writes a customrecord_orderful_transaction row keyed by a consolidation key of the form <direction>-856_ship_notice_manifest-<ifId>:
SELECT id, custrecord_ord_tran_orderful_id, BUILTIN.DF(custrecord_ord_tran_status) AS status,
custrecord_ord_tran_error
FROM customrecord_orderful_transaction
WHERE custrecord_orderful_consolidation_key = '2-856_ship_notice_manifest-<ifId>'
ORDER BY custrecord_ord_tran_orderful_id DESC
- Expect exactly ONE active row. If two OT rows (and two Orderful transactions) exist for the same IF, the scheduled outbound backstop MR likely dispatched alongside the User Event (the ready-flag stays
Tafter dispatch) — observed live as byte-identical duplicate documents at the partner. Verify which copy the partner should keep, and inactivate the superseded OT row (they can't be deleted — transaction join dependency). - Status
Errorwith"None of the following item fulfillments have cartons"→ Step 2 didn't produce cartons for this IF (committed before the carton records existed, or the dataset returned none). - Status
Error/"See Validation Tab for error detail"→ it reached Orderful but failed guideline validation. Pull structured errors withfetch-validations, then fix mapping withwriting-outbound-jsonata. Runaudit-outbound-rulesfirst.
Then confirm on Orderful's side:
GET https://api.orderful.com/v3/transactions/{orderful_id}
Headers: orderful-api-key: ${ORDERFUL_API_KEY}
VALID + DELIVERED → done.
Behaviour rules
- Always run the Step 0 pre-flight before transforming. The empty-IF / "at least one line item" failure is almost always a readiness gap (multi-ship-to, missing line location, nothing committed) — not a transform-syntax problem. Diagnosing it after the fact wastes far more time than the pre-flight.
- Pack with carton records, not native packages. The 856 generator ignores the NetSuite Packages sublist; it reads
customrecord_orderful_carton+customrecord_orderful_shipped_item(or a configured analytics dataset). Native packages alone produce the "no cartons" hard error. - One
MANdriver per carton. Setting both_sscc18and_trackingemits twoMANsegments (GM+CP); the partner guideline usually allows only one. Set the allowed one and clear the other. - Don't fabricate SSCCs or addresses inline. A valid SSCC-18 goes on the carton record; the ship-to address belongs on the SO/IF. Mock missing source data at the record, never as a literal in JSONata — see
writing-outbound-jsonatarule 13. - Confirm sandbox vs. production. This writes a real Item Fulfillment and fires real outbound EDI. Confirm
NS_SB_*vsNS_PROD_*in.envmatches intent. - Test cycle order matters. The 856 should be
VALID + DELIVEREDbefore billing the 810 (bill-and-fire-810) — a rejected 856 often predicts a rejected 810.
Reference material
reference/outbound-source-readiness.md— the source-record readiness checklist for 855/856/810.bill-and-fire-810— the Invoice/810 counterpart leg.fetch-validations— structured errors when the 856 landsINVALID.audit-outbound-rules/writing-outbound-jsonata— fix guideline-validation failures.alternative-packing-source— when cartons come from an analytics dataset instead of carton records.