Quotes: settings, costing and lookups
Replace quote cost data
/a/Quote_costsSet
Replace meta.costs and rebuild authoritative header and section totals from rows.
When to use this endpoint
Use for the legacy Costs dialog, which sends its complete costing object. This is a replacement, so retain sections you intend to keep. It runs the same row calculation as Quote_calculateCost.
Authentication and permissions
Authenticates a signed API key or CRM session as its tenant user. Requires Quotes View (pageQuotes_view). Non-admin users blocked by legacy crm_access bit 32 are denied. Roleless non-admin users fail closed. Also requires Quotes Edit (pageQuotes_edit); client administrators inherit page permissions.
Uses local tenant IDs. The active quote must also satisfy the account’s customer department/account-manager visibility filters for non-admin users, including signed-key requests.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | Positive integer-compatible quote ID | Yes | Positive local quote ID, integer-converted; an active quote is required. |
costs | JSON-encoded array/object string | Yes | Must decode to a PHP array; native request arrays are not accepted by json_decode. Entire meta.costs is replaced. Sections stock, sor, labour, other and verbose must be lists of row objects. Supplied *_total_* summaries are overwritten by calculated values. |
Example request
$result = FM_api('Quote_costsSet', [
'id'=>'1042',
'costs'=>json_encode([
'other'=>[['description'=>'Call-out','qty'=>1,'cost'=>40,'price'=>100]],
'other_total_cost'=>40,
'other_total_price'=>100,
'other_total_gross'=>120,
]),
]);
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":"quote does not exist"} | The active quote is missing (lookup failures are reported separately). |
{"error":"Unable to save quote. Check the saved state before retrying."} | Database, audit, transaction or commit acknowledgement failed. Refresh saved state before retrying an uncertain commit. |
{"error":"permission denied"} | Required Quotes permission is absent or the non-admin actor is blocked by legacy access settings. |
{"error":"missing or invalid parameter: id"} | Missing/nonpositive quote 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
- costs must be a JSON-encoded array/object string; native request arrays fail. Replaces the entire costs structure while preserving other valid metadata keys. Malformed outer metadata fails without replacement.
- Calculates authoritative totals from stock, sor, labour, other and verbose rows; supplied section summaries are overwritten. Sections must be lists of row objects, and numeric inputs must be finite. Migrated numeric-prefix strings such as 2 units are preserved and calculated from their numeric prefix. Oversized totals reject the whole save.
- Uses the same calculation as Quote_calculateCost, including row discounts, header discount/markup and tax category lookup. Saving again does not compound discounts.
- Locks the active quote in a tenant transaction. Bound writes, last_edited (server-local date/time), quote history and quotes cache invalidation are checked together. Audit or primary write failure rolls back. Existing caller transactions retain a savepoint; an uncertain commit returns an error and is never replayed automatically.
- No direct mobile app/m-service caller for this RPC was found.
- Calculation semantics match Quote_calculateCost: total_net is pre-adjustment; tax/gross/profit use row and header adjustments. A positive chosen taxType overrides tax. Tax-only legacy rows retain percentage interpretation.
Side effects
- Updates the named data and last_edited, records quote history and requests quotes cache invalidation in the tenant transaction. No ordinary mention notice, notification or email is sent by this dedicated handler.
See it in Fieldmotion
CRM1
Quotes → Costs (widgetQuoteCosts) serializes its complete costs object into costs. Notes/Fields stock-copy tools also build row and summary values before calling Quote_costsSet.
CRM2
No direct Quote_costsSet caller found. Mapped cost widgets save rows through Quote_updateData, followed by Quote_calculateCost.
Related endpoints
Quote_costsGetloads the saved object before replacement.Quote_calculateCostrecomputes summaries from individual rows.Quote_setDiscountuses the same row calculation with a new header rate.