ModulesDocketsLine Rates

Line Rates

A worker line and an equipment line each name the rate they bill at. The ids come from the request body, and the database will accept any contract’s row. So the API checks every rate id against the docket before it writes the line.

Which fields are checked

LineFields
Workerclassification_id, rate_line_id
Equipmentoperator_classification_id, operator_rate_line_id, rate_line_id

Every door that writes one of these lines runs the check: add and edit a worker, add and edit an equipment line, and the office create of a subcontractor docket.

The rule

A rate passes when both of these hold:

  1. It is a live rate on a live contract owned by the organisation that owns the docket’s project. A supplier-owned docket still bills on the head contractor’s contract, so the check uses the project’s organisation, not the docket’s.
  2. When the docket has a contract, the rate is on that contract. On a worker edit where the docket has no contract, the worker’s own assigned contract stands in, the same as the rate line check always did.

A subcontractor worker with a person on it is checked against that person’s own supplier engagements instead. That check also holds the rate to the project’s organisation.

A subcontractor headcount row with no person has no engagements to read, so an edit that changes its rate goes through the rule above, the same as adding one.

Editing a line and sending a rate it already has is never refused, and the rate is left as it is. That holds for either stored column, a legacy id or the rate line made from it, and it holds when the rate was deleted or the docket’s contract changed after the line was rated. Only a changed rate is checked, so older lines stay editable.

The legacy equipment_rate_id keeps its own supplier check, and follows the same rule on an edit.

A worker line’s worked_through_break_type_ids follow it too. The mobile app sends the line’s whole set after every break change, so a break type the line already has is kept, and only a new one is checked.

The refusal

The API answers 400 with the code rate-not-on-docket, the message “That rate isn’t available on this docket. Pick one from the list.” and a fields entry naming the request field. Nothing is written.

The refusal does not say whether the rate is unknown, deleted, on another contract or another organisation’s. A scheduled item line whose rate line is off the docket’s contract gets the same 400.

Cost

One query per write that names a rate, for every rate on the request at once. A write that names no rate, or only the rate the line already has, costs nothing extra.

Code

  • backend/api/internal/docket/rate_anchor_scope.go holds the rule.
  • ListRateAnchorContractsInProjectOrg in backend/db/queries/docket_workers.sql is its query.
  • backend/api/internal/docket/line_ref_scope_guard_test.go finds every line write that takes a request body and fails if one skips the check.