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

ParameterTypeRequiredDescription
suppliersNative array of objectsYesOrdered groups {id:supplierID,formID:formID,purchaseOrderEntry:[mappedLine,...]}. Each group creates one header; repeated suppliers are not merged. Empty array returns an empty id list.
referenceTextWorkflow inputShared reference for every header, consumed directly; supply it even if blank. Mapped limit is 32 Unicode characters.
targetNative picker objectNoCopied header link {type:4,id:jobID} for a Job. Omission creates unlinked orders; no job_id parameter is read.
suppliers[].purchaseOrderEntryNative arrayNoMapped 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

ResponseCondition
{"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