Initial Ladill Care release.
Deploy Ladill Care / deploy (push) Failing after 13s

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:
isaacclad
2026-06-29 11:36:22 +00:00
co-authored by Cursor
commit 6c9c742ed8
300 changed files with 30741 additions and 0 deletions
+189
View File
@@ -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}.");
}
}
}