forked from Yara724/api
Merge pull request 'YARA-1135' (#227) from s.yahyaee/yara724-api:main into main
Reviewed-on: Yara724/api#227
This commit is contained in:
@@ -265,6 +265,21 @@ export class ClaimCase {
|
|||||||
@Prop({ type: Types.ObjectId, index: true })
|
@Prop({ type: Types.ObjectId, index: true })
|
||||||
fileMakerApprovalActorId?: Types.ObjectId;
|
fileMakerApprovalActorId?: Types.ObjectId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* V5 split flow: number of times the FileMaker has rejected this claim.
|
||||||
|
* Capped at 2; on the 3rd attempt the reject endpoint returns 422 and
|
||||||
|
* the FileMaker must approve instead.
|
||||||
|
*/
|
||||||
|
@Prop({ type: Number, default: 0 })
|
||||||
|
fileMakerRejectionCount?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* V5 split flow: reason text from the most recent FileMaker rejection.
|
||||||
|
* Overwritten on each rejection.
|
||||||
|
*/
|
||||||
|
@Prop({ type: String })
|
||||||
|
fileMakerRejectionReason?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Legacy fields kept optional to simplify progressive migration.
|
* Legacy fields kept optional to simplify progressive migration.
|
||||||
* If you choose to migrate later, we can remove these.
|
* If you choose to migrate later, we can remove these.
|
||||||
|
|||||||
@@ -77,6 +77,19 @@ export class ClaimListItemV2Dto {
|
|||||||
example: true,
|
example: true,
|
||||||
})
|
})
|
||||||
requiresFileMakerApproval?: boolean;
|
requiresFileMakerApproval?: boolean;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description:
|
||||||
|
"V5 only. Number of times the FileMaker has rejected this claim (0–2). When this reaches 2 the FileMaker can only approve.",
|
||||||
|
example: 1,
|
||||||
|
})
|
||||||
|
fileMakerRejectionCount?: number;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: "V5 only. Reason text supplied with the most recent FileMaker rejection.",
|
||||||
|
example: "Damage assessment is incorrect — please re-evaluate parts X and Y",
|
||||||
|
})
|
||||||
|
fileMakerRejectionReason?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class GetClaimListV2ResponseDto {
|
export class GetClaimListV2ResponseDto {
|
||||||
|
|||||||
@@ -22,4 +22,5 @@ export class BusinessRuleException extends HttpException {
|
|||||||
|
|
||||||
export const BusinessErrorCode = {
|
export const BusinessErrorCode = {
|
||||||
DAMAGE_EXPERT_RESEND_LIMIT_EXCEEDED: "DAMAGE_EXPERT_RESEND_LIMIT_EXCEEDED",
|
DAMAGE_EXPERT_RESEND_LIMIT_EXCEEDED: "DAMAGE_EXPERT_RESEND_LIMIT_EXCEEDED",
|
||||||
|
FILE_MAKER_REJECTION_LIMIT_EXCEEDED: "FILE_MAKER_REJECTION_LIMIT_EXCEEDED",
|
||||||
} as const;
|
} as const;
|
||||||
|
|||||||
@@ -4257,6 +4257,9 @@ export class ExpertClaimService {
|
|||||||
(!!blame?.isMadeByFileMaker &&
|
(!!blame?.isMadeByFileMaker &&
|
||||||
String(blame?.status ?? "") === "WAITING_FOR_EXPERT"),
|
String(blame?.status ?? "") === "WAITING_FOR_EXPERT"),
|
||||||
assignedToMe: !!assignedToMe,
|
assignedToMe: !!assignedToMe,
|
||||||
|
requiresFileMakerApproval: !!(c as any).requiresFileMakerApproval,
|
||||||
|
fileMakerRejectionCount: (c as any).fileMakerRejectionCount ?? 0,
|
||||||
|
fileMakerRejectionReason: (c as any).fileMakerRejectionReason ?? undefined,
|
||||||
};
|
};
|
||||||
}) as ClaimListItemV2Dto[];
|
}) as ClaimListItemV2Dto[];
|
||||||
|
|
||||||
@@ -4330,6 +4333,8 @@ export class ExpertClaimService {
|
|||||||
createdAt: c.createdAt,
|
createdAt: c.createdAt,
|
||||||
awaitingFactorValidation: claimIsAwaitingExpertFactorValidationV2(c),
|
awaitingFactorValidation: claimIsAwaitingExpertFactorValidationV2(c),
|
||||||
requiresFileMakerApproval: !!(c as any).requiresFileMakerApproval,
|
requiresFileMakerApproval: !!(c as any).requiresFileMakerApproval,
|
||||||
|
fileMakerRejectionCount: (c as any).fileMakerRejectionCount ?? 0,
|
||||||
|
fileMakerRejectionReason: (c as any).fileMakerRejectionReason ?? undefined,
|
||||||
needsFileReviewerCompletion:
|
needsFileReviewerCompletion:
|
||||||
String(blame?.status ?? "") === "WAITING_FOR_FILE_REVIEWER" ||
|
String(blame?.status ?? "") === "WAITING_FOR_FILE_REVIEWER" ||
|
||||||
(!!blame?.isMadeByFileMaker &&
|
(!!blame?.isMadeByFileMaker &&
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
ApiBody,
|
ApiBody,
|
||||||
ApiOperation,
|
ApiOperation,
|
||||||
ApiParam,
|
ApiParam,
|
||||||
|
ApiResponse,
|
||||||
ApiTags,
|
ApiTags,
|
||||||
} from "@nestjs/swagger";
|
} from "@nestjs/swagger";
|
||||||
import { IsOptional, IsString } from "class-validator";
|
import { IsOptional, IsString } from "class-validator";
|
||||||
@@ -91,9 +92,28 @@ export class FileMakerClaimApprovalV5Controller {
|
|||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: "Reject the completed claim back to FileReviewer (FileMaker V5)",
|
summary: "Reject the completed claim back to FileReviewer (FileMaker V5)",
|
||||||
description:
|
description:
|
||||||
"Rejects a claim that is in WAITING_FOR_FILE_MAKER_APPROVAL status. " +
|
"Rejects a claim that is in `WAITING_FOR_FILE_MAKER_APPROVAL` status. " +
|
||||||
"Moves claim to FILE_MAKER_REJECTED so the FileReviewer can re-lock, " +
|
"Moves the claim back to `WAITING_FOR_DAMAGE_EXPERT` and the linked blame back to " +
|
||||||
"adjust the damage assessment, and restart user interaction as needed.",
|
"`WAITING_FOR_FILE_REVIEWER` so the FileReviewer can re-lock, adjust the damage " +
|
||||||
|
"assessment, and restart user interaction as needed.\n\n" +
|
||||||
|
"**Rejection limit:** the FileMaker may reject at most **2 times** per claim. " +
|
||||||
|
"On the 3rd attempt the endpoint returns **422** with " +
|
||||||
|
"`errorCode: FILE_MAKER_REJECTION_LIMIT_EXCEEDED` — the FileMaker must approve instead. " +
|
||||||
|
"The current count is returned as `fileMakerRejectionCount` in the response and in " +
|
||||||
|
"`GET v2/expert-claim/requests`.",
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 422,
|
||||||
|
description:
|
||||||
|
"Business rule violation: rejection limit exceeded. " +
|
||||||
|
"`errorCode: FILE_MAKER_REJECTION_LIMIT_EXCEEDED` — the claim has already been rejected twice.",
|
||||||
|
schema: {
|
||||||
|
example: {
|
||||||
|
errorCode: "FILE_MAKER_REJECTION_LIMIT_EXCEEDED",
|
||||||
|
message:
|
||||||
|
"This claim has already been rejected twice. You must approve it now — no further rejections are allowed.",
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
async reject(
|
async reject(
|
||||||
@Param("claimRequestId") claimRequestId: string,
|
@Param("claimRequestId") claimRequestId: string,
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ import { WorkflowStepDbService } from "src/workflow-step-management/entities/db-
|
|||||||
import { WorkflowStepModel } from "src/workflow-step-management/entities/schema/workflow-step.schema";
|
import { WorkflowStepModel } from "src/workflow-step-management/entities/schema/workflow-step.schema";
|
||||||
import { BlameStatus } from "src/Types&Enums/blame-request-management/blameStatus.enum";
|
import { BlameStatus } from "src/Types&Enums/blame-request-management/blameStatus.enum";
|
||||||
import { CaseStatus } from "src/Types&Enums/blame-request-management/caseStatus.enum";
|
import { CaseStatus } from "src/Types&Enums/blame-request-management/caseStatus.enum";
|
||||||
|
import { BusinessRuleException, BusinessErrorCode } from "src/expert-claim/exceptions/business-rule.exception";
|
||||||
import { ExpertFileActivityDbService } from "src/users/entities/db-service/expert-file-activity.db.service";
|
import { ExpertFileActivityDbService } from "src/users/entities/db-service/expert-file-activity.db.service";
|
||||||
import {
|
import {
|
||||||
ExpertFileActivityType,
|
ExpertFileActivityType,
|
||||||
@@ -10360,7 +10361,17 @@ export class RequestManagementService {
|
|||||||
|
|
||||||
this.assertFileMakerApprovalAccess(claim, fileMaker);
|
this.assertFileMakerApprovalAccess(claim, fileMaker);
|
||||||
|
|
||||||
if (claim.status !== ClaimCaseStatus.WAITING_FOR_FILE_MAKER_APPROVAL) {
|
// After 2 rejections the claim is in WAITING_FOR_DAMAGE_EXPERT (forced-approve
|
||||||
|
// path); any other status is still an error.
|
||||||
|
const rejectionCount = (claim as any).fileMakerRejectionCount ?? 0;
|
||||||
|
const forcedApprove =
|
||||||
|
rejectionCount >= 2 &&
|
||||||
|
claim.status === ClaimCaseStatus.WAITING_FOR_DAMAGE_EXPERT;
|
||||||
|
|
||||||
|
if (
|
||||||
|
claim.status !== ClaimCaseStatus.WAITING_FOR_FILE_MAKER_APPROVAL &&
|
||||||
|
!forcedApprove
|
||||||
|
) {
|
||||||
throw new BadRequestException(
|
throw new BadRequestException(
|
||||||
`Claim must be in WAITING_FOR_FILE_MAKER_APPROVAL status to approve. Current: ${claim.status}`,
|
`Claim must be in WAITING_FOR_FILE_MAKER_APPROVAL status to approve. Current: ${claim.status}`,
|
||||||
);
|
);
|
||||||
@@ -10404,11 +10415,14 @@ export class RequestManagementService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* V5: FileMaker rejects the completed claim back to expert review.
|
* V5: FileMaker rejects the completed claim back to FileReviewer for correction.
|
||||||
*
|
*
|
||||||
* Moves claim from WAITING_FOR_FILE_MAKER_APPROVAL → WAITING_FOR_DAMAGE_EXPERT
|
* Allowed at most 2 times per claim lifetime. On the 3rd attempt the endpoint
|
||||||
* so the FileReviewer can re-lock it and adjust the damage assessment or
|
* returns 422 FILE_MAKER_REJECTION_LIMIT_EXCEEDED and the FileMaker must approve.
|
||||||
* restart the back-and-forth with the user.
|
*
|
||||||
|
* Transition (both blame + claim are updated atomically):
|
||||||
|
* claim: WAITING_FOR_FILE_MAKER_APPROVAL → WAITING_FOR_DAMAGE_EXPERT
|
||||||
|
* blame: (any status) → WAITING_FOR_FILE_REVIEWER
|
||||||
*/
|
*/
|
||||||
async fileMakerRejectV5(
|
async fileMakerRejectV5(
|
||||||
fileMaker: any,
|
fileMaker: any,
|
||||||
@@ -10418,6 +10432,7 @@ export class RequestManagementService {
|
|||||||
claimRequestId: string;
|
claimRequestId: string;
|
||||||
publicId: string;
|
publicId: string;
|
||||||
status: string;
|
status: string;
|
||||||
|
fileMakerRejectionCount: number;
|
||||||
message: string;
|
message: string;
|
||||||
}> {
|
}> {
|
||||||
const claim = await this.claimCaseDbService.findById(claimRequestId);
|
const claim = await this.claimCaseDbService.findById(claimRequestId);
|
||||||
@@ -10431,14 +10446,26 @@ export class RequestManagementService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const currentRejections = (claim as any).fileMakerRejectionCount ?? 0;
|
||||||
|
if (currentRejections >= 2) {
|
||||||
|
throw new BusinessRuleException(
|
||||||
|
BusinessErrorCode.FILE_MAKER_REJECTION_LIMIT_EXCEEDED,
|
||||||
|
"This claim has already been rejected twice. You must approve it now — no further rejections are allowed.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const newRejectionCount = currentRejections + 1;
|
||||||
const actorName = `${fileMaker.firstName || ""} ${fileMaker.lastName || ""}`.trim();
|
const actorName = `${fileMaker.firstName || ""} ${fileMaker.lastName || ""}`.trim();
|
||||||
|
|
||||||
await this.claimCaseDbService.findByIdAndUpdate(claimRequestId, {
|
await this.claimCaseDbService.findByIdAndUpdate(claimRequestId, {
|
||||||
$set: {
|
$set: {
|
||||||
status: ClaimCaseStatus.FILE_MAKER_REJECTED,
|
status: ClaimCaseStatus.WAITING_FOR_DAMAGE_EXPERT,
|
||||||
claimStatus: ClaimStatus.NEEDS_REVISION,
|
claimStatus: ClaimStatus.NEEDS_REVISION,
|
||||||
|
fileMakerRejectionCount: newRejectionCount,
|
||||||
|
fileMakerRejectionReason: reason ?? null,
|
||||||
"workflow.currentStep": ClaimWorkflowStep.EXPERT_DAMAGE_ASSESSMENT,
|
"workflow.currentStep": ClaimWorkflowStep.EXPERT_DAMAGE_ASSESSMENT,
|
||||||
"workflow.nextStep": ClaimWorkflowStep.EXPERT_DAMAGE_ASSESSMENT,
|
"workflow.nextStep": ClaimWorkflowStep.EXPERT_DAMAGE_ASSESSMENT,
|
||||||
|
"workflow.locked": false,
|
||||||
},
|
},
|
||||||
$push: {
|
$push: {
|
||||||
history: {
|
history: {
|
||||||
@@ -10449,17 +10476,43 @@ export class RequestManagementService {
|
|||||||
actorType: "file_maker",
|
actorType: "file_maker",
|
||||||
},
|
},
|
||||||
timestamp: new Date(),
|
timestamp: new Date(),
|
||||||
metadata: { reason: reason ?? null },
|
metadata: { reason: reason ?? null, rejectionCount: newRejectionCount },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Reset blame back to WAITING_FOR_FILE_REVIEWER so the reviewer's list
|
||||||
|
// query (which looks for that status + assignedFileReviewerId) surfaces it.
|
||||||
|
if (claim.blameRequestId) {
|
||||||
|
await this.blameRequestDbService.findByIdAndUpdate(
|
||||||
|
String(claim.blameRequestId),
|
||||||
|
{
|
||||||
|
$set: { status: CaseStatus.WAITING_FOR_FILE_REVIEWER },
|
||||||
|
$push: {
|
||||||
|
history: {
|
||||||
|
type: "V5_FILE_MAKER_REJECTED_BLAME_RESET",
|
||||||
|
actor: {
|
||||||
|
actorId: new Types.ObjectId(fileMaker.sub),
|
||||||
|
actorName,
|
||||||
|
actorType: "file_maker",
|
||||||
|
},
|
||||||
|
timestamp: new Date(),
|
||||||
|
metadata: { claimRequestId, reason: reason ?? null },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
claimRequestId,
|
claimRequestId,
|
||||||
publicId: claim.publicId,
|
publicId: claim.publicId,
|
||||||
status: ClaimCaseStatus.FILE_MAKER_REJECTED,
|
status: ClaimCaseStatus.WAITING_FOR_DAMAGE_EXPERT,
|
||||||
|
fileMakerRejectionCount: newRejectionCount,
|
||||||
message:
|
message:
|
||||||
"Claim rejected by FileMaker. FileReviewer can re-lock the claim and adjust the damage assessment or restart the user interaction.",
|
newRejectionCount >= 2
|
||||||
|
? "Claim rejected by FileMaker (2nd rejection — next action must be approval). FileReviewer can re-lock and adjust the assessment."
|
||||||
|
: "Claim rejected by FileMaker. FileReviewer can re-lock the claim and adjust the damage assessment or restart the user interaction.",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user