# Solution Design Document

## Validation Checklist

- [ ] All required sections are complete
- [ ] No [NEEDS CLARIFICATION] markers remain
- [ ] All context sources are listed with relevance ratings
- [ ] Project commands are discovered from actual project files
- [ ] Constraints → Strategy → Design → Implementation path is logical
- [ ] Architecture pattern is clearly stated with rationale
- [ ] Every component in diagram has directory mapping
- [ ] Every interface has specification
- [ ] Error handling covers all error types
- [ ] Quality requirements are specific and measurable
- [ ] Every quality requirement has test coverage
- [ ] **All architecture decisions confirmed by user**
- [ ] Component names consistent across diagrams
- [ ] A developer could implement from this design

---

## Constraints

**CON-1 Framework & Technology Stack**
- PHP 8.x with Slim 2.6.2 framework
- Twig 1.44.8 templating
- MySQL multi-store database architecture
- Syncfusion EJ2 Schedule component (existing, must extend)
- Bootstrap 5.3.3 with custom design tokens

**CON-2 Existing System Dependencies**
- Spec 013 (Employee Scheduling): Syncfusion calendar, `scheduleShifts` table, shift CRUD
- Spec 022 (Mobile Scheduling): `scheduleAvailability`, `scheduleAvailabilityExceptions`, `scheduleTimeOffRequests` tables
- Spec 023 (Shift Approvals): Time-off approval workflow, open shift claiming
- BuyerKiosk Provider Only: Features restricted to `schedulingProvider='buyerkiosk'` stores

**CON-3 Performance Targets**
- Template load: < 3 seconds for 100 shifts
- Overlay render: < 500ms for 25 employees × 7 days
- Analytics data: < 2 seconds for 12-week lookback with hourly granularity

**CON-4 Browser Support**
- Chrome, Safari, Edge (latest 2 versions)
- Mobile-responsive for tablet scheduling

## Implementation Context

**IMPORTANT**: You MUST read and analyze ALL listed context sources to understand constraints, patterns, and existing architecture.

### Required Context Sources

- ICO-1 [Scheduling Infrastructure]
  ```yaml
  # Core scheduling components
  - file: public_html/js/scheduling/ScheduleCalendar.js
    relevance: CRITICAL
    sections: [toolbar config, event templates, overlay integration points]
    why: "Main calendar class - must extend for templates and overlays"

  - file: userfrosting/src/BuyerKiosk/Scheduling/Controllers/SchedulingController.php
    relevance: HIGH
    why: "Existing shift API patterns to follow"

  - file: userfrosting/src/BuyerKiosk/Scheduling/Repositories/ShiftRepository.php
    relevance: HIGH
    sections: [findByDateRange, copyWeek]
    why: "Repository patterns and copy functionality as template baseline"

  - file: userfrosting/templates/themes/default/scheduling/calendar.html
    relevance: HIGH
    sections: [toolbar buttons, modals]
    why: "Existing calendar UI - add template/overlay controls"
  ```

- ICO-2 [Availability Infrastructure]
  ```yaml
  - file: userfrosting/src/BuyerKiosk/MobileScheduling/Models/Availability.php
    relevance: HIGH
    why: "Availability model with dayOfWeek patterns"

  - file: userfrosting/src/BuyerKiosk/MobileScheduling/Repositories/AvailabilityRepository.php
    relevance: HIGH
    sections: [getEffectiveAvailability, findExceptionsByDateRange]
    why: "Key methods for overlay data"

  - file: userfrosting/src/BuyerKiosk/MobileScheduling/Repositories/TimeOffRequestRepository.php
    relevance: HIGH
    sections: [findByDateRange]
    why: "Time-off data for overlay"
  ```

- ICO-3 [Analytics Infrastructure]
  ```yaml
  - file: userfrosting/src/BuyerKiosk/Analytics/Repositories/WaitTimeRepository.php
    relevance: HIGH
    sections: [fetchWaitTimeHeatmap]
    why: "Existing heatmap generation - extend for overlay"

  - file: userfrosting/src/BuyerKiosk/Analytics/Repositories/FinancialRepository.php
    relevance: MEDIUM
    sections: [fetchDailyBuysToSales]
    why: "Sales data patterns"
  ```

- ICO-4 [Database Migrations]
  ```yaml
  - file: userfrosting/migrations/input/20251220_013_001_schedule_shifts.json
    relevance: HIGH
    why: "Shift table schema - adding recurrence fields"

  - file: userfrosting/migrations/input/20251222_022_006_schedule_availability.json
    relevance: MEDIUM
    why: "Availability schema reference"
  ```

### Implementation Boundaries

- **Must Preserve**:
  - Existing shift CRUD behavior (`/api/:typeNum/schedule/shifts/*`)
  - Syncfusion calendar core configuration
  - Copy Previous Week functionality
  - Time-off display in employee headers
  - Labor cost calculations

- **Can Modify**:
  - Calendar toolbar (add Templates dropdown, Overlays dropdown)
  - `ScheduleCalendar.js` class (add overlay rendering, template methods)
  - `SchedulingController.php` (add template endpoints)
  - Calendar template (add new modals)

- **Must Not Touch**:
  - Mobile API endpoints (`/api/mobile/*`) - separate spec ownership
  - Ably real-time sync - maintain existing patterns
  - Role configuration system
  - Store operating hours settings

### External Interfaces

#### System Context Diagram

```mermaid
graph TB
    Manager[Store Manager] --> Calendar[Schedule Calendar]

    Calendar --> ScheduleAPI[Schedule API]
    Calendar --> AnalyticsAPI[Analytics API]

    ScheduleAPI --> StoreDB[(Store Database)]
    ScheduleAPI --> CentralDB[(Central Database)]
    AnalyticsAPI --> StoreDB

    StoreDB --> ShiftsTable[scheduleShifts]
    StoreDB --> TemplatesTable[scheduleTemplates NEW]
    StoreDB --> AvailabilityTable[scheduleAvailability]
    StoreDB --> TimeOffTable[scheduleTimeOffRequests]
    StoreDB --> BuyQueueTable[buyQueue]
    StoreDB --> SalesTable[dailySalesData]

    CentralDB --> UsersTable[users]
```

#### Interface Specifications

```yaml
# Inbound Interfaces
inbound:
  - name: "Schedule Calendar UI"
    type: HTTPS
    format: REST JSON
    authentication: Session/Cookie
    data_flow: "Template CRUD, overlay data requests, shift operations"

# Outbound Interfaces (this feature calls)
outbound:
  - name: "Store Database"
    type: MySQL
    connection: PDO via dbConnectByName()
    data_flow: "Template storage, shift data, availability, analytics"

  - name: "Central Database"
    type: MySQL
    connection: PDO (kiosk_users)
    data_flow: "Employee lookup for template validation"

# Data Interfaces
data:
  - name: "scheduleTemplates (NEW)"
    type: MySQL Table
    data_flow: "Template metadata and shift patterns"

  - name: "scheduleShifts (MODIFY)"
    type: MySQL Table
    data_flow: "Add recurrence fields for repeat shifts"
```

### Project Commands

```bash
# Environment Setup
Install Dependencies: cd userfrosting && composer install
Start Development: Local Apache via Valet/MAMP

# Testing Commands
All Tests: ./test.sh
Unit Tests: ./test.sh --testsuite unit
Integration Tests: ./test.sh --testsuite integration
Coverage Report: ./test.sh --coverage
Static Analysis: ./test.sh --stan

# PHPStan Analysis
cd userfrosting && ./vendor/bin/phpstan analyse
cd userfrosting && ./vendor/bin/phpstan analyse src/BuyerKiosk/Scheduling/

# Database Migrations
Run Migrations: php userfrosting/conductor run
Create Migration: Add JSON to userfrosting/migrations/input/

# CSS Build
Development: php userfrosting/conductor build-css
Production: php userfrosting/conductor build-css --minify
Watch Mode: php userfrosting/conductor build-css --watch

# Deployment
Full Deploy: ./deploy.sh (runs tests first)
```

## Solution Strategy

- **Architecture Pattern**: Modular Extension of Existing Calendar
  - Extend `ScheduleCalendar.js` with overlay and template capabilities
  - Add parallel PHP services (`TemplateService`, `OverlayDataService`)
  - New database tables for templates; extend shifts for recurrence

- **Integration Approach**:
  - Templates use existing `ShiftRepository` patterns
  - Overlays aggregate from existing availability/analytics repositories
  - No changes to mobile API - internal scheduling only

- **Justification**:
  - Syncfusion EJ2 Schedule supports custom rendering for overlays
  - Existing shift copy logic provides template baseline
  - Analytics infrastructure already generates hourly aggregates

- **Key Decisions**:
  - **UI Decisions (User Confirmed)**:
    - Templates: Toolbar button with dropdown
    - Overlays: Single dropdown with checkboxes
    - Conflict detection: Table list with actions
    - Analytics heat map: Background gradient colors
    - Load preview: Shift summary list (not full calendar)
    - Template management: Dedicated admin page
    - Overlay legend: Bottom bar
    - Recurrence: Full support with day picker UI (NEW - to be built)

## Building Block View

### Components

```mermaid
graph LR
    subgraph "Frontend"
        Calendar[ScheduleCalendar.js]
        TemplateUI[TemplateManager.js NEW]
        OverlayUI[OverlayRenderer.js NEW]
    end

    subgraph "Backend Controllers"
        ScheduleCtrl[SchedulingController]
        TemplateCtrl[TemplateController NEW]
        OverlayCtrl[OverlayDataController NEW]
    end

    subgraph "Services"
        TemplateSvc[TemplateService NEW]
        ConflictSvc[ConflictDetectionService NEW]
        OverlaySvc[OverlayDataService NEW]
        RecurrenceSvc[RecurrenceService NEW]
    end

    subgraph "Repositories"
        ShiftRepo[ShiftRepository]
        TemplateRepo[TemplateRepository NEW]
        AvailRepo[AvailabilityRepository]
        TimeOffRepo[TimeOffRequestRepository]
        WaitTimeRepo[WaitTimeRepository]
        SalesRepo[FinancialRepository]
    end

    subgraph "Database"
        Templates[(scheduleTemplates)]
        TemplateShifts[(scheduleTemplateShifts)]
        Shifts[(scheduleShifts)]
        Availability[(scheduleAvailability)]
        TimeOff[(scheduleTimeOffRequests)]
        BuyQueue[(buyQueue)]
        Sales[(dailySalesData)]
    end

    Calendar --> TemplateUI
    Calendar --> OverlayUI

    TemplateUI --> TemplateCtrl
    OverlayUI --> OverlayCtrl

    TemplateCtrl --> TemplateSvc
    TemplateSvc --> ConflictSvc
    TemplateSvc --> TemplateRepo
    TemplateSvc --> RecurrenceSvc

    OverlayCtrl --> OverlaySvc
    OverlaySvc --> AvailRepo
    OverlaySvc --> TimeOffRepo
    OverlaySvc --> WaitTimeRepo
    OverlaySvc --> SalesRepo

    TemplateRepo --> Templates
    TemplateRepo --> TemplateShifts
    ShiftRepo --> Shifts
    AvailRepo --> Availability
    TimeOffRepo --> TimeOff
    WaitTimeRepo --> BuyQueue
    SalesRepo --> Sales
```

### Directory Map

**Backend (PHP)**
```
userfrosting/src/BuyerKiosk/
├── Scheduling/
│   ├── Controllers/
│   │   ├── SchedulingController.php      # MODIFY: Add recurrence endpoints
│   │   ├── TemplateController.php        # NEW: Template CRUD API
│   │   └── OverlayDataController.php     # NEW: Overlay data aggregation
│   ├── Models/
│   │   ├── Shift.php                     # MODIFY: Add recurrence properties
│   │   ├── Template.php                  # NEW: Template entity
│   │   └── TemplateShift.php             # NEW: Shift pattern in template
│   ├── Repositories/
│   │   ├── ShiftRepository.php           # MODIFY: Add recurrence queries
│   │   └── TemplateRepository.php        # NEW: Template persistence
│   └── Services/
│       ├── TemplateService.php           # NEW: Template business logic
│       ├── ConflictDetectionService.php  # NEW: Availability/time-off conflicts
│       ├── OverlayDataService.php        # NEW: Aggregate overlay data
│       └── RecurrenceService.php         # NEW: Recurring shift expansion

userfrosting/routes/
├── scheduling.php                        # MODIFY: Add template routes
└── groups/
    └── schedule-templates.php            # NEW: Template route group
    └── schedule-overlays.php             # NEW: Overlay data routes

userfrosting/migrations/input/
├── 20260104_025_001_schedule_templates.json        # NEW
├── 20260104_025_002_schedule_template_shifts.json  # NEW
└── 20260104_025_003_schedule_shifts_recurrence.json # NEW
```

**Frontend (JavaScript)**
```
public_html/js/scheduling/
├── ScheduleCalendar.js               # MODIFY: Add overlay hooks, template integration
├── TemplateManager.js                # NEW: Template UI logic
├── OverlayRenderer.js                # NEW: Overlay visualization
├── ConflictResolver.js               # NEW: Conflict resolution UI
└── RecurrenceEditor.js               # NEW: Recurring shift UI

public_html/css/admin/modules/
└── schedule-overlays.css             # NEW: Overlay styling
```

**Templates (Twig)**
```
userfrosting/templates/themes/default/
├── scheduling/
│   ├── calendar.html                 # MODIFY: Add toolbar dropdowns, modals
│   └── partials/
│       ├── template-dropdown.html    # NEW: Template toolbar dropdown
│       ├── overlay-dropdown.html     # NEW: Overlay controls
│       ├── template-load-modal.html  # NEW: Load with conflict table
│       ├── template-save-modal.html  # NEW: Save template dialog
│       └── overlay-legend.html       # NEW: Bottom legend bar
├── admin/scheduling/
│   └── templates.html                # NEW: Template management page
```

### Interface Specifications

#### Data Storage Changes

```yaml
# NEW TABLE: scheduleTemplates
Table: scheduleTemplates
  templateId: INT UNSIGNED AUTO_INCREMENT PRIMARY KEY
  name: VARCHAR(100) NOT NULL
  description: TEXT NULL
  shiftCount: TINYINT UNSIGNED DEFAULT 0
  createdByUserId: INT UNSIGNED NOT NULL
  createdAt: TIMESTAMP DEFAULT CURRENT_TIMESTAMP
  updatedAt: TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE
  lastUsedAt: TIMESTAMP NULL
  UNIQUE KEY: uk_name (name)
  KEY: idx_created_by (createdByUserId)
  KEY: idx_last_used (lastUsedAt DESC)

# NEW TABLE: scheduleTemplateShifts
Table: scheduleTemplateShifts
  templateShiftId: INT UNSIGNED AUTO_INCREMENT PRIMARY KEY
  templateId: INT UNSIGNED NOT NULL (FK → scheduleTemplates)
  dayOfWeek: TINYINT UNSIGNED NOT NULL (0-6, Sunday=0)
  startTime: TIME NOT NULL
  endTime: TIME NOT NULL
  employeeId: INT UNSIGNED NULL (NULL = open shift)
  positionId: INT UNSIGNED NULL
  minRoleId: TINYINT UNSIGNED NULL
  notes: TEXT NULL
  KEY: idx_template (templateId)
  KEY: idx_day (dayOfWeek)

# MODIFY TABLE: scheduleShifts
Table: scheduleShifts
  ADD COLUMN: recurrenceRule VARCHAR(255) NULL
    # NULL = one-time shift
    # Format: "FREQ=WEEKLY;BYDAY=MO,WE,FR" (iCal RRULE subset)
  ADD COLUMN: recurrenceEndDate DATE NULL
  ADD COLUMN: parentShiftId INT UNSIGNED NULL (FK → scheduleShifts.shiftId)
    # Points to first shift in series
  ADD COLUMN: isRecurrenceInstance TINYINT(1) DEFAULT 0
  ADD INDEX: idx_parent_shift (parentShiftId)
  ADD INDEX: idx_recurrence (recurrenceRule)
```

#### Internal API Changes

```yaml
# Template Endpoints
Endpoint: List Templates
  Method: GET
  Path: /api/:typeNum/schedule/templates
  Response:
    templates: Array<{templateId, name, description, shiftCount, lastUsedAt, createdAt}>

Endpoint: Get Template Details
  Method: GET
  Path: /api/:typeNum/schedule/templates/:templateId
  Response:
    template: {templateId, name, description}
    shifts: Array<{dayOfWeek, startTime, endTime, employeeId, employeeName, positionId, positionName, minRoleId, isOpenShift}>

Endpoint: Save Template
  Method: POST
  Path: /api/:typeNum/schedule/templates
  Request:
    name: string (required, 1-100 chars)
    description: string (optional, max 500 chars)
    sourceWeekStart: date (YYYY-MM-DD, Monday of source week)
  Response:
    templateId: int
    shiftCount: int

Endpoint: Update Template
  Method: PUT
  Path: /api/:typeNum/schedule/templates/:templateId
  Request:
    name: string (optional)
    description: string (optional)
    replaceShifts: boolean (default false)
    sourceWeekStart: date (if replaceShifts=true)
  Response:
    templateId: int
    shiftCount: int

Endpoint: Delete Template
  Method: DELETE
  Path: /api/:typeNum/schedule/templates/:templateId
  Response:
    success: boolean

Endpoint: Preview Template Load
  Method: POST
  Path: /api/:typeNum/schedule/templates/:templateId/preview
  Request:
    targetWeekStart: date (YYYY-MM-DD)
    loadAsOpenShifts: boolean (default false)
  Response:
    summary:
      totalShifts: int
      activeEmployees: int
      inactiveEmployees: int
      availabilityConflicts: int
      timeOffConflicts: int
    shifts: Array<{
      dayOfWeek, date, startTime, endTime,
      employeeId, employeeName, isOpenShift,
      status: 'ok' | 'employee_inactive' | 'availability_conflict' | 'timeoff_conflict',
      conflictDetails: {type, description} | null
    }>

Endpoint: Apply Template
  Method: POST
  Path: /api/:typeNum/schedule/templates/:templateId/apply
  Request:
    targetWeekStart: date
    mode: 'replace' | 'merge'
    loadAsOpenShifts: boolean (default false)
    conflictResolutions: Array<{shiftIndex, action: 'create' | 'open_shift' | 'skip'}>
  Response:
    created: int
    openShifts: int
    skipped: int
    replaced: int (if mode='replace')

# Overlay Data Endpoints
Endpoint: Get Availability Overlay
  Method: GET
  Path: /api/:typeNum/schedule/overlays/availability
  Query:
    weekStart: date
    employeeIds: comma-separated ints (optional, all if omitted)
  Response:
    employees: Array<{
      employeeId,
      availability: Array<{dayOfWeek, startTime, endTime, isPreferred, hasException, exceptionReason}>
    }>

Endpoint: Get Time-Off Overlay
  Method: GET
  Path: /api/:typeNum/schedule/overlays/timeoff
  Query:
    weekStart: date
    employeeIds: comma-separated ints (optional)
  Response:
    requests: Array<{
      requestId, employeeId, employeeName,
      startDate, endDate, status: 'approved' | 'pending',
      requestType
    }>

Endpoint: Get Wait Time Overlay
  Method: GET
  Path: /api/:typeNum/schedule/overlays/waittime
  Query:
    lookbackWeeks: int (4, 8, or 12)
    excludeEvents: boolean (default true)
  Response:
    heatmap: Array<{dayOfWeek, hour, avgWaitMinutes, buyCount}>
    thresholds: {low: 3, moderate: 6, high: 10}

Endpoint: Get Sales Overlay
  Method: GET
  Path: /api/:typeNum/schedule/overlays/sales
  Query:
    lookbackWeeks: int (4, 8, or 12)
    metric: 'transactions' | 'revenue'
    excludeEvents: boolean (default true)
  Response:
    heatmap: Array<{dayOfWeek, hour, value, transactionCount}>
    thresholds: {low, moderate, high}

# Recurrence Endpoints
Endpoint: Create Recurring Shift
  Method: POST
  Path: /api/:typeNum/schedule/shifts/recurring
  Request:
    employeeId: int | null
    startDate: date (first occurrence)
    startTime: time
    endTime: time
    positionId: int | null
    minRoleId: int | null
    days: Array<0-6> (days of week)
    endDate: date | null (null = indefinite until end of schedule)
  Response:
    parentShiftId: int
    instancesCreated: int
    instances: Array<{shiftId, date}>

Endpoint: Update Recurring Shift
  Method: PUT
  Path: /api/:typeNum/schedule/shifts/:shiftId/recurring
  Request:
    scope: 'single' | 'future' | 'all'
    startTime: time (optional)
    endTime: time (optional)
    employeeId: int | null (optional)
    positionId: int | null (optional)
  Response:
    modified: int

Endpoint: Delete Recurring Shift
  Method: DELETE
  Path: /api/:typeNum/schedule/shifts/:shiftId/recurring
  Query:
    scope: 'single' | 'future' | 'all'
  Response:
    deleted: int
```

#### Application Data Models

```pseudocode
ENTITY: Template (NEW)
  FIELDS:
    templateId: int (PK)
    name: string (1-100 chars, unique per store)
    description: string (optional, max 500)
    shiftCount: int (derived)
    createdByUserId: int (FK users)
    createdAt: datetime
    updatedAt: datetime
    lastUsedAt: datetime (nullable)

  BEHAVIORS:
    getShifts(): TemplateShift[]
    toPreview(targetDate): ShiftPreview[]

ENTITY: TemplateShift (NEW)
  FIELDS:
    templateShiftId: int (PK)
    templateId: int (FK Template)
    dayOfWeek: int (0-6)
    startTime: time
    endTime: time
    employeeId: int (nullable)
    positionId: int (nullable)
    minRoleId: int (nullable)
    notes: string (nullable)

  BEHAVIORS:
    isOpenShift(): boolean
    toShift(date): Shift

ENTITY: Shift (MODIFIED)
  FIELDS:
    ~ Add: recurrenceRule: string (nullable, RRULE format)
    ~ Add: recurrenceEndDate: date (nullable)
    ~ Add: parentShiftId: int (nullable, FK Shift)
    ~ Add: isRecurrenceInstance: boolean

  BEHAVIORS:
    + isRecurring(): boolean
    + getRecurrencePattern(): {days: int[], endDate: date|null}
    + getSeriesInstances(): Shift[]
    + isSeriesParent(): boolean
```

## Runtime View

### Primary Flow: Load Template with Conflict Detection

1. Manager clicks "Templates" dropdown in calendar toolbar
2. Manager selects template from list
3. System shows load options (Replace/Merge, As Open Shifts checkbox)
4. Manager clicks "Load"
5. System fetches preview with conflict detection
6. System displays conflict table with actions per row
7. Manager resolves conflicts (Create, Open Shift, Skip per row)
8. Manager clicks "Apply Template"
9. System creates shifts per resolutions
10. Calendar refreshes to show new shifts

```mermaid
sequenceDiagram
    actor Manager
    participant Calendar
    participant TemplateCtrl
    participant TemplateSvc
    participant ConflictSvc
    participant ShiftRepo

    Manager->>Calendar: Select template from dropdown
    Manager->>Calendar: Choose load options
    Manager->>Calendar: Click "Load"

    Calendar->>TemplateCtrl: POST /templates/:id/preview
    TemplateCtrl->>TemplateSvc: previewLoad(templateId, targetWeek)
    TemplateSvc->>TemplateSvc: getTemplateShifts()
    TemplateSvc->>ConflictSvc: detectConflicts(shifts, targetWeek)
    ConflictSvc->>ConflictSvc: checkEmployeeActive()
    ConflictSvc->>ConflictSvc: checkAvailability()
    ConflictSvc->>ConflictSvc: checkTimeOff()
    ConflictSvc-->>TemplateSvc: ConflictResult[]
    TemplateSvc-->>TemplateCtrl: PreviewResponse
    TemplateCtrl-->>Calendar: JSON {summary, shifts with conflicts}

    Calendar->>Calendar: Display conflict table UI
    Manager->>Calendar: Resolve conflicts (per row actions)
    Manager->>Calendar: Click "Apply Template"

    Calendar->>TemplateCtrl: POST /templates/:id/apply
    TemplateCtrl->>TemplateSvc: applyTemplate(templateId, targetWeek, resolutions)
    TemplateSvc->>ShiftRepo: createBatch(resolvedShifts)
    ShiftRepo-->>TemplateSvc: Shift[]
    TemplateSvc-->>TemplateCtrl: ApplyResult
    TemplateCtrl-->>Calendar: JSON {created, skipped, openShifts}

    Calendar->>Calendar: Refresh schedule view
```

### Secondary Flow: Toggle Availability Overlay

```mermaid
sequenceDiagram
    actor Manager
    participant Calendar
    participant OverlayCtrl
    participant OverlaySvc
    participant AvailRepo
    participant TimeOffRepo

    Manager->>Calendar: Click Overlays dropdown
    Manager->>Calendar: Check "Availability" checkbox

    Calendar->>OverlayCtrl: GET /overlays/availability?weekStart=...
    OverlayCtrl->>OverlaySvc: getAvailabilityData(weekStart)
    OverlaySvc->>AvailRepo: findCurrentByEmployee(employeeIds)
    AvailRepo-->>OverlaySvc: Availability[]
    OverlaySvc-->>OverlayCtrl: AvailabilityOverlay
    OverlayCtrl-->>Calendar: JSON {employees, availability}

    Manager->>Calendar: Check "Time-Off" checkbox
    Calendar->>OverlayCtrl: GET /overlays/timeoff?weekStart=...
    OverlayCtrl->>OverlaySvc: getTimeOffData(weekStart)
    OverlaySvc->>TimeOffRepo: findByDateRange(start, end)
    TimeOffRepo-->>OverlaySvc: TimeOffRequest[]
    OverlaySvc-->>OverlayCtrl: TimeOffOverlay
    OverlayCtrl-->>Calendar: JSON {requests}

    Calendar->>Calendar: Render overlay backgrounds
    Calendar->>Calendar: Show legend bar at bottom
```

### Error Handling

- **Invalid template name (duplicate)**:
  - Error: "A template with this name already exists"
  - Recovery: Prompt for new name or overwrite option

- **Template load with 100% conflicts**:
  - Warning: "All shifts have conflicts. Load as open shifts?"
  - Recovery: Bulk action to convert all to open shifts

- **Employee no longer active**:
  - Info: "X shifts assigned to inactive employees"
  - Automatic: Convert to open shifts with notification

- **Overlay data unavailable**:
  - Warning: "Insufficient data for wait time overlay (need 2+ weeks)"
  - Display: Show "No data" in cells without threshold

- **Network failure during template apply**:
  - Error: "Failed to apply template. Some shifts may not have been created."
  - Recovery: Refresh calendar, retry apply

- **Concurrent edit conflict**:
  - Error: "Schedule was modified by another user. Please refresh."
  - Recovery: Auto-refresh and show conflict resolution

## Deployment View

### Single Application Deployment

No changes to existing deployment - this is a feature addition to the existing PHP application.

- **Environment**: Production PHP 8.x on Apache
- **Configuration**: No new environment variables
- **Dependencies**: No new external dependencies
- **Database**: Migration required (3 new migrations)

### Migration Sequence

1. Deploy code with new endpoints (backward compatible)
2. Run migrations:
   - `20260104_025_001_schedule_templates.json`
   - `20260104_025_002_schedule_template_shifts.json`
   - `20260104_025_003_schedule_shifts_recurrence.json`
3. Clear any relevant caches
4. Features automatically available to stores with `schedulingProvider='buyerkiosk'`

## Cross-Cutting Concepts

### Pattern Documentation

```yaml
# Existing patterns used
- pattern: docs/patterns/psr4-autoloading.md
  relevance: HIGH
  why: "All new classes follow PSR-4 namespace conventions"

- pattern: docs/patterns/namespace-structure.md
  relevance: HIGH
  why: "New services in BuyerKiosk\\Scheduling namespace"

# New patterns (to document)
- pattern: docs/patterns/calendar-overlays.md (NEW)
  relevance: MEDIUM
  why: "Document overlay rendering pattern for future overlays"
```

### System-Wide Patterns

- **Security**:
  - Permission check: `uri_store_settings` for template CRUD
  - Store group check: `checkStoreGroup($typeNum)` on all endpoints
  - No sensitive data in templates (just shift patterns)

- **Error Handling**:
  - API returns JSON `{success: false, error: 'ERROR_CODE', message: '...'}`
  - Frontend shows toast notifications via existing `showToast()` utility
  - Validation errors return 400 with field-specific messages

- **Performance**:
  - Template preview: Eager load all data in single query batch
  - Overlay data: Cache in Redis with 60s TTL (same as analytics)
  - Batch inserts for template apply (not individual INSERTs)

- **Logging/Auditing**:
  - Template save/load logged to audit table
  - Include template name, shift count, conflict resolutions

### Implementation Patterns

#### State Management Patterns

- **Frontend**: Overlay state stored in `ScheduleCalendar` instance
  - `this.activeOverlays = {availability: false, timeoff: false, waittime: false, sales: false}`
  - State persisted to localStorage per-store for user preference

- **Template State**: Not persistent (modal-driven workflows)

#### Integration Patterns

- **Overlay data aggregation**:
  - Single service (`OverlayDataService`) orchestrates multiple repositories
  - Returns normalized format for frontend rendering

- **Template conflict detection**:
  - Conflict service checks in priority order: time-off (blocking) > availability > employee active
  - Returns unified conflict result with type and description

## Architecture Decisions

- [x] **ADR-1 Template Storage Strategy**: Store templates as separate entities (not shift snapshots)
  - Rationale: Allows template management independent of live schedule; easier to update templates
  - Trade-offs: Additional tables, requires join for preview
  - User confirmed: ✅ Implicit (user chose "Dedicated Admin Page" for template management)

- [x] **ADR-2 Overlay Rendering Approach**: CSS background gradients on calendar cells
  - Rationale: Native CSS performance, no canvas complexity, works with existing Syncfusion cells
  - Trade-offs: Limited to gradient colors (no complex charts in cells)
  - User confirmed: ✅ (User chose "Background Heat Map")

- [x] **ADR-3 Conflict Resolution UX**: Table-based with per-row actions
  - Rationale: Clear, scannable, supports bulk actions; matches existing patterns (DataTables)
  - Trade-offs: More clicks than inline calendar editing
  - User confirmed: ✅ (User chose "Table List with Actions")

- [x] **ADR-4 Recurrence Implementation**: Full RRULE-like support with series tracking
  - Rationale: User requested full recurrence support; parent/child model enables edit/delete scope
  - Trade-offs: More complex than simple clone; requires series management UI
  - User confirmed: ✅ (User chose "Add Full Recurrence Support")

- [x] **ADR-5 Hourly Sales Data**: Derive from transaction timestamps
  - Rationale: Matches wait time granularity; enables true hourly heat map
  - Trade-offs: More complex query; may need query optimization for 12-week lookback
  - User confirmed: ✅ (User chose "Derive Hourly from Transactions")

- [x] **ADR-6 Template Limit**: 20 templates per store
  - Rationale: Prevent template bloat; reasonable for most stores
  - Trade-offs: Power users may want more
  - User confirmed: ✅

- [x] **ADR-7 Overlay Combination**: Single overlay at a time (mutually exclusive)
  - Rationale: Avoid visual clutter; each overlay needs full cell for gradient
  - Trade-offs: Can't see availability AND wait time simultaneously
  - User confirmed: ✅

## Quality Requirements

- **Performance**:
  - Template load preview: < 2 seconds for 50 shifts
  - Template apply: < 3 seconds for 100 shifts
  - Overlay data fetch: < 500ms per overlay type
  - Overlay render: < 100ms after data received

- **Usability**:
  - Template name autocomplete in save dialog
  - Keyboard navigation in conflict table
  - Clear visual distinction between overlay types
  - Accessible color contrast for heat map (not color-alone)

- **Reliability**:
  - Template apply is atomic (all-or-nothing)
  - Failed template apply does not corrupt existing schedule
  - Overlay gracefully degrades with missing data

## Risks and Technical Debt

### Known Technical Issues

- **Recurrence UI doesn't exist**: Will be built from scratch; no existing patterns to follow
- **Sales data has no hourly granularity stored**: Will derive from transaction timestamps in `buyQueue`/`dailySalesData`

### Technical Debt

- **Copy Previous Week duplication**: New template apply logic will share code with `copyWeek()`; should refactor to shared service
- **Overlay rendering in calendar.js**: May grow large; consider extracting to separate module early

### Implementation Gotchas

- **Syncfusion cell rendering**: Custom overlays require `eventRendered` or CSS-only approaches; can't easily modify cell backgrounds natively
- **Timezone handling**: All shift times stored UTC; overlay aggregation must use store timezone
- **Template employee validation**: Must check against central `kiosk_users` database via `user_employee_links` table
  - Store tables (scheduleShifts, scheduleTemplateShifts) contain store-level `employeeId`
  - To validate employee is active: `user_employee_links` → `users.id` → check `enabled`, `active`, `terminationDate`
  - Query pattern: `SELECT u.enabled, u.active, u.terminationDate FROM kiosk_users.user_employee_links l JOIN kiosk_users.users u ON l.userId = u.id WHERE l.employeeId = ? AND l.typeNum = ?`
  - Employee inactive if: `enabled = 0` OR `active = 0` OR `terminationDate IS NOT NULL`
- **Time-off request PK**: Uses `requestId` (not `timeOffRequestId`) in `scheduleTimeOffRequests` table
- **Shift timestamps**: `scheduleShifts.shiftStart` and `shiftEnd` are DATETIME (combined), not separate date/time columns

## Test Specifications

### Critical Test Scenarios

**Scenario 1: Save Template from Current Week**
```gherkin
Given: A schedule with 15 shifts for the current week
And: User has scheduling permissions
When: User clicks "Save as Template" and enters name "Standard Week"
Then: Template is created with 15 template shifts
And: Each shift captures: dayOfWeek, startTime, endTime, employeeId, positionId
And: Success toast shows "Template 'Standard Week' saved with 15 shifts"
```

**Scenario 2: Load Template with Employee Conflict**
```gherkin
Given: A template with 10 shifts, 2 assigned to terminated employee "Taylor"
When: User loads template for next week
Then: Preview shows "2 shifts converted to open shifts (employee no longer active)"
And: Taylor's shifts appear in conflict table with "Employee Inactive" status
And: Default action is "Create as Open Shift"
```

**Scenario 3: Load Template with Time-Off Conflict**
```gherkin
Given: A template with shift for Marcus on Tuesday
And: Marcus has approved time-off for next Tuesday
When: User previews template load
Then: Marcus's Tuesday shift shows "Time-Off Conflict" status
And: Action options are: "Skip" or "Create as Open Shift" (NOT "Create Anyway")
```

**Scenario 4: Availability Overlay Toggle**
```gherkin
Given: Calendar is showing Timeline Week view
And: 5 employees have availability patterns set
When: User enables "Availability" overlay from Overlays dropdown
Then: Each employee row shows color-coded background for their availability
And: Green = available, Red = unavailable, Gray = no data
And: Legend bar appears at bottom showing color meanings
```

**Scenario 5: Create Recurring Shift**
```gherkin
Given: Calendar in week view
When: User creates a shift for Marcus on Monday 9am-5pm
And: User clicks "Make Recurring"
And: User selects Mon, Wed, Fri
And: User sets end date to 4 weeks out
Then: System creates 12 shifts (3 days × 4 weeks)
And: All shifts show repeat icon
And: Parent shift stores recurrence rule
```

**Scenario 6: Edit Recurring Shift (Future Only)**
```gherkin
Given: A recurring shift series (Mon/Wed/Fri for 4 weeks)
When: User edits the Wednesday in week 2
And: User changes time to 10am-6pm
And: User selects "This and future shifts"
Then: Week 2 Wed and all future Wed/Fri/Mon shifts update to 10am-6pm
And: Week 1 shifts remain unchanged
```

### Test Coverage Requirements

- **Business Logic**:
  - Template CRUD operations
  - Conflict detection (employee, availability, time-off)
  - Recurrence expansion and series management
  - Overlay data aggregation with timezone handling

- **User Interface**:
  - Template dropdown and modals
  - Overlay toggle and rendering
  - Conflict table interactions
  - Recurrence editor

- **Integration Points**:
  - Template apply to shift repository
  - Overlay data from availability/analytics repositories
  - Employee validation against central database

- **Edge Cases**:
  - Empty template (no shifts)
  - Template with all inactive employees
  - Overlay with no historical data
  - Recurring shift crossing DST boundary

---

## Glossary

### Domain Terms

| Term | Definition | Context |
|------|------------|---------|
| Template | A saved pattern of weekly shifts that can be applied to future weeks | Core feature for schedule reuse |
| Open Shift | A shift not assigned to any employee, available for claiming | Used when template employees unavailable |
| Overlay | A visual layer on the calendar showing additional context data | Availability, time-off, analytics |
| Recurrence | A shift pattern that repeats on specific days of the week | E.g., "Every Mon/Wed/Fri" |

### Technical Terms

| Term | Definition | Context |
|------|------------|---------|
| RRULE | iCalendar recurrence rule format | Used to store recurrence patterns |
| Heat Map | Color gradient visualization based on data intensity | Wait time and sales overlays |
| dayOfWeek | Integer 0-6 representing Sunday through Saturday | Template shift storage |
| Parent Shift | The first shift in a recurring series; stores the recurrence rule | Series management |

### API/Interface Terms

| Term | Definition | Context |
|------|------------|---------|
| Preview | Read-only view of what template load would create with conflicts | `POST /templates/:id/preview` |
| Apply | Execute template load, creating actual shifts | `POST /templates/:id/apply` |
| Conflict Resolution | User's decision for each conflicting shift (create/skip/open) | Part of apply request |
