# Customer handover and service closure — Phase 3A.6

This phase extends baseline `d943202` (1,111 tests) with explicit service-center
release, verified physical custody transfer and administrative closure. It does
not change Customer/Device ownership, repair facts, independent QC or the frozen
Organization/RBAC/authorization architecture.

## Lifecycle and evidence

| Operation | Transition | Audit fact |
| --- | --- | --- |
| Mark ready | QC_PASSED → READY_FOR_DELIVERY | Release actor/time, exact passed QC and confirmed Device identity |
| Hand over | READY_FOR_DELIVERY → DELIVERED | Recipient snapshots, verification, accessory acknowledgements, actor/time; engineer assignment ends |
| Close | DELIVERED → CLOSED | Separate immutable closure actor/time/note |

QC PASS remains technical approval only. Readiness is an explicit operational
release, not delivery evidence. Custody remains with the center until handover
succeeds. A failed verification, rejected accessory reconciliation or failed write
leaves READY_FOR_DELIVERY with no handover. No failed-attempt journal is introduced.

The latest repair must be COMPLETED/REPAIRED and its latest QC COMPLETED/PASSED;
the completed diagnosis must belong to the same case and assignment period.
There must be no open diagnosis, repair or QC and no superseding repair/QC attempt.
Readiness captures the exact QC. Delivery rechecks this reference under locks.
Completed diagnosis with NULL RootCause remains valid immutable history.

CLOSED is terminal. Existing service guards reject intake, complaint, assignment,
diagnosis, repair, QC, cancellation and repeated delivery/closure operations.
Cancellation remains prohibited after successful repair/QC, including all three new
states. Refused collection, abandonment, disposal, write-off and reopening need
separate future exception workflows; cancellation is not a substitute.

## Exact models

All four models have UUID `id`, `created_at`, `updated_at`, protected references,
ordinary-save/delete guards and immutable model validation. No generic editing or
deletion API is provided.

| Model | Additional fields |
| --- | --- |
| ServiceCaseDeliveryRelease | service_case (one-to-one), quality_control, device, device_identity (64-character digest), readied_by, readied_at, note |
| ServiceCaseHandover | service_case (one-to-one), delivery_release (one-to-one), recipient_type, recipient_customer (nullable), recipient_name, recipient_mobile, recipient_relationship, recipient_identity_type, recipient_identity_reference, verification_method, verification_reference, handed_over_by, delivered_at, note |
| ServiceCaseHandoverAccessory | handover, intake_accessory, returned_quantity, condition_note |
| ServiceCaseClosure | service_case (one-to-one), handover (one-to-one), closed_by, closed_at, note |

Recipient name and relationship allow 200 characters, mobile 32, identity audit
reference 64, verification reference 120; notes allow 2,000. Text is trimmed and
mobile normalization reuses Customer conventions (formatting separators removed,
optional leading `+`, no inferred country code). Service timestamps are aware and
server-generated. Browser-supplied timestamps and actor selectors are not accepted.

PostgreSQL enforces controlled recipient/verification choices and coherent recipient
and document-reference fields, one release/handover/closure per case, one closure
per handover, unique handover/intake-accessory pairs and nonnegative returns. Local
timestamp checks require delivery/closure time no later than record creation.
Cross-table chronology (release ≤ delivery ≤ closure), returned ≤ received, case
matching, latest successful technical work, authorization and lifecycle are checked
by transactional services/model validation, not claimed as cross-table CHECKs.

## Recipient verification and privacy

CUSTOMER means the ServiceCase's presenting Customer, which may differ from the
Device owner. The service captures the fresh Customer display name and primary
mobile and retains its protected reference. A later Customer edit does not rewrite
the snapshot. Optional submitted name/mobile must agree with the current record.
Customer record verification or manual document checking is supported.

AUTHORIZED_REPRESENTATIVE requires an explicit name, mobile, relationship/reason and
verification reference. It uses representative-authorization or manual-document
verification. `recipient_customer` is NULL; no new Customer is created. The same
employee may perform delivery and closure when authorized for both.

Controlled verification methods are CUSTOMER_RECORD, AUTHORIZED_REPRESENTATIVE and
MANUAL_DOCUMENT_CHECK. Manual document checking additionally requires a controlled
type (NATIONAL_ID, PASSPORT, OTHER_DOCUMENT) and a minimal audit reference. Other
methods reject document fields. These are accountable operator assertions, not an
external identity-authentication system.

Never record passwords, PINs, OTPs, full national-ID/passport numbers, document
contents or unnecessary sensitive details. Use a minimal local review reference.
Fields are bounded and carry privacy instructions; free text cannot prove absence
of secrets automatically. This follows existing project conventions rather than
claiming a new credential detector. No document/signature images, uploads, OTP,
SMS, email or sensitive-document storage infrastructure exists in this phase.

## Device and accessory confirmation

The caller must confirm the stable Device UUID and its identity digest. The digest
is SHA-256 over Device UUID, catalog model/variant UUIDs and ordered active identifier
type/key pairs. It is a comparison token, not ownership evidence, an identifier
snapshot for reporting or encryption of an identity document. Readiness stores it;
delivery requires both fresh Device identity and the stored release digest to match.
Supported identifier writers lock the Device, so replacement cannot interleave
inside successful confirmation. An identity change after readiness blocks delivery;
release correction/revision is intentionally not implemented here.

Every immutable intake accessory must have exactly one explicit acknowledgement.
An empty list is valid only when nothing was received. Quantity must be an integer
from zero through the received quantity; any shortage needs a nonblank explanation.
Missing, duplicate, foreign, stale, negative or excess returns reject the entire
handover. Original intake descriptions and quantities remain unchanged. Admin gives
no default returned quantity; the employee must reconcile every item.

## Public transactional APIs

Import from `apps.service.handover_services`:

```python
mark_service_case_ready_for_delivery(
    *, service_case, actor, expected_device, expected_device_identity,
    note="", expected_updated_at=_UNSET)

handover_service_case(
    *, service_case, actor, expected_device, expected_device_identity,
    recipient_type, verification_method, accessories,
    recipient_name="", recipient_mobile="", recipient_relationship="",
    recipient_identity_type="", recipient_identity_reference="",
    verification_reference="", note="", expected_updated_at=_UNSET,
    expected_customer_updated_at=_UNSET)

close_service_case(*, service_case, actor, note="", expected_updated_at=_UNSET)
```

Omit optional values rather than importing `_UNSET`. The default expected revision
is the supplied object's `updated_at`, not an unconditional write. Refresh the
ServiceCase between operations: these functions return release, handover and closure
records respectively. Customer confirmation defaults to the supplied case's Customer
revision; callers should capture it explicitly with their presentation snapshot.

Use `handover_queries.device_identity_fingerprint(device)` to capture the displayed
identity token. Reconciliation is a list of dictionaries containing
`intake_accessory` (saved object with captured revision), `returned_quantity`, and
optional `condition_note`. Public services require persisted default-database
objects. Business denial raises PermissionDenied; invalid/stale domain facts raise
ValidationError without exposing database constraint values.

Handover locks and revalidates authorization, lifecycle, Customer, Device, technical
history and release, inserts recipient and accessory evidence, closes the existing
engineer assignment with the delivery actor/time and a clear delivery reason, then
sets DELIVERED. All writes share one atomic transaction. Any failure rolls back the
evidence, assignment ending and case state. No ownership history changes occur.

Closure requires DELIVERED, complete immutable handover/accessory evidence, latest
passed QC, no current engineer assignment and no open technical work. It records
closure separately and sets CLOSED atomically. Closure does not require current
Customer/Device/catalog activity after physical custody has already transferred;
the active actor and operational Company/Center authorization are still required.

## Authorization and Admin

Canonical permissions are `service.handover_servicecase` (release and delivery) and
`service.close_servicecase`. Each service calls the frozen `require_permission`
against the case's ServiceCenter. Normal users need an active, internally valid
posting and granting role on the same path. Company/Region/exact-Center scopes follow
the existing containment rules. Other Company/Center and mixed permission/scope
paths do not grant access. Staff, Groups and direct Django permissions do not grant
business scope. The established active-superuser explicit bypass remains in force;
this is administrative custody work, not technical engineer/QC impersonation.
Domain prerequisites, including active operational hierarchy, remain mandatory.
No authorization adapter/backend or `has_perm()` semantics are changed.

The ServiceCase Admin delivery link opens a dedicated native workflow. GET is
read-only. POST requires CSRF, native view/change access, business scope and a signed
revision covering case, Customer, Device fingerprint and accessory identities,
revisions, quantities and descriptions. Services recheck captured facts under locks.
The page shows Customer, Device identifiers, QC, intake accessories and finalized
handover/closure. Explicit Device, recipient verification and physical acceptance
confirmations are required for delivery. A failed collection is not a delivery.

Actors and times come from the session/server. The four generic evidence Admins are
readonly, scoped to handover/closure capability, and cannot add/delete. Admin logs
only a generic operation message, not recipient/verification data. Existing native
Admin pages retain their prior trusted-administration boundaries; this phase does
not turn the entire Admin into a new end-user authorization interface.

## Lock order and concurrency

Readiness/handover use the existing default-PostgreSQL coordination conventions:

1. Acting User SHARE (active, fresh).
2. Case Company SHARE, actor organizational paths SHARE in UUID order, referenced
   Roles SHARE in UUID order. Supported permission/posting/User/Company writers
   use conflicting locks; fresh authorization is evaluated after acquisition.
3. Customer SHARE; catalog Brand → Category → Model → optional Variant SHARE;
   Device SHARE. Company coordination protects Center/Region lifecycle/movement.
4. ServiceCase UPDATE; current engineer assignment UPDATE.
5. Completed diagnosis UPDATE → latest repair UPDATE → latest QC UPDATE.
6. Delivery release UPDATE (handover); insert handover; original intake accessories
   UPDATE in UUID order; insert acknowledgements; persist assignment ending and case.

Closure uses steps 1–2, then case → current-assignment check → diagnosis → repair →
QC → release → handover → intake accessories → acknowledgements (ordered UUIDs),
then inserts closure and updates case. Customer/catalog/Device are not locked for
administrative closure. No late engineer-User UPDATE is introduced when ending an
assignment. The case lock coordinates all supported technical/service writers.

Real PostgreSQL tests observe actual `pg_blocking_pids` waits. They cover duplicate
release/delivery/closure, both meaningful orders of role/posting/permission and
Company/Center/Customer/Device/User changes, identifier replacement, accessory
reconciliation, premature/stale closure, assignment mutation, stale Admin forms and
rollback before a competing retry. This is tested serialization, not a formal
deadlock-freedom proof.

## Queries and historical association

Import from `apps.service.handover_queries`:

```python
device_identity_fingerprint(device)
authorized_delivery_cases(*, actor, permission=HANDOVER_PERMISSION)
get_service_case_delivery_release(service_case)
get_service_case_handover(service_case)
handover_accessories(handover)
get_service_case_closure(service_case)
ready_for_delivery_cases_for_center(service_center, *, actor=None)
delivered_cases_for_center(service_center, *, actor=None)
closed_cases_for_center(service_center, *, actor=None)
handover_history_for_customer(customer)
```

Collections remain lazy SQL QuerySets. Queues order received_at/job_number/UUID;
handover history orders delivered_at/UUID; acknowledgements use created_at/UUID.
Passing an actor enforces frozen authorization in SQL (handover permission for
ready, closure permission for delivered/closed). `authorized_delivery_cases` also
enforces scope. Omitting actor makes center queues **trusted internal queries**;
raw detail, history and accessory helpers are likewise not caller authorization.
Application handlers must authorize disclosure. Detail returns a record or None;
invalid/unsaved inputs to historical queries return empty/None.

Customer history means presenting Customer → ServiceCase → stable Device →
diagnosis → repair → QC → release → handover → closure, including representative
collection. It does not claim ownership. General engineer queues retain readiness
until delivery ends the assignment; delivered/closed cases leave active work queues.

Representative query tests measure **one SQL query each** for ready queue, delivered
queue, handover detail with recipient/actor/case displays, accessory display, and
closure detail with actor/Customer/Center. Joined related fields avoid per-row
lookups; authorization filtering is performed in SQL, not Python table scans.

## Security boundaries and limitations

Migration `service.0006_handover_closure` adds the four models, permissions and three
states; historical migrations remain unchanged. One baseline test uses INVALID
instead of CLOSED for unsupported-status rejection, as explicitly approved. No frozen
Phase 1/2 code is modified. Database owners/raw SQL/QuerySet.update/bulk operations
can bypass application immutability, as in existing phases; this is not a tamper-proof
ledger. Supported services and Admin are the mutation boundary.

Evidence is immutable even if a recipient or cause is learned to be different later.
No correction/revision, release replacement, redelivery or reopening workflow exists.
Identifier corrections after readiness need a future explicit recovery design.
Device custody/recipient assertions do not prove legal ownership or external
identity authenticity. Free text needs responsible minimal entry. No detailed
failed-collection log or historical/backdated import is provided.

Timestamps and exact references support future repair/QC/delivery/closure durations,
turnaround, recipient/employee and accessory-discrepancy reporting. No reporting UI,
billing, payment, inventory, notification, shipping, SLA or Phase 3A.7 is implemented.

## Verification and file inventory

The final full `python manage.py test --noinput` run after the approved baseline
test adjustment passed **1,233 tests, 0 failures, 0 skips**, in **1,331.733 seconds**.
This single complete run includes **1,111 baseline tests** (one approved sentinel
adjustment) and **122 new tests**: 64 domain/authorization/query tests, 21 Admin
tests and all 37 real PostgreSQL concurrency tests. A fresh PostgreSQL test
database applied the complete migration graph.

`check`, `makemigrations --check`, `migrate` and `showmigrations service` passed;
service migrations 0001 through 0006 are applied, with no pending model changes.
Historical migrations and frozen Phase 1/2 source remain unchanged. CLOSED is
entered only through the controlled DELIVERED-to-CLOSED service path in supported
workflows; unsupported status values remain database-rejected. Privileged raw
writes remain outside the supported lifecycle boundary described above.

One existing baseline test was intentionally updated from `status="CLOSED"` to
`status="INVALID"` because Phase 3A.6 makes CLOSED a valid terminal lifecycle state.
The assertion and purpose of the test remain unchanged. No other baseline test
was changed.

New implementation files: `handover_models.py`, `handover_services.py`,
`handover_queries.py`, `handover_admin.py`, `templates/admin/service/handover.html`,
`migrations/0006_handover_closure.py` under `apps/service`, plus
`test_handover.py`, `test_handover_admin.py`, `test_handover_concurrency.py` and this
document. Integration touches `models.py`, `services.py`, `engineer_queries.py`,
`engineer_admin.py`, `admin.py`, the approved sentinel in `tests.py`, and the six
linked existing architecture/workflow documents. Changes comprise 12 modified
tracked files and 10 new files, all unstaged/uncommitted, at HEAD `d943202`.
No stage, commit, push or Phase 3A.7 work occurred.
