Lab Invoices (فواتير المختبر)
Lab Invoices handle the billing for laboratory services. Each invoice is linked to a lab request and patient, supports insurance contract integration, and follows a lifecycle from draft through posting (with journal entry creation) to payment or cancellation.
Entity Attributes
| Field | Type | Required | Description |
|---|---|---|---|
id | integer | auto | Primary key |
invoice_number | string | auto | Auto-generated invoice number |
lab_request_id | FK | yes | Associated lab request |
patient_id | FK | yes | Billed patient |
date | date | yes | Invoice date |
status | enum | auto | draft, posted, partially_paid, paid, cancelled |
subtotal | decimal(12,3) | auto | Sum of item prices |
discount_amount | decimal(12,3) | auto | Total discounts |
tax_amount | decimal(12,3) | auto | Total tax |
total | decimal(12,3) | auto | Net total |
amount_paid | decimal(12,3) | no | Amount received |
balance_due | decimal(12,3) | auto | Total minus amount paid |
insurance_contract_id | FK | no | Applied insurance contract |
insurance_amount | decimal(12,3) | no | Insurance share |
patient_amount | decimal(12,3) | no | Patient share |
journal_entry_id | FK | auto | Created on post |
cancel_journal_entry_id | FK | auto | Created on cancel |
notes | text | no | Invoice notes |
branch_id | FK | no | Branch |
posted_by | FK | auto | User who posted |
posted_at | datetime | auto | Post timestamp |
cancelled_by | FK | auto | User who cancelled |
cancelled_at | datetime | auto | Cancel timestamp |
cancellation_reason | string | no | Reason for cancellation |
invoice_type | enum | auto | standard, patient_invoice, insurance_invoice |
related_invoice_id | FK | no | Cross-linked invoice (patient/insurance pair) |
insurance_company_name | string | no | Denormalized insurance company name |
insurance_company_name_ar | string | no | Denormalized insurance company name (Arabic) |
partner_id | FK | no | BusinessPartner for insurance invoices |
Invoice Item Attributes
| Field | Type | Required | Description |
|---|---|---|---|
lab_invoice_id | FK | yes | Parent invoice |
investigation_id | FK | yes | Billed investigation |
description | string | no | Item description |
price | decimal(12,3) | yes | Unit price |
discount | decimal(12,3) | no | Item discount |
tax | decimal(12,3) | no | Item tax |
net_amount | decimal(12,3) | auto | Price - discount + tax |
Status Workflow
API Endpoints
| Method | Endpoint | Description | Permission |
|---|---|---|---|
GET | /api/lis/invoices | List invoices (paginated) | lis.invoices.view |
POST | /api/lis/invoices | Create invoice | lis.invoices.create |
GET | /api/lis/invoices/{id} | Get invoice details | lis.invoices.view |
PUT | /api/lis/invoices/{id} | Update invoice | lis.invoices.update |
DELETE | /api/lis/invoices/{id} | Delete invoice | lis.invoices.delete |
POST | /api/lis/invoices/{id}/post | Post invoice | lis.invoices.update |
POST | /api/lis/invoices/{id}/cancel | Cancel invoice | lis.invoices.update |
POST | /api/lis/invoices/generate | Generate invoice(s) from request | lis.insurance-invoices.generate |
Query Parameters (List)
| Parameter | Type | Description |
|---|---|---|
search | string | Search by invoice number |
status | string | Filter by status |
patient_id | integer | Filter by patient |
from | date | Start date filter |
to | date | End date filter |
invoice_type | string | Filter by type (standard, patient_invoice, insurance_invoice) |
Request / Response Examples
Create Invoice
bash
curl -X POST /api/lis/invoices \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"lab_request_id": 1,
"patient_id": 1,
"date": "2026-03-01",
"insurance_contract_id": 1,
"branch_id": 1,
"items": [
{"investigation_id": 1, "price": "5.000", "discount": "0.500", "tax": "0.000"},
{"investigation_id": 5, "price": "3.000", "discount": "0.000", "tax": "0.000"}
]
}'dart
final response = await dio.post('/api/lis/invoices', data: {
'lab_request_id': 1,
'patient_id': 1,
'date': '2026-03-01',
'insurance_contract_id': 1,
'branch_id': 1,
'items': [
{'investigation_id': 1, 'price': '5.000', 'discount': '0.500', 'tax': '0.000'},
{'investigation_id': 5, 'price': '3.000', 'discount': '0.000', 'tax': '0.000'},
],
});Post Invoice
bash
curl -X POST /api/lis/invoices/1/post \
-H "Authorization: Bearer {token}"dart
final response = await dio.post('/api/lis/invoices/1/post');Cancel Invoice
bash
curl -X POST /api/lis/invoices/1/cancel \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{"cancellation_reason": "Duplicate invoice"}'dart
final response = await dio.post('/api/lis/invoices/1/cancel', data: {
'cancellation_reason': 'Duplicate invoice',
});Business Rules
- Post restrictions -- Only
draftinvoices can be posted. Posting creates a journal entry in the Accounting module. - Cancel restrictions -- Only
draftandpostedinvoices can be cancelled. Cancelling a posted invoice creates a reversal journal entry. - Auto-totals -- Subtotal, discount, tax, and total are recalculated from line items.
- Insurance split -- If an insurance contract is applied,
insurance_amountandpatient_amountare calculated from contract terms. - Balance due --
balance_due = total - amount_paid, updated on each payment. - Delete restrictions -- Only draft invoices can be deleted. Posted invoices must be cancelled instead.
- Split invoicing -- For insured requests,
POST /invoices/generatecreates a patient_invoice + insurance_invoice pair. Standard requests get a single standard invoice. See Insurance Billing. - Doctor commissions -- Commissions are only calculated for
standardandpatient_invoicetypes. Insurance invoices are skipped to avoid double-counting. - Insurance accounting -- Insurance invoices use the contract's
insurance_receivable_account_idandrevenue_account_idfor the journal entry instead of default accounts.