Healthcare management app: patients, appointments, consultations, lab, pharmacy inventory, billing, and reports at care.ladill.com. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Care;
|
||||
|
||||
use App\Models\Consultation;
|
||||
use App\Models\Prescription;
|
||||
use App\Models\PrescriptionItem;
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class PrescriptionService
|
||||
{
|
||||
public function queryForOrganization(string $ownerRef, int $organizationId): Builder
|
||||
{
|
||||
return Prescription::owned($ownerRef)->where('organization_id', $organizationId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $filters
|
||||
*/
|
||||
public function list(string $ownerRef, int $organizationId, array $filters = [], ?int $branchId = null): LengthAwarePaginator
|
||||
{
|
||||
$query = $this->queryForOrganization($ownerRef, $organizationId)
|
||||
->with(['patient', 'practitioner', 'items'])
|
||||
->orderByDesc('created_at');
|
||||
|
||||
if ($status = $filters['status'] ?? null) {
|
||||
$query->where('status', $status);
|
||||
}
|
||||
|
||||
if ($patientId = $filters['patient_id'] ?? null) {
|
||||
$query->where('patient_id', $patientId);
|
||||
}
|
||||
|
||||
if ($branchId !== null) {
|
||||
$query->whereHas('visit', fn (Builder $q) => $q->where('branch_id', $branchId));
|
||||
}
|
||||
|
||||
return $query->paginate((int) ($filters['per_page'] ?? 20))->withQueryString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, Prescription>
|
||||
*/
|
||||
public function pharmacyQueue(string $ownerRef, int $organizationId): Collection
|
||||
{
|
||||
return $this->queryForOrganization($ownerRef, $organizationId)
|
||||
->where('status', Prescription::STATUS_ACTIVE)
|
||||
->with(['patient', 'practitioner', 'items', 'visit.branch'])
|
||||
->orderBy('created_at')
|
||||
->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public function createFromConsultation(
|
||||
Consultation $consultation,
|
||||
string $ownerRef,
|
||||
array $data,
|
||||
?string $actorRef = null,
|
||||
): Prescription {
|
||||
$consultation->loadMissing('visit');
|
||||
|
||||
$status = ($data['activate'] ?? true) ? Prescription::STATUS_ACTIVE : Prescription::STATUS_DRAFT;
|
||||
|
||||
$prescription = Prescription::create([
|
||||
'owner_ref' => $ownerRef,
|
||||
'organization_id' => $consultation->visit->organization_id,
|
||||
'visit_id' => $consultation->visit_id,
|
||||
'consultation_id' => $consultation->id,
|
||||
'patient_id' => $consultation->patient_id,
|
||||
'practitioner_id' => $data['practitioner_id'] ?? $consultation->practitioner_id,
|
||||
'status' => $status,
|
||||
'notes' => $data['notes'] ?? null,
|
||||
'prescribed_by' => $actorRef,
|
||||
]);
|
||||
|
||||
$this->syncItems($prescription, $ownerRef, $data['items'] ?? []);
|
||||
|
||||
AuditLogger::record(
|
||||
$ownerRef,
|
||||
'prescription.created',
|
||||
$consultation->visit->organization_id,
|
||||
$actorRef,
|
||||
Prescription::class,
|
||||
$prescription->id,
|
||||
);
|
||||
|
||||
return $prescription->fresh(['items', 'patient', 'practitioner']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public function update(Prescription $prescription, string $ownerRef, array $data, ?string $actorRef = null): Prescription
|
||||
{
|
||||
if ($prescription->status !== Prescription::STATUS_DRAFT) {
|
||||
throw new InvalidArgumentException('Only draft prescriptions can be edited.');
|
||||
}
|
||||
|
||||
$prescription->update([
|
||||
'practitioner_id' => $data['practitioner_id'] ?? $prescription->practitioner_id,
|
||||
'notes' => $data['notes'] ?? $prescription->notes,
|
||||
]);
|
||||
|
||||
if (array_key_exists('items', $data)) {
|
||||
$this->syncItems($prescription, $ownerRef, $data['items']);
|
||||
}
|
||||
|
||||
AuditLogger::record($ownerRef, 'prescription.updated', $prescription->organization_id, $actorRef, Prescription::class, $prescription->id);
|
||||
|
||||
return $prescription->fresh(['items', 'patient', 'practitioner']);
|
||||
}
|
||||
|
||||
public function activate(Prescription $prescription, string $ownerRef, ?string $actorRef = null): Prescription
|
||||
{
|
||||
$this->assertStatus($prescription, Prescription::STATUS_DRAFT);
|
||||
|
||||
$prescription->update(['status' => Prescription::STATUS_ACTIVE]);
|
||||
AuditLogger::record($ownerRef, 'prescription.activated', $prescription->organization_id, $actorRef, Prescription::class, $prescription->id);
|
||||
|
||||
return $prescription->fresh(['items', 'patient']);
|
||||
}
|
||||
|
||||
public function dispense(Prescription $prescription, string $ownerRef, ?string $actorRef = null): Prescription
|
||||
{
|
||||
$this->assertStatus($prescription, Prescription::STATUS_ACTIVE);
|
||||
|
||||
$prescription->update([
|
||||
'status' => Prescription::STATUS_DISPENSED,
|
||||
'dispensed_at' => now(),
|
||||
'dispensed_by' => $actorRef,
|
||||
]);
|
||||
|
||||
AuditLogger::record($ownerRef, 'prescription.dispensed', $prescription->organization_id, $actorRef, Prescription::class, $prescription->id);
|
||||
|
||||
return $prescription->fresh(['items', 'patient']);
|
||||
}
|
||||
|
||||
public function cancel(Prescription $prescription, string $ownerRef, ?string $actorRef = null): Prescription
|
||||
{
|
||||
if (in_array($prescription->status, [Prescription::STATUS_DISPENSED, Prescription::STATUS_CANCELLED], true)) {
|
||||
throw new InvalidArgumentException('Prescription cannot be cancelled.');
|
||||
}
|
||||
|
||||
$prescription->update(['status' => Prescription::STATUS_CANCELLED]);
|
||||
AuditLogger::record($ownerRef, 'prescription.cancelled', $prescription->organization_id, $actorRef, Prescription::class, $prescription->id);
|
||||
|
||||
return $prescription->fresh(['items', 'patient']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, array<string, mixed>> $items
|
||||
*/
|
||||
protected function syncItems(Prescription $prescription, string $ownerRef, array $items): void
|
||||
{
|
||||
$prescription->items()->delete();
|
||||
|
||||
foreach ($items as $index => $row) {
|
||||
if (empty($row['name'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
PrescriptionItem::create([
|
||||
'owner_ref' => $ownerRef,
|
||||
'prescription_id' => $prescription->id,
|
||||
'is_procedure' => (bool) ($row['is_procedure'] ?? false),
|
||||
'name' => $row['name'],
|
||||
'dosage' => $row['dosage'] ?? null,
|
||||
'frequency' => $row['frequency'] ?? null,
|
||||
'duration' => $row['duration'] ?? null,
|
||||
'route' => $row['route'] ?? null,
|
||||
'quantity' => $row['quantity'] ?? null,
|
||||
'instructions' => $row['instructions'] ?? null,
|
||||
'sort_order' => $index,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
protected function assertStatus(Prescription $prescription, string ...$allowed): void
|
||||
{
|
||||
if (! in_array($prescription->status, $allowed, true)) {
|
||||
throw new InvalidArgumentException("Cannot transition from {$prescription->status}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user