# Drug Restock (Reorder) Sheet

## Purpose

Each drug prescription can notify a designated recipient when the drug is running low (restock reminder). But if that reminder is missed, there was no place to see, at a glance, **which drugs a patient still needs to reorder** and **when each was last restocked**.

This feature adds:

1. A **per-patient restock sheet** inside the patient page — every prescribed drug with its remaining doses, whether it needs reordering, and the date it was last restocked.
2. A **cross-patient restock dashboard** — all drugs currently to reorder across every patient a staff member oversees, so a facility managing many users doesn't have to open each patient's sheet one by one.

## How it works

### Remaining doses

A prescription's remaining doses are a **running balance**, derived not stored:

```
available_doses = drug.quantity_per_package + Σ(restock box sizes) − (confirmed administrations × doses per administration)
```

i.e. the initial package **plus every restocked box**, minus everything administered. Confirmed administrations are counted from the `reminders` table (an administration reminder confirmed by a recipient).

### Restock alert (email / WhatsApp)

When a prescription drops to/below its restock threshold, a `DrugRestockReminder`
is sent to its **restock recipients** (scope `restock`) on the channels each
recipient has enabled (`mail`, `whatsapp`, always `database`).

This is driven by the scheduled `CheckDrugRestockAlerts` job (every five minutes),
**independently** of the administration reminders. Previously the check only ran
inside the administration-reminder job — i.e. at reminder-send time, *before* the
dose was confirmed — so a one-off ("unique") administration never caught the drop
once its single dose was confirmed, and a restock-only recipient with no
administration schedule was never evaluated at all. The dedicated job fixes both.

To avoid repeats, the alert fires **once per low-stock episode**: `restock_alerted_at`
is stamped when it fires and cleared once the doses go back above the threshold
(e.g. after a restock), re-arming it for the next episode.

### "To reorder" status

```
needs_restock = available_doses ≤ restock_threshold
```

`restock_threshold` is the largest `reminder_offset` among the prescription's **restock** recipients (`drug_prescription_recipients.scope = 'restock'`), i.e. the same point at which the restock reminder fires. If a prescription has **no** restock recipient configured, the threshold is `0`, so it is flagged only when it hits 0 doses.

### Marking a restock

The **"Segna riassortito"** action opens a dialog that shows the **current remaining doses**, lets the operator enter the size of the **new box** (`doses`, defaulting to one package), and displays the resulting **total** — so it is clear that a new box is being added on top of what was left (e.g. 10 left + a 30 box = 40). Confirming records a `drug_restocks` row (`restocked_at`, `restocked_by`, `doses`) and the box is **added** to the remaining doses; the status flips back to OK and the restock reminders stop firing once the doses are above the threshold. Nothing is deleted — the restock history is preserved.

> The previous "Comunica riassortimento" action (which deleted the prescription's reminder rows) has been removed: it was superseded by this restock flow and, under the additive model, deleting confirmed administrations would have wrongly inflated the remaining doses.

### Per-patient sheet

Reached from the patient page via the **"Farmaci da riordinare"** card. Lists **all** the patient's prescriptions with: drug, remaining/total doses, status badge (amber *Da riassortire* / green *OK*), last restock date, and the *Segna riassortito* action. Visible to anyone who can view the patient (`view-patient-dashboard`: the patient, their medical operators, company admin, admin) — and any of them can mark a restock.

### Cross-patient dashboard

Reached from the admin / company-admin / medical-operator dashboard via the same card. Lists **only the drugs that currently need reordering**, one row per (patient, drug), scoped by role:

| Role | Sees |
|------|------|
| Admin | all patients |
| Company admin | patients of their company |
| Medical operator | their own patients |
| anyone else | 403 |

Client-side **filters**: free-text search (patient or drug name) and a patient dropdown. Each row links to the patient and offers the *Segna riassortito* action.

## Key entities & DB

| Entity | Notes |
|--------|-------|
| `drug_restocks` | *(new)* One row per reorder event: `drug_prescription_id`, `restocked_by` (nullable user), `restocked_at`, `doses` (box size added). |
| `DrugPrescription::availableDoses()` | Running balance: initial package + Σ restock box sizes − confirmed administrations. |
| `DrugPrescription::restockedDoses()` | Total doses added across all restocks. |
| `DrugPrescription::needsRestock()` | `availableDoses() ≤ restockThreshold()`. |
| `DrugPrescription::restockThreshold()` | Max `reminder_offset` among restock recipients (0 if none). |
| `DrugPrescription::markRestocked($by, $doses = package)` | Adds a box (`drug_restocks` row) to the remaining doses. |
| `DrugPrescription::forStaff($user)` | Scopes prescriptions to those a staff member may oversee (admin / company admin / medical operator). |

## Routes

| Method | Path | Name | Who |
|--------|------|------|-----|
| GET | `/{patient}/drug-restocks` | `patient-drug-restocks` | Anyone who can view the patient |
| GET | `/drug-restocks` | `drug-restocks-dashboard` | Admin / company admin / medical operator |
| POST | `/drug-prescriptions/{drugPrescription}/restock` | `drug-prescriptions.restock` | Anyone who can view the patient |

## Technical flow

1. Administration reminders are confirmed by recipients over time; each confirmation consumes doses.
2. When `available_doses` drops to/below the restock threshold, the prescription is flagged *Da riassortire* and (as before) the restock reminder is sent to the restock recipients.
3. A staff member (or the patient) opens the restock sheet or the cross-patient dashboard and presses **Segna riassortito**.
4. `markRestocked()` stores the reorder date; `available_doses` resets to full and the status returns to OK.

## Deleting a prescription

Deleting a drug prescription removes everything tied to it: its **restocks** (`drug_restocks`, via the DB cascade), its future **administration slots** (`clearAdministrationReminders`), and its own **reminder history** (the polymorphic `reminders` rows are deleted explicitly so nothing is left orphaned). Audit logs are kept.

## Notes / edge cases

- **Threshold without a restock recipient**: a prescription with no `scope = restock` recipient uses threshold `0`, so it only appears "to reorder" at 0 doses. Configure a restock recipient (with a `reminder_offset`) to be warned earlier.
- **Permissions**: marking a restock is deliberately allowed to anyone with access to the patient (patient included) — whoever physically reorders can update it.
- **Performance**: `available_doses` / `needs_restock` are computed per prescription (a couple of queries each), consistent with the already-appended `available_doses`. The dashboard counters iterate the visible prescriptions. For very large facilities this can be optimised later by persisting a `needs_restock` flag updated when administrations are confirmed / a restock is recorded.

## Related documentation

- [Reminders](./reminders.md) — drug administration & restock reminders
- [Business Logic](../business-logic.md) — patients, companies, roles
