When to use this endpoint

Use for the CRM1 Resources stock widget’s complete desired state, after reading Job_getStock and resolving source IDs.

Do not confuse this with Job_setStockUsed: that endpoint modifies usage rows without adjusting warehouse/container levels.

Authentication and permissions

Authenticate with signed API-key fields or a logged-in CRM session. The handler performs no additional page or role-permission check.

Job and form IDs refer to the authenticated account. Cross-account writes occur only through the stored outsourcing relationship on the routes that explicitly mirror changes.

The dispatcher also applies account/user and IP restrictions. A CRM session carrying a CSRF token must supply it through X-FMCSRF or _csrf; missing/incorrect tokens can be rejected before this handler runs.

Parameters

ParameterTypeRequiredDescription
idInteger stringYesPositive physical job ID.
valJSON array stringNoFull desired direct stock list, plus optional table-linked entries. Defaults to blank, which clears direct usage; use [] explicitly to clear. Rows use id, amt and source, optionally cost/price. No numeric amount/stock existence/availability validation is enforced.
val[].sourceStringPer row0 denotes legacy warehouse stock; encoded type_ID selects a legacy stock_container (1=user, 2=vehicle, 3=location). Source IDs are integer-converted. Tokens without an underscore are treated as warehouse for quantity adjustment; use canonical tokens.
val[].linkMethodObject inside val JSONNoAbsent or method=linkToJob keeps a direct row. method=table queues an addressed table cell using table_id/table_row/table_column; other methods are removed from the direct list without a table update.
val[].cost / val[].priceNumbers inside val JSONNoExplicit snapshots are preserved. Missing values use stock average cost and the resolved customer’s price band. Customer resolution follows asset owner/location chains with cycle protection.

Example request

$result = FM_api('Job_setStock', [
	'id'=>'1042',
	'val'=>json_encode([['id'=>'205','amt'=>'2','source'=>'0']]),
]);

Example response

{"ok":1}

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: id"}Nonpositive ID.
{"error":"job does not exist"}No active job is read.
{"error":"Unable to save job stock"}The primary database save failed. Successful-save history and subsequent follow-ups are not run. Stock quantity/usage database changes are rolled back together.
{"error":"invalid parameter: val"}Nonempty stock input does not decode to an array. Blank input still clears direct usage.
{"ok":1,"warning":"Job saved; follow-up incomplete. Inspect saved state before retrying.","warnings":["Job_updateCosts: Unable to save job"],"saved":1}The primary save succeeded, but a recalculation, asset rebuild, notification or outsourced mirror failed. warnings identifies the failed follow-up; any normal response fields remain present. A benign no-change result from a follow-up is not a failure. Read saved state before retrying.

Legacy application errors may be returned with HTTP 200. Check the decoded response for error, not only the HTTP status.

Behaviour and edge cases

  • Aggregates old/new direct quantities by source and stock ID and applies old-minus-new deltas. Zero-amount direct rows are removed. Existing rows lacking source may use their containers key only when exactly one container is present; unidentifiable old rows are skipped.
  • An old item removed from a source is returned even when other requested items still use that source. Unchanged retained items have no quantity delta.
  • Main form meta.do_not_update_stock_levels disables warehouse/container quantity adjustments, but does not prevent usage saves, table-cell edits or cost recalculation.
  • Table-linked entries replace each addressed cell with an encoded version-2 stock object (stock entries with id/amt/containers and v=2). Other cells survive. These entries are excluded from the direct quantity delta; this path does not itself deduct stock for those cell changes.
  • Missing cost/price snapshots are enriched; relative customer bands multiply/round base price, absolute bands use a stock-specific band price when present. Existing explicit price, including zero, is retained.
  • Malformed truthy JSON is rejected as invalid parameter: val. No positive-stock/availability check, deduplication of stored rows, amount bound, paging or batch limit is provided.

Side effects

  • Locks the active job before reading usage, then updates warehouse stock.in_stock or stock_container.amt and saves usage in one tenant database transaction. Failed quantity/app-sync/job writes roll back the transaction. Missing containers are inserted. This is not the newer stock-inventory movement workflow.
  • After a successful commit, crossing a configured amt_trigger downward can queue threshold email to its configured recipient; mail failure produces a saved-state warning. Container changes mark users, vehicles or locations edited for app synchronization. Vehicle source IDs are coerced to integers, so UUID-based sources do not retain that identity.
  • Checked active-job stock_used/subform_values/timestamp/edit-marker UPDATE and meetings-cache invalidation. Records generic job history only after commit, then calls Job_updateCosts and Job_updatePrices (costs already invokes prices); failures produce saved-state warnings. No outsourced mirror is made.

See it in Fieldmotion

CRM1

Open a job’s Resources/Costs stock table and edit rows or quantities. pageJobCosts.js sends the full JSON list to Job_setStock. The job editor also calls it immediately when deleting a saved resource row.

CRM2

No direct canonical or legacy caller was found in CRM2’s source. Its layout-driven resource editing is a separate save contract. No direct Job_setStock request was found in its resource editor.

Related endpoints

  • Job_getStock reads the job’s direct and field-specific stock usage before a resource edit.
  • Job_setStockUsed adjusts usage rows without moving inventory.
  • Job_updateCosts refreshes cost/resource snapshots and invokes the price follow-up.