---
title: Controlled drugs
description: Flag controlled medicines, read the dispense log, and keep statutory registers inspection ready.
---

You keep controlled medicines traceable from the product record to the patient handoff. The flag lives on the medicine, each dispense writes protected `controlled_dispense_log` rows, and two register endpoints expose the books. Start in `apps/server/src/features/medicines/route.ts` and `apps/server/src/features/compliance/route.ts`.

### Flag a medicine as controlled

You mark a medicine with the `is_controlled` boolean, defined in `apps/server/src/features/medicines/schemas.ts` and stored as a boolean column on `medicines` in `packages/database/schema.sql`. Set it when you create or update the product.

```bash
curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"Morphine 10mg","is_controlled":true,"selling_price":5000,"vat_category":"standard"}' \
  "$API/medicines"
```

Treat this flag as permanent once stock exists. If you clear it later, earlier `controlled_dispense_log` rows keep their history, but new dispenses stop writing log rows. You should confirm the classification with your pharmacist before you change it.

### Capture the audit row at dispense

You never write the log by hand. When you dispense a prescription through `POST /prescriptions/:id/dispense` in `apps/server/src/features/prescriptions/route.ts`, every line with `is_controlled = 1` produces one `controlled_dispense_log` row per batch slice, recording `sale_id`, `medicine_id`, `batch_id`, `quantity`, `patient_national_id`, `doctor_license`, and `authorizing_user_id`.

The patient id falls back through `patient_id_number`, then the request `patient_national_id`, then the patient name, so always capture the national id on the prescription for controlled items. The doctor licence falls back through `prescriber_license`, then the prescriber name, then the request `doctor_license`, and finally the `RX-PRESCRIBER` marker in `apps/server/src/features/prescriptions/lifecycle/service.ts`. Direct register sales walk the same rules when they build their controlled rows in `apps/server/src/features/sales/creation/service.ts`, with `insertControlledDispenseLogs` in `apps/server/src/features/sales/creation/mutations.ts` holding the writes, so both dispense paths stay auditable.

### Query the controlled registry

You inspect the log with `GET /medicines/controlled/registry`, restricted to `admin`, `auditor`, `branch_manager`, and `pharmacist` roles. Filter by product or patient and page through the results.

```bash
curl -H "Authorization: Bearer $TOKEN" \
  "$API/medicines/controlled/registry?medicine_id=<id>&page=1&page_size=50"
```

Each row joins the medicine name, the authorizing user, and the sale invoice number, ordered newest first by `listControlledRegistry` in `apps/server/src/features/medicines/queries.ts`. Use `patient_id` to pull one patient history during a query or recall.

### Keep the statutory books

You serve inspectors from the compliance feature in `apps/server/src/features/compliance/route.ts`. `GET /compliance/prescription-book` lists every prescription with line counts and a two year retention note. `GET /compliance/classified-drugs-book` lists controlled dispenses with patient and doctor identifiers and a Class A/B/C Group II retention note.

```bash
curl -H "Authorization: Bearer $TOKEN" \
  "$API/compliance/classified-drugs-book?from_date=2026-01-01&to_date=2026-09-27"
```

Filter both books with `from_date` and `to_date` before an NDA visit so you can export exactly the period the inspector asks for.

### Prepare for an NDA visit

You walk into an inspection with three exports ready. Pull the prescription book for the requested period, then the classified drugs book for the same window, and confirm every controlled row has a patient id and a doctor license.

```bash
curl -H "Authorization: Bearer $TOKEN" \
  "$API/compliance/prescription-book?from_date=2026-01-01&to_date=2026-09-27"
```

Check the `retention_note` on each response and keep two years of prescription history on file. If a controlled row shows a patient name instead of a national id, coach the dispenser to capture `patient_id_number` on the prescription next time. Your registers live under [Compliance](/operations/compliance) alongside the other operational books.

### Preview fiscal payloads and devices

You preview the URA EFRIS payload for any sale with `GET /compliance/efris-preview/:saleId`, which returns seller TIN, invoice number, ICV, buyer details, VAT breakdown, and line items ready to submit. You list registered fiscal devices with `GET /compliance/devices`, and an admin can disable a lost till with `POST /compliance/devices/:deviceId/revoke` in `apps/server/src/features/compliance/route.ts`, which the tills pick up as a `devices` sync change. Check both in [Compliance](/operations/compliance) before you file.

### Next steps

Walk the full dispense flow in [Prescriptions](/dispensing/prescriptions), then set up pickup and refill follow-ups in [Will-call and refills](/dispensing/will-call-and-refills).
