# Booking & Resource Architecture Redesign **Date:** 2025-11-14 **Duration:** ~2+ hours **Initial Scope:** Create IResource interface and fix `Date | any` type **Actual Scope:** Fundamental redesign of booking architecture with service-level resource assignment --- ## Executive Summary What started as a simple task to create an `IResource` interface evolved into a comprehensive architectural redesign after critical developer questioning revealed fundamental flaws in the initial approach. Through 4 major iterations and multiple course corrections, we arrived at a clean architecture that properly models split-resource bookings (e.g., student + master stylist working on the same booking). **Key Achievement:** Avoided building 3 different incorrect architectures by thinking through real business scenarios before committing to code. --- ## Original Request > "Can we create an IResource now? Then you can also fix `data: Date | any`" **Expected:** 10 minutes - create interface, update type, done. **Reality:** 2+ hours of architectural discussion, 4 major iterations, 930 lines of documentation. --- ## Iteration History ### Iteration 1: "Booking HAS resourceId (Primary Resource)" ❌ **My Initial Assumption:** ```typescript export interface IBooking { id: string; resourceId: string; // Primary resource customerId?: string; type: BookingType; services?: IBookingService[]; } ``` **Rationale:** Bookings are always resource-bound (someone performs the service), so booking should have a resourceId. **Developer Challenge:** > "A booking with 2 services can be split between multiple resources. For example, a large hair coloring - the student handles hair wash and eyebrows, the master stylist does the actual coloring." **Problem Identified:** - Booking.resourceId assumes ONE primary resource - But in reality: hårvask (student) + bundfarve (master) = TWO resources, neither "primary" - Model doesn't support split-resource bookings **Result:** ❌ Discarded after 15 minutes of discussion --- ### Iteration 2: "Booking HAS Primary + Services Have Resources" ❌ **My Second Attempt:** ```typescript export interface IBooking { id: string; resourceId: string; // "Primary" responsible person customerId?: string; services?: IBookingService[]; } export interface IBookingService { serviceId: string; resourceId: string; // Who performs THIS service } ``` **Rationale:** Keep primary resource for "who is responsible" while allowing service-level assignment. **Developer Challenge:** > "Why should there be a primary? Use case: Customer chooses service, UI shows it can be split between Karina and Nanna, customer says yes. Here there's no primary, both are master stylists." **Problem Identified:** - "Primary resource" is an artificial concept - Equal splits (Karina + Nanna) don't have a "responsible person" - Creates confusion: "Who is primary if they're equal?" **Result:** ❌ Discarded after thorough use case review --- ### Iteration 3: "Booking WITHOUT resourceId, Only Services Have Resources" ✅ **My Third Attempt:** ```typescript export interface IBooking { id: string; customerId: string; // REQUIRED services: IBookingService[]; // REQUIRED // NO resourceId at all! } export interface IBookingService { serviceId: string; resourceId: string; // Resource performs THIS service } ``` **My Concern:** > "But what about vacation/break? Those are resource-bound but have no services!" **Developer Response:** > "Vacation is not a booking, it's just a CalendarEvent with resourceId. Bookings are only for customers. We always have a booking for customer services." **Breakthrough Realization:** - **Booking = Customer + Services** (business domain) - **CalendarEvent = Scheduling + Resource** (time domain) - Vacation/break/meeting are CalendarEvents WITHOUT bookings **Architecture Clarified:** ```typescript // Booking = ONLY customer services export interface IBooking { customerId: string; // REQUIRED - always customer services: IBookingService[]; // REQUIRED - always has services // NO resourceId // NO type (always customer) } // CalendarEvent = ALL scheduling export interface ICalendarEvent { type: CalendarEventType; // 'customer' | 'vacation' | 'break' | ... bookingId?: string; // Only if type = 'customer' resourceId?: string; // Which resource owns this slot } ``` **Result:** ✅ **ACCEPTED** - Clean separation of concerns --- ### Iteration 4: "BookingType Should Be Removed" ✅ **My Miss:** I created `BookingType` union even though `IBooking` no longer had a `type` field. **Developer Catch:** > "But wait... booking type is gone, right? It's always customer-related." **Fix Applied:** - Renamed `BookingType` → `CalendarEventType` - Clarified: Only `ICalendarEvent` uses this type, not `IBooking` - Updated all references **Result:** ✅ Type system now consistent --- ## Key Architectural Decisions ### Decision 1: Booking = Customer ONLY **Before:** Booking could be customer, vacation, break, meeting **After:** Booking is ONLY for customer services ```typescript // ✅ Valid { "customerId": "CUST456", "services": [{ "resourceId": "EMP001" }] } // ❌ Invalid - vacation is NOT a booking { "type": "vacation", "resourceId": "EMP001" } ``` **Vacation is now:** ```typescript { "type": "vacation", "bookingId": null, // NO booking "resourceId": "EMP001" // Just a time slot } ``` --- ### Decision 2: Resources Assigned Per Service **Before:** Booking.resourceId (primary) **After:** IBookingService.resourceId (per service) **Enables:** - Split resources: student + master - Equal splits: Karina + Nanna - Resource reassignment: "student is sick, assign to another student" **Example - Split Resource Booking:** ```json { "customerId": "CUST456", "services": [ { "serviceName": "Hårvask", "resourceId": "STUDENT001", "baseDuration": 30 }, { "serviceName": "Bundfarve", "resourceId": "EMP001", "baseDuration": 90 } ] } ``` Two CalendarEvents created: - Event 1: Student's calendar, 13:00-13:30 - Event 2: Master's calendar, 13:30-15:00 - Both reference same booking --- ### Decision 3: 1 Booking → N Events **Relationship:** One booking can span multiple events **Scenarios:** 1. **Split Session:** 180min service split across 2 days (same resource) 2. **Split Resources:** Student + Master (different resources) 3. **Equal Split:** Karina + Nanna (two masters, equal) **Key Insight:** Events are scheduled per resource, not per booking. --- ### Decision 4: CalendarEvent.type Determines Booking Existence ```typescript type CalendarEventType = | 'customer' // HAS booking | 'vacation' // NO booking | 'break' // NO booking | 'meeting' // NO booking | 'blocked'; // NO booking ``` **Business Rule:** If `event.type === 'customer'`, then `event.bookingId` must be set. --- ## Database Schema Changes ### Before (Iteration 1 - Wrong) ```sql CREATE TABLE Booking ( ResourceId VARCHAR(50) NOT NULL, -- ❌ Wrong: assumes one resource CustomerId VARCHAR(50) NULL, -- ❌ Wrong: should be required Type VARCHAR(20) NOT NULL -- ❌ Wrong: always customer ); CREATE TABLE BookingService ( BookingId VARCHAR(50) NOT NULL, ServiceId VARCHAR(50) NOT NULL -- ❌ Missing: ResourceId ); ``` ### After (Final Design - Correct) ```sql CREATE TABLE Booking ( Id VARCHAR(50) PRIMARY KEY, CustomerId VARCHAR(50) NOT NULL, -- ✅ REQUIRED Status VARCHAR(20) NOT NULL, TotalPrice DECIMAL(10,2) NULL, -- ✅ NO ResourceId (moved to service level) -- ✅ NO Type (always customer) FOREIGN KEY (CustomerId) REFERENCES Customer(Id) ); CREATE TABLE BookingService ( BookingId VARCHAR(50) NOT NULL, ServiceId VARCHAR(50) NOT NULL, ResourceId VARCHAR(50) NOT NULL, -- ✅ NEW: Resource per service CustomPrice DECIMAL(10,2) NULL, SortOrder INT NULL, FOREIGN KEY (BookingId) REFERENCES Booking(Id), FOREIGN KEY (ServiceId) REFERENCES Service(Id), FOREIGN KEY (ResourceId) REFERENCES Resource(Id) -- ✅ NEW FK ); CREATE TABLE CalendarEvent ( Id VARCHAR(50) PRIMARY KEY, Start DATETIME NOT NULL, End DATETIME NOT NULL, Type VARCHAR(50) NOT NULL, -- ✅ Distinguishes customer vs vacation BookingId VARCHAR(50) NULL, -- ✅ Nullable (only customer has booking) ResourceId VARCHAR(50) NULL, -- ✅ Denormalized for performance CustomerId VARCHAR(50) NULL, -- ✅ Denormalized for performance FOREIGN KEY (BookingId) REFERENCES Booking(Id) ); ``` --- ## Code Changes Summary ### Files Created (3) 1. **src/types/ResourceTypes.ts** - IResource interface 2. **src/types/BookingTypes.ts** - IBooking + IBookingService + CalendarEventType 3. **src/types/CustomerTypes.ts** - ICustomer interface ### Files Modified (2) 4. **src/types/ColumnDataSource.ts** - Changed `Date | any` to `Date | IResource` 5. **src/types/CalendarTypes.ts** - Updated ICalendarEvent to use CalendarEventType ### Documentation Created (1) 6. **docs/booking-event-architecture.md** - 930 lines comprehensive guide - 6 detailed examples - Database schemas (SQL + IndexedDB) - 5 business rules - 4 common pitfalls - Analytics patterns - Query examples --- ## Statistics | Metric | Count | |--------|-------| | **Time Spent** | 2+ hours | | **Initial Estimate** | 10 minutes | | **Major Iterations** | 4 | | **Architectural Pivots** | 3 | | **Developer Catches** | 5+ critical issues | | **Interfaces Created** | 5 (IResource, IBooking, IBookingService, ICustomer, CalendarEventType) | | **Interfaces Modified** | 2 (IColumnInfo, ICalendarEvent) | | **Documentation Written** | 930 lines | | **Examples Documented** | 6 scenarios | | **Business Rules Defined** | 5 | | **Common Pitfalls Identified** | 4 | | **Database Tables Changed** | 3 (Booking, BookingService, CalendarEvent) | --- ## Developer Interventions (Critical Thinking Points) ### 1. "What if multiple resources work on same booking?" **Prevented:** Single-resource assumption that would block split bookings ### 2. "Why should there be a primary resource?" **Prevented:** Artificial hierarchy that doesn't match business reality ### 3. "Is vacation a booking?" **Resulted in:** Clean separation - Booking = customers only, Events = everything ### 4. "If the student is sick?" **Resulted in:** Service-level resource assignment enabling reassignment ### 5. "BookingType is gone now, right?" **Resulted in:** Consistent naming - CalendarEventType for events, not bookings --- ## What We Almost Built (And Avoided) ### ❌ Wrong Architecture 1: Primary Resource Model Would have required: - Complex logic to determine "who is primary" - Arbitrary decision making (is master or student primary?) - Doesn't support equal splits (Karina + Nanna) ### ❌ Wrong Architecture 2: Booking.resourceId + Service.resourceId Would have resulted in: - Redundant data - Confusion about which resourceId to use - "Primary" concept that doesn't exist in business logic ### ❌ Wrong Architecture 3: Vacation as Booking Would have created: - Optional fields everywhere (services? customer? for vacation?) - Complex validation logic - Confusing domain model --- ## Final Architecture Benefits ### ✅ Clean Separation of Concerns - **Booking** = Customer + Services (WHAT + HOW MUCH) - **CalendarEvent** = Time + Resource (WHEN + WHO) ### ✅ Supports All Business Scenarios 1. Simple booking (one customer, one service, one resource) 2. Split session (one booking, multiple days) 3. Split resources (student + master) 4. Equal splits (two masters, no primary) 5. Queued bookings (no time slot yet) 6. Vacation/break (no booking, just time blocking) ### ✅ Type-Safe - Required fields are enforced: `customerId: string`, `services: IBookingService[]` - No optional fields that "sometimes" matter - CalendarEventType clearly distinguishes booking vs non-booking events ### ✅ Flexible Resource Assignment - Assign different resources per service - Reassign if resource unavailable (student sick) - No "primary" to maintain ### ✅ Query-Optimized - CalendarEvent denormalizes resourceId for fast queries - No JOIN needed to find "Karina's events" - IndexedDB-friendly (no JOIN support) --- ## Lessons Learned ### 1. Question Assumptions Early **My assumption:** "Bookings are resource-bound" **Reality:** Bookings are customer-bound, resources are service-bound ### 2. Use Real Business Scenarios Abstract discussions led to wrong models. Concrete examples ("student + master split") revealed the truth. ### 3. Don't Force Hierarchies "Primary resource" seemed logical but didn't match business reality. Equal splits exist. ### 4. Separation of Concerns Matters Mixing booking (business) with scheduling (time) created optional fields everywhere. ### 5. Developer Domain Knowledge is Critical I had TypeScript expertise, but developer had business domain knowledge. The combination caught 5+ critical flaws before they became code. --- ## Documentation Deliverables ### booking-event-architecture.md (930 lines) **Sections:** 1. Core Principles - Separation of concerns 2. Entity Definitions - Booking vs CalendarEvent 3. Relationship Model - 1:N (one booking → many events) 4. 6 Detailed Examples: - Split session (same resource, multiple days) - Split resources (master + student) - Equal split (two masters) - Queued booking - Vacation (no booking) - Simple reminder 5. Database Schema - SQL (normalized) + IndexedDB (denormalized) 6. API Data Flow - Backend JOIN → Frontend denormalization 7. Business Rules - 5 rules with code examples 8. Analytics & Reporting - Service duration vs actual time 9. TypeScript Interfaces - Complete reference 10. Common Pitfalls - 4 mistakes to avoid 11. Summary Table - Quick reference 12. Decision Guide - "Where does this belong?" --- ## Build Results ✅ **All TypeScript compilation successful** - 57 files processed - 0 errors - 0 warnings - Build time: ~1.2s ✅ **Type safety improved** - Removed `Date | any` (was unsafe) - Added `Date | IResource` (type-safe union) - Added CalendarEventType (was `string`) --- ## Conclusion **Initial request:** "Create IResource interface" **Time estimated:** 10 minutes **Time actual:** 2+ hours **Why the difference?** Because the developer asked the right questions at the right time: 1. "What if multiple resources work together?" 2. "Why assume a primary?" 3. "Is vacation really a booking?" 4. "What if we need to reassign?" Each question revealed a flaw in my assumptions. We iterated 4 times, discarded 3 approaches, and arrived at an architecture that actually matches the business model. **This is exactly how good software development should work.** The 2 hours spent thinking prevented weeks of refactoring later. Critical thinking and domain knowledge prevented premature optimization and over-engineering. --- ## Next Steps (Not Part of This Session) **Phase 2: Resource Calendar Implementation** - Create ResourceColumnDataSource (returns IColumnInfo with IResource data) - Create ResourceHeaderRenderer (renders resource headers instead of dates) - Implement resource-mode switching (date-mode ↔ resource-mode) - Add repository methods for resource queries **Phase 3: Booking Management** - Implement booking creation flow - Service-to-event mapping logic - Split-resource UI (assign services to different resources) - Resource reassignment (when student is sick) --- **Session End** **Key Takeaway:** Slow down to speed up. 2 hours of architecture discussion saved weeks of wrong implementation.