# Service appointments, queue and front-desk operations

Phase 4A adds `apps.frontdesk`; the frozen Customer, Device, Organization, ServiceCase,
RBAC and reporting domains are reused unchanged. `frontdesk.0001_initial` creates
AppointmentSlot, Appointment, QueueSequence, QueueEntry and FrontdeskEvent.
Only app registration and URL inclusion change existing source files.

## Availability and appointments

Availability is explicitly configured for one center and one calendar date, with
local start/end times, positive capacity and active flag. No recurring schedule,
engineer capacity, implicit availability or automatic no-show policy is inferred.
Times use the project's configured timezone (`Asia/Dhaka`). Overnight and overlapping
slots are rejected, including overlaps with inactive slots. Center/date/time are
immutable through supported APIs; only capacity and active state can be configured.

Every booking requires an active, not-yet-ended slot. Scheduled, checked-in and
completed appointments consume its capacity. Cancelled/no-show appointments do not.
Completed visits still consume the original appointment allocation. Capacity cannot
be reduced below reservations. Deactivating availability stops new bookings while
preserving existing appointments and their ability to check in.

Appointment lifecycle:

```text
SCHEDULED -> CHECKED_IN -> COMPLETED
SCHEDULED -> CANCELLED
SCHEDULED -> NO_SHOW (explicit authorized action, after slot end)
CHECKED_IN -> CANCELLED (through cancellation of its unfinished queue visit)
```

Cancellation/no-show requires a reason. Rescheduling is cancellation plus a new
booking, never rewriting the original date/time. Check-in requires the appointment's
calendar date; it can occur before its start time or after its end time on that day.
There is no automatic late-arrival cutoff. Explicit no-show makes the appointment
terminal and prevents check-in.

## Arrivals and queue

Walk-ins need an existing active Customer; a Device may be selected later. No
appointment is required. The customer-creation form delegates to the existing
Customer service. Devices are registered and their ownership managed through the
existing registry/Admin, not a second front-desk registry.

An arrival issues a positive numeric token unique within center/business date.
A dated QueueSequence is allocated under the ServiceCenter row lock, including its
first creation. No unlocked `MAX(token)+1` is used. Business date comes from the
server's configured local date; callers cannot backdate arrivals.

```text
WAITING -> CALLED -> SERVING -> COMPLETED
WAITING / CALLED / SERVING -> CANCELLED (reason required, no intake linked)
```

Call next selects today's lowest WAITING token. It does not prioritize appointments
over walk-ins or infer triage urgency. Multiple counters may serve different entries.
Explicit calls can clear older waiting entries. There is no skipped/missed state.
Completion may represent an enquiry with no ServiceCase. Cancellation is prohibited
once intake exists. Completed/cancelled visits cannot reopen.

## Intake integration and idempotency

Check-in atomically changes the appointment and creates its one-to-one QueueEntry.
Retries, including concurrent requests, return that same queue record without
rewriting actor/timestamp evidence. An ended visit is never reopened by retrying.

Intake requires SERVING state and a registered Device. `start_intake` invokes the
existing `create_service_case`, preserving active hierarchy/customer/device,
identifier policy, ownership-company affinity, job numbering and warranty snapshot
rules. A physically arriving appointment customer also uses the existing WALK_IN
intake channel; the appointment relationship distinguishes that visit analytically.
The new one-to-one QueueEntry -> ServiceCase link requires no ServiceCase migration.
Intake retries return the same case, including after front-desk completion. A retry
with a different device is rejected. A previously selected visit device is not
silently replaced.

Front-desk completion does **not** progress the ServiceCase. Its initial state remains
RECEIVED until existing technical services change it. Quotation-free/direct intake
workflows do not need appointments or queue records.

Each new walk-in call represents a distinct arrival; generic walk-in/booking request
deduplication keys are not introduced. Check-in and queue-to-intake retries are
idempotent as described above.

## Authorization, search and UI

`/front-desk/` lists accessible centers. `/front-desk/<center UUID>/` shows today's
appointments and queue, including waiting, called, serving and completed entries.
A date selector exposes historical and future appointments without rewriting them.
Operation URLs present confirmation/forms on GET; all writes require POST and CSRF.
Pages are not cached. Login uses the existing reporting login, which accepts normal
authenticated business users; Django Admin staff status is not a business grant.

The new Django permissions are:

- `frontdesk.manage_slots`
- `frontdesk.view_appointment`, `frontdesk.create_appointment`
- `frontdesk.cancel_appointment`, `frontdesk.check_in_appointment`
- `frontdesk.view_queue`, `frontdesk.manage_queue`

Assign these through existing Role/UserRoleAssignment administration. Every service
checks its required permission against the existing ServiceCenter scope adapter.
Company, region, center and department containment remain exactly as the authorization
engine defines them; there is no new adapter or broadened department access. Active
superusers retain the explicit engine bypass, but writes still require active domain
entities. Staff flags, Groups and direct Django permissions do not create scope.

Intake additionally requires `service.add_servicecase` at the center. Customer
creation requires `customers.add_customer` at the company; company-wide customer
search/selection requires `customers.view_customer` at the company. This intentionally
does not turn a center-only grant into company-wide customer access. Configure the
needed separate company capability when a front-desk user must search customers.

Device selection/search requires `devices.view_device` at the center and an existing
customer relationship in the accessible company or an accessible ServiceCase.
Devices with ownership history in another company are excluded. Unassociated devices
are not globally enumerated: establish authoritative relationships through the existing
registry before selecting them in the UI. The intake service retains its existing
ability to handle an unowned device and does not require the presenter to be its owner.
Case search separately requires `service.view_servicecase` at the requested center.

Search uses exact customer number, normalized mobile/contact, IMEI/serial identity,
or job number. Identifiers resolve through the existing historical identity key;
there are no substring identifier scans or cross-company result fallbacks. Results
are bounded to 25 per type. Forms validate selection against the same scoped queries.

## History, locks and reporting evidence

Services use PostgreSQL transactions. Lock order is actor SHARE, company SHARE, RBAC
dependencies SHARE, center UPDATE, then local slot/appointment/queue records and
existing intake dependencies. Company SHARE coordinates frozen hierarchy writers;
actor/path/role locks protect against supported access revocation. The center lock
serializes booking/capacity changes, first token allocation, check-in, call-next and
intake retries. This favors correctness over throughput for this initial phase.

Database constraints enforce positive capacity/tokens, time order, valid coherent
states, unique dated tokens, and one-to-one appointment/queue/case links. Supported
model save/delete and bulk mutation paths reject direct operational writes. Internal
`_persist` is service-only, not a public alternative API. SQL maintenance is outside
these service guarantees, consistent with the project's existing domain boundary.

Append-only FrontdeskEvent records capture actor, action, reason and timestamp for
booking, capacity configuration, arrivals, lifecycle changes and intake creation.
Operational rows retain arrival/called/serving/end timestamps; cancelled history is
not deleted. Appointment linkage distinguishes attended appointments from walk-ins.
These are evidence for future volume/wait/handling analytics, with no fabricated
SLA, targets or modifications to Phase 3D reporting.

## Verification and limitations

New focused tests cover domain transitions, capacity, invalid/foreign inputs, scoped
UI/search, CSRF, rollback, history and actual PostgreSQL blocking races. Existing
tests and historical migrations are unchanged. Only focused Phase 4A tests and one
final `audit_project --quick` are required; no full regression is run in this phase.

No recurring schedules, external booking portal, notifications, reminders, kiosk,
automatic no-show job, appointment rescheduling-in-place, engineer scheduling or
new frontend framework is included. Forms use server-rendered choices; very large
registries may later benefit from a scoped autocomplete. No existing frozen domain
rule was changed to support this phase.

Final verification on 2026-09-30:

- Phase 4A: 48 tests passed in 54.015 seconds, including nine PostgreSQL concurrency
  tests that verify an actual blocking lock wait. No failures or skips.
- One final `audit_project --quick`: all required checks and 19 smoke tests passed;
  33.962 seconds for tests, 78.692 seconds overall.
- `frontdesk.0001_initial` applied; migration drift and unapplied-migration checks pass.
- `git diff --check` passes. Existing tracked changes are only one app-registration
  line and one URL-inclusion line; new front-desk files/documentation remain untracked.
- No full regression, staging, commit or push was performed.
