Purchase orders
Create a mapped Purchase Order
/a/PurchaseOrder_createData
Create a header from mapped layout fields, then recalculate its stored totals.
When to use this endpoint
Use this for a CRM2-style header before creating its lines. Use PurchaseOrder_new for the smaller legacy field set; use createFromJob or createFromRequisitionOrder for those multi-record workflows.
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 |
|---|---|---|---|
supplierID / supplier | Integer / native picker object | Yes on creation | Available, active, non-held supplier ID, or native supplier={id:42}. The supplier must be visible through its own page/record scope. |
formID | Integer-compatible | No | Default 0 on creation. A nonzero value must identify an active type-5 Purchase Order form. |
reference | Text | No | At most 32 Unicode characters in mapped validation; omitted creation uses the database default. Unlike the legacy new route, no "no reference" fallback is added. |
poNum | Integer-compatible | No | Displayed order number; distinct from row ID. Creation selects the next number under the owner lock when omitted, or rejects a supplied nonpositive/used number. |
target / targetType / targetID | Native picker / integer-compatible | No | target={type:4,id:87}, or separate targetType/targetID. Link types: 0 No Link, 1 Contract, 2 Customer, 3 Quote, 4 Job, 5 Department, 6 Location, 7 Warehouse. Types 0/7 use ID 0. This header link does not itself allocate stock. |
status | Integer-compatible | No | Received status: 0 Open (creation default), 1 Part Received, 2 Complete, 3 Over-received, 4 Cancelled. This is separate from approval, sent history and metaStatus. |
deliveryDate | ISO date | No | Delivery date, YYYY-MM-DD. Mapped create/update validates SQL-date input; checked updateField additionally applies its strict real-date check. |
deliveryAddress / attention / instructions | Text | No | Delivery address, contact/attention and supplier instructions. Omission on update leaves existing values. |
deliveryNet / deliveryTax | Numeric-compatible | No | Carriage net and explicit tax amounts. Supplying a delivery tax code does not calculate deliveryTax on the server. |
deliveryTaxcodeID / deliveryTaxcode | Text / native picker | No | Stored carriage tax code, or picker {id:"T1"}. Do not substitute a percentage for the explicit deliveryTax amount. |
orderNet / orderTax | Numeric-compatible | No | Mapped order amounts. Recalculation replaces persisted header totals with stored line totals plus carriage; this is not a way to recalculate line amounts. |
taxInc / rowData / roID | Integer-compatible / text / integer-compatible | No | Tax-inclusion flag, legacy row payload, and linked Requisition Order ID. roID alone does not convert a requisition or move its allocations. |
customValues / meta / invoiceReceived | JSON-compatible values | No | Stored custom values, workflow metadata and invoice marker. Creation defaults customValues to {}. Existing invoice endpoints are preferable for maintaining the invoice marker. |
metaLink / metaStatus / metaOwing / metaPaymentList / metaOrderFullyReceivedDate | Mapped metadata values | No | Paths within meta: link, status, owing, paymentList and orderFullyReceivedDate. The last path is a date; the others use mapped text/JSON conversion. |
cDate / lastEdited / createdByID / createdBy / dateApproved / dateSent | Date-time / integer / picker | Legacy controls | Server-owned metadata. Creation uses the current actor and server times; a createdBy picker may only select that actor. Changed creation/edit/approval/sent metadata is rejected; use the dedicated workflow actions. |
Example request
$result = FM_api('PurchaseOrder_createData', [
'supplier'=>['id'=>'42'],
'formID'=>'5',
'reference'=>'PO-2026-1042',
'deliveryNet'=>'10',
'deliveryTax'=>'2',
]);
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: supplierID"} | Missing or invalid supplier selector. |
{"error":"supplier does not exist or is not available"} | The supplier is hidden, missing, inactive or held. |
Legacy application errors may be returned with HTTP 200. Check the decoded response for error, not only the HTTP status.
Behaviour and edge cases
- Requires an available, visible, non-held supplier. The form defaults to 0; a nonzero form must be active and type 5. Targets are validated for their own View permission and record scope; No Link/Warehouse use ID 0 and are unavailable to scoped roles that cannot view such orders.
- Creation stamps the current actor and server creation/edit times. Supplied approval/sent/creation/edit timestamps and a different creator are rejected. The layout-createdBy picker may identify only the current actor.
- The next displayed number is selected under the same tenant-owner lock as insertion. An explicitly supplied poNum must be positive and unused. The returned id is the inserted header ID, not poNum.
- Unknown mapped keys are ignored. The mapper enforces reference length and typed values. Persisted totals are recalculated from line totals plus carriage; an empty header therefore contains carriage only. Layout formulas are not executed by this route.
- Repeated successful calls create separate headers; there is no client idempotency key. A grouped/layout caller can include header and line creation in its owning transaction.
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.
- Inserts the header, writes generic field and checked creation history, recalculates totals and queues the create notification. Direct creation does not add lines, receipts or stock containers.
See it in Fieldmotion
CRM1
No direct CRM1 caller was found. CRM1 uses PurchaseOrder_new and the legacy PurchaseOrderEntry_new/save line editor instead.
CRM2
In CRM2 Purchase Orders, click Create Purchase Order, choose the supplier/form/reference, and inspect Layout_setValue when saving. Its purchaseOrder mapping calls PurchaseOrder_createData internally; a nested call need not appear separately in the browser. The popup uses poDefaultForm when configured, or the only available form, and checks stock/supplier compatibility in the client.
Related endpoints
PurchaseOrderEntry_createData— Append mapped lines to the new header.PurchaseOrder_new— Create the smaller legacy header.