When to use this endpoint

Use for the legacy Quotes Discount cell. This recalculates costs with the same row engine 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

ParameterTypeRequiredDescription
idPositive integer-compatible quote IDYesPositive local quote ID, integer-converted; an active quote is required.
valFloat-compatible percentageNoFinite rate; omitted defaults to zero. Absolute rounded value must not exceed 999.99. Markup accounts add the adjustment; other accounts subtract it.

Example request

$result = FM_api('Quote_setDiscount', ['id'=>'1042','val'=>'10']);

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":"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

  • Omitted val defaults to zero. Requires finite numeric input within the DECIMAL(5,2) supported range (absolute rounded value at most 999.99). An unchanged rate returns warning=no change needed.
  • If costs exists, recalculates all sections from rows through the same engine as Quote_calculateCost. Client summaries are not authoritative.
  • If no costs exists, applies the rate to existing pre-discount total_net and default-vat for manual totals. features2.discountMarkup=1 adds the adjustment; otherwise subtracts it. The stored total_net remains pre-discount.
  • 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 → Discount cell sends Quote_setDiscount; its numeric input has client-side 0–100 bounds.

CRM2

No direct Quote_setDiscount caller found. moduleQuotes saves the mapped discountRate and invokes Quote_calculateCost through onDataChange.

Related endpoints