Ship commercial Dentistry suite with odontogram and treatment plans.
Deploy Ladill Care / deploy (push) Failing after 36s

Replace placeholder clinical forms with patient-level FDI charting, multi-visit plans, procedure-to-bill linking, imaging, reports, and demo seed data.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-07-18 16:33:34 +00:00
co-authored by Cursor
parent a00a8249ad
commit 0edd653187
37 changed files with 2906 additions and 35 deletions
+60
View File
@@ -0,0 +1,60 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToOwner;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Str;
class DentalChart extends Model
{
use BelongsToOwner;
protected $table = 'care_dental_charts';
protected $fillable = [
'uuid', 'owner_ref', 'organization_id', 'patient_id',
'notation', 'charted_at', 'charted_by',
];
protected function casts(): array
{
return ['charted_at' => 'datetime'];
}
protected static function booted(): void
{
static::creating(function (DentalChart $chart) {
if (! $chart->uuid) {
$chart->uuid = (string) Str::uuid();
}
});
}
public function getRouteKeyName(): string
{
return 'uuid';
}
public function organization(): BelongsTo
{
return $this->belongsTo(Organization::class, 'organization_id');
}
public function patient(): BelongsTo
{
return $this->belongsTo(Patient::class, 'patient_id');
}
public function teeth(): HasMany
{
return $this->hasMany(DentalTooth::class, 'dental_chart_id');
}
public function events(): HasMany
{
return $this->hasMany(DentalChartEvent::class, 'dental_chart_id');
}
}
+52
View File
@@ -0,0 +1,52 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToOwner;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Str;
class DentalChartEvent extends Model
{
use BelongsToOwner;
protected $table = 'care_dental_chart_events';
protected $fillable = [
'uuid', 'owner_ref', 'organization_id', 'dental_chart_id', 'patient_id',
'visit_id', 'tooth_code', 'event_type', 'payload', 'recorded_by', 'recorded_at',
];
protected function casts(): array
{
return [
'payload' => 'array',
'recorded_at' => 'datetime',
];
}
protected static function booted(): void
{
static::creating(function (DentalChartEvent $event) {
if (! $event->uuid) {
$event->uuid = (string) Str::uuid();
}
});
}
public function chart(): BelongsTo
{
return $this->belongsTo(DentalChart::class, 'dental_chart_id');
}
public function visit(): BelongsTo
{
return $this->belongsTo(Visit::class, 'visit_id');
}
public function patient(): BelongsTo
{
return $this->belongsTo(Patient::class, 'patient_id');
}
}
+66
View File
@@ -0,0 +1,66 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToOwner;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Str;
class DentalImage extends Model
{
use BelongsToOwner;
public const MODALITY_PA = 'pa';
public const MODALITY_BW = 'bw';
public const MODALITY_OPG = 'opg';
public const MODALITY_PHOTO = 'photo';
protected $table = 'care_dental_images';
protected $fillable = [
'uuid', 'owner_ref', 'organization_id', 'patient_id', 'visit_id',
'patient_attachment_id', 'modality', 'tooth_codes', 'caption',
'uploaded_by', 'captured_at',
];
protected function casts(): array
{
return [
'tooth_codes' => 'array',
'captured_at' => 'datetime',
];
}
protected static function booted(): void
{
static::creating(function (DentalImage $image) {
if (! $image->uuid) {
$image->uuid = (string) Str::uuid();
}
});
}
public function getRouteKeyName(): string
{
return 'uuid';
}
public function attachment(): BelongsTo
{
return $this->belongsTo(PatientAttachment::class, 'patient_attachment_id');
}
public function visit(): BelongsTo
{
return $this->belongsTo(Visit::class, 'visit_id');
}
public function patient(): BelongsTo
{
return $this->belongsTo(Patient::class, 'patient_id');
}
}
+54
View File
@@ -0,0 +1,54 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Str;
class DentalPlanItem extends Model
{
public const STATUS_PROPOSED = 'proposed';
public const STATUS_ACCEPTED = 'accepted';
public const STATUS_DONE = 'done';
public const STATUS_CANCELLED = 'cancelled';
protected $table = 'care_dental_plan_items';
protected $fillable = [
'uuid', 'treatment_plan_id', 'tooth_code', 'surfaces', 'procedure_code',
'label', 'priority', 'status', 'estimate_minor', 'bill_line_item_id', 'notes',
];
protected function casts(): array
{
return ['surfaces' => 'array'];
}
protected static function booted(): void
{
static::creating(function (DentalPlanItem $item) {
if (! $item->uuid) {
$item->uuid = (string) Str::uuid();
}
});
}
public function getRouteKeyName(): string
{
return 'uuid';
}
public function plan(): BelongsTo
{
return $this->belongsTo(DentalTreatmentPlan::class, 'treatment_plan_id');
}
public function billLineItem(): BelongsTo
{
return $this->belongsTo(BillLineItem::class, 'bill_line_item_id');
}
}
+67
View File
@@ -0,0 +1,67 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToOwner;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Str;
class DentalProcedure extends Model
{
use BelongsToOwner;
public const STATUS_COMPLETED = 'completed';
public const STATUS_CANCELLED = 'cancelled';
protected $table = 'care_dental_procedures';
protected $fillable = [
'uuid', 'owner_ref', 'organization_id', 'patient_id', 'visit_id', 'plan_item_id',
'tooth_code', 'surfaces', 'procedure_code', 'label', 'anesthesia', 'status',
'amount_minor', 'bill_line_item_id', 'provider_ref', 'notes', 'performed_at',
];
protected function casts(): array
{
return [
'surfaces' => 'array',
'performed_at' => 'datetime',
];
}
protected static function booted(): void
{
static::creating(function (DentalProcedure $procedure) {
if (! $procedure->uuid) {
$procedure->uuid = (string) Str::uuid();
}
});
}
public function getRouteKeyName(): string
{
return 'uuid';
}
public function visit(): BelongsTo
{
return $this->belongsTo(Visit::class, 'visit_id');
}
public function patient(): BelongsTo
{
return $this->belongsTo(Patient::class, 'patient_id');
}
public function planItem(): BelongsTo
{
return $this->belongsTo(DentalPlanItem::class, 'plan_item_id');
}
public function billLineItem(): BelongsTo
{
return $this->belongsTo(BillLineItem::class, 'bill_line_item_id');
}
}
+36
View File
@@ -0,0 +1,36 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class DentalTooth extends Model
{
public const STATUS_PRESENT = 'present';
public const STATUS_MISSING = 'missing';
public const STATUS_EXTRACTED = 'extracted';
public const STATUS_IMPLANT = 'implant';
protected $table = 'care_dental_teeth';
protected $fillable = [
'dental_chart_id', 'tooth_code', 'status', 'surfaces', 'conditions', 'notes',
];
protected function casts(): array
{
return [
'surfaces' => 'array',
'conditions' => 'array',
];
}
public function chart(): BelongsTo
{
return $this->belongsTo(DentalChart::class, 'dental_chart_id');
}
}
+72
View File
@@ -0,0 +1,72 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToOwner;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Str;
class DentalTreatmentPlan extends Model
{
use BelongsToOwner, SoftDeletes;
public const STATUS_DRAFT = 'draft';
public const STATUS_ACCEPTED = 'accepted';
public const STATUS_IN_PROGRESS = 'in_progress';
public const STATUS_COMPLETED = 'completed';
protected $table = 'care_dental_treatment_plans';
protected $fillable = [
'uuid', 'owner_ref', 'organization_id', 'patient_id', 'visit_id',
'status', 'title', 'estimate_minor', 'accepted_at', 'completed_at', 'created_by',
];
protected function casts(): array
{
return [
'accepted_at' => 'datetime',
'completed_at' => 'datetime',
];
}
protected static function booted(): void
{
static::creating(function (DentalTreatmentPlan $plan) {
if (! $plan->uuid) {
$plan->uuid = (string) Str::uuid();
}
});
}
public function getRouteKeyName(): string
{
return 'uuid';
}
public function organization(): BelongsTo
{
return $this->belongsTo(Organization::class, 'organization_id');
}
public function patient(): BelongsTo
{
return $this->belongsTo(Patient::class, 'patient_id');
}
public function visit(): BelongsTo
{
return $this->belongsTo(Visit::class, 'visit_id');
}
public function items(): HasMany
{
return $this->hasMany(DentalPlanItem::class, 'treatment_plan_id')->orderBy('priority')->orderBy('id');
}
}
+45
View File
@@ -0,0 +1,45 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToOwner;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Str;
class DentalVisitNote extends Model
{
use BelongsToOwner;
protected $table = 'care_dental_visit_notes';
protected $fillable = [
'uuid', 'owner_ref', 'organization_id', 'visit_id', 'patient_id',
'chief_complaint', 'soft_tissue', 'occlusion', 'notes',
'recorded_by', 'recorded_at',
];
protected function casts(): array
{
return ['recorded_at' => 'datetime'];
}
protected static function booted(): void
{
static::creating(function (DentalVisitNote $note) {
if (! $note->uuid) {
$note->uuid = (string) Str::uuid();
}
});
}
public function visit(): BelongsTo
{
return $this->belongsTo(Visit::class, 'visit_id');
}
public function patient(): BelongsTo
{
return $this->belongsTo(Patient::class, 'patient_id');
}
}