---
title: Prescriptions
description: Create, list, dispense, and cancel branch prescriptions in PharmaFlow.
---

You handle every prescription as a branch-scoped record that moves through PENDING, DISPENSED, and CANCELLED states. The implementation lives in `apps/server/src/features/prescriptions/route.ts` with read helpers in `apps/server/src/features/prescriptions/queries.ts` and the create, dispense, and cancel business logic in `apps/server/src/features/prescriptions/lifecycle/service.ts`.

### List prescriptions by status

You fetch your branch queue with `GET /prescriptions`, optionally filtered by status. Pending rows sort first so your dispensers always see what needs work.

```bash
curl -H "Authorization: Bearer $TOKEN" \
  "$API/prescriptions?status=PENDING&page=1&page_size=20"
```

Pass `status` as `PENDING`, `DISPENSED`, or `CANCELLED`; the route uppercases your value before filtering. Leave it empty to list everything in your branch. Pagination uses the shared `parsePagination` helper from `@pharmaflow/shared`, and each row includes its lines through `loadItems` in `apps/server/src/features/prescriptions/queries.ts`.

### Create a pending prescription

You create a prescription with `POST /prescriptions`. You must send `patient_name`, `prescriber_name`, and a non-empty `items` array.

```bash
curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"patient_name":"Jane Doe","patient_id_number":"CM123456","prescriber_name":"Dr. Smith","prescriber_license":"LIC-42","items":[{"medicine_id":"<id>","quantity":2,"dosage_instructions":"1 tab twice daily"}]}' \
  "$API/prescriptions"
```

The server mints your `rx_number` with `nextRxNumber` in `apps/server/src/features/prescriptions/lifecycle/queries.ts`, formatted as `RX-<branch>-<year>-<sequence>` per branch and year. Your new record starts in `PENDING` status, and each line is written to `prescription_items` with a sync change emitted per line. If you omit a required field or send zero items, you get a `400` response.

### Read one prescription

You fetch a single record with `GET /prescriptions/:prescriptionId` when you need dosages, prescriber details, or the linked sale. The response includes the header plus every line with medicine names and string prices.

```bash
curl -H "Authorization: Bearer $TOKEN" \
  "$API/prescriptions/<rx-id>"
```

The lookup is scoped to your branch, so an id from another branch returns `404` exactly like a missing record. Use `dispensed_by_name` on the response to see who completed the dispense, and `sale_id` to jump to the linked sale.

### Dispense into a linked sale

You convert a pending prescription into stock movement and money with `POST /prescriptions/:id/dispense`. This is the only path that marks a prescription DISPENSED.

```bash
curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"payment_method":"cash"}' \
  "$API/prescriptions/<rx-id>/dispense"
```

The route re-prices every line at the current `selling_price` from `medicines`, applies VAT per line through `vatRateForCategory` in `apps/server/src/features/settings/tax.ts` against the policy loaded by `loadVatPolicy` in `apps/server/src/features/prescriptions/lifecycle/queries.ts`, and deducts FEFO batches via `loadFefoBatches` in the same file. It writes one `sales` row with a fresh invoice number, one `sale_items` row per batch slice, matching `stock_movements` rows, and links the sale back onto the prescription with `sale_id`, `dispensed_by`, and `dispensed_at`. Your response carries `rx_id`, `rx_number`, `sale_id`, `invoice_number`, `total_amount`, and `dispensed_at`. Dispensing an already dispensed or cancelled prescription returns `400`.

### Cancel a prescription

You void a pending prescription with `POST /prescriptions/:id/cancel`. Only `PENDING` records can be cancelled, and `cancelPrescription` in `apps/server/src/features/prescriptions/lifecycle/service.ts` refuses anything else with a 400.

```bash
curl -X POST -H "Authorization: Bearer $TOKEN" \
  "$API/prescriptions/<rx-id>/cancel"
```

Cancellation sets the status to `CANCELLED` and emits a sync change so other branches converge. You cannot reopen a cancelled or dispensed prescription through this endpoint; create a new one instead.

### Track controlled lines at dispense time

When a line has `is_controlled = 1`, the dispense transaction also writes one `controlled_dispense_log` row per batch slice, capturing the patient national id and doctor license. The log rows are installed by `insertControlledDispenseLogs` in `apps/server/src/features/prescriptions/lifecycle/mutations.ts` and each one is emitted as a `controlled_dispense_log` sync change. You can read the full audit trail in [Controlled drugs](/dispensing/controlled-drugs).

### Next steps

Review your statutory registers in [Compliance](/operations/compliance) before your next NDA inspection, and park finished orders for pickup using [Will-call and refills](/dispensing/will-call-and-refills).
