1
0
forked from Yara724/api

Fixed Bugs: 1) V4 incorrect advancement to file maker approval state, 2) V4 race condition for finishing file maker steps

This commit is contained in:
2026-07-23 13:43:37 +03:30
parent c3eb36dc41
commit f9f462d47b
2 changed files with 78 additions and 26 deletions

View File

@@ -8643,31 +8643,10 @@ export class ClaimRequestManagementService {
if (allDocumentsUploaded) {
if (options?.v3InPersonFlow) {
// V4 split flow: mark the claim as waiting for the FileReviewer.
// The FileReviewer calls accident-fields then select-outer-parts, at
// which point advanceV3ClaimToOuterPartsIfReady() moves the claim to
// SELECTING_OUTER_PARTS / SELECT_OUTER_PARTS.
$set["status"] = ClaimCaseStatus.WAITING_FOR_FILE_REVIEWER;
$set["workflow.nextStep"] = ClaimWorkflowStep.SELECT_OUTER_PARTS;
completedStepsEntries.push(
ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS,
);
historyEntries.push({
type: "STEP_COMPLETED",
actor: {
actorId: new Types.ObjectId(currentUserId),
actorName: claimCase.owner?.fullName || "User",
actorType: "user",
},
timestamp: new Date(),
metadata: {
stepKey: ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS,
description:
"FileMaker documents complete. File sealed — awaiting FileReviewer.",
v3InPersonFlow: true,
},
});
// V4/V5 split flow: the status transition to WAITING_FOR_FILE_REVIEWER is
// deferred to a post-write re-check (see below) to eliminate the race
// condition where two concurrent uploads each see a stale snapshot and
// neither fires the transition. We skip adding it to $set here.
} else {
$set["status"] = ClaimCaseStatus.WAITING_FOR_DAMAGE_EXPERT;
$set["claimStatus"] = ClaimStatus.PENDING;
@@ -8734,6 +8713,77 @@ export class ClaimRequestManagementService {
updatePayload,
);
// ─── V4/V5 in-person flow: post-write race-safe status transition ──────
// After the write we re-read the actual DB state. If all pre-capture
// documents are now present but the status has not yet advanced (i.e. a
// concurrent request uploaded the last doc a split-second before us and
// the stale snapshot we read earlier missed it — or vice-versa), we apply
// WAITING_FOR_FILE_REVIEWER atomically using findOneAndUpdate with a status
// condition, so exactly one writer wins regardless of concurrency.
if (options?.v3InPersonFlow && !isResendUpload && !isCapturePhaseDocUpload) {
const afterWrite = await this.claimCaseDbService.findById(claimRequestId);
if (afterWrite) {
const blameForRecheck = afterWrite.blameRequestId
? await this.blameRequestDbService.findById(
afterWrite.blameRequestId.toString(),
)
: null;
const isCarBodyRecheck = blameForRecheck?.type === BlameRequestType.CAR_BODY;
const allDoneNow =
afterWrite.status === ClaimCaseStatus.UPLOADING_REQUIRED_DOCUMENTS &&
afterWrite.workflow?.currentStep === ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS &&
this.allV3PreCaptureDocumentsComplete(afterWrite, isCarBodyRecheck);
if (allDoneNow) {
// Atomic conditional update: only succeeds when status is still
// UPLOADING_REQUIRED_DOCUMENTS, preventing double-application.
const advanced = await this.claimCaseDbService.findOneAndUpdate(
{
_id: afterWrite._id,
status: ClaimCaseStatus.UPLOADING_REQUIRED_DOCUMENTS,
},
{
$set: {
status: ClaimCaseStatus.WAITING_FOR_FILE_REVIEWER,
"workflow.nextStep": ClaimWorkflowStep.SELECT_OUTER_PARTS,
},
$push: {
"workflow.completedSteps": ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS,
history: {
type: "STEP_COMPLETED",
actor: {
actorId: new Types.ObjectId(currentUserId),
actorName: afterWrite.owner?.fullName || "User",
actorType: "user",
},
timestamp: new Date(),
metadata: {
stepKey: ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS,
description:
"FileMaker documents complete. File sealed — awaiting FileReviewer.",
v3InPersonFlow: true,
},
},
},
},
);
if (advanced) {
// This request won the race — reflect the final state in the response.
allDocumentsUploaded = true;
}
} else if (
afterWrite.status === ClaimCaseStatus.WAITING_FOR_FILE_REVIEWER ||
(afterWrite.workflow?.completedSteps ?? []).includes(
ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS,
)
) {
// Another concurrent request already advanced the status — treat
// this upload as the completing upload for response purposes.
allDocumentsUploaded = true;
}
}
}
// Auto-submit Fanavaran attachments when all documents are uploaded
if (allDocumentsUploaded && !isResendUpload && !options?.v3InPersonFlow) {
this.submitFanavaranAttachmentsV2(claimRequestId, resolveFanavaranClientKey()).catch(

View File

@@ -87,7 +87,9 @@ export class FileMakerBlameV4Controller {
) {
return this.requestManagementService.createExpertInitiatedBlameV2(
fileMaker,
{ ...dto, creationMethod: CreationMethod.IN_PERSON },
// requiresFileMakerApproval is V5-only; explicitly exclude it from V4 so
// a client that accidentally sends the field cannot poison the blame record.
{ ...dto, creationMethod: CreationMethod.IN_PERSON, requiresFileMakerApproval: false },
);
}