forked from Yara724/api
Compare commits
20 Commits
4d5b91d4fe
...
49fe548215
| Author | SHA1 | Date | |
|---|---|---|---|
| 49fe548215 | |||
|
|
777eae1028 | ||
| b67dd733cd | |||
|
|
4818f73252 | ||
| cc8a5354c7 | |||
|
|
2d5ade33d2 | ||
| b73c92c21f | |||
| f9f462d47b | |||
| c3eb36dc41 | |||
|
|
f75ecf5c2c | ||
| 75c4cb6a05 | |||
|
|
7a4277f8b2 | ||
| e50bc78344 | |||
|
|
2c1cd93dd0 | ||
| ffd44df718 | |||
|
|
64865b70f2 | ||
| c207c30be9 | |||
|
|
fcb169e9ac | ||
| 1867292499 | |||
|
|
2cc96f6132 |
@@ -419,8 +419,10 @@ export class ClaimRequestManagementService {
|
|||||||
claimCase: any,
|
claimCase: any,
|
||||||
isCarBody: boolean,
|
isCarBody: boolean,
|
||||||
assumeUploadedKey?: string,
|
assumeUploadedKey?: string,
|
||||||
|
skipMetalPlate?: boolean,
|
||||||
): boolean {
|
): boolean {
|
||||||
for (const k of this.v3PreCaptureDocumentKeys(isCarBody)) {
|
for (const k of this.v3PreCaptureDocumentKeys(isCarBody)) {
|
||||||
|
if (skipMetalPlate && OPTIONAL_CAPTURE_PHASE_DOC_KEYS_V4V5.includes(k as any)) continue;
|
||||||
const ok =
|
const ok =
|
||||||
k === assumeUploadedKey
|
k === assumeUploadedKey
|
||||||
? true
|
? true
|
||||||
@@ -442,9 +444,11 @@ export class ClaimRequestManagementService {
|
|||||||
claimCase: any,
|
claimCase: any,
|
||||||
isCarBody: boolean,
|
isCarBody: boolean,
|
||||||
assumeUploadedKey?: string,
|
assumeUploadedKey?: string,
|
||||||
|
skipMetalPlate?: boolean,
|
||||||
): number {
|
): number {
|
||||||
let n = 0;
|
let n = 0;
|
||||||
for (const k of this.v3PreCaptureDocumentKeys(isCarBody)) {
|
for (const k of this.v3PreCaptureDocumentKeys(isCarBody)) {
|
||||||
|
if (skipMetalPlate && OPTIONAL_CAPTURE_PHASE_DOC_KEYS_V4V5.includes(k as any)) continue;
|
||||||
const ok =
|
const ok =
|
||||||
k === assumeUploadedKey
|
k === assumeUploadedKey
|
||||||
? true
|
? true
|
||||||
@@ -6331,8 +6335,17 @@ export class ClaimRequestManagementService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// V5 flow: FileMaker approval required before fanavaran.
|
// V5 flow: FileMaker approval required before fanavaran.
|
||||||
// Instead of submitting, hold the claim at WAITING_FOR_FILE_MAKER_APPROVAL.
|
// Cross-check the blame record — only V5 blame files carry requiresFileMakerApproval.
|
||||||
|
// V4 claims may have the flag set from an old bug; we ignore it if the blame
|
||||||
|
// itself does not carry the flag.
|
||||||
if ((claimCase as any).requiresFileMakerApproval) {
|
if ((claimCase as any).requiresFileMakerApproval) {
|
||||||
|
const blameId = (claimCase as any).blameRequestId;
|
||||||
|
const blame = blameId
|
||||||
|
? await this.blameRequestDbService.findById(String(blameId))
|
||||||
|
: null;
|
||||||
|
const isV5 = !!(blame as any)?.requiresFileMakerApproval;
|
||||||
|
|
||||||
|
if (isV5) {
|
||||||
await this.claimCaseDbService.findByIdAndUpdate(claimCaseId, {
|
await this.claimCaseDbService.findByIdAndUpdate(claimCaseId, {
|
||||||
$set: { status: ClaimCaseStatus.WAITING_FOR_FILE_MAKER_APPROVAL },
|
$set: { status: ClaimCaseStatus.WAITING_FOR_FILE_MAKER_APPROVAL },
|
||||||
$push: {
|
$push: {
|
||||||
@@ -6353,6 +6366,11 @@ export class ClaimRequestManagementService {
|
|||||||
"Claim is now in WAITING_FOR_FILE_MAKER_APPROVAL.",
|
"Claim is now in WAITING_FOR_FILE_MAKER_APPROVAL.",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
// V4 claim with stale flag — clear it so this path is not hit again.
|
||||||
|
await this.claimCaseDbService.findByIdAndUpdate(claimCaseId, {
|
||||||
|
$unset: { requiresFileMakerApproval: "" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const skipReason = await this.getFanavaranAutoSubmitSkipReason(claimCase);
|
const skipReason = await this.getFanavaranAutoSubmitSkipReason(claimCase);
|
||||||
if (skipReason) {
|
if (skipReason) {
|
||||||
@@ -8606,6 +8624,7 @@ export class ClaimRequestManagementService {
|
|||||||
claimCase,
|
claimCase,
|
||||||
isCarBodyUpload,
|
isCarBodyUpload,
|
||||||
body.documentKey,
|
body.documentKey,
|
||||||
|
options?.skipMetalPlate,
|
||||||
);
|
);
|
||||||
|
|
||||||
allDocumentsUploaded = options?.v3InPersonFlow
|
allDocumentsUploaded = options?.v3InPersonFlow
|
||||||
@@ -8620,6 +8639,7 @@ export class ClaimRequestManagementService {
|
|||||||
claimCase,
|
claimCase,
|
||||||
isCarBodyUpload,
|
isCarBodyUpload,
|
||||||
body.documentKey,
|
body.documentKey,
|
||||||
|
options?.skipMetalPlate,
|
||||||
)
|
)
|
||||||
: this.countRemainingV2OwnerDocuments(
|
: this.countRemainingV2OwnerDocuments(
|
||||||
claimCase,
|
claimCase,
|
||||||
@@ -8629,31 +8649,10 @@ export class ClaimRequestManagementService {
|
|||||||
|
|
||||||
if (allDocumentsUploaded) {
|
if (allDocumentsUploaded) {
|
||||||
if (options?.v3InPersonFlow) {
|
if (options?.v3InPersonFlow) {
|
||||||
// V4 split flow: mark the claim as waiting for the FileReviewer.
|
// V4/V5 split flow: the status transition to WAITING_FOR_FILE_REVIEWER is
|
||||||
// The FileReviewer calls accident-fields then select-outer-parts, at
|
// deferred to a post-write re-check (see below) to eliminate the race
|
||||||
// which point advanceV3ClaimToOuterPartsIfReady() moves the claim to
|
// condition where two concurrent uploads each see a stale snapshot and
|
||||||
// SELECTING_OUTER_PARTS / SELECT_OUTER_PARTS.
|
// neither fires the transition. We skip adding it to $set here.
|
||||||
$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,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
$set["status"] = ClaimCaseStatus.WAITING_FOR_DAMAGE_EXPERT;
|
$set["status"] = ClaimCaseStatus.WAITING_FOR_DAMAGE_EXPERT;
|
||||||
$set["claimStatus"] = ClaimStatus.PENDING;
|
$set["claimStatus"] = ClaimStatus.PENDING;
|
||||||
@@ -8720,6 +8719,88 @@ export class ClaimRequestManagementService {
|
|||||||
updatePayload,
|
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 skipMetalPlateRecheck = !!(blameForRecheck as any)?.isMadeByFileMaker;
|
||||||
|
const allDoneNow =
|
||||||
|
afterWrite.status === ClaimCaseStatus.UPLOADING_REQUIRED_DOCUMENTS &&
|
||||||
|
afterWrite.workflow?.currentStep === ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS &&
|
||||||
|
this.allV3PreCaptureDocumentsComplete(afterWrite, isCarBodyRecheck, undefined, skipMetalPlateRecheck);
|
||||||
|
|
||||||
|
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;
|
||||||
|
// Now that the FileMaker's document phase is complete, promote the
|
||||||
|
// blame file to WAITING_FOR_FILE_REVIEWER so the frontend shows the
|
||||||
|
// correct sealed state when the FileMaker returns.
|
||||||
|
const blameId = (afterWrite as any).blameRequestId;
|
||||||
|
if (blameId) {
|
||||||
|
await this.blameRequestDbService.findByIdAndUpdate(
|
||||||
|
String(blameId),
|
||||||
|
{ $set: { status: BlameCaseStatus.WAITING_FOR_FILE_REVIEWER } },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} 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
|
// Auto-submit Fanavaran attachments when all documents are uploaded
|
||||||
if (allDocumentsUploaded && !isResendUpload && !options?.v3InPersonFlow) {
|
if (allDocumentsUploaded && !isResendUpload && !options?.v3InPersonFlow) {
|
||||||
this.submitFanavaranAttachmentsV2(claimRequestId, resolveFanavaranClientKey()).catch(
|
this.submitFanavaranAttachmentsV2(claimRequestId, resolveFanavaranClientKey()).catch(
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
||||||
import { IsEnum, IsOptional, IsString } from "class-validator";
|
import { IsBoolean, IsEnum, IsOptional } from "class-validator";
|
||||||
import { CreationMethod } from "../entities/schema/request-management.schema";
|
import { CreationMethod } from "../entities/schema/request-management.schema";
|
||||||
|
|
||||||
export class CreateExpertInitiatedFileDto {
|
export class CreateExpertInitiatedFileDto {
|
||||||
@@ -18,4 +18,17 @@ export class CreateExpertInitiatedFileDto {
|
|||||||
})
|
})
|
||||||
@IsEnum(CreationMethod)
|
@IsEnum(CreationMethod)
|
||||||
creationMethod: CreationMethod;
|
creationMethod: CreationMethod;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* V5 only. When true the resulting claim will require FileMaker approval
|
||||||
|
* before fanavaran submission. V4 files must leave this unset (or false).
|
||||||
|
*/
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description:
|
||||||
|
"V5 only — when true the claim requires FileMaker approval before fanavaran submission.",
|
||||||
|
example: false,
|
||||||
|
})
|
||||||
|
@IsBoolean()
|
||||||
|
@IsOptional()
|
||||||
|
requiresFileMakerApproval?: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,6 +117,14 @@ export class BlameRequest {
|
|||||||
@Prop({ default: false })
|
@Prop({ default: false })
|
||||||
isMadeByFileMaker?: boolean;
|
isMadeByFileMaker?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* V5 only. When true the resulting claim requires FileMaker approval before
|
||||||
|
* fanavaran submission. V4 files never set this; it is written at creation
|
||||||
|
* time by the V5 controller and propagated to the linked claim.
|
||||||
|
*/
|
||||||
|
@Prop({ default: false })
|
||||||
|
requiresFileMakerApproval?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The FileReviewer who claimed this file for completion (V4 flow only).
|
* The FileReviewer who claimed this file for completion (V4 flow only).
|
||||||
* Set when a FileReviewer calls assign on this file; enforces one-reviewer-per-file.
|
* Set when a FileReviewer calls assign on this file; enforces one-reviewer-per-file.
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import {
|
|||||||
LocationDto,
|
LocationDto,
|
||||||
} from "./dto/create-request-management.dto";
|
} from "./dto/create-request-management.dto";
|
||||||
import { CreateExpertInitiatedFileDto } from "./dto/expert-initiated.dto";
|
import { CreateExpertInitiatedFileDto } from "./dto/expert-initiated.dto";
|
||||||
|
import { CreationMethod } from "./entities/schema/request-management.schema";
|
||||||
import { SendPartyOtpsDto } from "./dto/send-party-otps.dto";
|
import { SendPartyOtpsDto } from "./dto/send-party-otps.dto";
|
||||||
import { VerifyPartyOtpsDto } from "./dto/verify-party-otps.dto";
|
import { VerifyPartyOtpsDto } from "./dto/verify-party-otps.dto";
|
||||||
import { SendPartyOtpDto, VerifyPartyOtpDto } from "./dto/party-otp.dto";
|
import { SendPartyOtpDto, VerifyPartyOtpDto } from "./dto/party-otp.dto";
|
||||||
@@ -75,17 +76,17 @@ export class ExpertInitiatedBlameMirrorController {
|
|||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: "[Expert mirror] Create expert-initiated blame file",
|
summary: "[Expert mirror] Create expert-initiated blame file",
|
||||||
description:
|
description:
|
||||||
"Use creationMethod=IN_PERSON for the on-site flow. Creates a BlameRequest owned by the parties but filled by the expert.",
|
"Creates an IN_PERSON blame file filled by the expert on behalf of both parties. creationMethod is always forced to IN_PERSON regardless of what is sent.",
|
||||||
})
|
})
|
||||||
@ApiBody({ type: CreateExpertInitiatedFileDto })
|
@ApiBody({ type: CreateExpertInitiatedFileDto })
|
||||||
async create(
|
async create(
|
||||||
@CurrentUser() expert: any,
|
@CurrentUser() expert: any,
|
||||||
@Body() dto: CreateExpertInitiatedFileDto,
|
@Body() dto: CreateExpertInitiatedFileDto,
|
||||||
) {
|
) {
|
||||||
return this.requestManagementService.createExpertInitiatedBlameV2(
|
return this.requestManagementService.createExpertInitiatedBlameV2(expert, {
|
||||||
expert,
|
...dto,
|
||||||
dto,
|
creationMethod: CreationMethod.IN_PERSON,
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post("send-link/:requestId")
|
@Post("send-link/:requestId")
|
||||||
|
|||||||
@@ -87,7 +87,9 @@ export class FileMakerBlameV4Controller {
|
|||||||
) {
|
) {
|
||||||
return this.requestManagementService.createExpertInitiatedBlameV2(
|
return this.requestManagementService.createExpertInitiatedBlameV2(
|
||||||
fileMaker,
|
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 },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ export class FileMakerBlameV5Controller {
|
|||||||
) {
|
) {
|
||||||
return this.requestManagementService.createExpertInitiatedBlameV2(
|
return this.requestManagementService.createExpertInitiatedBlameV2(
|
||||||
fileMaker,
|
fileMaker,
|
||||||
{ ...dto, creationMethod: CreationMethod.IN_PERSON },
|
{ ...dto, creationMethod: CreationMethod.IN_PERSON, requiresFileMakerApproval: true },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -278,6 +278,32 @@ export class RequestManagementService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IN_PERSON THIRD_PARTY (expert/registrar-initiated): after first-party description
|
||||||
|
// the expert collects the first party's signature before moving to the second party.
|
||||||
|
// Skip FIRST_INVITE_SECOND and hold at WAITING_FOR_SIGNATURES so the sign endpoint
|
||||||
|
// can be called for the first party. The workflow advances to FIRST_INVITE_SECOND
|
||||||
|
// only after expertUploadPartySignatureV2/V3 records the first-party confirmation.
|
||||||
|
if (
|
||||||
|
req.type === BlameRequestType.THIRD_PARTY &&
|
||||||
|
req.creationMethod === CreationMethod.IN_PERSON &&
|
||||||
|
(req.expertInitiated || req.registrarInitiated) &&
|
||||||
|
submittedStepKey === WorkflowStep.FIRST_DESCRIPTION
|
||||||
|
) {
|
||||||
|
const completed = Array.isArray(req.workflow.completedSteps)
|
||||||
|
? req.workflow.completedSteps
|
||||||
|
: [];
|
||||||
|
if (!completed.includes(submittedStepKey))
|
||||||
|
completed.push(submittedStepKey);
|
||||||
|
req.workflow.completedSteps = completed;
|
||||||
|
req.workflow.currentStep = WorkflowStep.WAITING_FOR_SIGNATURES;
|
||||||
|
req.workflow.nextStep = undefined;
|
||||||
|
req.status = CaseStatus.WAITING_FOR_SIGNATURES;
|
||||||
|
this.logger.debug(
|
||||||
|
"[WORKFLOW] IN_PERSON THIRD_PARTY: advanced to WAITING_FOR_SIGNATURES after FIRST_DESCRIPTION (first-party sign before second-party invite)",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// IN_PERSON THIRD_PARTY: guilt is decided on-site by the expert — there is
|
// IN_PERSON THIRD_PARTY: guilt is decided on-site by the expert — there is
|
||||||
// no waiting-for-field-expert phase. Replace WAITING_FOR_GUILT_DECISION
|
// no waiting-for-field-expert phase. Replace WAITING_FOR_GUILT_DECISION
|
||||||
// with WAITING_FOR_SIGNATURES wherever it would surface as current/next step.
|
// with WAITING_FOR_SIGNATURES wherever it would surface as current/next step.
|
||||||
@@ -2087,13 +2113,9 @@ export class RequestManagementService {
|
|||||||
? req.workflow.completedSteps
|
? req.workflow.completedSteps
|
||||||
: [];
|
: [];
|
||||||
if (!completed.includes(stepKey)) completed.push(stepKey);
|
if (!completed.includes(stepKey)) completed.push(stepKey);
|
||||||
// Same as post–field-expert reply: guilt phase is satisfied without an expert; parties must still sign.
|
|
||||||
if (!completed.includes(WorkflowStep.WAITING_FOR_GUILT_DECISION)) {
|
|
||||||
completed.push(WorkflowStep.WAITING_FOR_GUILT_DECISION);
|
|
||||||
}
|
|
||||||
req.workflow.completedSteps = completed;
|
req.workflow.completedSteps = completed;
|
||||||
req.workflow.currentStep = WorkflowStep.WAITING_FOR_SIGNATURES;
|
req.workflow.currentStep = WorkflowStep.WAITING_FOR_SIGNATURES;
|
||||||
req.workflow.nextStep = undefined;
|
req.workflow.nextStep = WorkflowStep.WAITING_FOR_SIGNATURES;
|
||||||
req.status = CaseStatus.WAITING_FOR_SIGNATURES;
|
req.status = CaseStatus.WAITING_FOR_SIGNATURES;
|
||||||
closedByMutualAgreement = true;
|
closedByMutualAgreement = true;
|
||||||
|
|
||||||
@@ -2174,12 +2196,9 @@ export class RequestManagementService {
|
|||||||
? req.workflow.completedSteps
|
? req.workflow.completedSteps
|
||||||
: [];
|
: [];
|
||||||
if (!completed.includes(stepKey)) completed.push(stepKey);
|
if (!completed.includes(stepKey)) completed.push(stepKey);
|
||||||
if (!completed.includes(WorkflowStep.WAITING_FOR_GUILT_DECISION)) {
|
|
||||||
completed.push(WorkflowStep.WAITING_FOR_GUILT_DECISION);
|
|
||||||
}
|
|
||||||
req.workflow.completedSteps = completed;
|
req.workflow.completedSteps = completed;
|
||||||
req.workflow.currentStep = WorkflowStep.WAITING_FOR_SIGNATURES;
|
req.workflow.currentStep = WorkflowStep.WAITING_FOR_SIGNATURES;
|
||||||
req.workflow.nextStep = undefined;
|
req.workflow.nextStep = WorkflowStep.WAITING_FOR_SIGNATURES;
|
||||||
req.status = CaseStatus.WAITING_FOR_SIGNATURES;
|
req.status = CaseStatus.WAITING_FOR_SIGNATURES;
|
||||||
} else if (!closedByMutualAgreement) {
|
} else if (!closedByMutualAgreement) {
|
||||||
if (stepKey === WorkflowStep.SECOND_DESCRIPTION) {
|
if (stepKey === WorkflowStep.SECOND_DESCRIPTION) {
|
||||||
@@ -4816,6 +4835,9 @@ export class RequestManagementService {
|
|||||||
? FilledBy.EXPERT
|
? FilledBy.EXPERT
|
||||||
: FilledBy.CUSTOMER,
|
: FilledBy.CUSTOMER,
|
||||||
...(isFileMakerRole ? { isMadeByFileMaker: true } : {}),
|
...(isFileMakerRole ? { isMadeByFileMaker: true } : {}),
|
||||||
|
// V5 only: flag that causes the resulting claim to require FileMaker
|
||||||
|
// approval before fanavaran submission. V4 files never set this.
|
||||||
|
...(dto.requiresFileMakerApproval ? { requiresFileMakerApproval: true } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const requestId = String((created as any)._id);
|
const requestId = String((created as any)._id);
|
||||||
@@ -6637,11 +6659,11 @@ export class RequestManagementService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
await this.verifyExpertAccessForBlameV2(req, expert);
|
await this.verifyExpertAccessForBlameV2(req, expert);
|
||||||
// if (req.status !== CaseStatus.WAITING_FOR_SIGNATURES) {
|
if (req.status !== CaseStatus.WAITING_FOR_SIGNATURES) {
|
||||||
// throw new BadRequestException(
|
throw new BadRequestException(
|
||||||
// "Request is not waiting for signatures. Current status: " + req.status,
|
"Request is not waiting for signatures. Current status: " + req.status,
|
||||||
// );
|
);
|
||||||
// }
|
}
|
||||||
const partyIndex = this.getPartyIndex(req, partyRole);
|
const partyIndex = this.getPartyIndex(req, partyRole);
|
||||||
if (partyIndex === -1) {
|
if (partyIndex === -1) {
|
||||||
throw new BadRequestException(
|
throw new BadRequestException(
|
||||||
@@ -6728,6 +6750,23 @@ export class RequestManagementService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
} else if (
|
||||||
|
// THIRD_PARTY IN_PERSON: first party just signed — advance workflow to
|
||||||
|
// FIRST_INVITE_SECOND so the frontend knows to proceed to the second-party
|
||||||
|
// registration step. Status stays WAITING_FOR_SIGNATURES.
|
||||||
|
req.type === BlameRequestType.THIRD_PARTY &&
|
||||||
|
req.creationMethod === CreationMethod.IN_PERSON &&
|
||||||
|
(req.expertInitiated || req.registrarInitiated) &&
|
||||||
|
partyRole === PartyRole.FIRST
|
||||||
|
) {
|
||||||
|
await this.blameRequestDbService.findByIdAndUpdate(requestId, {
|
||||||
|
$set: {
|
||||||
|
"workflow.currentStep": WorkflowStep.FIRST_INVITE_SECOND as any,
|
||||||
|
"workflow.nextStep": WorkflowStep.SECOND_INITIAL_FORM as any,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
message =
|
||||||
|
"First party signature recorded. Proceed to register the second party.";
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
requestId: String(req._id),
|
requestId: String(req._id),
|
||||||
@@ -6753,6 +6792,18 @@ export class RequestManagementService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
await this.verifyExpertAccessForBlameV2(req, expert);
|
await this.verifyExpertAccessForBlameV2(req, expert);
|
||||||
|
// In the V2 IN_PERSON mirror flow guilt is resolved automatically after both
|
||||||
|
// parties complete their narrative steps (addDescriptionV2). The sign step for
|
||||||
|
// both parties must happen BEFORE accident-fields is called — calling it earlier
|
||||||
|
// would overwrite the workflow and cause the frontend to skip the sign page.
|
||||||
|
if (
|
||||||
|
req.creationMethod === CreationMethod.IN_PERSON &&
|
||||||
|
req.status !== CaseStatus.COMPLETED
|
||||||
|
) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
`accident-fields can only be submitted after both parties have signed (current status: ${req.status}). Complete both signatures first.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (!req.expert) req.expert = {} as any;
|
if (!req.expert) req.expert = {} as any;
|
||||||
if (!req.expert.decision) req.expert.decision = {} as any;
|
if (!req.expert.decision) req.expert.decision = {} as any;
|
||||||
@@ -6781,29 +6832,24 @@ export class RequestManagementService {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// For IN_PERSON expert files: after accident fields are filled, guilt is
|
// IN_PERSON V2 mirror: accident-fields is a post-sign supplemental step called
|
||||||
// decided and we can go straight to signatures (no separate review needed).
|
// only after COMPLETED (both parties have signed). Record WAITING_FOR_GUILT_DECISION
|
||||||
|
// in completedSteps here — it was intentionally omitted from description so the
|
||||||
|
// frontend does not route to this page before signatures are collected.
|
||||||
if (req.creationMethod === CreationMethod.IN_PERSON) {
|
if (req.creationMethod === CreationMethod.IN_PERSON) {
|
||||||
const completed = Array.isArray(req.workflow?.completedSteps)
|
const completed = Array.isArray(req.workflow?.completedSteps)
|
||||||
? req.workflow.completedSteps
|
? req.workflow.completedSteps
|
||||||
: [];
|
: [];
|
||||||
const currentStep = req.workflow?.currentStep as WorkflowStep | undefined;
|
|
||||||
if (currentStep && !completed.includes(currentStep)) {
|
|
||||||
completed.push(currentStep);
|
|
||||||
}
|
|
||||||
if (!completed.includes(WorkflowStep.WAITING_FOR_GUILT_DECISION)) {
|
if (!completed.includes(WorkflowStep.WAITING_FOR_GUILT_DECISION)) {
|
||||||
completed.push(WorkflowStep.WAITING_FOR_GUILT_DECISION);
|
completed.push(WorkflowStep.WAITING_FOR_GUILT_DECISION);
|
||||||
}
|
}
|
||||||
if (req.workflow) {
|
if (req.workflow) {
|
||||||
req.workflow.completedSteps = completed;
|
req.workflow.completedSteps = completed;
|
||||||
req.workflow.currentStep = WorkflowStep.WAITING_FOR_SIGNATURES;
|
|
||||||
req.workflow.nextStep = undefined;
|
|
||||||
}
|
}
|
||||||
req.status = CaseStatus.WAITING_FOR_SIGNATURES;
|
|
||||||
|
|
||||||
if (!Array.isArray(req.history)) req.history = [];
|
if (!Array.isArray(req.history)) req.history = [];
|
||||||
req.history.push({
|
req.history.push({
|
||||||
type: "ACCIDENT_FIELDS_SAVED_ADVANCED_TO_SIGNATURES",
|
type: "ACCIDENT_FIELDS_SAVED",
|
||||||
actor: {
|
actor: {
|
||||||
actorId: new Types.ObjectId(String(expert.sub)),
|
actorId: new Types.ObjectId(String(expert.sub)),
|
||||||
actorName:
|
actorName:
|
||||||
@@ -6812,7 +6858,6 @@ export class RequestManagementService {
|
|||||||
},
|
},
|
||||||
metadata: {
|
metadata: {
|
||||||
accidentWay: fields.accidentWay,
|
accidentWay: fields.accidentWay,
|
||||||
advancedTo: WorkflowStep.WAITING_FOR_SIGNATURES,
|
|
||||||
},
|
},
|
||||||
} as any);
|
} as any);
|
||||||
}
|
}
|
||||||
@@ -8834,9 +8879,10 @@ export class RequestManagementService {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
// V5 (isMadeByFileMaker): mark approval gate at claim creation so the flag
|
// V5 only: propagate the approval gate from the blame record to the claim.
|
||||||
// is always present regardless of whether upload-video is called.
|
// isMadeByFileMaker is true for both V4 and V5; only V5 blame records also
|
||||||
...((req as any).isMadeByFileMaker
|
// carry requiresFileMakerApproval=true (set at create time by the V5 controller).
|
||||||
|
...((req as any).requiresFileMakerApproval
|
||||||
? {
|
? {
|
||||||
requiresFileMakerApproval: true,
|
requiresFileMakerApproval: true,
|
||||||
fileMakerApprovalActorId: new Types.ObjectId(actor.sub),
|
fileMakerApprovalActorId: new Types.ObjectId(actor.sub),
|
||||||
@@ -9242,12 +9288,11 @@ export class RequestManagementService {
|
|||||||
fresh.workflow.currentStep = WorkflowStep.FIRST_INVITE_SECOND;
|
fresh.workflow.currentStep = WorkflowStep.FIRST_INVITE_SECOND;
|
||||||
fresh.workflow.nextStep = WorkflowStep.SECOND_INITIAL_FORM;
|
fresh.workflow.nextStep = WorkflowStep.SECOND_INITIAL_FORM;
|
||||||
} else {
|
} else {
|
||||||
// CAR_BODY: FileMaker is done — seal the file for FileReviewer pickup
|
// CAR_BODY: FileMaker's narrative is done — move to document-upload step.
|
||||||
|
// blame.status transitions to WAITING_FOR_FILE_REVIEWER only after the
|
||||||
|
// FileMaker completes all document uploads, not here at sign time.
|
||||||
fresh.workflow.currentStep = WorkflowStep.FIRST_COMPLETED;
|
fresh.workflow.currentStep = WorkflowStep.FIRST_COMPLETED;
|
||||||
fresh.workflow.nextStep = WorkflowStep.WAITING_FOR_GUILT_DECISION;
|
fresh.workflow.nextStep = WorkflowStep.WAITING_FOR_GUILT_DECISION;
|
||||||
if (isFileMakerActor) {
|
|
||||||
fresh.status = CaseStatus.WAITING_FOR_FILE_REVIEWER;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.pushWorkflowSteps(fresh, [
|
this.pushWorkflowSteps(fresh, [
|
||||||
@@ -9259,10 +9304,8 @@ export class RequestManagementService {
|
|||||||
]);
|
]);
|
||||||
fresh.workflow.currentStep = WorkflowStep.SECOND_COMPLETED;
|
fresh.workflow.currentStep = WorkflowStep.SECOND_COMPLETED;
|
||||||
fresh.workflow.nextStep = WorkflowStep.WAITING_FOR_GUILT_DECISION;
|
fresh.workflow.nextStep = WorkflowStep.WAITING_FOR_GUILT_DECISION;
|
||||||
// THIRD_PARTY: second party sign is FileMaker's last step — seal for FileReviewer
|
// blame.status transitions to WAITING_FOR_FILE_REVIEWER only after the
|
||||||
if (isFileMakerActor) {
|
// FileMaker completes all document uploads, not here at sign time.
|
||||||
fresh.status = CaseStatus.WAITING_FOR_FILE_REVIEWER;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
await (fresh as any).save();
|
await (fresh as any).save();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user