Appointments
Scheduling system for building inspections, customer meetings, and other project-related events. Supports proposal-based booking where customers pick time slots through the Portal.
Data model note (2026-06): there is no standalone
appointmentstable. Appointments live in two places:appointment_proposals(the proposal/booking flow, anchored on a quote) and appointment-type entries inactivity_logs(the confirmed appointment record). Project and building context is resolved through the quote (appointment-service.ts).
Source Files
| Layer | Path |
|---|---|
| Schema | backend/src/db/schema.ts |
| Service | backend/src/services/appointment-service.ts |
| Portal Page | frontend/src/pages/portal/PortalAppointmentView.tsx |
| Queries | frontend/src/lib/queries/appointmentQueries.ts |
| Portal Queries | frontend/src/lib/queries/portalAppointmentQueries.ts |
Database Tables
| Table | Purpose |
|---|---|
appointment_proposals | Proposal/booking flow — time slot options sent to customers, acceptance tracking, portal token |
activity_logs | Confirmed appointments recorded as appointment-type activity entries (see Audit Logs) |
Key Fields — Appointment Proposals
| Field | Type | Notes |
|---|---|---|
quoteId | uuid FK | Anchor entity — the quote this appointment belongs to (required, cascade) |
phaseId / taskId | uuid | Workflow context the proposal was created from |
createdByUserId | uuid FK | Staff member who created the proposal |
status | varchar | open, accepted, declined, expired |
proposedSlots | jsonb | Array of slots: dateTime, duration (min), location (onsite/virtual/phone + address or URL) |
acceptedSlotIndex | integer | Which slot the customer chose (0-based) |
customerAcceptedAt | timestamp | When the customer accepted |
expiresAt | timestamp | Proposal expiry (default 7 days) |
portalToken | varchar(64) | Customer portal access token (SHA-256 hash, unique) |
notes | text | Internal notes |
Key Fields — Appointment Activity Entries
Acceptance writes an activity_logs row (e.g., type appointment_accepted) carrying:
| Field | Type | Notes |
|---|---|---|
appointmentDate | timestamp | Confirmed date and time (from the accepted slot) |
appointmentType | varchar | phone, onsite, virtual |
scheduledDate | date | Operator-set organisation date for week planning (I#1865) |
buildingId / projectId / quoteId | uuid FK | Context resolved via the quote |
Proposal-Based Booking
- Staff creates an
appointment_proposalsrow with multiple time slot options - Customer receives a notification with a tokenized link to the Portal
- Customer views proposals on
PortalAppointmentViewand selects a slot - Acceptance sets
status: accepted+acceptedSlotIndexand writes an appointment activity entry toactivity_logs - Both parties receive confirmation via Notifications
Workflow Integration
Appointments are created as part of Workflows tasks. Typical workflow tasks that generate appointments:
| Task Type | Appointment Type |
|---|---|
| Site inspection | Building survey visit |
| Customer consultation | Advisory meeting |
| Results presentation | Final review meeting |
The workflow task tracks the appointment status and can auto-advance when the appointment is completed.
Relationships
Appointment Proposal *──1 Quote (anchor; project/building resolved via quote)
Appointment Proposal *──1 Users (createdByUserId)
Appointment Proposal ──> Workflow Phase/Task (phaseId, taskId)
Appointment Proposal ──> Activity Log entry (on acceptance)
Frontend
- Internal view — appointments appear inline within Workflows task cards and on project detail pages
- Portal view —
PortalAppointmentView.tsxprovides the customer-facing proposal picker - Queries — separate query files for internal (
appointmentQueries.ts) and portal (portalAppointmentQueries.ts) contexts
Related Pages
Workflows | Quotes | Projects | Buildings | Audit Logs | Portal | Notifications | Users | Service Layer Pattern