Purchase orders
Create supplier orders from selected Job stock
/a/PurchaseOrder_createFromJob
Create one order per supplied supplier group and append its mapped line rows.
When to use this endpoint
Use after grouping the Job stock to order by supplier and choosing each supplier’s form. The client supplies all lines; the endpoint does not discover the Job’s stock automatically.
Authentication and permissions
Authenticate with signed API-key fields or a logged-in CRM session. Session requests use dispatcher CSRF protection when configured. API keys use their owner’s permissions.
Requires explicit View on Purchase Order or Purchase Orders, plus Add on either page. Non-administrators with legacy Purchase Orders visibility disabled (crm_access bit 256) are denied.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
suppliers | Native array of objects | Yes | Ordered groups {id:supplierID,formID:formID,purchaseOrderEntry:[mappedLine,...]}. Each group creates one header; repeated suppliers are not merged. Empty array returns an empty id list. |
reference | Text | Workflow input | Shared reference for every header, consumed directly; supply it even if blank. Mapped limit is 32 Unicode characters. |
target | Native picker object | No | Copied header link {type:4,id:jobID} for a Job. Omission creates unlinked orders; no job_id parameter is read. |
suppliers[].purchaseOrderEntry | Native array | No | Mapped PurchaseOrderEntry_createData payloads. poID is overwritten with the newly created header ID. Supply stock, amt, costNet and explicit totalNet/totalTax as needed. |
Example request
$result = FM_api('PurchaseOrder_createFromJob', [
'suppliers'=>[[
'id'=>'42', 'formID'=>'5',
'purchaseOrderEntry'=>[[
'stock'=>['id'=>'71','type'=>'1'],
'amt'=>'2','costNet'=>'12.50',
'totalNet'=>'25','totalTax'=>'5',
]],
]],
'reference'=>'Job materials',
'target'=>['type'=>'4','id'=>'87'],
]);
Example response
{
"ok": 1,
"id": [
1042
]
}
Errors and unusual responses
| Response | Condition |
|---|---|
{"error":"not logged in"} | No authenticated tenant context is available. Invalid signed credentials can be rejected by the dispatcher before the handler runs. |
{"error": "missing or invalid parameter: suppliers"} | The suppliers key is absent. |
{"error": "missing or invalid parameter: stock"} | A nested line is missing its required stock picker; earlier headers/lines may already exist. |
{"error": "failed to create purchase order entry"} | A nested result has no error but no new line ID. |
Legacy application errors may be returned with HTTP 200. Check the decoded response for error, not only the HTTP status.
Behaviour and edge cases
- Consumes native supplier groups in order; it does not decode a suppliers JSON string or read a job_id. An empty native list returns {ok:1,id:[]}. Each group supplies its supplier, form and optional mapped lines.
- Headers use checked supplier/form/target/protected-field validation and owner-locked numbering. Nested line creation requires Purchase Order Edit in addition to the outer Add/View rights.
- Returns header IDs in group order, with no separate entry-ID list. Per-line net/tax remain supplied mapped amounts; cost/quantity formulas are not run automatically.
- All groups, headers, lines, initial containers and history commit together. Invalid later rows roll back earlier rows and headers. A committed follow-up warning must not trigger replay.
- The supplied target is a header link. It does not copy resources into Job stock_used; setLinkToJob is the separate allocation/merge operation. The CRM2 stock picker preserves each selected row’s own stock ID, quantity and supplier cost.
Side effects
- Writes use the tenant-owner lock and a transaction or caller savepoint. A rejected operation rolls back its local writes and checked history. Earlier separate API requests remain committed.
- Edit events, notifications and form follow-ups run after the owning commit. A saved/no-replay warning identifies committed data with a failed follow-up; inspect the saved state rather than repeating creation or sending.
- Creates headers and nested lines/initial availability, with checked history. The route does not send supplier email or mapped-header creation notifications.
See it in Fieldmotion
CRM1
No direct CRM1 caller was found; CRM1 normally creates the empty header and saves lines separately.
CRM2
In CRM2 Job → Resources → Stock, choose stock, supplier and form, then Create Purchase Order. Filter for PurchaseOrder_createFromJob; widgetAvailableStock sends supplier groups, reference and the Job target.
Related endpoints
PurchaseOrderEntry_createData— Nested line contract and initial containers.PurchaseOrder_setLinkToJob— Copy saved resources into the Job.