Redesigns booking and resource architecture

Fundamentally refactors booking system to support:
- Split-resource bookings
- Service-level resource assignment
- Clear separation between booking and calendar events

Introduces new interfaces and type definitions to model complex booking scenarios, enabling more flexible and accurate resource management

Improves type safety by removing ambiguous type annotations
This commit is contained in:
Janus C. H. Knudsen 2025-11-14 23:05:57 +01:00
parent f86ae09ec3
commit a360fad265
7 changed files with 1557 additions and 2 deletions

63
src/types/BookingTypes.ts Normal file
View file

@ -0,0 +1,63 @@
/**
* Booking entity - represents customer service bookings ONLY
*
* IMPORTANT ARCHITECTURE:
* - Booking = Customer + Services (business container)
* - Booking does NOT have start/end - timing is on CalendarEvent
* - Booking does NOT have resourceId - resources assigned per service
* - One booking can have multiple CalendarEvents (split sessions or resources)
* - Booking can exist without events (queued/pending state)
* - Vacation/break/meeting are NOT bookings - only CalendarEvents
*
* Resource Assignment:
* - Resources are assigned at SERVICE level (IBookingService.resourceId)
* - One service can be performed by one resource
* - Multiple services can be performed by different resources
* - Example: Hårvask by Student, Bundfarve by Master (same booking, 2 resources)
*
* Matches backend Booking table structure
*/
export interface IBooking {
id: string;
customerId: string; // REQUIRED - booking is always for a customer
status: BookingStatus;
createdAt: Date;
// Services (REQUIRED - booking always has services)
services: IBookingService[];
// Payment
totalPrice?: number; // Can differ from sum of service prices
// Metadata
tags?: string[];
notes?: string;
}
/**
* CalendarEventType - Used by ICalendarEvent.type
* Note: Only 'customer' events have associated IBooking
* Other types (vacation/break/meeting/blocked) are CalendarEvents without bookings
*/
export type CalendarEventType =
| 'customer' // Customer appointment (HAS booking)
| 'vacation' // Vacation/time off (NO booking)
| 'break' // Lunch/break (NO booking)
| 'meeting' // Meeting (NO booking)
| 'blocked'; // Blocked time (NO booking)
export type BookingStatus =
| 'created' // AftaleOprettet
| 'arrived' // Ankommet
| 'paid' // Betalt
| 'noshow' // Udeblevet
| 'cancelled'; // Aflyst
export interface IBookingService {
serviceId: string;
serviceName: string; // Denormalized for display
baseDuration: number; // Minutes (from Service.duration)
basePrice: number; // From Service.basePrice
customPrice?: number; // Override if different from basePrice
resourceId: string; // Resource who performs THIS service
}

View file

@ -1,4 +1,5 @@
// Calendar type definitions
import { CalendarEventType } from './BookingTypes';
// Time period view types (how much time to display)
export type ViewPeriod = 'day' | 'week' | 'month';
@ -20,10 +21,15 @@ export interface ICalendarEvent {
description?: string;
start: Date;
end: Date;
type: string; // Flexible event type - can be any string value
type: CalendarEventType; // Event type - only 'customer' has associated booking
allDay: boolean;
syncStatus: SyncStatus;
// References (denormalized for IndexedDB performance)
bookingId?: string; // Reference to booking (only if type = 'customer')
resourceId?: string; // Resource who owns this time slot
customerId?: string; // Denormalized from Booking.customerId
recurringId?: string;
metadata?: Record<string, any>;
}

View file

@ -1,10 +1,12 @@
import { IResource } from './ResourceTypes';
/**
* Column information container
* Contains both identifier and actual data for a column
*/
export interface IColumnInfo {
identifier: string; // "2024-11-13" (date mode) or "person-1" (resource mode)
data: Date | any; // Date for date-mode, IResource for resource-mode
data: Date | IResource; // Date for date-mode, IResource for resource-mode
}
/**

View file

@ -0,0 +1,11 @@
/**
* Customer entity
* Matches backend Customer table structure
*/
export interface ICustomer {
id: string;
name: string;
phone: string;
email?: string;
metadata?: Record<string, any>;
}

View file

@ -0,0 +1,21 @@
/**
* Resource entity - represents people, rooms, equipment, etc.
* Matches backend Resource table structure
*/
export interface IResource {
id: string; // Primary key (e.g., "EMP001", "ROOM-A")
name: string; // Machine name (e.g., "karina.knudsen")
displayName: string; // Human-readable name (e.g., "Karina Knudsen")
type: ResourceType; // Resource category
avatarUrl?: string; // Avatar/icon URL (e.g., "/avatars/karina.jpg")
color?: string; // Color for visual distinction in calendar
isActive?: boolean; // Whether resource is currently active
metadata?: Record<string, any>; // Flexible extension point
}
export type ResourceType =
| 'person' // Employees, team members
| 'room' // Meeting rooms, offices
| 'equipment' // Shared equipment, tools
| 'vehicle' // Company vehicles
| 'custom'; // User-defined types