523 lines
16 KiB
Markdown
523 lines
16 KiB
Markdown
|
|
# 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.
|