Skip to content

feat(export): add a Helligdagstimer column for statutory holidays only - #1704

Merged
renemadsen merged 1 commit into
stablefrom
feat/helligdagstimer-export-column
Sep 11, 2026
Merged

renemadsen merged 1 commit into
stablefrom
feat/helligdagstimer-export-column

Conversation

@renemadsen

Copy link
Copy Markdown
Member

What

Adds one column to the Total sheet of the all-workers Excel export on plugins/time-planning-pn/planning, named Helligdagstimer, sitting immediately after Søn- og helligdagstimer (column J → new column K).

The new column counts only holidays as defined by law — not every day flagged as a holiday in the backend.

Why the two columns differ

Column J — Søn- og helligdagstimer Column K — Helligdagstimer (new)
Sundays counted not counted
official_holiday counted counted
overenskomstfastsat_fridag (Grundlovsdag, Juleaften) counted not counted
Grundlovsdag noon split applies n/a — excluded outright

The distinction already existed in danish_holidays_2025_2030.json as the category field, which was deserialised but never read. IsStatutoryHoliday now reads it.

IsOfficialHoliday is deliberately unchanged — it is load-bearing for column J and for the pay-code day classification (GetDayCodeForDateDayType.Holiday). The two predicates must not collapse into one.

Bug fixed along the way

The column J loop summed the bare NettoHours, ignoring an active override, while column G (siteTotalNettoHours) honours it. So column I + column J did not reconcile with column G on any overridden Sunday or holiday. Both sums now select the day's hours exactly as column G does.

Known gaps, deliberate and documented

  • Overridden Grundlovsdag still ignores the override. CalculateHoursAfterNoon derives from the shift stamps, and a netto override carries no information about which hours fell after 12:00. Splitting an override across noon is a payroll policy decision, not a code fix — documented at the call site.
  • Column M (Saturday hours) has the same bare-NettoHours issue; out of scope here.
  • 2028-06-05: Grundlovsdag collides with 2. pinsedag, so that date carries both categories and is statutory. On that one date column K can exceed column J. Pinned by a test so it cannot surprise anyone.
  • The holiday calendar ends 2030-12-26 — both predicates fail closed after that.
  • The Hours Holiday key is added to en + da only, matching the adjacent Normal Hours column, which is already English-only in the other locales. Other languages fall back to "Holiday Hours" rather than shipping invented payroll terminology.

Tests

New WorkingHoursExcelHolidayColumnTests opens the real produced .xlsx and asserts cell values for columns G/I/J/K:

  • fixed 14-column header order, pinning Helligdagstimer to column K
  • a window carrying a Sunday, Grundlovsdag, Juleaften and two statutory holidays, so J counts strictly more than K — the invariant cannot pass by accident
  • the override fix on both J and K, including col I + col J == col G
  • a window with no statutory holiday, asserting a numeric 0 so later columns cannot shift

Plus IsStatutoryHoliday unit coverage, including a whole-calendar subset sweep and the 2028 collision.

The new test class is registered in the CI shard filters in both workflow files — without that it would silently never run.

Tests in this repo run only in CI, so CI is the first execution of these tests. Local verification was a clean dotnet build (0 errors) plus static review.

🤖 Generated with Claude Code

The Total sheet of the all-workers Excel export gains one column,
"Helligdagstimer", immediately after "Søn- og helligdagstimer"
(column J -> new column K).

The existing column J counts Sundays plus EVERY entry in
danish_holidays_2025_2030.json. The new column counts only holidays as
defined by law, i.e. entries whose category is 'official_holiday' --
never the agreement-based days off ('overenskomstfastsat_fridag':
Grundlovsdag and Juleaften), and never a Sunday.

The distinction already existed in the JSON's `category` field, which
was deserialised but never read. IsStatutoryHoliday now reads it.
IsOfficialHoliday is deliberately left alone: it is load-bearing for
column J AND for the pay-code day classification
(GetDayCodeForDate -> DayType.Holiday), so the two predicates must not
collapse into one.

Also fixes a pre-existing bug in the column J loop, which summed the
bare NettoHours and ignored an active override while column G
(siteTotalNettoHours) honours it -- so column I + column J disagreed
with column G on any overridden Sunday or holiday. Both sums now select
the day's hours exactly as column G does.

Known gap, left as-is and documented at the call site: an overridden
Grundlovsdag still ignores the override, because CalculateHoursAfterNoon
derives from the shift stamps and a netto override carries no
information about which hours fell after 12:00. Splitting an override
across noon is a payroll policy decision, not a code fix.

Column M (Saturday hours) has the same bare-NettoHours issue and is
deliberately out of scope here.

The new test class is registered in the CI shard filters in both
workflow files; without that it would silently never run.

Tests run only in CI, so these have not been executed locally.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 11, 2026 09:21

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.

🟢 Approval recommended

No unresolved issues were identified that would block approval.

Pull request overview

Adds a statutory-holiday-only hours column to the all-workers Excel export while preserving existing Sunday/holiday calculations.

Changes:

  • Adds and localizes the new Total-sheet column.
  • Introduces category-based statutory holiday detection and override-aware totals.
  • Adds workbook, helper, and CI-shard test coverage.
File summaries
File Description
eFormAPI/Plugins/TimePlanning.Pn/TimePlanning.Pn/Services/TimePlanningWorkingHoursService/TimePlanningWorkingHoursService.cs Updated as part of this pull request.
eFormAPI/Plugins/TimePlanning.Pn/TimePlanning.Pn/Resources/Translations.resx Updated as part of this pull request.
eFormAPI/Plugins/TimePlanning.Pn/TimePlanning.Pn/Resources/Translations.Designer.cs Updated as part of this pull request.
eFormAPI/Plugins/TimePlanning.Pn/TimePlanning.Pn/Resources/Translations.da.resx Updated as part of this pull request.
eFormAPI/Plugins/TimePlanning.Pn/TimePlanning.Pn/Infrastructure/Helpers/PlanRegistrationHelper.cs Updated as part of this pull request.
eFormAPI/Plugins/TimePlanning.Pn/TimePlanning.Pn.Test/WorkingHoursExcelShiftColumnOrderTests.cs Updated as part of this pull request.
eFormAPI/Plugins/TimePlanning.Pn/TimePlanning.Pn.Test/WorkingHoursExcelHolidayColumnTests.cs Updated as part of this pull request.
eFormAPI/Plugins/TimePlanning.Pn/TimePlanning.Pn.Test/TimePlanningWorkingHoursExportTests.cs Updated as part of this pull request.
eFormAPI/Plugins/TimePlanning.Pn/TimePlanning.Pn.Test/PlanRegistrationHelperHolidayTests.cs Updated as part of this pull request.
.github/workflows/dotnet-core-pr.yml Updated as part of this pull request.
.github/workflows/dotnet-core-master.yml Updated as part of this pull request.
Review details

Files not reviewed (1)

  • eFormAPI/Plugins/TimePlanning.Pn/TimePlanning.Pn/Resources/Translations.Designer.cs: Generated file
  • Files reviewed: 10/11 changed files
  • Comments generated: 0
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@renemadsen
renemadsen merged commit 3a48d4c into stable Sep 11, 2026
77 of 78 checks passed
@renemadsen
renemadsen deleted the feat/helligdagstimer-export-column branch September 11, 2026 10:10
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