Scheduling, waitlist, outbound sync outbox, and Filament admin surfaces for FlowRise HMS.
End-user workflows for reception, clinical staff, and administrators: docs/user-guide/appointments.md.
All domain models extend Modules\Core\Models\BaseModel, which applies BelongsToBranch:
- Global scope: queries default to
branch_id = current_branch_idfrom LaravelContextorAuth::user()->branch_id. - Creating:
branch_idis auto-filled from that context when absent.
Console commands, queues, and tests must set branch context or call Model::withoutGlobalScopes() when operating across branches.
| Model | Table | Purpose |
|---|---|---|
Appointment |
appointments |
Core booked encounter shell (patient, location, status, times). |
AppointmentParticipant |
appointment_participants |
FHIR-style participants (participant_type, actor_reference string). |
AppointmentResource |
appointment_resources |
Room/equipment allocations with time windows; feeds conflict checks. |
AppointmentRecurrenceRule |
appointment_recurrence_rules |
Metadata only — no engine expands occurrences yet. |
AppointmentAudit |
appointment_audits |
Optional domain audit rows (manual / Filament); not auto-written by scheduling. |
ScheduleBlock |
appointment_schedule_blocks |
Practitioner/location blocks for conflict detection. |
WaitlistEntry |
appointment_waitlist_entries |
Prioritized wait queue per branch/patient preferences. |
AppointmentSyncOutbox |
appointment_sync_outbox |
Reliable outbound events for integrations. |
patient,location,department,branch(explicitbranch; matches trait).primaryPractitioner→Staffviapractitioner_primary_id(UUID, no FK in DB).creator/updater→App\Models\Userviacreated_by/updated_by.- Children:
participants,resources,recurrenceRules,appointmentAudits,syncOutboxEntries(filtered toaggregate_type = appointment).
branch()from trait;appointment()→Appointmentonaggregate_idwhenaggregate_typeisappointment(AppointmentSyncOutbox::AGGREGATE_TYPE_APPOINTMENT).- Query helpers:
scopeForAppointmentAggregate(),scopeDue()(pending +available_atelapsed).
patient,preferredPractitioner(Staff, nullable UUID no FK),preferredLocation,preferredDepartment,branch(trait).
appointment,branch(trait),actor→Userwhenactor_idpoints at a web user.
branch(trait),practitioner→ Staff,location,department.
appointment,branch(trait).
These columns are strings without FK until interoperability stabilizes:
appointments.practitioner_primary_idappointment_participants.actor_referenceappointment_schedule_blocks.practitioner_id(nullable)appointment_waitlist_entries.preferred_practitioner_id
Prefer Staff UUIDs where Staff exists; document external FHIR ids separately if needed.
- schedule / reschedule / checkIn / cancel mutate
Appointmentand enqueueAppointmentSyncOutboxrows. - Practitioner conflicts:
AppointmentConflictServicechecks overlappingAppointmentrows andScheduleBlock. - Resource conflicts:
hasResourceConflictwhenAppointmentResourcerows exist.
Events emitted today:
event_name |
When |
|---|---|
appointment.booked |
After create |
appointment.rescheduled |
After reschedule (+ version bump) |
appointment.checked_in |
After check-in |
appointment.cancelled |
After cancel |
Idempotency: idempotency_key is sha256("{appointment_id}|{event_name}|{version}"). AppointmentSyncOutbox::firstOrCreate skips duplicates for the same triple.
Payload (JSON): appointment_id, status, ISO8601 start_at, end_at.
Artisan command appointment:process-sync-outbox (Modules\Appointment\Console\Commands\ProcessAppointmentSyncOutboxCommand) selects due pending rows (respects withoutGlobalScopes) and marks them completed — replace internals with real HTTP / message-bus dispatch.
Scheduler entry (project routes/console.php from repository root):
Schedule::command('appointment:process-sync-outbox')->everyMinute();Ensure host cron runs php artisan schedule:run every minute in production.
Filament: no manual create route for outbox rows — entries are system-generated; operators may view/edit for troubleshooting.
WaitlistScoringService returns a numeric score only; persist computed_priority_score from Filament or a future job.
Rules stored on AppointmentRecurrenceRule are not expanded into additional appointments. Admin CRUD is informational until a recurrence engine is implemented.
- Spatie activity log runs on all
BaseModelchildren (configurable per model). AppointmentAuditis not populated automatically by scheduling today — use Filament for explicit audit rows or add observers later.
- Maintain schedule blocks for clinicians/locations that must not receive bookings.
- Attach resources when rooms/assets must participate in conflict detection.
- Monitor sync outbox for stuck
failed/ highattemptsrows after real integrations ship.
php artisan appointment:process-sync-outbox --limit=50./vendor/bin/pest Modules/Appointment/tests