# Phase 3: Repository Implementation - Complete

## Overview
Successfully implemented all 4 repository classes for the Goals module using TDD approach (tests first, then implementation).

## Files Created

### Implementation (4 files)
1. `src/BuyerKiosk/Goals/Repositories/SalesHistoryRepository.php` (149 lines)
2. `src/BuyerKiosk/Goals/Repositories/GoalConfigRepository.php` (133 lines)
3. `src/BuyerKiosk/Goals/Repositories/GoalForecastRepository.php` (221 lines)
4. `src/BuyerKiosk/Goals/Repositories/GoalConfigAuditRepository.php` (336 lines)

### Tests (4 files)
1. `tests/Unit/Goals/Repositories/SalesHistoryRepositoryTest.php` (9 tests)
2. `tests/Unit/Goals/Repositories/GoalConfigRepositoryTest.php` (9 tests)
3. `tests/Unit/Goals/Repositories/GoalForecastRepositoryTest.php` (10 tests)
4. `tests/Unit/Goals/Repositories/GoalConfigAuditRepositoryTest.php` (9 tests)

## Test Results
✅ **37 tests, 204 assertions, all passing**

```
Goal Config Audit Repository: 9 tests ✔
Goal Config Repository: 9 tests ✔
Goal Forecast Repository: 10 tests ✔
Sales History Repository: 9 tests ✔
```

## Repository Details

### 1. SalesHistoryRepository (Store DB)
**Purpose:** Access historical sales data for goal calculations

**Methods:**
- `getSalesForDate(string $date): ?array`
  - Queries dailyCloseReports first, falls back to LiveFinancials
  - Returns ['netSalesRetail' => float, 'buysCost' => float]

- `getAvailableSameDayHistory(int $dayOfWeek, int $monthsBack = 12): array`
  - Returns same-day-of-week averages for Method 0
  - Converts PHP day-of-week (1=Mon...7=Sun) to MySQL DAYOFWEEK (1=Sun...7=Sat)
  - Returns array of ['netSalesRetail', 'buysCost', 'date']

- `getHistoryDepthForDayOfWeek(int $dayOfWeek): int`
  - Returns months of history available for confidence calculation
  - Uses TIMESTAMPDIFF(MONTH, MIN(reportDate), MAX(reportDate))

**Key Pattern:** PHP→MySQL day-of-week conversion
```php
$mysqlDayOfWeek = ($dayOfWeek === 7) ? 1 : $dayOfWeek + 1;
```

### 2. GoalConfigRepository (Central DB - kiosk_buykiosk)
**Purpose:** CRUD operations on goalConfigurations table

**Methods:**
- `getByStoreId(int $storeId): ?GoalConfiguration`
- `getByTypeNum(string $typeNum): ?GoalConfiguration`
- `save(GoalConfiguration $config): int`
  - INSERT...ON DUPLICATE KEY UPDATE pattern
  - Returns existing or new ID
- `getTimeBands(int $storeId): ?array`
  - Returns decoded hourly time bands JSON

**Key Pattern:** Upsert with JSON encoding
```php
INSERT INTO goalConfigurations (...) VALUES (...)
ON DUPLICATE KEY UPDATE
  activeMethod = VALUES(activeMethod),
  methodSettings = VALUES(methodSettings),
  ...
```

### 3. GoalForecastRepository (Store DB)
**Purpose:** Manage goalForecast cache table

**Methods:**
- `getByDate(string $date): ?array`
- `getByDateRange(string $startDate, string $endDate): array`
- `upsert(array $data): bool`
  - Single row upsert
- `batchInsert(array $forecasts): int`
  - Chunked inserts (50 rows per chunk)
  - Handles 365-day forecast generation
- `deleteServerComputed(): int`
  - Removes server-computed goals (preserves sync_fallback)
- `hasServerGoal(string $date): bool`

**Key Pattern:** Batch insert with chunking
```php
const BATCH_CHUNK_SIZE = 50;

foreach (array_chunk($forecasts, self::BATCH_CHUNK_SIZE) as $chunk) {
  // Build multi-value INSERT
}
```

### 4. GoalConfigAuditRepository (Central DB - kiosk_buykiosk)
**Purpose:** Audit logging for configuration changes

**Methods:**
- `logCreate(...)`: int`
- `logUpdate(...)`: int`
- `logMethodSwitch(...)`: int`
- `getHistory(int $storeId, int $page = 1, int $perPage = 20): array`
  - Paginated results
  - LEFT JOIN kiosk_users.users for actor names
- `getSnapshotById(int $auditId): ?array`
- `compareSnapshots(int $auditId1, int $auditId2): array`
  - Recursive diff showing changed/added/removed fields

**Key Pattern:** Audit with UTC timestamps
```php
INSERT INTO goalConfigAudit (..., occurredAt) 
VALUES (..., UTC_TIMESTAMP())
```

## Test Coverage Highlights

### Edge Cases Tested
✅ NULL returns when data not found  
✅ Fallback behavior (dailyCloseReports → LiveFinancials)  
✅ Day-of-week conversion (PHP 7 → MySQL 1 for Sunday)  
✅ Pagination with LIMIT/OFFSET  
✅ Batch insert chunking  
✅ JSON encoding/decoding  
✅ Recursive snapshot comparison  

### PdoMockBuilder Patterns Used
- Fluent query expectations with `expectQuery()`
- Return data mocking with `willReturn()`
- INSERT ID mocking with `withLastInsertId()`
- Row count mocking with `withRowCount()`
- Consecutive calls with `willReturnOnConsecutiveCalls()`

## Design Decisions

1. **Dependency Injection:** All repositories accept PDO in constructor for testability
2. **Type Safety:** All methods have return type hints
3. **JSON Handling:** Encode on write, decode on read with null safety
4. **Batch Operations:** Use chunking to avoid MySQL packet size limits
5. **UTC Timestamps:** Audit entries use UTC_TIMESTAMP() for consistency
6. **Pagination:** Standard page/perPage pattern with total count

## Integration Points

These repositories will be consumed by:
- **Goal Calculation Services** (Phase 4) - use SalesHistoryRepository, GoalForecastRepository
- **Goal Configuration Service** (Phase 4) - use GoalConfigRepository, GoalConfigAuditRepository
- **API Controllers** (Phase 5) - expose repository data via REST endpoints
- **Mobile Apps** - read from goalForecast cache table

## Next Steps (Phase 4)

Implement goal calculation services:
1. `Method0CalculatorService` - Year-over-year growth using SalesHistoryRepository
2. `Method1CalculatorService` - Annual target distribution
3. `Method2CalculatorService` - Month-specific custom targets
4. `GoalComputationService` - Orchestrates method selection and caching
