Implements polymorphic data seeding mechanism for initial application setup - Adds Mock repositories for Event, Booking, Customer, and Resource entities - Creates DataSeeder to automatically populate IndexedDB from JSON sources - Enhances index.ts initialization process with data seeding step - Adds mock JSON data files for comprehensive test data Improves offline-first and development testing capabilities
23 KiB
Mock Data Repository Implementation - Status Documentation
Document Generated: 2025-11-19 Analysis Scope: Mock Repository Implementation vs Target Architecture Files Analyzed: 4 repositories, 4 type files, 2 architecture docs
Executive Summary
This document compares the current Mock Repository implementation against the documented target architecture. The analysis covers 4 entity types: Event, Booking, Customer, and Resource.
Overall Status: Implementation is structurally correct but Event entity is missing critical fields required for the booking architecture.
Compliance Score: 84%
1. Event Entity Comparison
Current RawEventData Interface
Location: src/repositories/MockEventRepository.ts
interface RawEventData {
id: string;
title: string;
start: string | Date;
end: string | Date;
type: string;
color?: string;
allDay?: boolean;
[key: string]: unknown;
}
Target ICalendarEvent Interface
Location: src/types/CalendarTypes.ts
export interface ICalendarEvent extends ISync {
id: string;
title: string;
description?: string;
start: Date;
end: Date;
type: CalendarEventType;
allDay: boolean;
bookingId?: string;
resourceId?: string;
customerId?: string;
recurringId?: string;
metadata?: Record<string, any>;
}
Documented JSON Format
Source: docs/mock-data-migration-guide.md, docs/booking-event-architecture.md
{
"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 }
}
Field-by-Field Comparison - Event Entity
| Field | Current RawData | Target Interface | Documented JSON | Status |
|---|---|---|---|---|
id |
✅ string |
✅ string |
✅ Present | MATCH |
title |
✅ string |
✅ string |
✅ Present | MATCH |
description |
❌ Missing | ✅ string? |
❌ Missing | MISSING |
start |
✅ string | Date |
✅ Date |
✅ Present | MATCH |
end |
✅ string | Date |
✅ Date |
✅ Present | MATCH |
type |
✅ string |
✅ CalendarEventType |
✅ "customer" |
MATCH (needs cast) |
allDay |
✅ boolean? |
✅ boolean |
✅ false |
MATCH |
bookingId |
❌ Missing | ✅ string? |
✅ Present | CRITICAL MISSING |
resourceId |
❌ Missing | ✅ string? |
✅ Present | CRITICAL MISSING |
customerId |
❌ Missing | ✅ string? |
✅ Present | CRITICAL MISSING |
recurringId |
❌ Missing | ✅ string? |
❌ Not in example | MISSING |
metadata |
✅ Via [key: string] |
✅ Record<string, any>? |
✅ Present | MATCH |
syncStatus |
❌ Missing | ✅ SyncStatus (via ISync) |
✅ "synced" |
MISSING (added in processing) |
color |
✅ string? |
❌ Not in interface | ❌ Not documented | EXTRA (legacy) |
Critical Missing Fields - Event Entity
1. bookingId (CRITICAL)
Impact: Cannot link customer events to booking data Required For:
- Type
'customer'events MUST havebookingId - Loading booking details when event is clicked
- Cascading deletes (cancel booking → delete events)
- Backend JOIN queries between CalendarEvent and Booking tables
Example:
{
"id": "EVT001",
"type": "customer",
"bookingId": "BOOK001", // ← CRITICAL - Links to booking
...
}
2. resourceId (CRITICAL)
Impact: Cannot filter events by resource (calendar columns) Required For:
- Denormalized query performance (no JOIN needed)
- Resource calendar views (week view with resource columns)
- Resource utilization analytics
- Quick filtering: "Show all events for EMP001"
Example:
{
"id": "EVT001",
"resourceId": "EMP001", // ← CRITICAL - Which stylist
...
}
3. customerId (CRITICAL)
Impact: Cannot query customer events without loading booking Required For:
- Denormalized query performance
- Customer history views
- Quick customer lookup: "Show all events for CUST001"
- Analytics and reporting
Example:
{
"id": "EVT001",
"type": "customer",
"customerId": "CUST001", // ← CRITICAL - Which customer
...
}
4. description (OPTIONAL)
Impact: Cannot add detailed event notes Required For:
- Event details panel
- Additional context beyond title
- Notes and instructions
Action Items for Events
-
Add to RawEventData:
description?: stringbookingId?: string(CRITICAL)resourceId?: string(CRITICAL)customerId?: string(CRITICAL)recurringId?: stringmetadata?: Record<string, any>(make explicit)
-
Update Processing:
- Explicitly map all new fields in
processCalendarData() - Remove or document legacy
colorfield - Ensure
allDaydefaults tofalseif missing - Validate that
type: 'customer'events havebookingId
- Explicitly map all new fields in
-
JSON File Requirements:
- Customer events MUST include
bookingId,resourceId,customerId - Vacation/break/meeting events MUST NOT have
bookingIdorcustomerId - Vacation/break events SHOULD have
resourceId
- Customer events MUST include
2. Booking Entity Comparison
Current RawBookingData Interface
Location: src/repositories/MockBookingRepository.ts
interface RawBookingData {
id: string;
customerId: string;
status: string;
createdAt: string | Date;
services: RawBookingService[];
totalPrice?: number;
tags?: string[];
notes?: string;
[key: string]: unknown;
}
interface RawBookingService {
serviceId: string;
serviceName: string;
baseDuration: number;
basePrice: number;
customPrice?: number;
resourceId: string;
}
Target IBooking Interface
Location: src/types/BookingTypes.ts
export interface IBooking extends ISync {
id: string;
customerId: string;
status: BookingStatus;
createdAt: Date;
services: IBookingService[];
totalPrice?: number;
tags?: string[];
notes?: string;
}
export interface IBookingService {
serviceId: string;
serviceName: string;
baseDuration: number;
basePrice: number;
customPrice?: number;
resourceId: string;
}
Documented JSON Format
{
"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"
}
Field-by-Field Comparison - Booking Entity
Main Booking:
| Field | Current RawData | Target Interface | Documented JSON | Status |
|---|---|---|---|---|
id |
✅ string |
✅ string |
✅ Present | MATCH |
customerId |
✅ string |
✅ string |
✅ Present | MATCH |
status |
✅ string |
✅ BookingStatus |
✅ "created" |
MATCH (needs cast) |
createdAt |
✅ string | Date |
✅ Date |
✅ Present | MATCH |
services |
✅ RawBookingService[] |
✅ IBookingService[] |
✅ Present | MATCH |
totalPrice |
✅ number? |
✅ number? |
✅ Present | MATCH |
tags |
✅ string[]? |
✅ string[]? |
❌ Not in example | MATCH |
notes |
✅ string? |
✅ string? |
✅ Present | MATCH |
syncStatus |
❌ Missing | ✅ SyncStatus (via ISync) |
❌ Not in example | MISSING (added in processing) |
BookingService:
| Field | Current RawData | Target Interface | Documented JSON | Status |
|---|---|---|---|---|
serviceId |
✅ string |
✅ string |
✅ Present | MATCH |
serviceName |
✅ string |
✅ string |
✅ Present | MATCH |
baseDuration |
✅ number |
✅ number |
✅ Present | MATCH |
basePrice |
✅ number |
✅ number |
✅ Present | MATCH |
customPrice |
✅ number? |
✅ number? |
✅ Present | MATCH |
resourceId |
✅ string |
✅ string |
✅ Present | MATCH |
Status - Booking Entity
RawBookingData: PERFECT MATCH ✅ RawBookingService: PERFECT MATCH ✅
All fields present and correctly typed. syncStatus correctly added during processing.
Validation Recommendations
- Ensure
customerIdis not null/empty (REQUIRED) - Ensure
servicesarray has at least one service (REQUIRED) - Validate
statusis valid BookingStatus enum value - Validate each service has
resourceId(REQUIRED)
3. Customer Entity Comparison
Current RawCustomerData Interface
Location: src/repositories/MockCustomerRepository.ts
interface RawCustomerData {
id: string;
name: string;
phone: string;
email?: string;
metadata?: Record<string, any>;
[key: string]: unknown;
}
Target ICustomer Interface
Location: src/types/CustomerTypes.ts
export interface ICustomer extends ISync {
id: string;
name: string;
phone: string;
email?: string;
metadata?: Record<string, any>;
}
Documented JSON Format
{
"id": "CUST001",
"name": "Maria Jensen",
"phone": "+45 12 34 56 78",
"email": "maria.jensen@example.com",
"metadata": {
"preferredStylist": "EMP001",
"allergies": ["ammonia"]
}
}
Field-by-Field Comparison - Customer Entity
| Field | Current RawData | Target Interface | Documented JSON | Status |
|---|---|---|---|---|
id |
✅ string |
✅ string |
✅ Present | MATCH |
name |
✅ string |
✅ string |
✅ Present | MATCH |
phone |
✅ string |
✅ string |
✅ Present | MATCH |
email |
✅ string? |
✅ string? |
✅ Present | MATCH |
metadata |
✅ Record<string, any>? |
✅ Record<string, any>? |
✅ Present | MATCH |
syncStatus |
❌ Missing | ✅ SyncStatus (via ISync) |
❌ Not in example | MISSING (added in processing) |
Status - Customer Entity
RawCustomerData: PERFECT MATCH ✅
All fields present and correctly typed. syncStatus correctly added during processing.
4. Resource Entity Comparison
Current RawResourceData Interface
Location: src/repositories/MockResourceRepository.ts
interface RawResourceData {
id: string;
name: string;
displayName: string;
type: string;
avatarUrl?: string;
color?: string;
isActive?: boolean;
metadata?: Record<string, any>;
[key: string]: unknown;
}
Target IResource Interface
Location: src/types/ResourceTypes.ts
export interface IResource extends ISync {
id: string;
name: string;
displayName: string;
type: ResourceType;
avatarUrl?: string;
color?: string;
isActive?: boolean;
metadata?: Record<string, any>;
}
Documented JSON Format
{
"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"]
}
}
Field-by-Field Comparison - Resource Entity
| Field | Current RawData | Target Interface | Documented JSON | Status |
|---|---|---|---|---|
id |
✅ string |
✅ string |
✅ Present | MATCH |
name |
✅ string |
✅ string |
✅ Present | MATCH |
displayName |
✅ string |
✅ string |
✅ Present | MATCH |
type |
✅ string |
✅ ResourceType |
✅ "person" |
MATCH (needs cast) |
avatarUrl |
✅ string? |
✅ string? |
✅ Present | MATCH |
color |
✅ string? |
✅ string? |
✅ Present | MATCH |
isActive |
✅ boolean? |
✅ boolean? |
✅ Present | MATCH |
metadata |
✅ Record<string, any>? |
✅ Record<string, any>? |
✅ Present | MATCH |
syncStatus |
❌ Missing | ✅ SyncStatus (via ISync) |
❌ Not in example | MISSING (added in processing) |
Status - Resource Entity
RawResourceData: PERFECT MATCH ✅
All fields present and correctly typed. syncStatus correctly added during processing.
Validation Recommendations
- Validate
typeis valid ResourceType enum value ('person' | 'room' | 'equipment' | 'vehicle' | 'custom')
Summary Table
| Entity | Core Fields Status | Missing Critical Fields | Extra Fields | Overall Status |
|---|---|---|---|---|
| Event | ✅ Basic fields OK | ❌ 4 critical fields missing | ⚠️ color (legacy) |
NEEDS UPDATES |
| Booking | ✅ All fields present | ✅ None | ✅ None | COMPLETE ✅ |
| Customer | ✅ All fields present | ✅ None | ✅ None | COMPLETE ✅ |
| Resource | ✅ All fields present | ✅ None | ✅ None | COMPLETE ✅ |
Architecture Validation Rules
Event Type Constraints
From docs/booking-event-architecture.md:
// Rule 1: Customer events MUST have booking reference
if (event.type === 'customer') {
assert(event.bookingId !== undefined, "Customer events require bookingId");
assert(event.customerId !== undefined, "Customer events require customerId");
assert(event.resourceId !== undefined, "Customer events require resourceId");
}
// Rule 2: Non-customer events MUST NOT have booking reference
if (event.type !== 'customer') {
assert(event.bookingId === undefined, "Only customer events have bookingId");
assert(event.customerId === undefined, "Only customer events have customerId");
}
// Rule 3: Vacation/break events MUST have resource assignment
if (event.type === 'vacation' || event.type === 'break') {
assert(event.resourceId !== undefined, "Vacation/break events require resourceId");
}
// Rule 4: Meeting/blocked events MAY have resource assignment
if (event.type === 'meeting' || event.type === 'blocked') {
// resourceId is optional
}
Booking Constraints
// Rule 5: Booking ALWAYS has customer
assert(booking.customerId !== "", "Booking requires customer");
// Rule 6: Booking ALWAYS has services
assert(booking.services.length > 0, "Booking requires at least one service");
// Rule 7: Each service MUST have resource assignment
booking.services.forEach(service => {
assert(service.resourceId !== undefined, "Service requires resourceId");
});
// Rule 8: Service resourceId becomes Event resourceId
// When a booking has ONE service:
// event.resourceId = booking.services[0].resourceId
// When a booking has MULTIPLE services:
// ONE event per service, each with different resourceId
Denormalization Rules
From docs/booking-event-architecture.md (lines 532-547):
Backend performs JOIN and denormalizes:
SELECT
e.Id,
e.Type,
e.Title,
e.Start,
e.End,
e.AllDay,
e.BookingId,
e.ResourceId, -- Already on CalendarEvent (denormalized)
b.CustomerId -- Joined from Booking table
FROM CalendarEvent e
LEFT JOIN Booking b ON e.BookingId = b.Id
WHERE e.Start >= @start AND e.Start <= @end
Why denormalization:
- Performance: No JOIN needed in frontend queries
- Resource filtering: Quick "show all events for EMP001"
- Customer filtering: Quick "show all events for CUST001"
- Offline-first: Complete event data available without JOIN
Recommended Implementation
Phase 1: Update RawEventData Interface (HIGH PRIORITY)
File: src/repositories/MockEventRepository.ts
interface RawEventData {
// Core fields (required)
id: string;
title: string;
start: string | Date;
end: string | Date;
type: string;
allDay?: boolean;
// Denormalized references (NEW - CRITICAL for booking architecture)
bookingId?: string; // Reference to booking (customer events only)
resourceId?: string; // Which resource owns this slot
customerId?: string; // Customer reference (denormalized from booking)
// Optional fields
description?: string; // Detailed event notes
recurringId?: string; // For recurring events
metadata?: Record<string, any>; // Flexible metadata
// Legacy (deprecated, keep for backward compatibility)
color?: string; // UI-specific field
}
Phase 2: Update processCalendarData() Method
private processCalendarData(data: RawEventData[]): ICalendarEvent[] {
return data.map((event): ICalendarEvent => {
// Validate event type constraints
if (event.type === 'customer') {
if (!event.bookingId) {
console.warn(`Customer event ${event.id} missing bookingId`);
}
if (!event.resourceId) {
console.warn(`Customer event ${event.id} missing resourceId`);
}
if (!event.customerId) {
console.warn(`Customer event ${event.id} missing customerId`);
}
}
return {
id: event.id,
title: event.title,
description: event.description,
start: new Date(event.start),
end: new Date(event.end),
type: event.type as CalendarEventType,
allDay: event.allDay || false,
// Denormalized references (CRITICAL)
bookingId: event.bookingId,
resourceId: event.resourceId,
customerId: event.customerId,
// Optional fields
recurringId: event.recurringId,
metadata: event.metadata,
syncStatus: 'synced' as const
};
});
}
Phase 3: Testing (RECOMMENDED)
-
Test customer event with booking reference
- Verify
bookingId,resourceId,customerIdare preserved - Verify type is correctly cast to
CalendarEventType
- Verify
-
Test vacation event without booking
- Verify
bookingIdandcustomerIdareundefined - Verify
resourceIdIS present (required for vacation/break)
- Verify
-
Test split-resource booking scenario
- Booking with 2 services (different resources)
- Should create 2 events with different
resourceId
-
Test event-booking relationship queries
- Load event by
bookingId - Load all events for
resourceId - Load all events for
customerId
- Load event by
Architecture Compliance Score
| Aspect | Score | Notes |
|---|---|---|
| Repository Pattern | 100% | Correctly implements IApiRepository |
| Entity Interfaces | 75% | Booking/Customer/Resource perfect, Event missing 4 critical fields |
| Data Processing | 90% | Correct date/type conversions, needs explicit field mapping |
| Type Safety | 85% | Good type assertions, needs validation |
| Documentation Alignment | 70% | Partially matches documented examples |
| Overall | 84% | Good foundation, Event entity needs updates |
Gap Analysis Summary
What's Working ✅
- Repository pattern: Correctly implements IApiRepository interface
- Booking entity: 100% correct (all fields match)
- Customer entity: 100% correct (all fields match)
- Resource entity: 100% correct (all fields match)
- Date processing: string | Date → Date correctly handled
- Type assertions: string → enum types correctly cast
- SyncStatus injection: Correctly added during processing
- Error handling: Unsupported operations (create/update/delete) throw errors
- fetchAll() implementation: Correctly loads from JSON and processes data
What's Missing ❌
Event Entity - 4 Critical Fields:
-
bookingId - Cannot link events to bookings
- Impact: Cannot load booking details when event is clicked
- Impact: Cannot cascade delete when booking is cancelled
- Impact: Cannot query events by booking
-
resourceId - Cannot query by resource
- Impact: Cannot filter calendar by resource (columns)
- Impact: Cannot show resource utilization
- Impact: Denormalization benefit lost (requires JOIN)
-
customerId - Cannot query by customer
- Impact: Cannot show customer history
- Impact: Cannot filter events by customer
- Impact: Denormalization benefit lost (requires JOIN)
-
description - Cannot add detailed notes
- Impact: Limited event details
- Impact: No additional context beyond title
What Needs Validation ⚠️
- Event type constraints: Customer events require
bookingId - Booking constraints: Must have
customerIdandservices[] - Resource assignment: Vacation/break events require
resourceId - Enum validation: Validate
type,statusmatch enum values
What Needs Cleanup 🧹
- Legacy
colorfield: Present in RawEventData but not in ICalendarEvent - Index signature: Consider removing
[key: string]: unknownonce all fields are explicit
Next Steps
Immediate (HIGH PRIORITY)
- Update Event Entity
- Add 4 missing fields to
RawEventData - Update
processCalendarData()with explicit mapping - Add validation for type constraints
- Add 4 missing fields to
Short-term (MEDIUM PRIORITY)
-
Create Mock Data Files
- Update
wwwroot/data/mock-events.jsonwith denormalized fields - Ensure
mock-bookings.json,mock-customers.json,mock-resources.jsonexist - Verify relationships (event.bookingId → booking.id)
- Update
-
Add Validation Layer
- Validate event-booking relationships
- Validate required fields per event type
- Log warnings for data integrity issues
Long-term (LOW PRIORITY)
-
Update Tests
- Test new fields in event processing
- Test validation rules
- Test cross-entity relationships
-
Documentation
- Update CLAUDE.md with Mock repository usage
- Document validation rules
- Document denormalization strategy
Conclusion
The Mock Repository implementation has a strong foundation with 3 out of 4 entities (Booking, Customer, Resource) perfectly matching the target architecture.
The Event entity needs critical updates to support the booking architecture's denormalization strategy. Adding the 4 missing fields (bookingId, resourceId, customerId, description) will bring the implementation to 100% compliance with the documented architecture.
Estimated effort: 1-2 hours for updates + testing
Risk: Low - changes are additive (no breaking changes to existing code)
Priority: HIGH - required for booking architecture to function correctly