# Schedule System Implementation - Backend

## Overview
The Schedule System backend provides a unified interface for retrieving employee schedules from external providers (WhenIWork, Homebase). It includes caching at both Redis and database levels, clocked-in status enrichment, and a provider abstraction pattern for future extensibility.

## Files Created

### 1. Migration File
**Location:** `userfrosting/migrations/input/20250305_001_daybook_schedule_cache.json`

Creates the `daybook_schedule_cache` table for persistent storage of schedule data:
- Stores shifts with employee ID mapping
- Tracks provider source (wheniwork, homebase, manual)
- Includes shift times, position, and notes
- Indexed for efficient date-based queries

**To Run:**
```bash
cd userfrosting && php conductor run
```

### 2. Abstract Provider Base Class
**Location:** `userfrosting/models/Class/Daybook/ScheduleProvider.php`
**Namespace:** `BuyerKiosk\Daybook`

Abstract base class that all schedule providers extend. Provides:
- Redis caching infrastructure (5-minute TTL)
- Database connection management
- Cache key generation based on store + date
- Abstract methods that concrete providers must implement:
  - `getScheduleForDate(\DateTime $date): array`
  - `getEmployeeShift(int $employeeId, \DateTime $date): ?array`
  - `syncScheduleToCache(\DateTime $date): bool`
  - `getProviderName(): string`
  - `isEnabled(): bool`

### 3. WhenIWork Provider Implementation
**Location:** `userfrosting/models/Class/Daybook/WhenIWorkSchedule.php`
**Namespace:** `BuyerKiosk\Daybook`

Concrete implementation for WhenIWork integration:
- Checks if WhenIWork is enabled via `$store->getWiwEnable()`
- Fetches shifts from WhenIWork API using the existing `\Wheniwork` client
- Maps WhenIWork user IDs to local employee IDs using:
  - New employee system: `externalId` field with `source='wheniwork'`
  - Legacy system: `employeeID` column
- Formats shift data into standardized structure
- Implements two-tier caching (Redis + database)

**Key Methods:**
- `fetchShiftsFromAPI(\DateTime $date)` - Retrieves shifts from WhenIWork API
- `formatShifts(array $shifts)` - Converts API response to internal format
- `getLocalEmployeeId(int $wiwUserId)` - Maps WiW user ID to local employee ID
- `formatDateTime(string $wiwDateTime)` - Parses WiW datetime format

### 4. Homebase Provider Stub
**Location:** `userfrosting/models/Class/Daybook/HomebaseSchedule.php`
**Namespace:** `BuyerKiosk\Daybook`

Placeholder implementation with TODO comments for future Homebase integration. Currently returns empty arrays and disabled status.

### 5. Schedule Manager
**Location:** `userfrosting/models/Class/Daybook/ScheduleManager.php`
**Namespace:** `BuyerKiosk\Daybook`

High-level orchestration class that:
- Resolves which provider to use (priority: WhenIWork, then Homebase)
- Provides convenience methods for today's schedule
- Enriches shift data with clocked-in status from `FinancialsController`
- Handles cache refresh operations
- Returns standardized response format

**Response Format:**
```php
[
    'enabled' => true,
    'provider' => 'wheniwork',
    'date' => '2025-03-05',
    'shifts' => [
        [
            'employeeId' => 123,
            'providerEmployeeId' => '456',
            'shiftStart' => '2025-03-05 09:00:00',
            'shiftEnd' => '2025-03-05 17:00:00',
            'position' => 'Buyer',
            'notes' => null,
            'provider' => 'wheniwork',
            'isClockedIn' => true
        ]
    ]
]
```

**Clocked-In Status:**
- Uses existing `FinancialsController` to get current labor data
- Maps WhenIWork user IDs to local employee IDs
- Adds `isClockedIn` flag to each shift

### 6. API Controller
**Location:** `userfrosting/controllers/Daybook/ScheduleApiController.php`
**Namespace:** `BuyerKiosk\Daybook`

REST API controller with three endpoints:
- `getTodaySchedule(string $typeNum)` - Get schedule for today
- `getScheduleForDate(string $typeNum, string $date)` - Get schedule for specific date
- `refreshSchedule(string $typeNum)` - Clear cache and re-sync from provider

All methods return JSON responses with error handling.

### 7. Routes Configuration
**Location:** `userfrosting/routes/daybook/schedule.php`

Defines three REST API endpoints:
- `GET /api/:typeNum/daybook/schedule/` - Today's schedule
- `GET /api/:typeNum/daybook/schedule/:date/` - Schedule for specific date (Y-m-d format)
- `POST /api/:typeNum/daybook/schedule/refresh/` - Refresh cache

All routes:
- Require store access via `checkAccessAndReturnStoreObject()`
- Use `typeNum` pattern matching: `[a-z]{2}\d+`
- Return JSON responses

**Route Registration:**
Added to `public_html/index.php` after existing daybook routes:
```php
include("../userfrosting/routes/daybook/schedule.php");
```

## Architecture

### Provider Pattern
The system uses an abstract provider pattern to support multiple scheduling systems:
1. `ScheduleProvider` - Abstract base class with common caching logic
2. Concrete providers (WhenIWorkSchedule, HomebaseSchedule) implement specific integrations
3. `ScheduleManager` handles provider resolution and orchestration

### Caching Strategy
Two-tier caching for optimal performance:
1. **Redis Cache** (5-minute TTL) - Fast, temporary storage
2. **Database Cache** (`daybook_schedule_cache` table) - Persistent fallback

Cache flow:
1. Check Redis cache
2. If miss, fetch from provider API
3. Store in both Redis and database
4. Return data

### Employee ID Mapping
Supports both legacy and new employee systems:
- **New System:** Matches `externalId` where `source='wheniwork'`
- **Legacy System:** Falls back to `employeeID` column
- Graceful handling when employees not found (logs error, skips shift)

## API Usage Examples

### Get Today's Schedule
```bash
GET /api/ou00/daybook/schedule/
```

Response:
```json
{
  "success": true,
  "data": {
    "enabled": true,
    "provider": "wheniwork",
    "date": "2025-03-05",
    "shifts": [
      {
        "employeeId": 123,
        "providerEmployeeId": "456",
        "shiftStart": "2025-03-05 09:00:00",
        "shiftEnd": "2025-03-05 17:00:00",
        "position": "Buyer",
        "notes": null,
        "provider": "wheniwork",
        "isClockedIn": true
      }
    ]
  }
}
```

### Get Schedule for Specific Date
```bash
GET /api/ou00/daybook/schedule/2025-03-10/
```

### Refresh Schedule Cache
```bash
POST /api/ou00/daybook/schedule/refresh/
```

Response:
```json
{
  "success": true,
  "message": "Schedule refreshed successfully",
  "provider": "wheniwork"
}
```

## Database Schema

### daybook_schedule_cache Table
```sql
CREATE TABLE `daybook_schedule_cache` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `date` date NOT NULL,
  `employeeId` int(10) unsigned NOT NULL,
  `providerEmployeeId` varchar(50) DEFAULT NULL COMMENT 'External ID from WiW/Homebase',
  `provider` enum('wheniwork', 'homebase', 'manual') DEFAULT 'wheniwork',
  `shiftStart` datetime NOT NULL,
  `shiftEnd` datetime NOT NULL,
  `position` varchar(100) DEFAULT NULL,
  `notes` text DEFAULT NULL,
  `cachedAt` timestamp DEFAULT current_timestamp(),
  PRIMARY KEY (`id`),
  KEY `idx_date_employee` (`date`, `employeeId`),
  KEY `idx_date` (`date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
```

## Dependencies

### Existing Classes Used
- `\Store` - Store configuration and integration settings
- `\Wheniwork` - WhenIWork API client (from `userfrosting/lib/wheniwork-api/`)
- `\BuyerKiosk\WhenIWork\FinancialsController` - Labor data for clocked-in status
- `\Predis\Client` - Redis client for caching
- `dbConnectByName()` - Database connection helper from BaseModel
- `checkAccessAndReturnStoreObject()` - Authorization helper

### Store Methods Used
- `getTypeNum()` - Store identifier
- `getDbName()` - Database name
- `getTimeZone()` - Store timezone
- `getWiwEnable()` - WhenIWork enabled flag
- `getWiwToken()` - WhenIWork API token
- `getWiwLocationID()` - WhenIWork location ID

## Error Handling

All classes implement comprehensive error handling:
- Try-catch blocks around API calls and database operations
- Error logging via `error_log()` for debugging
- Graceful degradation (return empty arrays on failure)
- HTTP 500 errors with JSON error messages in API endpoints
- Validation of date formats before processing

## Testing Checklist

### Manual Testing
1. **Migration:**
   - [ ] Run migration: `cd userfrosting && php conductor run`
   - [ ] Verify table created: `SHOW TABLES LIKE 'daybook_schedule_cache'`

2. **WhenIWork Integration (stores with WiW enabled):**
   - [ ] GET `/api/:typeNum/daybook/schedule/` returns today's shifts
   - [ ] Verify `isClockedIn` status matches current clock-ins
   - [ ] Check Redis cache is populated (5-minute TTL)
   - [ ] Verify database cache has entries
   - [ ] Test specific date endpoint with future/past dates

3. **Cache Refresh:**
   - [ ] POST `/api/:typeNum/daybook/schedule/refresh/` clears and rebuilds cache
   - [ ] Verify new data appears after changes in WhenIWork

4. **Error Cases:**
   - [ ] Store without WhenIWork enabled returns `enabled: false`
   - [ ] Invalid date format returns 400 error
   - [ ] Employee ID mapping works for both new and legacy systems

### Integration Testing
- [ ] Verify no conflicts with existing Daybook endpoints
- [ ] Check autoload includes new classes (run `composer dump-autoload`)
- [ ] Test with multiple stores/timezones

## Future Enhancements

1. **Homebase Integration:**
   - Implement API client for Homebase
   - Complete `HomebaseSchedule` class methods
   - Add Homebase API credentials to Store model

2. **Manual Schedule Entry:**
   - Add endpoints for creating manual shifts
   - UI for managers to override/supplement provider data

3. **Multi-day Range Queries:**
   - Support date range parameters (e.g., next 7 days)
   - Weekly view aggregation

4. **Schedule Notifications:**
   - Alert employees of schedule changes
   - Integration with SMS/push notifications

5. **Conflict Detection:**
   - Detect overlapping shifts
   - Flag unusual scheduling patterns

## Notes

- All classes use protected visibility for properties/methods that need to be accessed by child classes (not private)
- Employee ID lookup supports both `externalId` (new system) and `employeeID` (legacy) columns
- Redis cache TTL is configurable via `$cacheTTL` property (default 300 seconds)
- Database cache entries are replaced on each sync (DELETE + INSERT pattern)
- Timezone handling uses store's configured timezone for all date operations
- WhenIWork datetime format: "Day, DD Mon YYYY HH:MM:SS O"
