Skip to content

fix(snapshot,gps): the start snapshot and position create the day's row when none exists (#1746) - #1752

Merged
renemadsen merged 3 commits into
stablefrom
fix/1746-snapshot-gps-without-plan-row
Oct 4, 2026
Merged

renemadsen merged 3 commits into
stablefrom
fix/1746-snapshot-gps-without-plan-row

Conversation

@renemadsen

Copy link
Copy Markdown
Member

Summary

With snapshots enabled, the Start1 snapshot (and the start GPS position) was lost on every day that had no pre-created PlanRegistration. This change creates the day's row when it is missing, so the snapshot and position have a row to point at.

  • New PlanRegistrationHelper.FindOrCreateDayRowAsync, used by TimePlanningPictureSnapshotService.Create and TimePlanningGpsCoordinateService.Create. It returns the day's row. When there is none, it creates the same empty row the planning gap-fill creates, and carries the predecessor's closing flex balance forward. That keeps the chain intact even if no registration follows. The registration that does follow updates this row like any pre-created one.
  • A row is only created when all of these hold:
    • the site has a non-removed AssignedSite (otherwise the call is refused with SiteNotFound);
    • the day is within one day of today (otherwise the service's generic error);
    • the day is not inside the reconciled lock.
  • The snapshot row is resolved before the S3 upload, so a refused snapshot no longer leaves an orphaned file.
  • The registration that follows can win the insert race. In that case the unique index (SdkSitId, Date, WorkflowState) rejects the second row; the helper detaches it and adopts the existing row instead of failing.

Root cause

The app sends the snapshot and position before the save that creates the row (app side: flutter-time#594). Create then dereferenced planRegistration.Id on null. The exception was caught and logged, and the snapshot was dropped after its file had already been uploaded.

Tests

  • PictureSnapshotServiceTests:
    • no row → success, exactly one row is created, the snapshot points to it, and the balance is carried forward;
    • existing row → linked, no second row;
    • no AssignedSite → SiteNotFound, nothing created;
    • five days back → refused, nothing created.
  • GpsCoordinateServiceTests: the same no-row, no-AssignedSite and out-of-window cases. Also CreateOrGetExistingDayRowAsync on a unique-index violation: it returns the existing row, detaches the rejected one, and the context stays usable.

Not verified

  • Tests were not run locally; CI runs them.
  • The locked-day refusal inside the ±1-day window can only trigger when the server's local date differs from the UTC date. It has no dedicated test.
  • Update in both services still dereferences a missing row; that is outside this issue.

Refs #1746

🤖 Generated with Claude Code

…ow when none exists (#1746)

The app sends the Start1 snapshot and GPS position before the save that
creates the day's PlanRegistration. On a day without a pre-created row,
Create dereferenced a null row: the snapshot was lost (after its file had
been uploaded to S3) and so was the position.

PlanRegistrationHelper.FindOrCreateDayRowAsync resolves the row first and,
when there is none, creates the empty row the planning gap-fill creates,
carrying the predecessor's closing flex balance forward. It only does so for
a site with a time registration (AssignedSite), within a day of today and
outside the reconciled lock; otherwise the call is refused and nothing is
uploaded. If the racing registration wins the insert, the unique index
rejects the second row and the existing one is adopted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 3, 2026 05:15

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

Unresolved insert-race handling and flex-balance precision issues affect registration correctness.

Review effort: Balanced
Findings: 1 High severity · 1 Medium severity

Open (2)
What changed in this PR

Prevents start snapshots and GPS positions from being lost when the worker’s daily PlanRegistration does not yet exist.

Changes:

  • Adds guarded day-row creation with flex-balance carry-forward and insert-conflict recovery.
  • Resolves snapshot rows before uploading files.
  • Adds tests for linking, creation, refusals, and insert conflicts.
File Description
eFormAPI/​Plugins/​TimePlanning.Pn/​TimePlanning.Pn/​Services/​TimePlanningPictureSnapshotService/​TimePlanningPictureSnapshotService.cs Resolves the day row before uploading snapshots.
eFormAPI/​Plugins/​TimePlanning.Pn/​TimePlanning.Pn/​Services/​TimePlanningGpsCoordinateService/​TimePlanningGpsCoordinateService.cs Resolves the day row before saving GPS coordinates.
eFormAPI/​Plugins/​TimePlanning.Pn/​TimePlanning.Pn/​Infrastructure/​Helpers/​PlanRegistrationHelper.cs Adds shared day-row creation and conflict recovery.
eFormAPI/​Plugins/​TimePlanning.Pn/​TimePlanning.Pn.Test/​PictureSnapshotServiceTests.cs Tests snapshot linking and creation guards.
eFormAPI/​Plugins/​TimePlanning.Pn/​TimePlanning.Pn.Test/​GpsCoordinateServiceTests.cs Tests GPS linking, creation guards, and insert conflicts.

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

…test arrange (#1746)

The new day row carried the predecessor's balance through the decimal
chain even on one-minute sites, replacing the authoritative seconds
balance with its (possibly stale) decimal. It now uses FlexChain.CarryChain
with the site's mode, as the other balance paths do.

The snapshot test helper awaited GetCore() inside Returns(), which let
another substitute call bind the return value; resolve the core first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 3, 2026 08:47
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

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

🔵 Needs a closer look

Accepted tomorrow-dated rows skip balance carry-forward and can leave an incorrect zero balance.

Review effort: Balanced
Findings: None

Resolved since last review (2)
Previously missed (1)

In code that hasn't changed since last review

Medium severity Carry forward predecessor balance for accepted future dates

eFormAPI/​Plugins/​TimePlanning.Pn/​TimePlanning.Pn/​Infrastructure/​Helpers/​PlanRegistrationHelper.cs:2385

A request for today.AddDays(1) passes the date check but skips balance carry-forward here. For example, a predecessor balance of 2.5 hours becomes zero on the new row. Unlike the planning gap-fill, these snapshot/GPS callers do not subsequently recompute the row. If no registration follows, the zero balance remains and can seed later rows when FlexChainComputedThrough is null. Run the predecessor lookup and CarryChain for every accepted date, and add a tomorrow-dated regression test.

@renemadsen
renemadsen merged commit 165ca0d into stable Oct 4, 2026
40 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