Skip to content

feat(reconciliation): GET reconciliation/summary for Afstem stats - #1723

Merged
renemadsen merged 5 commits into
stablefrom
feat/reconciliation-summary
Sep 19, 2026
Merged

renemadsen merged 5 commits into
stablefrom
feat/reconciliation-summary

Conversation

@renemadsen

Copy link
Copy Markdown
Member

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)].

  • Period: PayrollPeriod.LastClosed(UtcNow.Date, CutoffDay) — the most recent closed monthly period from PayrollIntegrationSettings.CutoffDay (default 19, clamped to the month length and 1..31). Pure, oracle-tested for every day of 2027–2028 × every cutoff.
  • Workers in period: a non-removed registration in the period with NettoHoursInSeconds > 0 || Start1StartedAt != null || PlanHoursInSeconds > 0 || PlanHours > 0. Worked time comes from the one-minute-interval fields only (all customers run UseOneMinuteIntervals=true); planned time is PlanHours, the column normal planning writers set.
  • Lock: boundaries only from DayLockHelper.LockedThroughForSitesAsync / IsLocked; BoundaryRows goes private → internal (visibility only — the service-plugin twin is unaffected).
  • Response (OperationDataResult, camelCase): cutoffDay, periodStart, periodEnd, workersInPeriod, workersLockedThroughPeriod, workersNeverReconciled, coveragePercent, oldestBoundary, workersWithAnyReconciled, lastReconciledAt — dates yyyy-MM-dd, lastReconciledAt UTC with Z. Errors: 200 + success:false. Pinned by ReconciliationSummaryContractTests.

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

  • A day with only a NettoHoursOverride and no stamps does not count a worker.
  • No new index: 2–3 scans of PlanRegistrations per 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

renemadsen and others added 5 commits September 19, 2026 09:28
…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>
Copilot AI lite review requested due to automatic review settings September 19, 2026 08:30

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 Low severity

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 new ReconciliationSummaryService.
  • 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 DayLockHelper for 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.

Comment on lines +11 to +15
**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.
Comment on lines +56 to +60
// 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.
@renemadsen
renemadsen merged commit fe24f22 into stable Sep 19, 2026
41 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants