RBAC and billing in CareFlow — keeping hospital data consistent
Hospital workflows are unforgiving: a patient registration, a prescription, and a cashier receipt touch three departments that should never disagree. CareFlow had to feel like one system, not three spreadsheets taped together.
Roles that match the org chart
I used Spatie Laravel Permission instead of hand-rolled is_admin flags. Roles like admin, doctor, pharmacist, cashier, and registrar each get a narrow permission set:
// seeder
$doctor->givePermissionTo(['view:patients', 'create:ehr']);
$cashier->givePermissionTo(['view:billing', 'create:payment']);
The payoff is auditability. Every controller gates with $this->authorize('create:payment'), and Blade directives hide UI that the user cannot act on — no silent 403s.
Transactions are the truth
Billing is the riskiest path. A single payment must:
- Decrement pharmacy stock
- Create a billing record
- Mark the inpatient bed as available
If any step fails, everything must roll back. Laravel's DB::transaction makes that explicit:
DB::transaction(function () use ($patient, $items) {
$billing = Billing::create([...]);
foreach ($items as $item) {
StockMovement::create([...]);
Medicine::where('id', $item->id)->decrement('stock', $item->qty);
}
Bed::where('id', $patient->bed_id)->update(['status' => 'available']);
});
No distributed saga needed — just honest, isolated SQL transactions and eager-loaded relationships to avoid N+1 on the dashboard: Patient::with(['visits', 'prescriptions.medicine'])->get().
Reporting without melting the server
Management wants PDFs filtered by date, department, and payment status. Rendering 2,000 rows through DomPDF at request time is a quick way to hit OOM.
What helped:
- Aggregate in SQL first (
SUM(total) GROUP BY date) and only hydrate templates with summaries. - Cache compiled Blade templates for DomPDF between identical report shapes.
- Stream the PDF (
Dompdf::stream()) instead of buffering it fully in PHP memory.
Result: a monthly financial recap generates in <1s on a modest VPS, and every source-of-truth table agrees because the writes were transactional in the first place.
Consistency is a feature. CareFlow treats it as one — with RBAC that mirrors the org and transactions that refuse partial success.