# Work Order Flow

A work order records that a customer ordered a vendor package through a seller, and tracks it until it is
installed (Completed), cancelled or failed. Every rule below is enforced on the server
(`WorkOrderService`, `WorkOrderStatusService`, `WorkOrderPolicy`). Status, completion and cancellation rules are
also enforced by database CHECK constraints.

```
Seller → Customer → Work Order → Vendor → Package → Services → Play Type → Dates & Deadline → Status → Completed
```

---

## 1. Creating a work order

*Work Orders → New work order* (or *New work order* on a customer's page) guides the user through the steps:

| Step | What happens | Server-side checks |
|---|---|---|
| 1. Customer | Search existing customers or create one in a modal without leaving the page | Customer must be visible to the user, **active**, with a **service address**, in an active organization |
| 2. Seller | Defaults to the customer's assigned seller. Seller logins are locked to themselves. | Active seller of the **customer's organization** |
| 3. Vendor | Only vendors available to the customer's organization | Vendor active and allowed (sub-reseller vendor access) |
| 4. Package | Packages of the selected vendor. Selecting one shows its services, play type and pricing. | Package belongs to the vendor, is active, is effective on the work order date and has services |
| 5. Dates | Work order date (default today), requested installation date (optional), deadline (default work order date + **7 days**, configurable) | See [Dates](#4-dates-and-deadlines) |
| 6. Details | Priority (Low / Normal / High / Urgent), assignee, external vendor order number, customer notes, first internal note | Assignee must be assignable (see below). External number limited to safe characters. |
| 7. Save | **Save as draft** or **Save & submit** | Submitting requires `work-order.submit` |

On save the server:

* generates the number **`WO-YYYY-000001`** atomically (prefix configurable; the sequence restarts each
  calendar year, using the business-timezone year of creation),
* takes the organization from the **customer** (never from the browser),
* stores the **snapshot** (see below), the first status history entry, the optional note and an audit entry,
* notifies the assignee in the app.

### Snapshot (what was ordered)

Saved on the work order and in `work_order_services`, so later catalog or customer changes never rewrite history:

| Snapshot | Source |
|---|---|
| Vendor name, package code, name and description | Package at order time |
| Services and their package details (speeds, TV tier, lines…) | Package services |
| Service count and **play type** | Calculated from the services |
| Price, frequency, monthly price, installation and activation charges | Package pricing |
| Promotional price and end date | Only if the promotion applies **on the work order date** |
| Service address (line 1/2, unit, city, state, ZIP, county) | Customer's service address |

## 2. Play type

The play type is **calculated, never chosen**. `App\Enums\PlayType::fromServiceCount()` drives it, and a CHECK
constraint on `packages` and `work_orders` enforces it:

| Services | Play type | Example |
|---|---|---|
| 1 | **Single Play** | Internet only |
| 2 | **Double Play** | Internet + TV |
| 3 | **Triple Play** | Internet + TV + Home Phone |
| 4 | **Quad Play** | Internet + TV + Home Phone + Mobile (Mobile is seeded inactive and can be enabled) |
| 5+ | Multi Play | Future-proof; not used by the default Service Master |

"Double Play" is used throughout; "Secondary Play" never appears.

## 3. Status lifecycle

```mermaid
stateDiagram-v2
    [*] --> Draft : Save as draft
    [*] --> Submitted : Save & submit
    Draft --> Submitted : Submit
    Draft --> Cancelled
    Submitted --> InProgress
    Submitted --> Scheduled
    Submitted --> OnHold
    Submitted --> Cancelled
    InProgress --> Scheduled
    InProgress --> OnHold
    InProgress --> Completed
    InProgress --> Failed
    InProgress --> Cancelled
    Scheduled --> Scheduled : Reschedule
    Scheduled --> InProgress
    Scheduled --> OnHold
    Scheduled --> Completed
    Scheduled --> Failed
    Scheduled --> Cancelled
    OnHold --> InProgress
    OnHold --> Scheduled
    OnHold --> Cancelled
    Failed --> Cancelled
    Cancelled --> Submitted : Reopen (recovery)
    Failed --> Submitted : Reopen (recovery)
    Completed --> [*]
```

### Transitions, permissions and required data

| From → To | Permission | Required data |
|---|---|---|
| Draft → Submitted | `work-order.submit` | — |
| → In Progress | `work-order.change-status` | optional remarks |
| → Scheduled (incl. reschedule) | `work-order.change-status` | **Installation date** (on or after the work order date, within one year) and optional arrival window: Morning 8–12, Afternoon 12–5, Evening 5–8, All day 8–5. A reschedule records the previous date. |
| → On Hold | `work-order.change-status` | **Reason** |
| → Failed | `work-order.change-status` | **Reason** |
| → Completed | `work-order.complete` | **Completion date**: on or after the work order date and not in the future. Optional completion notes. |
| → Cancelled | `work-order.cancel` | **Cancellation reason** (min. 5 characters) |
| Cancelled / Failed → Submitted | `work-order.reopen` | **Reason** (min. 5 characters). Clears the cancellation data. |

* **Completed is final.** Draft can't be re-entered.
* The *Change status* menu on the work order page lists only the transitions the user may perform. The server checks
  the transition, the permission and the tenant again, under a **row lock** with the status the user saw. A
  stale page or a double click can't apply a transition twice.
* Every change writes an immutable **status history** row (from, to, who, when, remarks, IP, user agent) and an
  audit entry (`work_order.status_changed`, `work_order.completed`, `work_order.cancelled`, `work_order.reopened`).
* **Seller logins** can only submit their own drafts.

### Status groups used in lists, the dashboard and reports

| Group | Statuses |
|---|---|
| Open | Draft, Submitted, In Progress, Scheduled, On Hold |
| Active | Submitted, In Progress, Scheduled, On Hold |
| Pending | Submitted, On Hold |
| Finished (terminal) | Completed, Cancelled, Failed |

## 4. Dates and deadlines

| Date | Type | Rule |
|---|---|---|
| Work order date | calendar date | Required. Within the last 365 days and at most 90 days ahead (checked when entered or changed). |
| Requested installation date | calendar date | Optional, on or after the work order date |
| Deadline | calendar date | Required, on or after the work order date (DB CHECK), within one year |
| Scheduled installation date + window | calendar date | Set by the *Schedule* transition |
| Completion date | calendar date | Required to complete (DB CHECK), between the work order date and today |
| Submitted / completed / cancelled at, created / updated at | timestamps | Stored in **UTC**, shown in the business timezone (default **America/New_York**, DST-aware) |

### Deadline indicator

"Today" is the calendar date in the business timezone. The warning period defaults to **3 days** (Settings).

| Indicator | Rule |
|---|---|
| **Overdue** | Open and deadline before today |
| **Due Today** | Open and deadline is today |
| **Due Soon** | Open and deadline within the warning period |
| **On Track** | Open and deadline later than that |
| **Completed On Time** | Completed and completion date ≤ deadline |
| **Completed Late** | Completed and completion date > deadline |
| Not Applicable | Cancelled or Failed |

Example (warning period 3 days, today 10/18/2026): deadline 10/20 → *Due Soon*; deadline 10/18 → *Due Today*;
deadline 10/17 → *Overdue*. A completion on 10/19 with deadline 10/20 → *Completed On Time*.

`DeadlineCalculator` is the single definition behind the badges, dashboard cards, the *Upcoming* and *Overdue*
lists, alerts and the Deadline / Overdue reports, so they always agree.

### Alerts

`php artisan work-orders:deadline-alerts` runs hourly from 06:00 to 20:00 in the business timezone. It sends
an in-app notification once per deadline for orders that are *Due Soon / Due Today* and once when they become
*Overdue*. Changing the deadline resets both alerts. Status changes to Scheduled, On Hold, Completed,
Cancelled, Failed, and reopenings, notify the assignee and the creator (never the person who made the change,
never inactive users). No e-mail or SMS is sent, by design.

## 5. Editing a work order

| Status | Editable fields |
|---|---|
| Draft, Submitted, On Hold | Seller, vendor + package (refreshes the price and service snapshot), dates, priority, assignee, external number, customer notes, optional "refresh service address from customer" |
| In Progress, Scheduled | Same, except vendor and package |
| Completed, Cancelled, Failed | Read-only (notes can still be added by users with `work-order.note`) |

* Seller logins can edit only **Draft** orders.
* Changes are compared field by field and audited (`work_order.updated`). The audit entry shows the old and new
  package and address.
* **Assignees** must be active users of the order's organization or its main reseller, whose role is not
  seller-restricted and holds `work-order.change-status`. Assigning needs `work-order.assign` and notifies the new assignee.

## 6. Notes, attachments and printing

* **Internal notes**: append-only, timestamped, attributed. They are never shown to customers.
* **Attachments**: up to 25 files of 10 MB each (PDF, images, Office, CSV, TXT). The content is checked against the
  extension and stored privately. Downloads are authorized and audited. Deleting removes the stored file but keeps
  the record (who and when, soft delete) and an audit entry. See
  [SECURITY.md](SECURITY.md#8-file-uploads-asvs-v12).
* **Print**: a print-optimized page (customer, service address, package snapshot, services, dates, status history)
  for the browser's *Print / Save as PDF*.

## 7. Acceptance example (seeded)

| Field | Value |
|---|---|
| Work order | **WO-2026-000001** |
| Seller | John Smith (Queens Connect Partners) |
| Customer | Michael Brown, Forest Hills, NY |
| Vendor → Package | Example Telecom Provider → Home Connect Plus |
| Services → Play type | Internet + TV + Home Phone → **Triple Play** |
| Price | $129.99 / month |
| Work order date → Deadline | 10/15/2026 → 10/20/2026 |
| Status | Scheduled for 10/18/2026, morning window |

The automated test `test_specification_acceptance_flow` replays this scenario with simulated dates: create and submit on
10/15, schedule on 10/16, complete on 10/18. It checks play type, price, seller, the status history and the
*Completed On Time* indicator. Tenant isolation for work orders is covered by `WorkOrderAccessTest`.
