docs(08-01): complete backend aggregation plan
This commit is contained in:
parent
c28220c83f
commit
3a91f993a0
1 changed files with 89 additions and 0 deletions
|
|
@ -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
|
||||||
Loading…
Add table
Reference in a new issue