Jobs: forms and resources
Replace a job’s schedule of rates
/a/Job_setScheduleOfRates
Replace the job’s rate rows, persist compatibility and current formats, and calculate their cost total.
When to use this endpoint
Use after selecting rate records and quantities in the Resources rate widget. Send the complete desired list, not a delta.
Call Job_updatePrices afterward when sell totals must immediately reflect the changed rates; this handler does not do so.
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
| Parameter | Type | Required | Description |
|---|---|---|---|
id | Integer string | Yes | Positive physical job ID. |
val | JSON array string | Yes | Complete rate list. [] clears it. Nonempty input must have sor_id on row 0. Normal rows contain sor_id and numeric amt, with optional price, stock_cost, labour_cost and cost. Missing price defaults to zero through compatibility conversion, not the rate’s catalog sell price. |
Example request
$result = FM_api('Job_setScheduleOfRates', [
'id'=>'1042', 'val'=>json_encode([['sor_id'=>205,'amt'=>2,'price'=>45.50]]),
]);
Example response
{"ok":1}
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: id"} | Nonpositive ID. |
{"error":"missing parameter: val"} | val is absent/null. |
{"error":"invalid parameter: val"} | Decoded input is not an array or a nonempty list lacks row-0 sor_id. |
{"error":"Unable to save job"} | The primary database save failed. Successful-save history and subsequent follow-ups are not run. |
{"error":"job does not exist"} | The job does not exist or is soft-deleted; no answer or resource write is performed. |
Legacy application errors may be returned with HTTP 200. Check the decoded response for error, not only the HTTP status.
Behaviour and edge cases
- Writes both legacy tuple schedule_of_rates and canonical schedule_of_rates2 JSON. The rate parser skips rows without positive numeric rate IDs/numeric amounts; full row validation is not performed at the endpoint boundary.
- When absent, stock_cost/labour_cost/cost are enriched from rate components: current stock average costs and rate labour totals. Existing supplied unit-cost fields are preserved. sor_cost sums unit cost×amount and rounds to three decimals.
- No active-rate existence check is imposed. Missing rate components contribute zero unless explicit costs were supplied. Existing cost snapshots are not automatically refreshed to later catalog costs.
- Requires an active job before computing and saving rate snapshots. Missing/deleted jobs return job does not exist. No pagination, batch limit or concurrency guard.
Side effects
- Checked active-job rate/cost/timestamp/edit-marker UPDATE, meetings-cache invalidation and generic job history only after a successful save.
- No Job_updateCosts/Job_updatePrices follow-up, stock deduction, invoice write or outsourced mirror is performed by this handler.
See it in Fieldmotion
CRM1
Open the job Resources/Costs page and edit schedule-of-rates rows. pageJobCosts.js debounces changes for 200 ms and sends the full list to Job_setScheduleOfRates.
CRM2
No direct canonical or legacy caller was found in CRM2’s source. Its layout-driven resource editing is a separate save contract. Its Costs page explicitly finishes with Job_updatePrices rather than directly calling this rate setter.
Related endpoints
ScheduleOfRates_getreads a selected rate record before preparing usage rows.Job_updateCostsrefreshes cost/resource snapshots and invokes the price follow-up.Job_updatePricesrefreshes sell totals after price-bearing resource changes.