Skip to content

fix(export): don't crash on shift slot ids > 289 (out-of-range Options index)#1603

Merged
renemadsen merged 1 commit into
stablefrom
fix/export-shift-index-out-of-range
Jun 10, 2026
Merged

fix(export): don't crash on shift slot ids > 289 (out-of-range Options index)#1603
renemadsen merged 1 commit into
stablefrom
fix/export-shift-index-out-of-range

Conversation

@renemadsen

Copy link
Copy Markdown
Member

The bug

The all-workers (and single-worker) timeplanning Excel export threw and aborted:

Error while filling data row: Index was out of range ...
  at System.Collections.Generic.List`1.get_Item(Int32 index)
  at ...FillDataRow(...) at ...GenerateExcelDashboard(...ReportForAllWorkers...)

Root cause

GetShiftTime resolved a shift's time by indexing plr.Options — a fixed 288-element list (slots 1–288 → "00:00".."23:55", only 289 special-cased to "24:00"). A shift slot id ≥ 290 indexes past the end → IndexOutOfRange. The Pause columns are the worst case: they always pass actualStamp=null, so they hit this lookup even when UseOneMinuteIntervals is on.

Confirmed against real data: a customer site had Pause1Id = 388 and 517 (both > 288) — exactly the crash.

The fix

Compute the time arithmetically — minutes = (shift-1)*5, formatted HH:mm — instead of indexing Options. This reproduces the old output exactly for the valid 1–288 range, keeps 289 → "24:00", and extends gracefully past midnight (290 → "24:05", 313 → "26:00"). No array index, no crash. (Drop-in; the now-unused plr param is kept to avoid touching ~15 call sites — a separate cleanup.)

Tests (the coverage gap)

  • Unit: GetShiftTime across the valid range, 289, out-of-range (290/313), 0/nullPlanRegistrationHelperTests (shard f).
  • Export regression: seeds out-of-range Stop1Id=313 + Pause1Id=295 and asserts both export overloads succeed and render "26:00" — DagsoversigtWorksheetExportTests (shard g). Verified it fails without the fix.

Separate follow-ups (not in this PR)

  1. The bad Pause1Id values (≈32–43 h) are a data/encoding bug upstream — the existing if (Start1Id > 289) workaround doesn't cover Pause or shifts 2–5, nor run at export time.
  2. Export temp filenames use second-resolution UtcNow with no uniqueness → concurrent exports can collide on the same path.

🤖 Generated with Claude Code

…s index)

GetShiftTime looked up a shift's time by indexing plr.Options, a fixed
288-element list (slots 1-288 -> "00:00".."23:55", with only 289 special-cased
to "24:00"). A shift slot id >= 290 therefore indexed past the end of the list
and threw IndexOutOfRange, aborting the whole Excel export (FillDataRow). The
Pause columns are the worst case: they always pass actualStamp=null and so hit
this lookup even when UseOneMinuteIntervals is on. Real data on a customer site
had Pause1Id = 388/517, crashing the all-workers report.

Compute the time arithmetically instead: minutes = (shift-1)*5, formatted HH:mm.
This reproduces the old Options output exactly for the valid 1..288 range,
keeps 289 -> "24:00", and extends gracefully past midnight (290 -> "24:05",
313 -> "26:00") with no array index and no crash.

Adds the missing test coverage:
- unit tests for GetShiftTime across the valid range, 289, out-of-range
  (290/313), and 0/null (PlanRegistrationHelperTests, shard f);
- an export regression test seeding out-of-range Stop1Id/Pause1Id and asserting
  both single-worker and all-workers GenerateExcelDashboard succeed
  (DagsoversigtWorksheetExportTests, shard g).

Note (separate, pre-existing): the bad Pause1Id values are themselves a data
bug, and export temp filenames use second-resolution UtcNow with no uniqueness
(concurrent exports can collide) -- both worth separate follow-ups.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 10, 2026 02: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.

Pull request overview

Fixes a production crash in the TimePlanning Excel export caused by out-of-range shift slot IDs (>= 290) by switching GetShiftTime from plr.Options indexing to arithmetic time computation, and adds unit + export regression tests to lock the behavior.

Changes:

  • Compute shift slot time as (shift-1)*5 minutes to avoid IndexOutOfRangeException in exports.
  • Add unit coverage for GetShiftTime(plr, shift) including cross-midnight/out-of-range slot IDs.
  • Add an end-to-end export regression test that seeds out-of-range Stop1Id/Pause1Id and validates both export overloads succeed and render "26:00".

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
eFormAPI/Plugins/TimePlanning.Pn/TimePlanning.Pn/Services/TimePlanningWorkingHoursService/TimePlanningWorkingHoursService.cs Replaces plr.Options indexing with arithmetic formatting for shift slot IDs to prevent export crashes.
eFormAPI/Plugins/TimePlanning.Pn/TimePlanning.Pn.Test/PlanRegistrationHelperTests.cs Adds unit test cases covering valid, boundary, and out-of-range slot IDs for the 2-arg GetShiftTime.
eFormAPI/Plugins/TimePlanning.Pn/TimePlanning.Pn.Test/DagsoversigtWorksheetExportTests.cs Adds regression test ensuring both export paths handle cross-midnight/out-of-range shift IDs without throwing and with correct output.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +2795 to +2796
var minutes = (shift.Value - 1) * 5;
return $"{minutes / 60:00}:{minutes % 60:00}";
Comment on lines +274 to +276
[Test]
public async Task Export_WithCrossMidnightShiftSlotId_DoesNotThrow()
{
@renemadsen renemadsen merged commit 4c2f6e1 into stable Jun 10, 2026
39 checks passed
@renemadsen renemadsen deleted the fix/export-shift-index-out-of-range branch June 10, 2026 03:07
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