# Service job parts requests and reservation

Phase 3B.4 integrates inventory with the existing service case without changing
the frozen service lifecycle or compatibility engine.

## Demand and decisions

`create_parts_request(actor, service_case, lines, note)` requires a DIAGNOSED or
REPAIRING case, the currently eligible assigned engineer, and scoped
`inventory.request_parts`. Each immutable line records a distinct part and a
positive requested quantity. The request starts REQUESTED; creation and every
decision append immutable `PartsRequestEvent` evidence.

`approve_parts_request` and `reject_parts_request` require scoped approve_parts.
Approval moves REQUESTED to APPROVED; rejection requires a reason and terminates
the request. `cancel_parts_request` accepts REQUESTED or APPROVED only. The
requester needs request_parts; another actor also needs approve_parts. Active
reservations must first be explicitly released. Issued demand cannot be erased
by cancelling its original request. A partially issued request may cancel its
remaining unissued demand only after all reservations are released and every
issued quantity is consumed or returned. Original lines, issues, and events remain.
There is no arbitrary demand/status edit.
FULFILLED is reserved for the explicit issue workflow introduced in 3B.5.

## Compatibility

Creation, approval, and reservation check fresh catalog and part state through
the frozen parts compatibility APIs. A device with a variant uses exact variant
compatibility, including model-wide mappings. A model-only device excludes
variant-only parts. Mapping every current variant never implies model-wide
compatibility. Catalog/device and part locks stabilize these checks.

## Reservation and release

`reserve_parts` binds a request line, company, case, part, usable source location,
quantity, actor, time, and explicit serialized unit selections. The request must
be APPROVED. Partial reservation uses multiple immutable reservation records;
active plus issued quantities cannot exceed demand. Partial/reserved progress is
derived by SQL rather than duplicating aggregate state on the request.

Reservation changes no physical ledger evidence. SQL ledger SUM determines on
hand; available equals on hand minus ACTIVE reservations. REQUIRED_SERIAL needs
one distinct unit per quantity. OPTIONAL_SERIAL independently protects serial
and anonymous buckets. A partial unique constraint prevents simultaneous active
reservation of one unit for different jobs.

`release_reservation` transitions ACTIVE to RELEASED with reason, actor, and
timestamp. Original quantity and unit history remain immutable. It allows
cleanup after service cancellation or part deactivation. Organization and actor
authorization must still be active. Release does not require current part/device
compatibility because it creates no new demand or physical stock.

ServiceCase cancellation itself remains frozen and does not silently rewrite
inventory history. It prevents further reservations. Existing reservations must
be explicitly released; their original case/request attribution remains.

## Concurrency and scope

Locks follow actor, company, RBAC dependencies, catalog/device, part category,
part, inventory locations, case, request, stock positions, reservations/units.
Multiple rows within a class use UUID order. All case-linked commands take the
same case update lock as the existing lifecycle services. Every outward stock
posting checks reservations while holding the same stock-position update lock.
Thus a transfer cannot spend reserved stock and two jobs cannot reserve the last
unit. Compatibility and part deactivation use shared/exclusive dependency locks.

Reservation requires reserve_parts at both the case service center and source
location. Company ownership is checked independently of authorization, including
for superusers. Native Django permissions alone do not create business scope.
Database triggers retain immutable request/line/event/reservation evidence and
validate serialized reservation ownership and state at transaction completion.

## Admin and queries

Admin creates requests and reservations through domain services. Existing records
are readonly. Approval, rejection, cancellation, and release use actor-bound
signed revision confirmations and CSRF-protected POST. Reservation creation
signs the displayed request revisions so stale selections cannot silently win.
Query helpers filter cases and inventory locations in SQL, prefetch detail
evidence, and expose SQL reserved/issued totals by line.

## Checkpoint verification

The combined inventory checkpoint passed 176 tests in 176.119 seconds, including
43 real PostgreSQL concurrency tests. Phase 3B.4 adds 45 tests (12 concurrency),
covering compatibility, company isolation, query budgets, optional-serial buckets,
immutable evidence, Admin, stale revisions, lifecycle, and rollback.
System checks, migration drift, and diff whitespace checks passed.
Adjustment-versus-reservation concurrency is verified in 3B.6
when the adjustment writer exists; it must use the same stock-position lock.
