Calendar/docs/mock-repository-implementation-status.md
Janus C. H. Knudsen 5648c7c304 Adds comprehensive mock data repositories and seeding infrastructure
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
2025-11-20 15:25:38 +01:00

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 have bookingId
  • 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

  1. Add to RawEventData:

    • description?: string
    • bookingId?: string (CRITICAL)
    • resourceId?: string (CRITICAL)
    • customerId?: string (CRITICAL)
    • recurringId?: string
    • metadata?: Record<string, any> (make explicit)
  2. Update Processing:

    • Explicitly map all new fields in processCalendarData()
    • Remove or document legacy color field
    • Ensure allDay defaults to false if missing
    • Validate that type: 'customer' events have bookingId
  3. JSON File Requirements:

    • Customer events MUST include bookingId, resourceId, customerId
    • Vacation/break/meeting events MUST NOT have bookingId or customerId
    • Vacation/break events SHOULD have resourceId

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 customerId is not null/empty (REQUIRED)
  • Ensure services array has at least one service (REQUIRED)
  • Validate status is 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 type is 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

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
    };
  });
}
  1. Test customer event with booking reference

    • Verify bookingId, resourceId, customerId are preserved
    • Verify type is correctly cast to CalendarEventType
  2. Test vacation event without booking

    • Verify bookingId and customerId are undefined
    • Verify resourceId IS present (required for vacation/break)
  3. Test split-resource booking scenario

    • Booking with 2 services (different resources)
    • Should create 2 events with different resourceId
  4. Test event-booking relationship queries

    • Load event by bookingId
    • Load all events for resourceId
    • Load all events for customerId

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:

  1. 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
  2. resourceId - Cannot query by resource

    • Impact: Cannot filter calendar by resource (columns)
    • Impact: Cannot show resource utilization
    • Impact: Denormalization benefit lost (requires JOIN)
  3. customerId - Cannot query by customer

    • Impact: Cannot show customer history
    • Impact: Cannot filter events by customer
    • Impact: Denormalization benefit lost (requires JOIN)
  4. 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 customerId and services[]
  • Resource assignment: Vacation/break events require resourceId
  • Enum validation: Validate type, status match enum values

What Needs Cleanup 🧹

  • Legacy color field: Present in RawEventData but not in ICalendarEvent
  • Index signature: Consider removing [key: string]: unknown once all fields are explicit

Next Steps

Immediate (HIGH PRIORITY)

  1. Update Event Entity
    • Add 4 missing fields to RawEventData
    • Update processCalendarData() with explicit mapping
    • Add validation for type constraints

Short-term (MEDIUM PRIORITY)

  1. Create Mock Data Files

    • Update wwwroot/data/mock-events.json with denormalized fields
    • Ensure mock-bookings.json, mock-customers.json, mock-resources.json exist
    • Verify relationships (event.bookingId → booking.id)
  2. Add Validation Layer

    • Validate event-booking relationships
    • Validate required fields per event type
    • Log warnings for data integrity issues

Long-term (LOW PRIORITY)

  1. Update Tests

    • Test new fields in event processing
    • Test validation rules
    • Test cross-entity relationships
  2. 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