Repository navigation
fix(report): guard missing case, site worker and worker in GenerateReportV2 - #1079
Merged
Merged
Conversation
…portV2
GenerateReportV2 resolved the reporting worker like this:
var dbCase = await sdkDbContext.Cases.FirstOrDefaultAsync(...);
var workerId = sdkDbContext.SiteWorkers.First(x => x.SiteId == dbCase.SiteId).WorkerId;
var worker = await sdkDbContext.Workers.FirstOrDefaultAsync(x => x.Id == workerId);
if (dbCase == null) { logger.LogError(...); continue; }
Three defects, each of which aborts the entire report because the method
wraps its whole body in a single try/catch:
- dbCase.SiteId is dereferenced before the dbCase null check, so a missing
case throws NullReferenceException instead of reaching the continue.
- SiteWorkers.First(...) throws "Sequence contains no elements" for a site
with no worker row -- the same failure shape that took down reports for
customer 879 via Properties.First(...), fixed in #1078.
- worker came from FirstOrDefaultAsync and was dereferenced unguarded, so a
dangling WorkerId throws NullReferenceException.
Move the dbCase guard above its first use, so the intended continue is
actually reachable. Resolve the site worker with FirstOrDefaultAsync and
fall back to a blank EmployeeNo, matching how #1078 handles an
unresolvable property name. EmployeeNo is display-only: WordService
interpolates it into a table cell and ExcelService passes it to
CreateCell, so "" is safe for both and the row still appears.
While the lookup was open, filter out removed SiteWorkers and order by Id.
The plugin already does this in GrpcSiteResolver and
BackendConfigurationAssignmentWorkerServiceHelper; without it the report
can show the EmployeeNo of a worker unassigned from that site months ago,
or a different one run-to-run when a site has several worker rows.
Warn once per site rather than once per planning case -- "site has no
active worker" is a per-site condition, so a single misconfigured site
would otherwise emit one identical line per case.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A9DDHNWkLapbuXHjFSqFt9
There was a problem hiding this comment.
Pull request overview
This PR hardens GenerateReportV2 in the backend report service to avoid aborting an entire report when related SDK data (case/site-worker/worker) is missing or removed, aligning behavior with the earlier “degrade gracefully” fix pattern from #1078.
Changes:
- Ensures the
dbCase == nullguard is reachable by removing dereferences before the null-check. - Replaces throwing site-worker/worker resolution with
FirstOrDefaultAsync+ safe fallback to blankEmployeeNo, and filters out removed site-worker links with deterministic ordering. - Deduplicates “missing worker for site” warnings to log once per site per report run.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #1078. Same method, same failure class, found during that investigation.
Problem
GenerateReportV2resolved the reporting worker like this:Three defects. Each aborts the entire report, because the method wraps its whole body in a single
try/catch:dbCase.SiteIdis dereferenced one line above thedbCase == nullguard, so a missing case throwsNullReferenceExceptioninstead of reaching the intendedcontinue. The guard is effectively dead code.SiteWorkers.First(...)throwsSequence contains no elementsfor a site with no worker row — the identical failure shape that broke reports for customer 879 viaProperties.First(...)in fix(report): don't abort report when a planning's property is unresolvable #1078.workercame fromFirstOrDefaultAsyncand was dereferenced unguarded, so a danglingWorkerIdthrowsNullReferenceException.GenerateReport(V1) already gets the ordering right; V1 has noEmployeeNo, so the worker lookup is V2-only.Fix
dbCaseguard above its first use, so thecontinueis actually reachable.FirstOrDefaultAsyncand fall back to a blankEmployeeNo, matching how fix(report): don't abort report when a planning's property is unresolvable #1078 handles an unresolvable property name.EmployeeNois display-only —WordService.cs:872interpolates it into a<td>andExcelService.cs:419passes it toCreateCell(string), so""is safe for both and the row still appears in the report.Also folded in (behavior change — please sanity-check)
The
SiteWorkerslookup had noWorkflowStatefilter and no ordering, so it could return a removed site-worker link, or a nondeterministic one when a site has several worker rows. This is pre-existing — the old.First(...)had the same predicate — but the line was already open and the plugin has a settled idiom for it (GrpcSiteResolver.cs:72-76,BackendConfigurationAssignmentWorkerServiceHelper.cs:925,942). Now filtered to non-removed and ordered byId.Consequence: if a site's only
SiteWorkerrow isremoved,EmployeeNonow renders blank where it previously showed that removed worker's number. That's the intended reading, but it is a visible change — happy to drop this hunk if you'd rather keep the fix strictly to the crash.Also added a
HashSetso "no active worker assigned to site N" warns once per site rather than once per planning case — otherwise one misconfigured site emits an identical line for every case in the range.Verification
dotnet build BackendConfiguration.Pn.csproj→ 0 errors, 92 warnings (byte-identical to the pre-change baseline)awaitis safe (the enclosingforeachiterates a materializedList, and these queries hitsdkDbContextwhile the loop source came fromitemsPlanningPnDbContext), and that nothing else in the file references the removed locals.SiteWorkers, 0 report cases lacking a worker. So this is preventive for 879 — unlike fix(report): don't abort report when a planning's property is unresolvable #1078, it is not fixing an observed failure there.Still open
CheckListTranslations…First(x => x.LanguageId == language.Id),Languages.Single(...),FieldTranslations.FirstAsync,FieldOptionTranslations.FirstAsync,match.NameandclTranslation.Textafter aFirstOrDefaultAsync. A sweep would be worth scheduling.🤖 Generated with Claude Code
https://claude.ai/code/session_01A9DDHNWkLapbuXHjFSqFt9