Skip to content
PharmaFlow Knowledgebase
Esc
↑↓navigate↵open⌘Jpreview
On this page

Controlled drugs

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.

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.

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.

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.

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 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 before you file.

Next steps

Walk the full dispense flow in Prescriptions, then set up pickup and refill follow-ups in Will-call and refills.

Last updated on October 20, 2026