arrow_back All features
science

Laboratory

Order tests and track results without the paperwork.

Lab tests without the paperwork

The Laboratory feature handles the journey of a lab test — from the doctor ordering it, to the result coming back, to the patient being told. It keeps everything connected to the patient's record.

What it does

Entering results on one page

Opening an order shows an Enter Results screen with one collapsible section per test, so a whole order is filled in without moving between pages. Expand all and Collapse all open or close every section at once.

Drafts and released results

A result is a draft until a person submits it. Drafts can be changed freely and are visible only inside the Laboratory module, labelled Draft. Until a result is submitted it does not appear anywhere else: not on the patient's portal, not in the appointment, not in certificates or exports, and it cannot be sent to anyone. It also does not raise a critical-value alert.

To release a test, use Submit Result on its section. The screen shows how many values are filled in and how many are N/A, then asks you to type a short confirmation code shown on screen. A wrong code releases nothing. Submitting is a separate act from typing: anyone who may record lab results can fill in and save drafts, but only someone who may validate lab results can submit. Once submitted, the result is final and locked, a critical value alerts the doctor who ordered the test, and the order is marked complete as soon as its last test is submitted.

Results that were already on file before this release are marked Final (legacy, not validated): they stay visible exactly as before, and the label simply says nobody validated them.

Blank and N/A values

A blank or N/A value shows as n/a. It carries no abnormal flag, and calculated fields count it as zero. A calculated field whose inputs are all blank shows n/a, and a calculated field worked out from some blank inputs is never flagged, so a missing value can never raise an abnormal or critical alert.

Correcting a released result

A released result is final and locked, so a mistake cannot simply be edited. Instead the laboratory asks for it to be pulled back, and a Clinic Admin approves.

  1. Request a reset. On the order page, a lab user chooses the specific tests to correct and gives a reason.
  2. Admins are told. Every Clinic Admin sees a notice in the app and receives an email with a one-time code.
  3. Approve. An admin opens the order, chooses Reset results and enters the code. The code is valid for 48 hours, and five wrong tries lock the request. Only one request can be open on an order at a time. An admin may decline the request; the person who made it, or an admin, may cancel it. An admin may approve their own request, and that is recorded.
  4. Correct and release again. The flagged tests go back to drafts, pre-filled with the old values. The lab user corrects them and releases them again in the usual way.

If no Clinic Admin could be emailed, the request is still saved, and the person who made it is told to ask an admin to check their email address. Only active admin accounts receive the code.

While a request is open, or flagged tests are still being revised, the order cannot be cancelled: finish or cancel the reset first. A request on an order that was cancelled meanwhile cannot be approved.

While any flagged test is being revised, the whole order's result sheet is withheld: it cannot be sent, and the patient portal shows that the result is being revised. The ordering doctor is told when results are pulled back, and again when the revised result is released. The corrected sheet says when it was revised and, where that is known, the date of the version it replaces.

Anyone who scans the QR code on an old printout is told that the result was modified. They then see either that it was reverted to draft, or the current final version.

History of the result sheet

Every request, approval, release, send and patient-portal download of the result sheet is recorded in the order's History. This is information only: nothing is blocked by it. Staff do not have a print or download button for the order sheet today, so the tracking covers sends and patient-portal downloads.

Billed when ordered

Placing an order bills it to the visit right away, on every path a test can be ordered from: the Laboratory module (web and API), the appointment booking form, and the APE form's Order button. A test with no price set is left off the bill — the order itself still goes through — and a message names it so the price can be added, or the line added by hand at the cashier.

If the same test is already billed for this encounter — from an earlier order, or from a checklist service or package that includes it — the new order's line shows as billed on another line for this visit instead of charging again. See Billing & Cashier for how that rule works across every source. Orders created by the APE spreadsheet importer, and by seeding, are never billed — they describe results from elsewhere or from setup, not a charge to raise.

Keeping the catalog tidy

Tests you no longer run can be deactivated rather than deleted — from the test list or its edit page — so every order and result that referred to them stays readable. A deactivated test stays in the list, marked Inactive, and can be activated again at any time.

A test's parameters can be put in any order on its edit page: drag a row by its handle, or use the up and down arrows beside it. Save the test and that order is what appears when results are entered and on the result sheet.

Ordering from the appointment page

When the Laboratory module is on, the appointment page has a Laboratory section. It lists every lab order on that visit, however it began: ticked on the booking form, or added later from this page. Each order shows its number, priority and status, with a link into the Laboratory module for anyone who may open it.

Under the list, Order laboratory tests opens the same form the booking form uses: tests grouped by category, a priority, a fasting flag and notes for the lab. Saving sends one order to the Laboratory queue, pinned to the visit's reference number, and bills it the way every other path does. A cancelled appointment takes no new orders, and the form is hidden from anyone who cannot edit the appointment or while another doctor holds its consultation.

Two shortcuts tick several tests at once, and any test can still be unticked:

Lab packages

Under Laboratory › Tests › Packages, a package is a named bundle of tests, such as an executive check-up. It can carry its own bundle price, or leave it blank so each test bills at its own price. The bundle price cannot be higher than the tests add up to.

When a doctor orders the whole package, the patient pays the bundle price in total, spread over the tests so each line on the bill and in the lab queue shows what was actually charged. If the doctor unticks any test, the package no longer applies and the rest bill at their own prices. A test already ordered on the same appointment is not ordered again and is left out of the spread, so the patient still pays the bundle price once. Tests ordered on other visits never reduce it.

A package holding a deactivated test is flagged in the list and left out of the ordering form until the test is brought back or removed from the package. Packages can be deactivated and reactivated like tests.

Selling tests as services

The tests here can be brought into Departments & Services with Import Lab Tests, so a test can be booked, packaged, assigned to a doctor and billed like any other service.

This catalog remains the source of truth for their price. Change a test's price here and the matching service follows automatically; the service's own price field is read-only to make that plain. Deactivating a test also retires its service, and reactivating restores it — so the two can never say different things.

Changing a test's price

On an order's page, anyone who can manage both pricing and the laboratory sees each test's price as a link to that test's edit form in the catalog; saving there brings you back to the order (opened any other way, the form returns to the catalog list). Other users see the price as plain text.

Saving a new price also updates tests already ordered and billed, but only while nothing has been paid on their bill. Those bill lines move to the new price, the bill's total is recalculated, and the order's own price for that test follows (matched by the visit reference, or for an order placed without one, by patient and the price it was billed at). A bill that has a payment on it, even a partial one, keeps the price the patient was quoted, and so does its order. Cancelled order items are left alone, and saving a test with no price never zeroes bills already raised. Tests billed through a service, checklist or package line are not changed.

For engineers: the Charge context re-prices the lines on laboratory.test_updated and then announces charge.lab_line_repriced; the Laboratory context updates the visit's order items from that. No migration is needed.

Why it helps

For the clinic

Orders and results stay organized and traceable, reducing the chance of a test being forgotten or a result going astray.

For doctors

Ordering a test takes a moment, and results appear alongside the rest of the patient's history for easy comparison over time.

For staff

The lab team and front desk can see what has been ordered and what is still pending, keeping everyone on the same page.

For patients

Their tests are tracked carefully, and a result that needs urgent attention is highlighted so the clinic can act quickly.

When a result is critically out of range, the system raises an alert directed to the doctor who ordered the test, so it reaches the right person and follow-up happens without delay.

For the diagnostics whose result is a picture and a radiologist's opinion rather than a number — X-rays, scans, ultrasounds — see Imaging & Radiology, which works the same way.

This is an optional feature your clinic can switch on when needed.

Deploying this update

Order-time billing and the once-per-visit rule need tenant migration 089 (patient_charge_items.lab_claims / waived_lab_tests / base_discount, and laboratory added to the item-type list).

Migrate every tenant first, then deploy the code. The new code writes those three columns on every charge line it inserts or updates, not just on lab lines. Against a tenant that has not been migrated, every charge-line write fails: the cashier adding an item or creating a charge, changing a line's brand, pharmacy and imaging charges, consultation charges, the charge API, the accounting screens, the availed-items checklist and the billing simulator. Orders themselves are still saved (the front desk sees "add the charge at the cashier"), but nothing can be billed anywhere on that tenant until 089 runs.

The migration is additive — new nullable columns and a wider item-type list — so the code you are replacing keeps working on a migrated database. That is what makes "migrate all tenants, then deploy" safe.

Clear the compiled container after deploying (rm -rf storage/cache/container-*) — the new lab charge creator is wired through dependency injection.

Seeding and the APE spreadsheet importer never bill. Seeding creates its orders with the order-writing methods this feature leaves untouched (createOrder() for lab and imaging orders, ApeLabWriter::applyOrders() for APE forms), which write the order rows only; billing lives in separate sibling methods that only the interactive paths (booking form, Laboratory module, APE form) call. The APE spreadsheet importer does not go through either: ApeImportService builds its lab orders and order items itself and never calls a charge creator. A seeded or imported encounter is never charged for orders it never actually incurred.

An order placed without a visit reference is billed to the patient's open charge that carries no reference (a new one is opened if there is none). It is never put on — or checked against — some other visit's bill, so a test already paid for on a different visit cannot make this one free.

Deploying lab packages and appointment ordering

Run tenant migration 096 on every tenant before deploying the code: it creates the package tables, and the appointment page and the booking form read them. Clear the compiled container afterwards (rm -rf storage/cache/container-*) because the order writer and the new appointment-page action are wired through dependency injection. Nothing is seeded: a clinic has no packages until it creates them under Laboratory, and the panel shortcuts appear on their own from the existing catalog.

Deploying one-page result entry

Run the pending tenant migrations on every tenant before deploying the code; this includes a backfill that marks results saved under the old flow as final, so nothing already on file suddenly looks unreleased. It is safe to repeat, and the release notes give a one-line repeat to run on every tenant right after the deploy goes live. Clear the compiled container afterwards (rm -rf storage/cache/container-*) because the release service is wired through dependency injection.

apps Back to all features