# Job parts custody, consumption, returns, and defective recovery

Phase 3B.5 adds explicit physical stock handling after reservation. It does not
change frozen repair or diagnostic history.

## Issue and custody

`issue_reserved_parts` requires an ACTIVE reservation on an APPROVED request,
fresh device compatibility, an operational case, and a currently eligible
assigned engineer. The actor needs issue_parts scope at both case and source.
The entire reservation is issued atomically to a dedicated managed CUSTODY
location associated with the case service center. Immutable `PartsIssue` records
reservation, case, assignment, recipient, issuer, time, and ledger movement.
Serialized units retain identity and enter IN_CUSTODY. Issue does not consume
stock. A request becomes FULFILLED only when SQL totals show every requested line
has been issued in full. Partial issue uses separately sized reservations.

Ordinary location/movement/document APIs cannot create or use managed custody
locations. There is no implicit transfer between jobs or engineers.

## Consumption and unused return

`consume_issued_parts` requires consume_parts scope and the responsible eligible
engineer. Consumption references a performed, active ServiceRepairAction in an
OPEN repair execution for the case/current assignment. Fresh compatibility and
taxonomy are checked under dependency locks. A CONSUME movement has a source,
no destination, and one negative ledger entry. It removes physical stock rather
than hiding it in a fictitious inventory sink. Serialized state becomes CONSUMED
with no location and permanent movement evidence. Completed repair history is
never retrospectively enriched.

`return_unused_parts` requires return_parts scope at case/custody/destination.
It posts a MOVE back into an active usable same-company location. It can resolve
unused custody after repair completion, cancellation, or part deactivation.
It never restores consumed stock. Positive disposition quantities and the case,
issue, and position locks prevent duplicate/over-disposition. SQL totals determine
unresolved custody; there is no editable issued/consumed quantity counter.

Both operations append immutable `PartsDisposition` evidence and require UUID
command keys. Admin signs the selected issue's creation revision and monotonic
resolved quantity; a partial disposition invalidates previously displayed forms.
Returned demand remains historical fulfilled demand; further work requires a new
explicit request instead of reopening old request evidence.

## Removed defective customer components

`recover_defective_component` records a performed action in an open repair, case,
device, actor/time, quantity, component description, optional observed identifier,
and optional consumed replacement linkage. Recovery is routed only to an active
same-company QUARANTINE or DEFECTIVE location. Its immutable `DefectiveRecovery`
record is the receipt evidence for removed customer property.

Removed customer components are a separate inventory class from SparePart
replacement stock. They do not increase the replacement-parts stock ledger and
cannot be issued or consumed as serviceable replacements. An unknown identifier
stays absent: the system never invents a serial number or adopts the replacement
unit's identity. Identified recovery rows represent one component. Separate
scoped recovery queries expose physical defective custody. This phase supports
recording and visibility, not disposal, refurbishment, manufacturer claims, or
conversion to serviceable stock. Locations holding recovery evidence cannot be
deactivated while that custody remains unresolved by a future explicit workflow.

## Closure integration and locking

The only frozen service integration is a guard in `close_service_case`, under its
existing case update lock, rejecting ACTIVE reservations or issued quantity not
fully consumed/returned. It acquires no later inventory locks. All job inventory
writers take that same case lock before posting, so closure cannot race an
unresolved inventory writer. Existing cases without inventory retain their
original closure behavior; baseline tests are not changed.

Locks follow actor/recipient, company/RBAC, catalog/device/taxonomy, part
dependencies, physical inventory locations, case/current assignment and repair
history where needed, request/issue, stock positions, reservations/units.
Same-class multirow locks are UUID ordered. Managed custody creation is private
to issue. All evidence, stock effects, serialized state, and status changes share
one transaction. Recovery and usage history have database immutability and
ownership/evidence triggers in addition to domain validation.

Ledger constraints now explicitly allow CONSUME's outward-only shape while
continuing to require a destination for every RECEIPT and MOVE. This extends the
new Phase 3B ledger types; it does not weaken the frozen application schema or
edit any historical migration.

## Administration and queries

Admin adds issue/disposition/recovery through the service APIs, restricts choices
by business scope, requires CSRF, and signs revisions. Posted history is readonly
and undeletable. Query helpers filter in SQL, join display dependencies, aggregate
remaining custody, and separately expose defective recovery. No native Django
permission or staff flag supplies business scope.

## Verification

The combined inventory and frozen handover run passed 282 tests in 294.341s,
including all 64 existing handover tests unchanged. The final movement-form change
was then verified with all 21 foundation/usage Admin tests passing in 14.773s.
Phase 3B.5 adds 43 tests, including 12 real PostgreSQL races. Inventory now has
219 tests, including 55 concurrency tests. Coverage includes actual closure,
consumption-versus-return, duplicate issue/recovery, rollback, zero-total-stock
policy locking, signed partial-disposition revisions, and SQL query budgets.
Django checks, migration drift checks, and diff whitespace checks passed.
