# UAT verification — Phase 5C operational interface

Baseline commit: `303b29c` (Phase 5B). Inspected with the Phase 5C working tree
still uncommitted; no historical migration, frozen domain service, frozen admin
form or frozen test was modified. UAT results are recorded in
`UAT_GAP_REGISTER.md`; the inspected journey map is `END_TO_END_SERVICE_JOURNEY.md`.

Scope of this phase: exercise the actual end-to-end service journey through the
non-staff operational interface using non-superuser personas and the frozen
services, and classify every observed deviation. Phase 5C adds **no** schema, no
permission and no lifecycle. Every mutation screen delegates to an existing
authoritative service.

## 1. Persona bundles

Personas are arbitrary scoped capability bundles, never runtime role names. Each
persona is a non-staff, non-superuser `accounts.User` with a same-path role
assignment created through `apps.access.services`.

| Persona | Capabilities granted in UAT |
| --- | --- |
| Front Desk (`front`) | `service.view/change/add_servicecase`, `service.handover_servicecase`, `service.close_servicecase`, `frontdesk.view/create/check_in_appointment`, `frontdesk.manage_queue`, `frontdesk.view_queue`, `devices.view/add_device`, `devices.add_customerdevicerelationship`, `customers.view_customer` (company scope) |
| Engineer (`engineer`) | `service.view_servicecase`, `service.handle_servicecase`, `inventory.request_parts`, `inventory.consume_parts`, `inventory.view_stock` (center scope) |
| Second engineer (`engineer2`) | `service.view_servicecase`, `service.handle_servicecase` |
| Quality control (`qc`) | `service.view_servicecase`, `service.perform_quality_control` |
| Parts / warehouse (`warehouse`) | `service.view_servicecase`, `inventory.view_stock`, `inventory.manage_inventory`, `inventory.receive_stock`, `inventory.approve_parts`, `inventory.reserve_parts`, `inventory.issue_parts`, `inventory.return_parts` |
| Commercial / cashier (`cashier`) | `service.view_servicecase`, `commercial.view/manage_servicequotation`, `commercial.record_quotationdecision`, `commercial.view/manage_serviceinvoice`, `commercial.finalize_serviceinvoice`, `commercial.view/receive_servicepayment` |
| Financial manager (`finance`) | `service.view_servicecase`, `commercial.view_servicequotation`, `commercial.view_serviceinvoice`, `commercial.view/receive_servicepayment`, `commercial.reverse_servicepayment`, `commercial.authorize_due_release` |
| Supervisor (`supervisor`) | all `reporting.*` permissions, `service.view_servicecase`, `sla.view_sla`, `sla.monitor_sla`, `sla.manage_slapolicy`, `communications.manage_templates`, `communications.send_notification`, `communications.view_notification`, `commercial.view_servicequotation`, `commercial.view_servicepayment` (company scope) |
| Configuration administrator (`admin`) | superuser, used only to configure catalog, taxonomy, roles, SLA policy and communication templates |

Supplied test data is synthetic only: two companies with regions/centers, one
department, two product models, one customer-owned device, one part, one store
location, one warranty snapshot, one SLA policy and one email template.

## 2. Actual journey exercised

`JourneyTests.test_operational_technical_and_payment_forms` performs the whole
journey through HTTP as non-staff personas and never calls a lifecycle service
directly for the operations it verifies:

1. Front Desk books an appointment, checks in, calls and serves the queue entry
   and records intake. The case becomes `RECEIVED`; the front desk shows an
   "Open job card" link to the case. Complaint, warranty snapshot, SLA
   enrollment and a workflow notification are recorded, and a send with no
   configured provider leaves the notification `FAILED` without touching intake.
2. Front Desk assigns the engineer; the engineer begins diagnosis, adds a fault
   finding and completes the diagnosis (`DIAGNOSED`).
3. The engineer begins repair and adds a repair action.
4. Cashier creates the quotation, prices one part line, submits it, and records
   the customer decision `APPROVED / IN_PERSON`.
5. Warehouse receives two parts; engineer requests two; warehouse approves,
   reserves and issues them; the engineer marks the action performed and consumes
   one; warehouse returns one unused.
6. The engineer completes the repair. Inspector submits, begins, answers every
   technical checklist code and the complaint check, then passes QC
   (`QC_PASSED`).
7. Front Desk marks ready for delivery. Cashier prepares and finalizes the
   invoice and receives payment.
8. Front Desk records physical handover and closes the case (`CLOSED`).

Every step asserts that the job card links the action for the acting persona
before the action is posted, that the form renders, and that the post returns a
redirect to the same job card.

## 3. Variant journeys

| Variant | Test | Verified outcome |
| --- | --- | --- |
| Appointment, full parts consumption, paid, closed | `test_appointment_parts_paid_closed_reconciles` | Reconciles end to end at service level; store keeps 1, custody returns to 0 |
| Walk-in, no parts, QC failure and rework, invoice-free closure | `test_walk_in_no_parts_qc_rework_and_invoice_free_closure` | Failed QC returns the case to `DIAGNOSED`; no ledger entry is created |
| Rejected quotation | `test_rejected_customer_quote_cannot_perform_work` | Work cannot be performed; no disposition exists |
| Warranty responsibility | `test_warranty_responsibility_needs_no_customer_payment` | `customer_pay_total` 0, `warranty_covered_total` 100, delivery allowed unpaid |
| Engineer reassignment and unassignment | `test_engineer_reassignment_and_unassignment_through_operational_ui` | Two assignment rows, ended reason retained, case returns to `RECEIVED`, ex-engineer denied |
| QC rework after an approved quotation | `test_qc_rework_loop_through_operational_ui` | New performed scope is refused until a revised quotation is approved again |
| Quotation rejection then revision | `test_quotation_revision_after_rejection_reaches_an_approved_quote` | Revision 2 DRAFT prefill, spare-row edit, add-line, re-submit, approve, invoice |
| Invoice reconciliation | `test_invoice_reconcile_prefills_and_preserves_prepared_allocation` | Automatic allocation is prefilled, confirmed as MANUAL, draft preserved, then finalized |
| Labour-only billing | same revision test | Prepared draft has no lines; labour is confirmed explicitly for 50 |
| Handover without settlement | `test_handover_needs_settlement_or_an_authorized_financial_release` | Handover refused with the clearance message; only an authorized release permits delivery |
| Payment reversal | `test_payment_reversal_restores_the_outstanding_balance` | Payment `VOIDED`, receipt retained, balance restored, cashier cannot reverse |
| Reservation release | `test_reservation_release_then_reserve_again` | Available stock restored, no issue created, stock reservable again |
| Signed-command replay | `test_parts_workflow_evidence_and_signed_command_replay` | Replayed receipt returns 400 and posts nothing |
| Quotation part compatibility | `test_quotation_lines_offer_only_compatible_parts` | Incompatible part is not offered |
| Customer-owned device registration | `test_customer_owned_device_registration_is_scoped_and_atomic` | Missing identifier, duplicate identity and foreign customer all refused |
| Device variant mismatch | `test_device_registration_rejects_a_variant_of_another_model` | Variant of another model refused on the field, no device created |
| SLA due, overdue, escalation, cancellation | `test_sla_exact_due_overdue_then_cancellation` | DUE_SOON at the exact due instant, OVERDUE after, CANCELLED after cancellation, no duplicate escalation |
| Scope and persona denial | `test_scope_and_persona_denials` | Foreign case 404, foreign center 403, service-level authorization intact |
| CSRF, stale form, IDOR, logged-out | `test_new_actions_csrf_idor_stale_get_and_persona_isolation` | 403 without a token, 405 for PUT, 400 for a stale signed form, 404 cross-company, 302 logged out |
| Region / other-center / department scope | `test_region_center_and_department_scope_at_new_boundaries` | Region 200, other center 404, department 404 |
| New-endpoint persona denial and cross-company IDOR | `test_new_endpoint_persona_denial_and_cross_company_idor` | Six persona/operation pairs denied 403 in-company and 404 cross-company |
| Screen layout contract | `test_operational_screens_mobile_and_desktop_contract` | 15 operational screens honour the shared shell, mobile and CSRF contract |

## 4. Reconciliation

`JourneyTests.reconcile` runs after the closed operational journey.

| Domain | Assertion |
| --- | --- |
| Inventory | Ledger is exactly `RECEIPT +2 STORE`, `MOVE +2 CUSTODY`, `MOVE -2 STORE`, `CONSUME -1 CUSTODY`, `MOVE -1 CUSTODY`, `MOVE +1 STORE`; store plus custody equals the net issued and consumed quantity; two dispositions exist for one issue |
| Commercial | One current approved quotation with `customer_pay_total` 200; one decision; one `FINALIZED` invoice with `customer_pay_total` 100 (actual consumption billed, not approved quantity); one posted allocation; one receipt; no reversal; settlement balance 0 and `financially_clear` |
| SLA | Observation `COMPLETED_ON_TIME` with `completed_at` equal to the authoritative `delivery_release.readied_at` |
| Communications | Exactly one notification; the failed send did not duplicate or replace it |
| Reporting | Operational dashboard reports zero open cases; inventory `usage_parts` reports 1 consumed and 1 returned unused; `settlements` reports `balance_due` 0; complaint frequency resolves to the intake symptom and product category |

## 5. Authorization, scoping and endpoint review

Each new mutation endpoint was reviewed for authentication, permission,
organization scope, HTTP method, CSRF, object scope, transaction usage and
business-logic placement.

| Endpoint | Auth | Permission | Organization scope | Method / CSRF | Object scope | Transactions | Logic |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `/workspace/customers/<pk>/register-device/` | `login_required`, `never_cache` | `devices.add_device`, `devices.add_customerdevicerelationship`, `devices.view_device` via `require_permission` | target `customer.company` | GET/POST, `{% csrf_token %}` | `customers(request.user)` filtered to an active customer | `transaction.atomic()` wraps `register_device` + `assign_device_owner` so a device is never orphaned | Delegates to `apps.devices.services` |
| `/workspace/cases/<pk>/workflow/<stage>/` | `login_required`, `never_cache` | stage permission from the frozen Admin forms | `is_authorized` on `case.service_center` | GET/POST, `{% csrf_token %}`, PUT 405 | scoped `cases(request.user)` plus engineer-assignment and inspector-eligibility checks | No view transaction; each frozen service owns its own | Delegates to engineer, diagnostic, repair, QC and handover services |
| `/workspace/cases/<pk>/commercial/<operation>/` | `login_required`, `never_cache` | operation-specific `commercial.*` capability plus the matching view capability | `is_authorized` on `case.service_center` | GET/POST, `{% csrf_token %}`, PUT 405 | scoped `cases`, `service_quotations`, `service_invoices`, `payments_for_invoice` | No view transaction; frozen services own theirs | Delegates to quotation, invoice and payment services |
| `/workspace/cases/<pk>/parts/<operation>/` | `login_required`, `never_cache` | `inventory.view_stock` plus the operation capability | `is_authorized` on `case.service_center`, locations filtered to `case.company_id` | GET/POST, `{% csrf_token %}`, PUT 405 | scoped `cases`, `parts_requests`, `stock_reservations`, `parts_issues`, `authorized_locations` | No view transaction; frozen services own theirs | Delegates to inventory, request and usage services |

No business rule is reimplemented in a view. The three capability helpers
(`workflows.permitted`, `commercial_workflows.available`, `parts_workflows.available`)
only decide whether a frozen form or service call is offered; every state
transition, eligibility rule, ledger effect and total remains in the domain.

Concurrency and idempotency:

- Every reused Admin form carries its own signed revision token, so a stale
  browser form is rejected with HTTP 400 before any service runs.
- Parts and commercial screens add a second signed token binding the case, the
  actor, the operation, the case revision and every displayed record revision.
- Parts receipts and dispositions receive an explicit UUID command key minted
  once per rendered form; replaying the identical signed submission is refused
  and posts nothing.
- Payment, consumption and return reversals remain governed by the frozen
  unique command constraints.

## 6. UI and mobile

`test_operational_screens_mobile_and_desktop_contract` renders 15 operational
screens (dashboard, search, case and appointment lists, case, customer and device
details, case communications and parts evidence, customer-owned device
registration, engineer assignment, repair, quotation lines, parts receipt,
reservations) and asserts for each that it returns 200, uses the shared shell,
declares the viewport meta, exposes the skip link, the application navigation and
both static assets, contains no fixed desktop `width`, contains no data table
requiring horizontal scrolling, and that every POST form carries a CSRF token.
The shared stylesheet is asserted to keep the 1000px and 760px breakpoints, the
stacked `table,tbody,tr,td` layout, the `td[data-label]` labels and
`:focus-visible`, and the progressive-enhancement script keeps `dataset.label`
without `innerHTML`.

## 7. Focused test runs

| Command | Result |
| --- | --- |
| `python manage.py test apps.operations.uat` | 22 tests, OK |
| `python manage.py test apps.operations apps.frontdesk` | 100 tests, OK |
| `python manage.py test apps.commercial apps.inventory` | 548 tests, OK |

No unrelated large suite was run during development, and
`audit_project --full` was not executed.