Inventory → Transactions
Purpose
Record every event that changes a container's balance — the entries that make up your DEA audit trail. This includes administrations to patients, dispenses to clients, waste, disposal, paper-trail adjustments, and intra-clinic transfers.
Transactions in CS Logbook are immutable. Once recorded, the original entry cannot be changed. Corrections route through a separate correction flow that strikes the original and posts a reversal + corrected entry pointing at it — the digital equivalent of a "single-line correction with a reason and date" in paper logs.
Regulatory basis: DEA rules require accurate, contemporaneous, immutable records of dispensing and disposal. CS Logbook records what your clinic did; it does not enforce your witnessing obligations and never blocks a save.
When to use it
- Administer to a patient — every dose given to an animal under your care
- Dispense to a client — controlled substances sent home with the owner
- Waste — drug intentionally wasted (expired, contaminated, partial dose remaining)
- Disposal — full container disposed via DEA Form 41 reverse-distribution or on-site destruction
- Adjustment — paper-trail correction reconciling a count discrepancy
- Transfer — moving substance between containers within your clinic (rare)
received transactions are auto-created when you complete the Receive container flow — do not file them manually.
Reading a transaction form
Fields are tagged Needed to save or Expected, and the bar at the bottom shows how many tagged fields are still blank. See 05: Filling in forms for what each tag means and how the layout works.
Transaction types reference
| Type | Direction | Required fields beyond defaults | Witness expected? |
|---|---|---|---|
received |
+ balance | (auto-created by Receive container form) | no |
administered |
− balance | patient, prescriber | no |
dispensed |
− balance | patient, prescriber | no |
waste |
− balance | reason | expected |
disposal |
− balance (full container) | DEA Form 41 ref | expected (on-site destruction) |
adjustment |
± balance | reason (optional; soft warning when blank) | no |
transfer |
balanced (one −, one +) | source container, destination container | no |
Walkthrough — administering to a patient
Step 1 — Open the transactions list
Navigate to /home/<your-clinic>/inventory/transactions.
The list shows every transaction with date, type, container, quantity, performer, and patient (if applicable). Filter by type, date range, container, or patient using the filter bar.

The page shows six transaction-type buttons (Administer, Dispense, Adjust, Waste, Dispose, Transfer). Each opens its own full-page route. Clicking Waste opens the Record Waste page (/inventory/transactions/waste/new):

Pick a container, enter the quantity wasted, the waste method, the witness (must be a different staff member than the performer), and the reason (recommended, not required):

After saving, the transaction appears in the transactions list:

Step 2 — Click the transaction type
There's no single "Add transaction" button — each of the six type buttons opens its own full-page route: Administer → /inventory/transactions/administration/new, Dispense → /dispense/new, Adjust → /adjustment/new, Waste → /waste/new, Dispose → /disposal/new, Transfer → /transfer/new. Each page carries a ‹ Back to transactions link. (For many entries at once, Bulk adjustment and Bulk waste live at /transactions/bulk-adjustment and /transactions/bulk-waste.)
Step 3 — Pick the container
Select from active containers (status unopened or opened). Disposed, depleted, expired, transferred, or destroyed containers are not selectable — that's the hard block from the DEA record-retention rule preserving audit integrity.
The form shows the container's current balance so you can see what you're drawing from.
Step 4 — Choose transaction type and fill required fields
For administered:
- Quantity — amount drawn (e.g.,
0.3); cannot exceed current balance - Date / time — when the dose was given (default: now)
- Patient — pick from your patient list, or quick-add a new patient inline
- Prescriber — the prescribing veterinarian; defaults to the signed-in user if they're a prescribing vet
- Notes — clinical context (e.g., "Pre-anesthetic for spay surgery")
Click Save. The container's balance decreases. The transaction is now permanent.
Step 5 — Quick-add a patient (if needed)
If the patient isn't yet in your system, click the + Add patient button next to the patient combobox. A mini-form opens that captures the minimum needed fields (client + name + species). The patient is created and auto-selected. See people/patients.md for the full patient form.
Walkthrough — recording waste with a witness
Step 1 — Open the transaction form
Same as above.
Step 2 — Select container, type = waste
Step 3 — Fill waste-specific fields
- Quantity — what was wasted (e.g.,
0.2mLof leftover at end of shift) - Reason — why it was wasted (
leftover at end of shift,contaminated,expired,dropped, etc.) - Witness — pick another clinic member who witnessed the waste.
A witness is expected but never required. The record saves without one, the transaction is flagged as incomplete in the list and on its detail page, and you can name a witness later from that detail page without filing a correction. This matters most for a solo practitioner, who would otherwise be unable to record a waste that genuinely happened.
The witness must be a different person than the one filing the transaction; the form will not accept the same person as both.
Click Save. The transaction is recorded with both performer and witness on the audit trail.
Walkthrough: Batch Entry (many administer or dispense records at once)
Use Batch Entry when you have several separate administrations or dispenses to record, for example catching up a busy day or entering a paper log. Each row is its own record, with its own date, time, container, client and patient. Open it from the Administer or Dispense menu and choose Many records (any patients or dates).
The Administer and Dispense menus each offer four ways to record:
| Choose | When |
|---|---|
| One patient, one container (Dispense: one client) | The everyday entry: one event from one container. |
| One patient, several containers (Dispense: one client) | One event that drew on more than one container, for example a protocol using several drugs. Shared details are entered once. |
| Many records (any patients or dates) | Several separate events, each with its own date, time, container, client and patient, such as catching up a paper log. This is Batch Entry. |
| Import from DaySmart | Batch Entry, pre-filled from a DaySmart Vet Controlled Substance Log export. |
- Choose the record type and, optionally, a substance. The type (Administer or Dispense) applies to every row and decides whether rows ask for a route or directions. Choosing a substance narrows every row's container list to that substance.
- Set the defaults. The date for new rows, the default performer and the default prescribing vet fill any row left on "use default". A value set on a row always wins.
- Fill the rows. Each row needs a date and time, a container, a quantity, a client and a patient before it can be saved. Route, directions, reason, witness and prescribing vet can be left blank; the row shows an amber note so you can add them if you have them. Your clinic's common reasons and directions appear under those fields: click one to fill it, then edit it if needed.
- Read the balances. Each row shows its container's balance before and after it. When the same container appears in more than one row, the rows are counted in date and time order, so the balance shown includes the rows dated before it, even if you typed them in a different order. Rows with the same date and time are counted in the order they appear in the grid, so there is no need to space their times apart. A summary under the rows shows each container's starting and ending balance.
- Click Save. All rows are recorded together, or none are.
How dates and times work in Batch Entry
- Every row records a date and a time. They are saved together as the moment the event happened, and that moment is what orders the container's history and the balances.
- Times are in the clinic's timezone, the one set in Settings → Clinic profile, whatever time zone your computer is in.
- A new row's time defaults to now when its date is today, and to 12:00 when its date is another day. Change it to the real time if you know it.
- "Date for new rows" only fills rows you add after changing it. Rows already in the grid keep their own dates.
- Same date and time is fine. Rows with an identical date and time are recorded in the order they appear in the grid. You do not need to space them a minute or a second apart.
- Container list by date: a row's container list leaves out containers opened after that row's date. A container used after its expiry date is still offered and is flagged in amber.
- Earlier than the container's last entry: a row dated earlier than the latest entry already recorded on its container is held back (see below). A row at exactly the same date and time as that entry is not held back; it is recorded after it.
- A row dated after today is flagged in amber. It still saves, so check the date before saving.
When a row would take a container below zero, the row is flagged and the next container in sequence is suggested. Choose another container for that row before saving.
When a container is unopened, saving asks you to confirm opening it. It is marked opened on the date of the first row that uses it, and the opening is recorded as part of the same save.
Held-back rows. A row dated before the latest entry already recorded on its container cannot be added into the middle of that container's history yet. Instead of being discarded, it is held back: it is saved separately and listed at the top of the Batch Entry page. Use Resume to bring those rows back into the grid (for example, to correct the date or choose another container) or Discard to remove them. Discarding is logged.
Walkthrough: importing a DaySmart Vet Controlled Substance Log
If your practice keeps a Controlled Substance Log in DaySmart Vet, you can bring its rows into Batch Entry instead of typing them. Open the Administer or Dispense menu and choose Import from DaySmart, or use the link at the top of the Batch Entry page.
- Upload the CSV export (up to 5 MB). The file is read on the server. If it does not look like a DaySmart Controlled Substance Log, the page lists the line and the problem and nothing is stored.
- Map each section. The export has one section per product, but no record type and no container. For each section, choose the substance, whether the rows were administered or dispensed, and the container to start from. When that container runs out, rows move on to the next container number of the same substance.
- Review the rows, 50 at a time. Each row opens in the Batch Entry editor, where you can
change anything before it is recorded. The export has a date but no time, so each row is set
to 12:00 (noon) on its date, in the clinic's timezone, and rows on the same date keep the
file's order. A note above the rows says so. Change a row's time if you know when it happened.
A noon row is held back if its container already has an entry later that same day, as described
under "How dates and times work in Batch Entry" above.
- Client and patient. A client already in CS Logbook is matched when the name and phone agree, and a patient when the name agrees. If there is more than one possible match, the row shows the candidates for you to pick from. Otherwise the client or patient from the file is added, with the address and phone from the file, when you approve the page.
- Already recorded rows (from an earlier import of the same file) are shown but not recorded again. Likely already recorded rows (the same prescription and invoice number, or the same patient, day, substance and quantity) start unchecked; check them to include them.
- Any value the import had to change or could not read, such as an unrecognized state or species, is listed in amber on the row.
- Approve the page. Every checked row on the page is recorded together. If a checked row still needs a client or patient chosen, nothing on that page is recorded until you choose one or uncheck the row. Pages are approved in order.
- Finish the import. This discards the uploaded copy. If some rows were not recorded, the page tells you how many before you confirm; those rows remain in your DaySmart export. An unfinished import is discarded automatically after 14 days.
Each imported record keeps the prescription and invoice number from DaySmart, so you can find it again. The running balance printed in the DaySmart export is not used: it includes receipts the export leaves out.
Walkthrough — correcting a transaction
Step 1 — Open the transaction detail
From the list, click into the transaction you need to correct.
Step 2 — Click "Correct transaction"
You're taken to the Correct Transaction page (/inventory/transactions/<id>/correct). Only a still-correctable transaction is eligible — an original that's already been corrected, or a correction entry itself, can't be corrected again (a deep-link to a non-correctable transaction sends you back to the detail page). The form asks for:
- Corrected quantity — the right amount (positive; the app re-signs reduction types negative automatically). Pre-filled with the original quantity.
- Corrected transaction date — the right date the event occurred. Pre-filled with the original.
- Correction date — when you're making the correction (defaults to today).
- Correction reason: why you're correcting (recommended, not required; goes on the audit trail when given).
Step 3 — Submit
The original transaction is not modified. Instead it is preserved and struck (DEA red-line strikethrough model): a reversal entry backs out the original and a corrected entry restates the balance, both referencing the original. On success you're returned to the transaction detail page.
This is the only way to change a recorded transaction. Direct edits to the underlying transaction row are hard-blocked at the database level — any attempt to UPDATE or DELETE a transaction record fails with an RLS rejection.
Field reference
Transaction form (generalized)
| Field | Label | Type | Required | Validation |
|---|---|---|---|---|
container_id |
Container | combobox | ✓ | uuid; container must be unopened or opened |
transaction_type |
Type | select | ✓ | enum: administered, dispensed, waste, disposal, adjustment, transfer (received is auto-only) |
quantity |
Quantity | number | ✓ | > 0; cannot exceed current_balance for negative-direction types |
unit |
Unit | display | auto | inherited from container |
transaction_date |
Date / time | datetime | ✓ | not in future |
patient_id |
Patient | combobox | conditional | uuid; required for administered / dispensed |
prescriber_user_id |
Prescriber | combobox | conditional | uuid; required for administered / dispensed; must be a prescribing vet |
witnessed_by |
Witness | combobox | optional | uuid; expected for waste / on-site disposal, never required; must differ from performer |
reason |
Reason | textarea | optional | expected for waste / adjustment / disposal, never required; max 500 |
notes |
Notes | textarea | optional | max 1000 |
Hidden / system fields: id, account_id, performed_by (signed-in user), lock_version (container optimistic concurrency), created_at.
Edge cases
Concurrent transactions on the same container
CS Logbook uses optimistic concurrency via lock_version. If two clinic members log transactions on the same container at the same moment, the second save will fail with "Concurrent transaction detected — please refresh and try again". The first transaction succeeds; the second user retries.
Waste with witness — performer ≠ witness
The form prevents you from selecting yourself as the witness. If you are the only person on shift, record the waste anyway. Leave the witness blank: the record saves, it is flagged as incomplete, and you can name a witness later from the transaction's detail page without filing a correction. Do not defer the entry and do not reach for the adjustment type, which would misclassify what actually happened about the gap).
Quantity exceeds current balance
The form rejects save if you try to draw more than the container holds. To handle a multi-container administration (e.g., 1.5mL when the active container has only 0.3mL left), use the transaction split flow — the form detects insufficient balance and prompts you to draw from another container with sufficient balance.
A correction can be refused by the entries that came after it
The two cases above are about an entry you are recording now. This one is about correcting an entry you recorded earlier, and it is a different situation: the transactions logged after it already depend on the quantity you are about to change.
When you submit a correction, CS Logbook replays that container's history from the corrected entry forward and restates every running balance after it. If the corrected quantity would leave a later transaction with a negative balance, the correction is refused and you see a message explaining that the entries recorded after the one you are correcting depend on the quantity you are changing.
Nothing is saved when that happens. The original is not struck, no reversal or corrected entry is posted, and the container balance is unchanged. You are free to try again with different values.
Two ways forward, and the right one depends on what actually happened:
- Correct the later entries first, then come back to this one. Use this when the later entries are also wrong.
- Choose a quantity that leaves every later transaction with a non-negative balance. Use this when the later entries are right and only this one was mis-recorded.
If neither fits, because the container's real history cannot be expressed that way, record what you
know in Notes on the affected entries and raise it with your DEA registrant. Do not delete
anything and do not reach for a new adjustment entry to paper over the gap; that would misclassify
what happened.
Hard block: edit / delete on existing transactions
Direct UPDATE or DELETE on a transaction record is rejected at the RLS layer. The only way to change a transaction is via the corrections RPC. This is a deliberate audit-integrity guard per DEA record-retention rules.
Subscription gating
received and administered / dispensed / waste / disposal transactions require an active subscription at INSERT time (per RLS WITH CHECK). If your billing lapses, you can still view existing transactions but cannot record new ones. See Settings → Billing for renewal.
Common mistakes
- Filing a manual
receivedtransaction after using the Receive container form. The form auto-creates the receipt; manual entry double-counts. - Using
adjustmentfor everything.adjustmentis for paper-trail corrections, not for waste or administration. Use the right type so reports group correctly. - Deferring a waste entry because no witness is available. The form does not block save. Record the event when it happens, leave the witness blank, and name the witness later from the transaction's detail page. A contemporaneous record with a gap you close afterwards beats a late record.
- Editing transactions outside the corrections RPC. Direct DB access is blocked, but if you find yourself running raw SQL to "fix" a transaction, stop. Use the corrections flow — that's the audit trail DEA inspectors will look for.
- Selecting the wrong patient. Patient mis-selection during a busy shift is the most common audit error. The patient combobox supports search by name and microchip — type quickly to filter, then double-check before saving.
Related
- Containers — what transactions affect
- People → Patients — the animals on
administered/dispensedtransactions - Glossary § Transaction
- Glossary § Transaction type
- Audits → Spot checks — verify balances against transactions
- Audits → Theft / loss — DEA Form 106