Files
ladill-care/app/Services/Care/PrescriptionService.php
T
isaaccladandCursor 015a4cc7fe
Deploy Ladill Care / deploy (push) Successful in 1m45s
Wire Care lists into Ladill Queue workflows instead of reception panels.
When Queue integration is on, role pages issue tickets into their own
department queues and drive Call next → Serve → Complete on the native
lists. Integration off leaves existing UI unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-17 16:27:48 +00:00

215 lines
7.9 KiB
PHP

<?php
namespace App\Services\Care;
use App\Models\Consultation;
use App\Models\Organization;
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 __construct(
protected CareQueueBridge $queueBridge,
) {}
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,
);
$prescription = $prescription->fresh(['items', 'patient', 'practitioner', 'visit']);
if ($status === Prescription::STATUS_ACTIVE) {
$organization = Organization::query()->find($prescription->organization_id);
if ($organization) {
$this->queueBridge->issueForPrescription($organization, $prescription);
}
}
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);
$prescription = $prescription->fresh(['items', 'patient', 'visit']);
$organization = Organization::query()->find($prescription->organization_id);
if ($organization) {
$this->queueBridge->issueForPrescription($organization, $prescription);
}
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);
$prescription = $prescription->fresh(['items', 'patient', 'visit']);
$organization = Organization::query()->find($prescription->organization_id);
if ($organization) {
$this->queueBridge->complete($organization, $prescription);
}
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}.");
}
}
}