Compounding software that records what actually happened
A formulation defines the product. A batch consumes real lots in one transaction. A unit gets a serial at fill and cannot ship until a scan proves it belongs to the order. That is the whole design.
Most compounding pharmacies run a manufacturing operation on tools built for retail dispensing: a formulation in a PDF, a batch log in a binder, lot numbers typed into a shipping label. Pharmacy Flow moves those records into one database with the mechanics a manufacturer expects: a bill of materials that drives consumption, a batch that cannot exist without its lots, a finished lot that stays in quarantine until QC releases it, and a serial on every unit that the ship station checks before the parcel leaves.
Formulations and variants
A formulation is the master record for a product family: form, components with draw per unit, standard batch size, labour minutes per batch and per unit, QA minutes per batch and the beyond-use-date rule in days. It moves through submit, approve and retire, and the approval is a signed action. Each formulation has variants: the strengths and fill volumes you sell, each with its own SKU and presentation. Everything downstream references a variant, not a product name. An order carries a variant id; the partner API accepts a variant id or SKU; a finished lot is tagged with the variant it was made as; and a unit can only be dispensed from a lot of the order's own variant. That last rule replaced a name-matching fallback that could dispense the wrong product, and it is enforced in the database.
The batch planner: pick a variant and quantity, see which lots will be drawn (first expiry first) and the projected cost per fill before anything is written.
The batch record
Creating a batch is one database transaction. Before writing, the planner walks the bill of materials and plans consumption from Available raw lots in expiry order. Then a single stored procedure does all of the following, or none of it:
1
Insert the finished lot in Quarantine
Lot number, quantity, unit of measure, unit cost, facility and variant. The hold reason is set to awaiting QC results.
2
Insert the batch as In Process
Batch number, target and produced units, formulation, variant, facility and the compounding pharmacist. It stays In Process until the QC actions move it to Released or Rejected.
3
Consume raw lots with quantities
Each drawn lot is decremented, marked Consumed when it reaches zero, and an issue transaction is written. A consumption row records lot, material, quantity and unit, so the genealogy has amounts, not just ids.
4
Create the QC tests as Pending
The batch is born with its test plan (potency, sterility and endotoxin in the default set) waiting for results.
Because the writes are atomic, a failure part-way can never consume material with no batch to show for it. The per-unit cost written on the lot comes from the costing engine: material draw at lot cost plus compounding and QA labour at the rates in Staff & Labor.
Batches stay In Process until QC releases the finished lot; consumed lots and quantities are on the record.
QC release and the beyond-use-date floor
A finished lot's BUD is computed at creation as the earlier of the formulation's rule and the soonest expiry date among the raw lots consumed. A batch made from a component that expires in twenty days gets a twenty-day BUD, whatever the formulation says. Recording a QC result, releasing the lot and rejecting it are three separate actions gated by the qa.release permission; the Technician role does not hold it. Until release, the lot is not Available and cannot be picked by the fill station. Lot status changes go through the same kind of state machine as orders, so a lot cannot skip from Quarantine to Consumed.
Fill, pack, verify, ship
Verify is an interlock, not a checkbox
Fill commissions the unit: the earliest-BUD available lot of the order's variant is claimed in a single conditional update, stock is decremented, a serial is minted, the SGTIN is formed from the product's GTIN and the serial, and a vial label is sent to the station's printer with the job recorded in a print ledger. Pack creates the parcel, picks a cold-chain packout and service for temperature-sensitive families, and prints the 4×6 label.
Verify scans order and serial and checks four things server-side: the unit belongs to this order, it has been packed, it is not recalled, and it is not past its BUD. Pass stamps verified_at; fail moves the order On Hold with the reason. Ship refuses any order without that stamp, and so does the database trigger. Every station writes an EPCIS-style event with the read point, disposition and serial.
Shipping aggregates parcels into an SSCC carton per facility, closes cartons onto pallets, and manifests pallets; each aggregation is an EPCIS event carrying the SSCC. Serialisation detail is in the serialisation and DSCSA guide.
Recall by lot, disposition by unit
A recall starts from a finished lot. The system traces every dispensed unit of that lot with its brand, patient reference, ship-to state and parcel tracking number, creates a numbered recall with classification and reason, and flags each unit Recalled. From that moment the unit fails pack-verify if it is still on the floor. Each affected partner brand gets an email and a signed recall.initiated webhook. Disposition is then tracked unit by unit (the recall moves to In Progress on the first disposition) and closed as Completed or Terminated with a timestamp.
A recall: every traced unit of the lot, its parcel, and its disposition status.
See a batch created and a unit recalled, live
Bring a formulation. We will create a batch, release it, fill an order, fail a verify on purpose and run a recall against the lot, on a tenant you can keep.
Deviations, CAPAs and change controls are records with their own lifecycles and advance and reject actions, each written to the event log with the actor. Environmental monitoring readings carry room, ISO class, metric, value and limit; a reading over its limit is graded Action and one within ten percent of the limit is graded Alert. Document control holds controlled documents with versions.
Approvals are electronic signatures: the identity comes from the session, never the form; the signer must re-enter their password at the moment of signing; and the record stores signer, role, meaning, note and time. Recording a temperature excursion on a parcel and releasing the QA hold are permission-gated actions too. More on Part 11 in a pharmacy.
Prescriptions, refills and state licences
Prescriptions carry refills authorised and remaining, the written date and the DEA schedule of the most restrictive component in the bill of materials. Refilling a Schedule II prescription is refused. Schedule III–V refills are refused beyond five, or beyond six months from the written date, and a controlled product with no classified schedule is held for manual verification. Every refill also runs the state-licence gate: the ship-to state needs an active nonresident-pharmacy licence on file and, for controlled products, permission for compounded controlled substances; otherwise the order is created On Hold with the reason. The same gate runs on API and portal orders.
503A and 503B on one platform
The mechanics above are the same whether you dispense patient-specific prescriptions under 503A or produce office stock as a 503B outsourcing facility; what differs is the volume and the regulatory expectations on the batch record and quality system. Sterile operations add the environmental monitoring and QC workflow described on the sterile compounding page; hazardous drugs add the handling flags covered in the USP <800> checklist. For the differences between the two models, read 503A vs 503B; for growth, how to scale a compounding pharmacy. Inventory, forecasting and purchasing are on the inventory page, and shipping and the execution layer on fulfilment automation.
Frequently asked questions
What happens if a batch fails part-way through creation?+
Nothing is written. The finished lot, the batch, the raw-lot decrements, the consumption rows and the pending QC tests are inserted inside one stored procedure; if any step fails the whole transaction rolls back.
How is the beyond-use date set?+
From the formulation's BUD rule in days, floored by the soonest expiry date among the raw lots the batch consumed. The floor is applied inside the batch transaction.
Can a technician release a finished lot?+
No. Recording QC results, releasing and rejecting a lot require the qa.release permission, which the Technician role does not hold. Permissions are stored in Postgres and re-checked by every server action.
What does the serial look like?+
A serial reference minted from a database sequence, combined with the product's GTIN into an SGTIN written as (01)GTIN(21)serial on the vial label and in the commissioning event.
Can an order ship without pack verification?+
No. The ship station checks for the verification stamp, and independently the database trigger rejects any move to Shipped when verified_at is empty.
Does it handle sterile and non-sterile compounding?+
Both use the same batch record and QC release. Sterile operations additionally use environmental monitoring readings with limits and the sterility and endotoxin tests in the default QC plan.
How is a recall scoped?+
By finished lot. Every dispensed unit of the lot is traced through its serial to the brand, patient reference, state and parcel, and each unit's disposition is tracked until the recall is closed.
A working platform, not a slide deck. Book a walkthrough and we'll run a real order from intake to carrier lane — or explore the public directory first, no account needed.