feat(calendar): add scheduling-template model and CRUD endpoints (PMS-403) #299

Merged
vas2000-work merged 2 commits from feat/PMS-403-scheduling-templates into main 2026-06-18 00:53:48 +02:00
Owner

The calendar module booked appointments ad hoc through POST /appointments with no concept of a reusable, named scheduling shape (e.g. "1h new-customer interview" or "2h on-site visit with 30 min travel before and after") that a dispatcher could pick to pre-fill an appointment. This adds the backend model and CRUD for user-customizable scheduling templates of two kinds: dispatch (on-site work, with optional pre/post travel buffers) and calendar (client interactions / status updates).

Migration 054_scheduling_templates.sql creates a tenant-scoped scheduling_templates table with kind (dispatch|calendar), appointment_type (mirroring the appointments CHECK), duration_minutes (> 0), travel_before/after_minutes (>= 0), optional default_title/default_location/default_ticket_id/notes, timestamps, a tenant index, an explicit updated_at trigger, and the fail-closed FORCE'd tenant_isolation RLS policy (the 024/038 DO-block loops already ran, so a table created now does not inherit either, matching 051_mileage_entries).

DTOs SchedulingTemplateResponse / CreateSchedulingTemplateRequest / UpdateSchedulingTemplateRequest / SchedulingTemplateFilter live in calendar/models.rs with validator constraints (name length 1..=100, positive duration, non-negative buffers, and a valid kind via a new utils::validation::validate_scheduling_template_kind that mirrors the contract-type/status guards so an out-of-set value returns 422 instead of a 500 from the DB CHECK). tenant_id is omitted from the wire shape; the update request is a partial PUT (all-Option, COALESCE) mirroring UpdateAppointmentRequest.

CalendarService gains list_templates / get_template / create_template / update_template / delete_template, each tenant-scoped via begin_with_tenant. Create and update validate default_ticket_id against the tenant through the existing validate_fk_opt helper and write AuditAction::Create / ::Update rows (before/after snapshots) in the same transaction. Routes GET|POST /scheduling-templates and GET|PUT|DELETE /scheduling-templates/{id} mount in calendar_routes, gated by RequireCalendar, with the list endpoint supporting a kind filter plus pagination.

Postgres-backed tests in tests/scheduling_templates.rs cover the CRUD lifecycle, kind filtering, tenant isolation (a template in tenant A is invisible to tenant B), and rejection of a cross-tenant default_ticket_id (400), a non-positive duration_minutes (422), and an invalid kind (422). The appointments table, CreateAppointmentRequest, and recurrence expansion are unchanged.

#PMS-403

The calendar module booked appointments ad hoc through POST /appointments with no concept of a reusable, named scheduling shape (e.g. "1h new-customer interview" or "2h on-site visit with 30 min travel before and after") that a dispatcher could pick to pre-fill an appointment. This adds the backend model and CRUD for user-customizable scheduling templates of two kinds: dispatch (on-site work, with optional pre/post travel buffers) and calendar (client interactions / status updates). Migration 054_scheduling_templates.sql creates a tenant-scoped scheduling_templates table with kind (dispatch|calendar), appointment_type (mirroring the appointments CHECK), duration_minutes (> 0), travel_before/after_minutes (>= 0), optional default_title/default_location/default_ticket_id/notes, timestamps, a tenant index, an explicit updated_at trigger, and the fail-closed FORCE'd tenant_isolation RLS policy (the 024/038 DO-block loops already ran, so a table created now does not inherit either, matching 051_mileage_entries). DTOs SchedulingTemplateResponse / CreateSchedulingTemplateRequest / UpdateSchedulingTemplateRequest / SchedulingTemplateFilter live in calendar/models.rs with validator constraints (name length 1..=100, positive duration, non-negative buffers, and a valid kind via a new utils::validation::validate_scheduling_template_kind that mirrors the contract-type/status guards so an out-of-set value returns 422 instead of a 500 from the DB CHECK). tenant_id is omitted from the wire shape; the update request is a partial PUT (all-Option, COALESCE) mirroring UpdateAppointmentRequest. CalendarService gains list_templates / get_template / create_template / update_template / delete_template, each tenant-scoped via begin_with_tenant. Create and update validate default_ticket_id against the tenant through the existing validate_fk_opt helper and write AuditAction::Create / ::Update rows (before/after snapshots) in the same transaction. Routes GET|POST /scheduling-templates and GET|PUT|DELETE /scheduling-templates/{id} mount in calendar_routes, gated by RequireCalendar, with the list endpoint supporting a kind filter plus pagination. Postgres-backed tests in tests/scheduling_templates.rs cover the CRUD lifecycle, kind filtering, tenant isolation (a template in tenant A is invisible to tenant B), and rejection of a cross-tenant default_ticket_id (400), a non-positive duration_minutes (422), and an invalid kind (422). The appointments table, CreateAppointmentRequest, and recurrence expansion are unchanged. #PMS-403
vas2000-work force-pushed feat/PMS-403-scheduling-templates from 8a5943fa66
Some checks failed
Integration / integration tests (pull_request) Failing after 46s
E2E / Playwright against staging (pull_request) Successful in 58s
Check / fmt + clippy + compile + unit/doc tests (pull_request) Successful in 1m46s
to 335773eb24
All checks were successful
Check / fmt + clippy + compile + unit/doc tests (pull_request) Successful in 1m16s
E2E / Playwright against staging (pull_request) Successful in 1m28s
Integration / integration tests (pull_request) Successful in 3m34s
Create release / Create release from merged PR (pull_request) Has been skipped
2026-06-18 00:41:17 +02:00
Compare
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/mokosh-server!299
No description provided.