forked from Yara724/api
Merge pull request 'Fixed v5 rejection/approval flow' (#230) from s.yahyaee/yara724-api:main into main
Reviewed-on: Yara724/api#230
This commit is contained in:
@@ -971,7 +971,7 @@ export class ExpertClaimService {
|
||||
daghi: {
|
||||
option: part.daghi.option,
|
||||
...(part.daghi.price && { price: part.daghi.price }),
|
||||
...(part.daghi.branchId && {
|
||||
...(part.daghi.branchId && Types.ObjectId.isValid(part.daghi.branchId) && {
|
||||
branchId: new Types.ObjectId(part.daghi.branchId),
|
||||
}),
|
||||
},
|
||||
@@ -2671,16 +2671,23 @@ export class ExpertClaimService {
|
||||
if (!reviewerBlame) {
|
||||
throw new NotFoundException("Linked blame file not found.");
|
||||
}
|
||||
const blameStatus = (reviewerBlame as any).status as string;
|
||||
if (blameStatus === "WAITING_FOR_FILE_REVIEWER") {
|
||||
// Phase 1: blame-assign path
|
||||
return this.assignFileReviewerToV4Blame(claimRequestId, claim, actor);
|
||||
}
|
||||
// Phase 2: blame is past the initial assignment step.
|
||||
// Only the reviewer who was assigned during Phase 1 may proceed.
|
||||
const assignedReviewerId = (reviewerBlame as any).assignedFileReviewerId
|
||||
? String((reviewerBlame as any).assignedFileReviewerId)
|
||||
: null;
|
||||
const blameStatus = (reviewerBlame as any).status as string;
|
||||
|
||||
if (blameStatus === "WAITING_FOR_FILE_REVIEWER") {
|
||||
if (assignedReviewerId && assignedReviewerId === actor.sub) {
|
||||
// Reviewer already assigned (e.g. after a FileMaker rejection that reset
|
||||
// blame back to WAITING_FOR_FILE_REVIEWER) — skip Phase 1 and fall
|
||||
// through to the damage-expert workflow lock below.
|
||||
} else {
|
||||
// Phase 1: first-time blame assignment
|
||||
return this.assignFileReviewerToV4Blame(claimRequestId, claim, actor);
|
||||
}
|
||||
} else {
|
||||
// Phase 2: blame is past the initial assignment step.
|
||||
// Only the reviewer who was assigned during Phase 1 may proceed.
|
||||
if (!assignedReviewerId) {
|
||||
throw new BadRequestException({
|
||||
success: false,
|
||||
@@ -2695,8 +2702,8 @@ export class ExpertClaimService {
|
||||
message: "This file is assigned to another reviewer.",
|
||||
});
|
||||
}
|
||||
// Assigned reviewer — fall through to the damage-expert workflow lock below.
|
||||
// (actor.role stays FILE_REVIEWER; assertExpertActorOnClaim checks clientKey scope)
|
||||
}
|
||||
// Fall through to the damage-expert workflow lock below.
|
||||
}
|
||||
|
||||
await this.assertExpertActorOnClaim(claim, actor);
|
||||
@@ -2777,9 +2784,15 @@ export class ExpertClaimService {
|
||||
const now = new Date();
|
||||
const lockSnapshot = await this.snapshotDamageExpert(actor.sub);
|
||||
const expiredAt = new Date(now.getTime() + EXPERT_WORKFLOW_LOCK_TTL_MS);
|
||||
const actorType = isFieldExpertOwner ? "field_expert" : "damage_expert";
|
||||
const actorType = isFieldExpertOwner
|
||||
? "field_expert"
|
||||
: (actor as any).role === RoleEnum.FILE_REVIEWER
|
||||
? "file_reviewer"
|
||||
: "damage_expert";
|
||||
const actorRoleLabel = isFieldExpertOwner
|
||||
? ("field_expert" as const)
|
||||
: (actor as any).role === RoleEnum.FILE_REVIEWER
|
||||
? ("file_reviewer" as const)
|
||||
: ("damage_expert" as const);
|
||||
|
||||
const lockedByPayload = {
|
||||
@@ -2834,7 +2847,13 @@ export class ExpertClaimService {
|
||||
},
|
||||
};
|
||||
|
||||
if (!claim.workflow?.assignedForReviewBy) {
|
||||
// Update assignedForReviewBy whenever a FILE_REVIEWER re-locks after a
|
||||
// FileMaker rejection — the stale payload (from a prior damage-expert cycle)
|
||||
// must be replaced with the actual reviewer so submit checks pass.
|
||||
if (
|
||||
!claim.workflow?.assignedForReviewBy ||
|
||||
(actor as any).role === RoleEnum.FILE_REVIEWER
|
||||
) {
|
||||
baseLockUpdate["workflow.assignedForReviewBy"] = lockedByPayload;
|
||||
}
|
||||
|
||||
@@ -3363,6 +3382,12 @@ export class ExpertClaimService {
|
||||
const nextCaseStatus = claimCaseStatusAfterExpertReplyV2(processedParts);
|
||||
let nextClaimStatus = ClaimStatus.APPROVED;
|
||||
|
||||
// V5 flow: FileMaker must approve before the owner is asked to sign.
|
||||
// When no factor upload is needed, skip the owner-sign step entirely here
|
||||
// and park the claim at WAITING_FOR_FILE_MAKER_APPROVAL. The owner SMS
|
||||
// and INSURER_REVIEW status are emitted by fileMakerApproveV5 instead.
|
||||
const isV5Claim = !!(claim as any).requiresFileMakerApproval;
|
||||
|
||||
if (needsFactorUpload) {
|
||||
nextClaimStatus = ClaimStatus.NEEDS_REVISION;
|
||||
if (mixedFactorAndPrice) {
|
||||
@@ -3372,6 +3397,10 @@ export class ExpertClaimService {
|
||||
currentStep = ClaimWorkflowStep.OWNER_UPLOAD_FACTOR_DOCUMENTS;
|
||||
nextWorkflowStep = ClaimWorkflowStep.EXPERT_COST_EVALUATION;
|
||||
}
|
||||
} else if (isV5Claim) {
|
||||
// V5, no factors: hold at WAITING_FOR_FILE_MAKER_APPROVAL
|
||||
currentStep = ClaimWorkflowStep.INSURER_REVIEW;
|
||||
nextWorkflowStep = ClaimWorkflowStep.CLAIM_COMPLETED;
|
||||
}
|
||||
|
||||
const mergedSelectedParts = [...ownerSelectedParts];
|
||||
@@ -3389,7 +3418,9 @@ export class ExpertClaimService {
|
||||
}
|
||||
|
||||
const updatePayload: Record<string, unknown> = {
|
||||
status: nextCaseStatus,
|
||||
status: isV5Claim && !needsFactorUpload
|
||||
? ClaimCaseStatus.WAITING_FOR_FILE_MAKER_APPROVAL
|
||||
: nextCaseStatus,
|
||||
claimStatus: nextClaimStatus,
|
||||
...(expertAddedParts.length > 0
|
||||
? { "damage.selectedParts": mergedSelectedParts }
|
||||
@@ -3445,8 +3476,9 @@ export class ExpertClaimService {
|
||||
idempotencyKey: `claim:${claimRequestId}:handled:${actor.sub}`,
|
||||
});
|
||||
|
||||
// V5: owner SMS is sent by fileMakerApproveV5 after FileMaker approval.
|
||||
const ownerPhoneNotify = await this.resolveClaimOwnerPhone(claim);
|
||||
if (ownerPhoneNotify && !needsFactorUpload) {
|
||||
if (ownerPhoneNotify && !needsFactorUpload && !isV5Claim) {
|
||||
const expertLastName =
|
||||
actor?.fullName?.trim()?.split(/\s+/).pop() || "کارشناس";
|
||||
await this.smsOrchestrationService.sendSignatureReviewNotice({
|
||||
@@ -3454,7 +3486,7 @@ export class ExpertClaimService {
|
||||
fileKind: "claim",
|
||||
publicId: claim.publicId,
|
||||
expertLastName,
|
||||
link: this.smsOrchestrationService.buildClaimLink(String(claim._id), "v1"),
|
||||
link: this.smsOrchestrationService.buildClaimLink(String(claim._id), "v2"),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -64,26 +64,19 @@ export class FileMakerClaimApprovalV5Controller {
|
||||
@ApiOperation({
|
||||
summary: "Approve the completed claim (FileMaker V5)",
|
||||
description:
|
||||
"Approves a claim that is in WAITING_FOR_FILE_MAKER_APPROVAL status. " +
|
||||
"Marks the claim COMPLETED and triggers fanavaran submission.",
|
||||
"Approves a claim that is in `WAITING_FOR_FILE_MAKER_APPROVAL` status. " +
|
||||
"Moves the claim to `INSURER_REVIEW_AWAITING_OWNER_SIGN` and sends the owner an SMS " +
|
||||
"with a signature link. Fanavaran submission is triggered automatically after the " +
|
||||
"owner signs.",
|
||||
})
|
||||
async approve(
|
||||
@Param("claimRequestId") claimRequestId: string,
|
||||
@CurrentUser() fileMaker: any,
|
||||
) {
|
||||
const result = await this.requestManagementService.fileMakerApproveV5(
|
||||
return this.requestManagementService.fileMakerApproveV5(
|
||||
fileMaker,
|
||||
claimRequestId,
|
||||
);
|
||||
|
||||
// Trigger fanavaran auto-submit now that the claim is COMPLETED and the gate
|
||||
// (requiresFileMakerApproval) has been cleared.
|
||||
const fanavaran =
|
||||
await this.claimRequestManagementService.autoSubmitToFanavaranV2OnClaimCompleted(
|
||||
claimRequestId,
|
||||
);
|
||||
|
||||
return { ...result, fanavaran };
|
||||
}
|
||||
|
||||
@Post("reject/:claimRequestId")
|
||||
|
||||
@@ -10389,28 +10389,53 @@ export class RequestManagementService {
|
||||
metadata: {},
|
||||
};
|
||||
|
||||
// Move claim to COMPLETED and clear the approval gate
|
||||
// V5 approval: move claim to INSURER_REVIEW_AWAITING_OWNER_SIGN so the
|
||||
// owner can now sign. Clear requiresFileMakerApproval so that when the
|
||||
// owner later signs and autoSubmitToFanavaranV2OnClaimCompleted runs, it
|
||||
// does not re-intercept the claim as a pending V5 gate.
|
||||
await this.claimCaseDbService.findByIdAndUpdate(claimRequestId, {
|
||||
$set: {
|
||||
status: ClaimCaseStatus.COMPLETED,
|
||||
status: ClaimCaseStatus.INSURER_REVIEW_AWAITING_OWNER_SIGN,
|
||||
claimStatus: ClaimStatus.APPROVED,
|
||||
requiresFileMakerApproval: false,
|
||||
"workflow.currentStep": ClaimWorkflowStep.CLAIM_COMPLETED,
|
||||
"workflow.currentStep": ClaimWorkflowStep.INSURER_REVIEW,
|
||||
"workflow.nextStep": ClaimWorkflowStep.CLAIM_COMPLETED,
|
||||
},
|
||||
$push: {
|
||||
"workflow.completedSteps": ClaimWorkflowStep.INSURER_REVIEW,
|
||||
history: historyEntry,
|
||||
},
|
||||
});
|
||||
|
||||
// Notify the owner that the claim is ready for their signature.
|
||||
const notifyUserId = (claim as any).damagedPartyUserId ?? (claim as any).owner?.userId;
|
||||
if (notifyUserId && claim.blameRequestId) {
|
||||
const blame = await this.blameRequestDbService.findById(
|
||||
String(claim.blameRequestId),
|
||||
);
|
||||
const ownerParty = (blame?.parties || []).find(
|
||||
(p: any) =>
|
||||
p?.person?.userId && String(p.person.userId) === String(notifyUserId),
|
||||
);
|
||||
const ownerPhone = ownerParty?.person?.phoneNumber
|
||||
?? (await this.userDbService.findOne({ _id: new Types.ObjectId(String(notifyUserId)) }))?.mobile;
|
||||
if (ownerPhone && typeof ownerPhone === "string") {
|
||||
await this.smsOrchestrationService.sendSignatureReviewNotice({
|
||||
receptor: ownerPhone,
|
||||
fileKind: "claim",
|
||||
publicId: claim.publicId,
|
||||
expertLastName: actorName.split(/\s+/).pop() || "کارشناس",
|
||||
link: this.smsOrchestrationService.buildClaimLink(claimRequestId, "v2"),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
claimRequestId,
|
||||
publicId: claim.publicId,
|
||||
status: ClaimCaseStatus.COMPLETED,
|
||||
status: ClaimCaseStatus.INSURER_REVIEW_AWAITING_OWNER_SIGN,
|
||||
message:
|
||||
"Claim approved by FileMaker. Claim is now marked COMPLETED. " +
|
||||
"Proceed with fanavaran submission via the claim service.",
|
||||
"Claim approved by FileMaker. Owner has been notified to sign. " +
|
||||
"Once the owner signs, the claim will be completed and submitted to Fanavaran.",
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user