fix(contracts): reject out-of-set contract_type with 422 instead of 500 #233

Merged
David merged 2 commits from fix/pms-299-contract-type-validation into main 2026-06-14 15:55:38 +02:00
Owner

POST /api/v1/contracts deserializes contract_type as a free String, so any value passes the request layer. The DB CHECK constraint on contracts.contract_type (migration 009) only allows managed_services, block_hours, time_and_materials, fixed_price, warranty. Values outside that set (e.g. recurring, retainer) reached the INSERT, violated the constraint, and surfaced as an unhandled 500 DATABASE_ERROR.

Add validate_contract_type (validation utils) backed by a CONTRACT_TYPES constant kept in sync with the DB CHECK set, and wire it onto CreateContractRequest::contract_type via #[validate(custom(...))]. Requests carrying a contract_type outside the set now fail validation and return a clear 422 listing the valid values, so no value that passes deserialization can produce a 500. recurring and retainer are item-level concepts (contract_items.item_type), not contract-level types, so they are correctly rejected at the contract layer.

#PMS-299

`POST /api/v1/contracts` deserializes `contract_type` as a free `String`, so any value passes the request layer. The DB CHECK constraint on `contracts.contract_type` (migration 009) only allows `managed_services`, `block_hours`, `time_and_materials`, `fixed_price`, `warranty`. Values outside that set (e.g. `recurring`, `retainer`) reached the INSERT, violated the constraint, and surfaced as an unhandled 500 DATABASE_ERROR. Add `validate_contract_type` (validation utils) backed by a `CONTRACT_TYPES` constant kept in sync with the DB CHECK set, and wire it onto `CreateContractRequest::contract_type` via `#[validate(custom(...))]`. Requests carrying a contract_type outside the set now fail validation and return a clear 422 listing the valid values, so no value that passes deserialization can produce a 500. `recurring` and `retainer` are item-level concepts (`contract_items.item_type`), not contract-level types, so they are correctly rejected at the contract layer. #PMS-299
fix(contracts): reject out-of-set contract_type with 422 instead of 500
All checks were successful
E2E / Playwright against staging (pull_request) Successful in 39s
Check / fmt + clippy + compile + unit/doc tests (pull_request) Successful in 1m21s
Integration / integration tests (pull_request) Successful in 3m30s
8b0625dc45
`POST /api/v1/contracts` deserializes `contract_type` as a free `String`, so any value passes the request layer. The DB CHECK constraint on `contracts.contract_type` (migration 009) only allows `managed_services`, `block_hours`, `time_and_materials`, `fixed_price`, `warranty`. Values outside that set (e.g. `recurring`, `retainer`) reached the INSERT, violated the constraint, and surfaced as an unhandled 500 DATABASE_ERROR.

Add `validate_contract_type` (validation utils) backed by a `CONTRACT_TYPES` constant kept in sync with the DB CHECK set, and wire it onto `CreateContractRequest::contract_type` via `#[validate(custom(...))]`. Requests carrying a contract_type outside the set now fail validation and return a clear 422 listing the valid values, so no value that passes deserialization can produce a 500. `recurring` and `retainer` are item-level concepts (`contract_items.item_type`), not contract-level types, so they are correctly rejected at the contract layer.

#PMS-299
Merge branch 'main' into fix/pms-299-contract-type-validation
All checks were successful
Create release / Create release from merged PR (pull_request) Has been skipped
Check / fmt + clippy + compile + unit/doc tests (pull_request) Successful in 3m35s
E2E / Playwright against staging (pull_request) Successful in 3m2s
Integration / integration tests (pull_request) Successful in 13m23s
187eb94c9d
David merged commit 6bfb215298 into main 2026-06-14 15:55:38 +02:00
David deleted branch fix/pms-299-contract-type-validation 2026-06-14 15:55:38 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/mokosh-server!233
No description provided.