forked from Yara724/api
fix: harden claim review and inquiry workflows
Preserve damage history and current vehicle price, restore depreciation mapping, normalize inquiry/report output, and support resumable expert review with paginated case retrieval.
This commit is contained in:
@@ -1,10 +1,14 @@
|
||||
import { BadRequestException } from "@nestjs/common";
|
||||
import { Types } from "mongoose";
|
||||
import { ClaimCaseStatus } from "src/Types&Enums/claim-request-management/claim-case-status.enum";
|
||||
import { DaghiOption } from "src/Types&Enums/claim-request-management/daghi-option.enum";
|
||||
import { RoleEnum } from "src/Types&Enums/role.enum";
|
||||
import { TypeOfDamage } from "src/Types&Enums/claim-request-management/type-of-damage.enum";
|
||||
import { BlameRequestType } from "src/Types&Enums/blame-request-management/blameRequestType.enum";
|
||||
import { ExpertClaimService } from "./expert-claim.service";
|
||||
|
||||
const V2_EXPERT_ID = "66ec0e480e321873c0900001";
|
||||
|
||||
const blankPricingReply = {
|
||||
description: "Damage assessment",
|
||||
parts: [
|
||||
@@ -26,7 +30,144 @@ function createService() {
|
||||
) as ExpertClaimService;
|
||||
}
|
||||
|
||||
function setupSuccessfulV2Submit(blameType: BlameRequestType) {
|
||||
const service = createService() as any;
|
||||
const findByIdAndUpdate = jest.fn().mockResolvedValue(undefined);
|
||||
const claim = {
|
||||
_id: "66ec0e480e321873c0900002",
|
||||
publicId: "CLM-1",
|
||||
blameRequestId: "66ec0e480e321873c0900003",
|
||||
status: ClaimCaseStatus.EXPERT_REVIEWING,
|
||||
workflow: { locked: true, lockedBy: { actorId: V2_EXPERT_ID } },
|
||||
vehicle: { carType: "sedan" },
|
||||
damage: {
|
||||
selectedParts: [
|
||||
{
|
||||
id: 1,
|
||||
name: "front",
|
||||
side: "",
|
||||
label_fa: "جلو کامل",
|
||||
catalogKey: "1",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
service.claimCaseDbService = {
|
||||
findById: jest.fn().mockResolvedValue(claim),
|
||||
findByIdAndUpdate,
|
||||
};
|
||||
service.blameRequestDbService = {
|
||||
findById: jest.fn().mockResolvedValue({
|
||||
type: blameType,
|
||||
expertInitiated: true,
|
||||
creationMethod: "IN_PERSON",
|
||||
}),
|
||||
};
|
||||
service.assertExpertActorOnClaim = jest.fn().mockResolvedValue(undefined);
|
||||
service.snapshotDamageExpert = jest.fn().mockResolvedValue(undefined);
|
||||
service.recordClaimExpertActivity = jest.fn().mockResolvedValue(undefined);
|
||||
|
||||
return { service, findByIdAndUpdate };
|
||||
}
|
||||
|
||||
const validV2Reply = {
|
||||
parts: [
|
||||
{
|
||||
partId: 1,
|
||||
typeOfDamage: TypeOfDamage.Repair,
|
||||
salary: "1000000",
|
||||
totalPayment: "1000000",
|
||||
factorNeeded: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
describe("ExpertClaimService expert-reply pricing", () => {
|
||||
it("requires the current car price for a car-body expert reply", async () => {
|
||||
const { service, findByIdAndUpdate } = setupSuccessfulV2Submit(
|
||||
BlameRequestType.CAR_BODY,
|
||||
);
|
||||
|
||||
await expect(
|
||||
service.submitExpertReplyV2("v2-claim", validV2Reply, {
|
||||
sub: V2_EXPERT_ID,
|
||||
fullName: "Expert One",
|
||||
role: RoleEnum.FIELD_EXPERT,
|
||||
}),
|
||||
).rejects.toMatchObject({
|
||||
response: {
|
||||
code: "CAR_PRICE_REQUIRED",
|
||||
field: "carPrice",
|
||||
},
|
||||
});
|
||||
|
||||
expect(findByIdAndUpdate).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("normalizes and persists the current car price on car-body replies", async () => {
|
||||
const { service, findByIdAndUpdate } = setupSuccessfulV2Submit(
|
||||
BlameRequestType.CAR_BODY,
|
||||
);
|
||||
|
||||
await service.submitExpertReplyV2(
|
||||
"v2-claim",
|
||||
{ ...validV2Reply, carPrice: "۱٬۲۵۰٬۰۰۰٬۰۰۰" },
|
||||
{
|
||||
sub: V2_EXPERT_ID,
|
||||
fullName: "Expert One",
|
||||
role: RoleEnum.FIELD_EXPERT,
|
||||
},
|
||||
);
|
||||
|
||||
expect(findByIdAndUpdate).toHaveBeenCalledTimes(1);
|
||||
expect(findByIdAndUpdate.mock.calls[0][1]).toMatchObject({
|
||||
"vehicle.price": "1250000000",
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects the current car price on third-party replies", async () => {
|
||||
const { service, findByIdAndUpdate } = setupSuccessfulV2Submit(
|
||||
BlameRequestType.THIRD_PARTY,
|
||||
);
|
||||
|
||||
await expect(
|
||||
service.submitExpertReplyV2(
|
||||
"v2-claim",
|
||||
{ ...validV2Reply, carPrice: "1250000000" },
|
||||
{
|
||||
sub: V2_EXPERT_ID,
|
||||
fullName: "Expert One",
|
||||
role: RoleEnum.FIELD_EXPERT,
|
||||
},
|
||||
),
|
||||
).rejects.toMatchObject({
|
||||
response: {
|
||||
code: "CAR_PRICE_NOT_ALLOWED",
|
||||
field: "carPrice",
|
||||
},
|
||||
});
|
||||
|
||||
expect(findByIdAndUpdate).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not overwrite vehicle.price on third-party replies", async () => {
|
||||
const { service, findByIdAndUpdate } = setupSuccessfulV2Submit(
|
||||
BlameRequestType.THIRD_PARTY,
|
||||
);
|
||||
|
||||
await service.submitExpertReplyV2("v2-claim", validV2Reply, {
|
||||
sub: V2_EXPERT_ID,
|
||||
fullName: "Expert One",
|
||||
role: RoleEnum.FIELD_EXPERT,
|
||||
});
|
||||
|
||||
expect(findByIdAndUpdate).toHaveBeenCalledTimes(1);
|
||||
expect(findByIdAndUpdate.mock.calls[0][1]).not.toHaveProperty(
|
||||
"vehicle.price",
|
||||
);
|
||||
});
|
||||
|
||||
it("allows a repair line without daghi and removes a stray daghi payload", () => {
|
||||
const service = createService() as any;
|
||||
|
||||
@@ -105,18 +246,27 @@ describe("ExpertClaimService expert-reply pricing", () => {
|
||||
const findByIdAndUpdate = jest.fn();
|
||||
service.claimCaseDbService = {
|
||||
findById: jest.fn().mockResolvedValue({
|
||||
blameRequestId: "blame-1",
|
||||
status: ClaimCaseStatus.EXPERT_REVIEWING,
|
||||
workflow: { locked: true, lockedBy: { actorId: "expert-1" } },
|
||||
}),
|
||||
findByIdAndUpdate,
|
||||
};
|
||||
service.blameRequestDbService = {
|
||||
findById: jest.fn().mockResolvedValue({ type: "CAR_BODY" }),
|
||||
};
|
||||
service.assertExpertActorOnClaim = jest.fn().mockResolvedValue(undefined);
|
||||
service.snapshotDamageExpert = jest.fn().mockResolvedValue(undefined);
|
||||
|
||||
await expect(
|
||||
service.submitExpertReplyV2("v2-claim", blankPricingReply, {
|
||||
sub: "expert-1",
|
||||
role: RoleEnum.FIELD_EXPERT,
|
||||
}),
|
||||
service.submitExpertReplyV2(
|
||||
"v2-claim",
|
||||
{ ...blankPricingReply, carPrice: "1000000" },
|
||||
{
|
||||
sub: "expert-1",
|
||||
role: RoleEnum.FIELD_EXPERT,
|
||||
},
|
||||
),
|
||||
).rejects.toBeInstanceOf(BadRequestException);
|
||||
|
||||
expect(findByIdAndUpdate).not.toHaveBeenCalled();
|
||||
@@ -127,18 +277,24 @@ describe("ExpertClaimService expert-reply pricing", () => {
|
||||
const findByIdAndUpdate = jest.fn();
|
||||
service.claimCaseDbService = {
|
||||
findById: jest.fn().mockResolvedValue({
|
||||
blameRequestId: "blame-1",
|
||||
status: ClaimCaseStatus.EXPERT_REVIEWING,
|
||||
workflow: { locked: true, lockedBy: { actorId: "expert-1" } },
|
||||
}),
|
||||
findByIdAndUpdate,
|
||||
};
|
||||
service.blameRequestDbService = {
|
||||
findById: jest.fn().mockResolvedValue({ type: "CAR_BODY" }),
|
||||
};
|
||||
service.assertExpertActorOnClaim = jest.fn().mockResolvedValue(undefined);
|
||||
service.snapshotDamageExpert = jest.fn().mockResolvedValue(undefined);
|
||||
|
||||
await expect(
|
||||
service.submitExpertReplyV2(
|
||||
"v2-claim",
|
||||
{
|
||||
description: "Damage assessment",
|
||||
carPrice: "1000000",
|
||||
parts: [
|
||||
{
|
||||
partId: 201,
|
||||
@@ -163,3 +319,132 @@ describe("ExpertClaimService expert-reply pricing", () => {
|
||||
expect(findByIdAndUpdate).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("ExpertClaimService damaged-part audit", () => {
|
||||
it("archives removed capture evidence before replacing the live arrays", async () => {
|
||||
const service = createService() as any;
|
||||
const expertId = "66ec0e480e321873c0900001";
|
||||
const findByIdAndUpdate = jest.fn().mockResolvedValue(undefined);
|
||||
service.claimCaseDbService = {
|
||||
findById: jest.fn().mockResolvedValue({
|
||||
_id: "66ec0e480e321873c0900002",
|
||||
status: ClaimCaseStatus.EXPERT_REVIEWING,
|
||||
workflow: { locked: true, lockedBy: { actorId: expertId } },
|
||||
vehicle: { carType: "SEDAN" },
|
||||
damage: {
|
||||
selectedParts: [
|
||||
{
|
||||
id: 101,
|
||||
name: "hood",
|
||||
side: "front",
|
||||
label_fa: "کاپوت",
|
||||
catalogKey: "front_hood",
|
||||
},
|
||||
{
|
||||
id: 202,
|
||||
name: "door",
|
||||
side: "left",
|
||||
label_fa: "درب چپ",
|
||||
catalogKey: "left_door",
|
||||
},
|
||||
],
|
||||
},
|
||||
media: {
|
||||
damagedParts: [
|
||||
{ path: "claims/hood.jpg", fileName: "hood.jpg" },
|
||||
{ path: "claims/door.jpg", fileName: "door.jpg" },
|
||||
],
|
||||
},
|
||||
}),
|
||||
findByIdAndUpdate,
|
||||
};
|
||||
service.assertExpertActorOnClaim = jest.fn().mockResolvedValue(undefined);
|
||||
service.snapshotDamageExpert = jest.fn().mockResolvedValue({
|
||||
firstName: "Expert",
|
||||
});
|
||||
|
||||
await service.updateClaimDamagedPartsV2(
|
||||
"66ec0e480e321873c0900002",
|
||||
{
|
||||
selectedParts: [
|
||||
{
|
||||
id: 202,
|
||||
name: "door",
|
||||
side: "left",
|
||||
label_fa: "درب چپ",
|
||||
catalogKey: "left_door",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
sub: expertId,
|
||||
fullName: "Expert One",
|
||||
role: RoleEnum.FIELD_EXPERT,
|
||||
},
|
||||
);
|
||||
|
||||
expect(findByIdAndUpdate).toHaveBeenCalledTimes(1);
|
||||
const update = findByIdAndUpdate.mock.calls[0][1];
|
||||
expect(update.$set["media.damagedParts"]).toEqual([
|
||||
expect.objectContaining({ path: "claims/door.jpg" }),
|
||||
]);
|
||||
expect(update.$push["damage.partSelectionHistory"]).toEqual(
|
||||
expect.objectContaining({
|
||||
changedBy: expect.objectContaining({ actorId: expertId }),
|
||||
removedParts: [
|
||||
expect.objectContaining({
|
||||
id: 101,
|
||||
capture: expect.objectContaining({ path: "claims/hood.jpg" }),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("ExpertClaimService FileReviewer assignment", () => {
|
||||
it("lets the assigned reviewer reopen a file while it is still awaiting FileReviewer work", async () => {
|
||||
const service = createService() as any;
|
||||
const reviewerId = new Types.ObjectId();
|
||||
const clientId = new Types.ObjectId();
|
||||
const blameId = new Types.ObjectId();
|
||||
const claimId = new Types.ObjectId();
|
||||
|
||||
service.expireClaimWorkflowLockV2IfStale = jest.fn().mockResolvedValue(undefined);
|
||||
service.claimCaseDbService = {
|
||||
findById: jest.fn().mockResolvedValue({
|
||||
_id: claimId,
|
||||
blameRequestId: blameId,
|
||||
status: ClaimCaseStatus.UPLOADING_REQUIRED_DOCUMENTS,
|
||||
}),
|
||||
};
|
||||
service.blameRequestDbService = {
|
||||
findById: jest.fn().mockResolvedValue({
|
||||
_id: blameId,
|
||||
type: BlameRequestType.CAR_BODY,
|
||||
status: "WAITING_FOR_FILE_REVIEWER",
|
||||
assignedFileReviewerId: reviewerId,
|
||||
parties: [
|
||||
{
|
||||
role: "FIRST",
|
||||
person: { clientId },
|
||||
},
|
||||
],
|
||||
}),
|
||||
};
|
||||
service.assertExpertActorOnClaim = jest.fn();
|
||||
|
||||
await expect(
|
||||
service.assignClaimForReviewV2(String(claimId), {
|
||||
sub: String(reviewerId),
|
||||
role: RoleEnum.FILE_REVIEWER,
|
||||
clientKey: String(clientId),
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
success: true,
|
||||
status: "already_assigned_to_you",
|
||||
});
|
||||
|
||||
expect(service.assertExpertActorOnClaim).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user