# Mock Data Migration Guide **Purpose:** This document explains the changes required to existing mock data files to support the new booking architecture with service-level resource assignment. **Date:** 2025-11-14 **Related:** [Booking & Resource Architecture Redesign](../coding-sessions/2025-11-14-booking-resource-architecture-redesign.md) --- ## Overview of Changes ### What Changed? The booking architecture has been fundamentally redesigned to support: - **Split-resource bookings** (student + master working on same booking) - **Equal-split bookings** (two masters, no primary resource) - **Service-level resource assignment** (resources assigned per service, not per booking) - **Clear separation** between Booking (business) and CalendarEvent (scheduling) ### Key Architectural Principles 1. **Booking = Customer Services ONLY** - Bookings are ALWAYS for customers - Bookings ALWAYS have services - Vacation/break/meeting are NOT bookings 2. **Resources Assigned Per Service** - No `Booking.resourceId` (removed) - Each `IBookingService` has `resourceId` - Enables split-resource and equal-split scenarios 3. **CalendarEvent.type Determines Booking Existence** - `type: 'customer'` → has `bookingId` - `type: 'vacation' | 'break' | 'meeting' | 'blocked'` → no `bookingId` 4. **Denormalization for Performance** - `CalendarEvent` denormalizes `resourceId`, `customerId` for fast IndexedDB queries - Backend performs JOIN, frontend receives denormalized data --- ## Data Structure Changes ### 1. CalendarEvent Changes **Before:** ```json { "id": "1", "title": "Balayage langt hår", "start": "2025-08-05T10:00:00", "end": "2025-08-05T11:00:00", "type": "work", "allDay": false, "syncStatus": "synced", "metadata": { "duration": 60 } } ``` **After:** ```json { "id": "EVT001", "title": "Balayage langt hår", "start": "2025-08-05T10:00:00", "end": "2025-08-05T11:00:00", "type": "customer", "allDay": false, "syncStatus": "synced", "bookingId": "BOOK001", "resourceId": "EMP001", "customerId": "CUST001", "metadata": { "duration": 60 } } ``` **Changes:** - `type`: Changed from `"work"` to `"customer"` (using CalendarEventType) - **NEW:** `bookingId` - Reference to booking (required if type = 'customer') - **NEW:** `resourceId` - Resource who owns this time slot (denormalized) - **NEW:** `customerId` - Customer for this event (denormalized from Booking) ### 2. New Entity: Booking **Structure:** ```json { "id": "BOOK001", "customerId": "CUST001", "status": "created", "createdAt": "2025-08-05T09:00:00", "services": [ { "serviceId": "SRV001", "serviceName": "Balayage langt hår", "baseDuration": 60, "basePrice": 800, "customPrice": 800, "resourceId": "EMP001" } ], "totalPrice": 800, "notes": "Kunde ønsker lys blond" } ``` **Key Points:** - NO `resourceId` at booking level (moved to service level) - NO `type` field (always customer-related) - `customerId` is REQUIRED - `services` is REQUIRED (array with at least one service) ### 3. New Entity: Customer **Structure:** ```json { "id": "CUST001", "name": "Maria Jensen", "phone": "+45 12 34 56 78", "email": "maria.jensen@example.com", "metadata": { "preferredStylist": "EMP001", "allergies": ["ammonia"] } } ``` ### 4. New Entity: Resource **Structure:** ```json { "id": "EMP001", "name": "karina.knudsen", "displayName": "Karina Knudsen", "type": "person", "avatarUrl": "/avatars/karina.jpg", "color": "#9c27b0", "isActive": true, "metadata": { "role": "master stylist", "specialties": ["balayage", "color", "bridal"] } } ``` --- ## Complete JSON Examples ### Example 1: Simple Customer Booking (Single Resource) **Scenario:** One customer, one service, one resource, one time slot. **Customer:** ```json { "id": "CUST001", "name": "Maria Jensen", "phone": "+45 12 34 56 78", "email": "maria.jensen@example.com" } ``` **Resource:** ```json { "id": "EMP001", "name": "karina.knudsen", "displayName": "Karina Knudsen", "type": "person", "avatarUrl": "/avatars/karina.jpg", "isActive": true } ``` **Booking:** ```json { "id": "BOOK001", "customerId": "CUST001", "status": "created", "createdAt": "2025-08-05T09:00:00", "services": [ { "serviceId": "SRV001", "serviceName": "Klipning og styling", "baseDuration": 60, "basePrice": 500, "customPrice": 500, "resourceId": "EMP001" } ], "totalPrice": 500 } ``` **CalendarEvent:** ```json { "id": "EVT001", "title": "Maria Jensen - Klipning og styling", "start": "2025-08-05T10:00:00", "end": "2025-08-05T11:00:00", "type": "customer", "allDay": false, "syncStatus": "synced", "bookingId": "BOOK001", "resourceId": "EMP001", "customerId": "CUST001" } ``` **Relationship:** - 1 Customer → 1 Booking → 1 Event → 1 Resource --- ### Example 2: Split-Resource Booking (Student + Master) **Scenario:** One customer, two services split between student (hair wash) and master (color treatment). **Resources:** ```json [ { "id": "STUDENT001", "name": "anne.elev", "displayName": "Anne (Elev)", "type": "person", "isActive": true, "metadata": { "role": "student" } }, { "id": "EMP001", "name": "karina.knudsen", "displayName": "Karina Knudsen", "type": "person", "isActive": true, "metadata": { "role": "master stylist" } } ] ``` **Customer:** ```json { "id": "CUST002", "name": "Louise Andersen", "phone": "+45 23 45 67 89", "email": "louise@example.com" } ``` **Booking (Single booking with 2 services, 2 different resources):** ```json { "id": "BOOK002", "customerId": "CUST002", "status": "created", "createdAt": "2025-08-05T12:00:00", "services": [ { "serviceId": "SRV002", "serviceName": "Hårvask", "baseDuration": 30, "basePrice": 100, "customPrice": 100, "resourceId": "STUDENT001" }, { "serviceId": "SRV003", "serviceName": "Bundfarve", "baseDuration": 90, "basePrice": 800, "customPrice": 800, "resourceId": "EMP001" } ], "totalPrice": 900, "notes": "Hårvask udføres af elev, farve af master stylist" } ``` **CalendarEvents (2 events, same booking, different resources):** ```json [ { "id": "EVT002", "title": "Louise Andersen - Hårvask", "start": "2025-08-05T13:00:00", "end": "2025-08-05T13:30:00", "type": "customer", "allDay": false, "syncStatus": "synced", "bookingId": "BOOK002", "resourceId": "STUDENT001", "customerId": "CUST002" }, { "id": "EVT003", "title": "Louise Andersen - Bundfarve", "start": "2025-08-05T13:30:00", "end": "2025-08-05T15:00:00", "type": "customer", "allDay": false, "syncStatus": "synced", "bookingId": "BOOK002", "resourceId": "EMP001", "customerId": "CUST002" } ] ``` **Relationship:** - 1 Customer → 1 Booking → 2 Events → 2 Resources (student + master) **Key Points:** - Both events reference the SAME `bookingId` - Each event has a DIFFERENT `resourceId` - No "primary" resource concept - both are equal in the booking - Student's calendar shows EVT002, Master's calendar shows EVT003 --- ### Example 3: Equal Split Booking (Two Masters) **Scenario:** One customer, one large service split equally between two master stylists. **Resources:** ```json [ { "id": "EMP001", "name": "karina.knudsen", "displayName": "Karina Knudsen", "type": "person", "isActive": true }, { "id": "EMP002", "name": "nanna.nielsen", "displayName": "Nanna Nielsen", "type": "person", "isActive": true } ] ``` **Customer:** ```json { "id": "CUST003", "name": "Sofie Hansen", "phone": "+45 34 56 78 90" } ``` **Booking:** ```json { "id": "BOOK003", "customerId": "CUST003", "status": "created", "createdAt": "2025-08-05T08:00:00", "services": [ { "serviceId": "SRV004", "serviceName": "Bryllupsfrisure - Del 1", "baseDuration": 60, "basePrice": 750, "customPrice": 750, "resourceId": "EMP001" }, { "serviceId": "SRV005", "serviceName": "Bryllupsfrisure - Del 2", "baseDuration": 60, "basePrice": 750, "customPrice": 750, "resourceId": "EMP002" } ], "totalPrice": 1500, "notes": "To stylister arbejder sammen om bryllupsfrisure" } ``` **CalendarEvents:** ```json [ { "id": "EVT004", "title": "Sofie Hansen - Bryllupsfrisure (Karina)", "start": "2025-08-05T10:00:00", "end": "2025-08-05T11:00:00", "type": "customer", "allDay": false, "syncStatus": "synced", "bookingId": "BOOK003", "resourceId": "EMP001", "customerId": "CUST003" }, { "id": "EVT005", "title": "Sofie Hansen - Bryllupsfrisure (Nanna)", "start": "2025-08-05T10:00:00", "end": "2025-08-05T11:00:00", "type": "customer", "allDay": false, "syncStatus": "synced", "bookingId": "BOOK003", "resourceId": "EMP002", "customerId": "CUST003" } ] ``` **Relationship:** - 1 Customer → 1 Booking → 2 Events (SAME time, different resources) - Both masters work simultaneously - no "primary" resource --- ### Example 4: Vacation Event (No Booking) **Scenario:** Resource on vacation - CalendarEvent only, NO booking. **Resource:** ```json { "id": "EMP001", "name": "karina.knudsen", "displayName": "Karina Knudsen", "type": "person", "isActive": true } ``` **CalendarEvent:** ```json { "id": "EVT006", "title": "Ferie - Spanien", "start": "2025-08-10T00:00:00", "end": "2025-08-17T23:59:59", "type": "vacation", "allDay": true, "syncStatus": "synced", "resourceId": "EMP001", "metadata": { "destination": "Mallorca", "emergency_contact": "+45 12 34 56 78" } } ``` **Key Points:** - `type: "vacation"` → NO `bookingId` - `resourceId` present (who is on vacation) - NO `customerId` (not customer-related) - NO Booking object exists for this event --- ### Example 5: Break Event (No Booking) **Scenario:** Lunch break - CalendarEvent only, NO booking. **CalendarEvent:** ```json { "id": "EVT007", "title": "Frokostpause", "start": "2025-08-05T12:00:00", "end": "2025-08-05T12:30:00", "type": "break", "allDay": false, "syncStatus": "synced", "resourceId": "EMP001" } ``` **Key Points:** - `type: "break"` → NO `bookingId` - NO `customerId` - NO Booking object --- ### Example 6: Meeting Event (No Booking) **Scenario:** Team meeting - CalendarEvent only, NO booking. **CalendarEvent:** ```json { "id": "EVT008", "title": "Team møde - Salgsmål Q3", "start": "2025-08-05T16:00:00", "end": "2025-08-05T17:00:00", "type": "meeting", "allDay": false, "syncStatus": "synced", "resourceId": "EMP001", "metadata": { "attendees": ["EMP001", "EMP002", "EMP003"], "location": "Mødelokale 1" } } ``` **Key Points:** - `type: "meeting"` → NO `bookingId` - NO `customerId` - Can have multiple attendees in metadata, but `resourceId` is the "owner" --- ### Example 7: Queued Booking (No Event Yet) **Scenario:** Customer booked services but no time slot assigned yet. **Booking:** ```json { "id": "BOOK004", "customerId": "CUST004", "status": "created", "createdAt": "2025-08-05T14:00:00", "services": [ { "serviceId": "SRV001", "serviceName": "Klipning", "baseDuration": 45, "basePrice": 450, "resourceId": "EMP001" } ], "totalPrice": 450, "notes": "Kunde venter på ledig tid" } ``` **CalendarEvents:** ```json [] ``` **Key Points:** - Booking EXISTS without CalendarEvent - No time slot assigned yet (queued state) - When time slot is assigned, CalendarEvent will be created with `bookingId: "BOOK004"` --- ## Migration Checklist ### For Existing `mock-events.json` - [ ] Update all `type` values to use CalendarEventType: - `"work"` → `"customer"` - `"meeting"` → `"meeting"` (unchanged) - Add `"vacation"`, `"break"`, `"blocked"` as needed - [ ] Add denormalized fields to customer events: - [ ] Add `bookingId` (if type = 'customer') - [ ] Add `resourceId` (which resource owns this slot) - [ ] Add `customerId` (if type = 'customer') - [ ] Create corresponding Booking objects for all customer events - [ ] Create Customer objects for all unique customers - [ ] Create Resource objects for all unique resources ### For Existing `mock-resource-events.json` - [ ] Extract resource data to separate `resources.json` file using IResource interface - [ ] Update event structure (same changes as above) - [ ] Link events to bookings and customers ### New Files to Create 1. **`customers.json`** - Array of ICustomer objects 2. **`resources.json`** - Array of IResource objects 3. **`bookings.json`** - Array of IBooking objects 4. **`services.json`** (optional) - Array of service definitions --- ## Type Reference ### CalendarEventType ```typescript 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) ``` ### BookingStatus ```typescript type BookingStatus = | 'created' // AftaleOprettet | 'arrived' // Ankommet | 'paid' // Betalt | 'noshow' // Udeblevet | 'cancelled'; // Aflyst ``` ### ResourceType ```typescript type ResourceType = | 'person' // Employee, stylist, etc. | 'room' // Meeting room, treatment room | 'equipment' // Chair, equipment | 'vehicle' // Company car | 'custom'; // Custom resource type ``` --- ## Quick Decision Guide **Question:** Where does this data belong? | Data | Entity | Example | |------|--------|---------| | Customer name, phone, email | Customer | `{ "id": "CUST001", "name": "Maria" }` | | Service name, price, duration | Booking.services | `{ "serviceId": "SRV001", "basePrice": 500 }` | | Which resource performs service | BookingService.resourceId | `{ "resourceId": "EMP001" }` | | When service happens | CalendarEvent | `{ "start": "...", "end": "..." }` | | Resource on vacation | CalendarEvent (type: vacation) | `{ "type": "vacation", "resourceId": "EMP001" }` | | Break/lunch | CalendarEvent (type: break) | `{ "type": "break", "resourceId": "EMP001" }` | | Team meeting | CalendarEvent (type: meeting) | `{ "type": "meeting" }` | --- ## Common Mistakes to Avoid ### ❌ Wrong: Adding resourceId to Booking ```json { "id": "BOOK001", "resourceId": "EMP001", // ❌ WRONG - removed from booking level "customerId": "CUST001", "services": [...] } ``` **Why wrong:** Resources are assigned per service, not per booking. ### ❌ Wrong: Adding type to Booking ```json { "id": "BOOK001", "type": "customer", // ❌ WRONG - Booking has no type field "customerId": "CUST001" } ``` **Why wrong:** Bookings are ALWAYS customer-related. Type belongs on CalendarEvent. ### ❌ Wrong: Creating Booking for Vacation ```json { "id": "BOOK999", "type": "vacation", // ❌ WRONG - Vacation is NOT a booking "resourceId": "EMP001" } ``` **Why wrong:** Only customer services are bookings. Vacation is a CalendarEvent only. ### ❌ Wrong: Customer Event Without bookingId ```json { "id": "EVT001", "type": "customer", "resourceId": "EMP001", // ❌ MISSING: bookingId } ``` **Why wrong:** If `type: 'customer'`, there MUST be a `bookingId`. --- ## Summary **Before:** - Events had basic structure with `type: "work" | "meeting"` - No separation between business (booking) and scheduling (event) - Resources implicitly tied to events **After:** - Clean separation: **Booking** (business) ↔ **CalendarEvent** (scheduling) - Resources assigned at **service level** (enables splits) - Denormalized data in events for performance (resourceId, customerId) - Type-safe with CalendarEventType, BookingStatus, ResourceType - Supports all business scenarios: split resources, equal splits, queued bookings **Next Steps:** 1. Create new entity files (customers.json, resources.json, bookings.json) 2. Update existing event files with new structure 3. Test data loading with new interfaces 4. Verify UI displays correctly with denormalized data --- **Related Documentation:** - [Booking & Resource Architecture Redesign](../coding-sessions/2025-11-14-booking-resource-architecture-redesign.md) - [Booking Event Architecture](./booking-event-architecture.md)