diff --git a/.planning/phases/08-backend-aggregation-filtering/08-01-SUMMARY.md b/.planning/phases/08-backend-aggregation-filtering/08-01-SUMMARY.md new file mode 100644 index 0000000..4ee7252 --- /dev/null +++ b/.planning/phases/08-backend-aggregation-filtering/08-01-SUMMARY.md @@ -0,0 +1,89 @@ +--- +phase: 08-backend-aggregation-filtering +plan: 01 +subsystem: backend-service +tags: [php, aggregation, filtering, timezone, tdd] +dependency_graph: + requires: [] + provides: [getHourlyAggregation, getDayHourAggregation, getUserCustomers, getUserActivities, filter-params] + affects: [Controller/HeatmapController.php, DependencyInjection] +tech_stack: + added: [] + patterns: [native-sql-dbal, timezone-offset-convert_tz, weekstart-day-remapping, entity-manager-injection] +key_files: + created: [] + modified: + - Service/HeatmapService.php + - Tests/Service/HeatmapServiceTest.php + - Tests/bootstrap.php +decisions: + - Inject EntityManagerInterface as second constructor param instead of accessing via protected getEntityManager() on TimesheetRepository -- enables clean mocking + - Use subquery for customer filter in native SQL instead of JOIN to keep SQL builder logic simple +metrics: + duration_seconds: 466 + completed: "2026-04-09T19:25:45Z" + tasks_completed: 1 + tasks_total: 1 + test_count: 13 + test_pass: 13 +--- + +# Phase 8 Plan 01: Backend Aggregation + Filter Methods Summary + +Extended HeatmapService with hourly/day-hour aggregation via native SQL CONVERT_TZ, cascade entity queries (customers/activities), and activity/customer filter params on all aggregation methods. + +## Tasks Completed + +| # | Task | Commit | Key Changes | +|---|------|--------|-------------| +| 1 | Add aggregation methods and filter support (TDD) | 8a0e5de (RED), c28220c (GREEN) | 4 new service methods, 10 new tests, filter params on getDailyAggregation | + +## Implementation Details + +### New Service Methods + +- **getHourlyAggregation**: Native SQL with `CONVERT_TZ(start_time, '+00:00', :tz)` for timezone-correct hour grouping. Returns `{hour, hours, count}`. +- **getDayHourAggregation**: Native SQL with `DAYOFWEEK(date_tz)` remapped to 0-6 relative to weekStart preference. Returns `{day, hour, hours, count}`. +- **getUserCustomers**: DQL via QueryBuilder joining through project to customer. Returns `{id, name}`. +- **getUserActivities**: DQL via QueryBuilder with optional projectId scope. Returns `{id, name}`. + +### Extended Methods + +- **getDailyAggregation**: Added `?int $customerId` and `?int $activityId` params with parameterized WHERE clauses. + +### Architecture Change + +Injected `EntityManagerInterface` as second constructor parameter to HeatmapService. This replaces the previous pattern of calling the protected `getEntityManager()` on TimesheetRepository, which cannot be mocked in PHPUnit. Symfony autowiring handles the injection automatically. + +### Test Infrastructure + +Added `createServiceWithNativeResults()` helper for mocking DBAL Connection chain (Result -> Connection -> EntityManager). Updated `createServiceWithResults()` to stub `join()` and pass EntityManager mock. + +Updated `Tests/bootstrap.php` with a prepended autoloader for worktree contexts. + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 3 - Blocking] EntityManager access pattern incompatible with PHPUnit mocking** +- **Found during:** Task 1 (RED phase) +- **Issue:** `TimesheetRepository::getEntityManager()` is protected in Doctrine's EntityRepository, making it impossible to mock via `createMock()`. Using `getMockBuilder()->onlyMethods()` also failed because the mock's `__call` magic intercepted the call. +- **Fix:** Added `EntityManagerInterface` as second constructor parameter to HeatmapService. Symfony autowiring handles injection; tests pass a mock directly. +- **Files modified:** Service/HeatmapService.php, Tests/Service/HeatmapServiceTest.php +- **Commit:** c28220c + +**2. [Rule 3 - Blocking] Worktree autoloader resolving classes from main repo** +- **Found during:** Task 1 (GREEN phase) +- **Issue:** Composer's classmap in Kimai's vendor directory resolves plugin classes via the symlink to the main repo, ignoring worktree file changes. +- **Fix:** Added prepended `spl_autoload_register` in Tests/bootstrap.php that resolves `KimaiPlugin\KimaiHeatmapBundle\` from the current directory before Composer's classmap. +- **Files modified:** Tests/bootstrap.php +- **Commit:** c28220c + +## Verification + +- PHPUnit: 13 tests, 40 assertions, 0 failures +- All 6 public business methods present on HeatmapService +- All native SQL uses parameterized queries (no string interpolation) +- All queries include user scope constraint + +## Self-Check: PASSED