feat(reconciliation): GET reconciliation/summary for Afstem stats - #1723
Conversation
…payroll period Pure function, no database: the latest cutoff day strictly before today (UTC), cutoff clamped to the month length and to 1..31. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…verage for the last closed period Counts workers with one-minute-interval time in the period (PlanHoursInSeconds/NettoHoursInSeconds > 0 or a Start1StartedAt stamp), classifies them locked/behind/never via DayLockHelper boundaries, and reports adoption (workers with any reconciled day, last ReconciledAt in UTC). CutoffDay from PayrollIntegrationSettings, default 19. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Thin [Authorize] controller over IReconciliationSummaryService, registered in DI. Contract tests pin the camelCase OperationDataResult wire shape the my-microting customer-stats scan consumes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…on query PlanHours > 0 now counts a worker in the period: normal planning writers only set the PlanHours column, so planned-but-absent workers were missing from the denominator. The endpoint requires the eForm Admin role like the payroll endpoints (worker phones share the same JWT kind). The two adoption queries are folded into one aggregate. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The checked-in plan document and one service comment contain verified inconsistencies with the implemented auth and hours predicate, which should be corrected to keep documentation and contract guidance reliable.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 4
Open (4)
What changed in this PR
Adds a new read-only reconciliation (“Afstem”) summary endpoint to the TimePlanning plugin API so external consumers (my-microting customer-stats scan / portal) can retrieve last-closed payroll-period reconciliation coverage, lag, and adoption metrics computed server-side next to the existing locking logic.
Changes:
- Introduces
GET /api/time-planning-pn/reconciliation/summary(Admin-only) backed by a newReconciliationSummaryService. - Adds pure payroll-period calculation (
PayrollPeriod.LastClosed) plus contract/DB-backed tests to pin the wire shape and counting semantics. - Updates CI shard filters to include the new test fixtures and adds an internal visibility change in
DayLockHelperfor shared boundary-row semantics.
| File | Description |
|---|---|
| eFormAPI/Plugins/TimePlanning.Pn/TimePlanning.Pn/Services/ReconciliationSummaryService/ReconciliationSummaryService.cs | Implements the summary query/aggregation over last closed period and lock boundaries. |
| eFormAPI/Plugins/TimePlanning.Pn/TimePlanning.Pn/Services/ReconciliationSummaryService/IReconciliationSummaryService.cs | Defines the service interface for the summary endpoint. |
| eFormAPI/Plugins/TimePlanning.Pn/TimePlanning.Pn/Infrastructure/Models/Reconciliation/ReconciliationSummaryModel.cs | Adds the DTO/wire contract model for the summary response. |
| eFormAPI/Plugins/TimePlanning.Pn/TimePlanning.Pn/Infrastructure/Helpers/PayrollPeriod.cs | Adds pure helper to compute the last closed monthly payroll period given a cutoff day. |
| eFormAPI/Plugins/TimePlanning.Pn/TimePlanning.Pn/Infrastructure/Helpers/DayLockHelper.cs | Changes BoundaryRows visibility to internal for reuse by the summary service. |
| eFormAPI/Plugins/TimePlanning.Pn/TimePlanning.Pn/EformTimePlanningPlugin.cs | Registers the new summary service in DI. |
| eFormAPI/Plugins/TimePlanning.Pn/TimePlanning.Pn/Controllers/ReconciliationSummaryController.cs | Adds the Admin-authorized endpoint routing to the summary service. |
| eFormAPI/Plugins/TimePlanning.Pn/TimePlanning.Pn.Test/ReconciliationSummaryServiceTests.cs | Adds MariaDB-backed tests validating counting/locking/adoption semantics. |
| eFormAPI/Plugins/TimePlanning.Pn/TimePlanning.Pn.Test/ReconciliationSummaryContractTests.cs | Pins HTTP contract, camelCase serialization, and controller auth/route attributes. |
| eFormAPI/Plugins/TimePlanning.Pn/TimePlanning.Pn.Test/PayrollPeriodTests.cs | Adds pure tests for payroll-period arithmetic across many dates/cutoffs. |
| docs/superpowers/plans/2026-09-19-reconciliation-summary-endpoint.md | Adds the implementation plan document for the endpoint. |
| .github/workflows/dotnet-core-pr.yml | Adds new fixtures to shard filters so they run in PR CI. |
| .github/workflows/dotnet-core-master.yml | Adds new fixtures to shard filters so they run in master CI. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| **Spec:** `/home/rene/Documents/workspace/microting/docker/angular-my-microting-plugin/.claude/worktrees/afstem-stats/docs/superpowers/specs/2026-09-19-afstem-stats-design.md`. This plan covers §3 and the timeplanning half of §7. §4–§6 (my-microting) are a separate plan, and that plan consumes the **Wire contract** section below. | ||
|
|
||
| ## Global Constraints | ||
|
|
||
| - Repo/worktree: `/home/rene/Documents/workspace/microting/eform-angular-timeplanning-plugin-summary`, branch `feat/reconciliation-summary` (off `origin/stable`). The PR targets `stable`. Never commit to `stable`. |
| - Repo/worktree: `/home/rene/Documents/workspace/microting/eform-angular-timeplanning-plugin-summary`, branch `feat/reconciliation-summary` (off `origin/stable`). The PR targets `stable`. Never commit to `stable`. | ||
| - Not in dev mode: edit the source plugin repo directly. Do not run `devinstall.sh`. | ||
| - All paths below are relative to the repo root. `P` = `eFormAPI/Plugins/TimePlanning.Pn`. | ||
| - Route: `GET /api/time-planning-pn/reconciliation/summary`, `[Authorize]`, read-only. It never writes. |
|
|
||
| ## Spec deviations / clarifications found in the real code | ||
|
|
||
| 1. **Hours predicate: one-minute mode only (user decision 2026-09-19).** Every customer is forced to `UseOneMinuteIntervals=true`, so the predicate is `PlanHoursInSeconds > 0 || NettoHoursInSeconds > 0 || Start1StartedAt != null`. The legacy 5-minute `PlanHours`/`NettoHours` doubles are deliberately not consulted; tests pin both directions. |
| // time comes from the PlanHours double, not PlanHoursInSeconds: | ||
| // normal planning writers only ever set PlanHours -- the seconds | ||
| // column is written only after a content handover -- so requiring | ||
| // PlanHoursInSeconds would miss ordinary planned-but-never-clocked | ||
| // workers. |

Why
my-microting's customer-stats scan and customer portal will show how well each customer keeps its payroll periods closed ("Afstem"): coverage, lag and adoption. The scan reaches every customer over HTTPS, so the numbers are computed here, next to
DayLockHelper, instead of being re-implemented in my-microting.What
GET /api/time-planning-pn/reconciliation/summary— read-only,[Authorize(Roles = EformRole.Admin)].PayrollPeriod.LastClosed(UtcNow.Date, CutoffDay)— the most recent closed monthly period fromPayrollIntegrationSettings.CutoffDay(default 19, clamped to the month length and 1..31). Pure, oracle-tested for every day of 2027–2028 × every cutoff.NettoHoursInSeconds > 0 || Start1StartedAt != null || PlanHoursInSeconds > 0 || PlanHours > 0. Worked time comes from the one-minute-interval fields only (all customers runUseOneMinuteIntervals=true); planned time isPlanHours, the column normal planning writers set.DayLockHelper.LockedThroughForSitesAsync/IsLocked;BoundaryRowsgoes private → internal (visibility only — the service-plugin twin is unaffected).OperationDataResult, camelCase):cutoffDay, periodStart, periodEnd, workersInPeriod, workersLockedThroughPeriod, workersNeverReconciled, coveragePercent, oldestBoundary, workersWithAnyReconciled, lastReconciledAt— datesyyyy-MM-dd,lastReconciledAtUTC withZ. Errors: 200 +success:false. Pinned byReconciliationSummaryContractTests.Tests
PayrollPeriodTests(pure),ReconciliationSummaryContractTests(pure),ReconciliationSummaryServiceTests(Testcontainers MariaDB) — all added to the CI shards (g, g, a). Run locally: all green incl. the DB fixture (14/14).Known / deliberate
NettoHoursOverrideand no stamps does not count a worker.PlanRegistrationsper call, once per customer per day under the scan's 30 s timeout. Revisit if a large customer approaches it.Spec: angular-my-microting-plugin
docs/superpowers/specs/2026-09-19-afstem-stats-design.md· Plan:docs/superpowers/plans/2026-09-19-reconciliation-summary-endpoint.md🤖 Generated with Claude Code