# Phase 2: Plugin Scaffold + Data Layer — Plan
**Goal:** Plugin shows a widget on the dashboard and serves aggregated daily time data via API.
**Requirements:** PLUG-01 (done), PLUG-02, PLUG-03, TEST-01, TEST-02
**Research:** phase-2/02-RESEARCH.md
## Success Criteria
1. Kimai discovers the plugin and lists it in the plugin admin page (DONE — Phase 1)
2. A widget placeholder appears on the Kimai dashboard
3. The API endpoint returns JSON with per-day aggregated hours and entry counts, correctly grouped by the user's timezone
4. PHPUnit tests pass for the data aggregation service and API endpoint
## Key Decisions from Research
- Extend `AbstractWidget` (not `AbstractWidgetType`) — same pattern as `DailyWorkingTimeChart`
- Query `t.date` (the `date_tz` column) for timezone-correct day grouping — no manual conversion needed
- Simple `JsonResponse` controller, NOT FOS Rest Bundle — cleaner for plugin
- Unit tests with mocked repository for Phase 2 — integration tests deferred
- `DashboardSubscriber` needed for widget to appear on default dashboard
## Waves
### Wave 1: Data Layer + Tests (autonomous)
#### Plan 02-01: HeatmapService, PHPUnit Tests, and API Endpoint
**Objective:** Build the data aggregation service with tests, then the API controller.
**Task 1: Update services.yaml for autowiring**
Update `Resources/config/services.yaml` to enable autowiring and autoconfigure for the plugin namespace:
```yaml
services:
_defaults:
autowire: true
autoconfigure: true
KimaiPlugin\KimaiHeatmapBundle\:
resource: '../../{Controller,Service,Widget,EventSubscriber}/'
KimaiPlugin\KimaiHeatmapBundle\KimaiHeatmapBundle:
tags: ['App\Plugin\PluginInterface']
```
**Task 2: Create HeatmapService**
Create `Service/HeatmapService.php`:
```php
*/
public function getDailyAggregation(User $user, \DateTimeInterface $begin, \DateTimeInterface $end, ?int $projectId = null): array
{
$qb = $this->repository->createQueryBuilder('t');
$qb
->select('COALESCE(SUM(t.duration), 0) as duration')
->addSelect('COUNT(t.id) as count')
->addSelect('DATE(t.date) as day')
->andWhere($qb->expr()->between('t.date', ':begin', ':end'))
->andWhere($qb->expr()->eq('t.user', ':user'))
->andWhere($qb->expr()->isNotNull('t.end'))
->setParameter('begin', $begin->format('Y-m-d'))
->setParameter('end', $end->format('Y-m-d'))
->setParameter('user', $user)
->groupBy('day')
->orderBy('day', 'ASC')
;
if ($projectId !== null) {
$qb->andWhere($qb->expr()->eq('t.project', ':project'))
->setParameter('project', $projectId);
}
$results = $qb->getQuery()->getResult();
return array_map(function (array $row) {
return [
'date' => $row['day'],
'hours' => round((int) $row['duration'] / 3600, 2),
'count' => (int) $row['count'],
];
}, $results);
}
}
```
**Task 3: Create PHPUnit config and tests**
Create `Tests/phpunit.xml`:
```xml
Heatmap visualization coming in Phase 3
{% endblock %} {% endembed %} ``` **Task 3: Create DashboardSubscriber** Create `EventSubscriber/DashboardSubscriber.php`: ```php ['onDashboard', 100], ]; } public function onDashboard(DashboardEvent $event): void { $widget = $this->widgetService->getWidget('HeatmapWidget'); if ($widget !== null) { $event->addWidget($widget); } } } ``` **Commit:** `feat: add dashboard widget with placeholder template` **Task 4: Verify (CHECKPOINT — requires manual verification)** 1. Clear Kimai cache: `cd dev/kimai && bin/console cache:clear` 2. Start dev stack: `process-compose -f dev/process-compose.yaml -p 0 up` 3. Open browser: `http://127.0.0.1:8010` 4. Login: `susan_super` / `password` **Verification checklist:** - [ ] "Activity Heatmap" widget appears on the dashboard - [ ] Widget shows placeholder text "Heatmap visualization coming in Phase 3" - [ ] Visiting `/heatmap/data` returns JSON with `days` array and `range` object - [ ] JSON `days` entries have `date`, `hours`, `count` fields - [ ] PHPUnit tests pass: `php dev/kimai/vendor/bin/phpunit --configuration Tests/phpunit.xml` ## Requirement Coverage | Requirement | Plan | Verified By | |-------------|------|-------------| | PLUG-01 | Phase 1 | Already done | | PLUG-02 | 02-02 | Widget visible on dashboard | | PLUG-03 | 02-01 | API returns JSON; PHPUnit test | | TEST-01 | 02-01 | HeatmapServiceTest passes | | TEST-02 | 02-01 | HeatmapControllerTest passes | ## Risks | Risk | Mitigation | |------|------------| | `@theme/embeds/card.html.twig` doesn't exist | Fall back to plain `