# 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.
