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:
@@ -131,6 +131,33 @@ export class ClaimDetailV2ResponseDto {
|
||||
url?: string;
|
||||
}>;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description:
|
||||
"Append-only expert damaged-part revisions. Removed rows retain their original capture URL even after the authoritative selection changes.",
|
||||
type: "array",
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {
|
||||
revisionId: { type: "string" },
|
||||
changedAt: { type: "string", format: "date-time" },
|
||||
changedBy: { type: "object" },
|
||||
removedParts: { type: "array", items: { type: "object" } },
|
||||
addedParts: { type: "array", items: { type: "object" } },
|
||||
},
|
||||
},
|
||||
})
|
||||
damagedPartsHistory?: Array<{
|
||||
revisionId: string;
|
||||
changedAt: Date | string;
|
||||
changedBy: {
|
||||
actorId: string;
|
||||
actorName?: string;
|
||||
actorType: string;
|
||||
};
|
||||
removedParts: Array<Record<string, unknown>>;
|
||||
addedParts: Array<Record<string, unknown>>;
|
||||
}>;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description:
|
||||
"True when user uploaded all required factors and the case awaits expert approve/reject.",
|
||||
|
||||
@@ -90,4 +90,21 @@ describe("SubmitExpertReplyV2Dto", () => {
|
||||
|
||||
expect(await validate(dto)).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("accepts a grouped Persian current car price as a string", async () => {
|
||||
const dto = plainToInstance(SubmitExpertReplyV2Dto, {
|
||||
carPrice: "۱٬۲۵۰٬۰۰۰٬۰۰۰",
|
||||
parts: [
|
||||
{
|
||||
partId: 201,
|
||||
typeOfDamage: TypeOfDamage.Repair,
|
||||
salary: "1000000",
|
||||
totalPayment: "1000000",
|
||||
factorNeeded: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(await validate(dto)).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -122,8 +122,9 @@ export class SubmitExpertReplyV2Dto {
|
||||
description?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: '1_000_000_000',
|
||||
description: "Today's car price",
|
||||
example: '1000000000',
|
||||
description:
|
||||
"Current vehicle price in Rial. Required for car-body claims and forbidden for third-party claims. This is independent from evaluation.priceDrop.carPrice.",
|
||||
})
|
||||
@IsOptional()
|
||||
@IsMoneyAmountString()
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -143,6 +143,10 @@ import {
|
||||
} from "src/helpers/outer-damage-parts";
|
||||
import { normalizeResendCarPartsForStorage } from "src/helpers/claim-expert-resend";
|
||||
import { ClaimVehicleTypeV2 } from "src/static/outer-car-parts-catalog";
|
||||
import {
|
||||
buildDamagedPartSelectionRevision,
|
||||
serializeDamagedPartSelectionHistory,
|
||||
} from "src/helpers/claim-damaged-part-audit";
|
||||
import { snapshotFromDamageExpert } from "src/helpers/expert-profile-snapshot";
|
||||
import { DamageExpertModel } from "src/users/entities/schema/damage-expert.schema";
|
||||
import { SmsOrchestrationService } from "src/sms-orchestration/sms-orchestration.service";
|
||||
@@ -172,6 +176,10 @@ import {
|
||||
import { buildEnrichedDamagedParts } from "./dto/claim-damaged-part.enricher";
|
||||
import { canonicalizeResendDocumentKey } from "src/helpers/claim-resend-document-keys";
|
||||
import { getExpertReplyPricingValidationError } from "src/helpers/expert-reply-pricing";
|
||||
import {
|
||||
normalizeMoneyAmountString,
|
||||
parseMoneyAmountToman,
|
||||
} from "src/utils/unicode-digits";
|
||||
|
||||
@Injectable()
|
||||
export class ExpertClaimService {
|
||||
@@ -2719,9 +2727,14 @@ export class ExpertClaimService {
|
||||
|
||||
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.
|
||||
// Phase 1 is intentionally idempotent. The linked claim can still be
|
||||
// in a data-capture status here, so falling through to the damage
|
||||
// assessment status gate would incorrectly reject the same reviewer.
|
||||
return {
|
||||
success: true,
|
||||
status: "already_assigned_to_you",
|
||||
message: "You have already taken this file.",
|
||||
};
|
||||
} else {
|
||||
// Phase 1: first-time blame assignment
|
||||
return this.assignFileReviewerToV4Blame(claimRequestId, claim, actor);
|
||||
@@ -3263,8 +3276,6 @@ export class ExpertClaimService {
|
||||
) {
|
||||
if (actor.role !== RoleEnum.FIELD_EXPERT) requireActorClientKey(actor);
|
||||
const claim = await this.claimCaseDbService.findById(claimRequestId);
|
||||
const blame = await this.blameRequestDbService.findById(claim.blameRequestId);
|
||||
|
||||
if (!claim) {
|
||||
throw new NotFoundException(
|
||||
this.expertReplySubmissionError(
|
||||
@@ -3274,6 +3285,18 @@ export class ExpertClaimService {
|
||||
);
|
||||
}
|
||||
|
||||
const blame = await this.blameRequestDbService.findById(
|
||||
claim.blameRequestId,
|
||||
);
|
||||
if (!blame) {
|
||||
throw new NotFoundException(
|
||||
this.expertReplySubmissionError(
|
||||
"پرونده تعیین خسارت مرتبط یافت نشد.",
|
||||
"BLAME_NOT_FOUND",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await this.assertExpertActorOnClaim(claim, actor);
|
||||
|
||||
if (claim.status !== ClaimCaseStatus.EXPERT_REVIEWING) {
|
||||
@@ -3304,12 +3327,33 @@ export class ExpertClaimService {
|
||||
);
|
||||
}
|
||||
|
||||
if (reply.carPrice && blame.type !== BlameRequestType.CAR_BODY) {
|
||||
throw new ForbiddenException("قیمت روز خودرو فقط در پرونده های بدنه باید بررسی شود")
|
||||
}
|
||||
const carPriceWasProvided = reply.carPrice != null;
|
||||
let normalizedCurrentCarPrice: string | undefined;
|
||||
|
||||
if (blame.type === BlameRequestType.THIRD_PARTY && reply.carPrice) {
|
||||
throw new ForbiddenException("قیمت روز خودرو فقط در پرونده های بدنه باید بررسی شود")
|
||||
if (blame.type === BlameRequestType.CAR_BODY) {
|
||||
const parsedCurrentCarPrice = parseMoneyAmountToman(reply.carPrice);
|
||||
if (
|
||||
parsedCurrentCarPrice == null ||
|
||||
!Number.isSafeInteger(parsedCurrentCarPrice) ||
|
||||
parsedCurrentCarPrice <= 0
|
||||
) {
|
||||
throw new BadRequestException(
|
||||
this.expertReplySubmissionError(
|
||||
"قیمت روز خودرو در پرونده بدنه الزامی است و باید مبلغی صحیح و بیشتر از صفر باشد.",
|
||||
"CAR_PRICE_REQUIRED",
|
||||
{ field: "carPrice" },
|
||||
),
|
||||
);
|
||||
}
|
||||
normalizedCurrentCarPrice = normalizeMoneyAmountString(reply.carPrice!);
|
||||
} else if (carPriceWasProvided) {
|
||||
throw new ForbiddenException(
|
||||
this.expertReplySubmissionError(
|
||||
"قیمت روز خودرو فقط برای پرونده بدنه قابل ثبت است.",
|
||||
"CAR_PRICE_NOT_ALLOWED",
|
||||
{ field: "carPrice" },
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const pricingValidationError = getExpertReplyPricingValidationError(
|
||||
@@ -3540,7 +3584,9 @@ export class ExpertClaimService {
|
||||
"evaluation.ownerInsurerApproval": "",
|
||||
"evaluation.ownerPricedPartsApproval": "",
|
||||
},
|
||||
"vehicle.price": reply.carPrice,
|
||||
...(normalizedCurrentCarPrice
|
||||
? { "vehicle.price": normalizedCurrentCarPrice }
|
||||
: {}),
|
||||
"workflow.currentStep": currentStep,
|
||||
"workflow.nextStep": nextWorkflowStep,
|
||||
[`evaluation.${replyField}`]: replyPayload,
|
||||
@@ -5050,6 +5096,10 @@ export class ExpertClaimService {
|
||||
buildFileLink,
|
||||
resolveStoredFileUrl,
|
||||
});
|
||||
const damagedPartsHistory = serializeDamagedPartSelectionHistory({
|
||||
history: (claim.damage as any)?.partSelectionHistory,
|
||||
currentSelectedParts: selectedNormExpert,
|
||||
});
|
||||
|
||||
// Vehicle payload — fall back to blame inquiry if claim vehicle is sparse
|
||||
let vehiclePayload = claim.vehicle as any;
|
||||
@@ -5229,6 +5279,7 @@ export class ExpertClaimService {
|
||||
: undefined,
|
||||
carAngles,
|
||||
damagedParts,
|
||||
damagedPartsHistory,
|
||||
awaitingFactorValidation: isFactorValidationPending,
|
||||
requiresFileMakerApproval: !!(claim as any).requiresFileMakerApproval,
|
||||
fileMakerRejectionCount: (claim as any).fileMakerRejectionCount ?? 0,
|
||||
@@ -5606,33 +5657,58 @@ export class ExpertClaimService {
|
||||
);
|
||||
const mergedExpertAdded = [...existingExpertAdded, ...expertAddedToAppend];
|
||||
|
||||
const changedAt = new Date();
|
||||
const partSelectionRevision = buildDamagedPartSelectionRevision({
|
||||
revisionId: new Types.ObjectId().toString(),
|
||||
changedAt,
|
||||
changedBy: {
|
||||
actorId: actor.sub,
|
||||
actorName: actor.fullName,
|
||||
actorType: "damage_expert",
|
||||
},
|
||||
expertProfileSnapshot: damagedPartsEditSnapshot,
|
||||
previousParts: previousNorm,
|
||||
selectedParts: nextNorm,
|
||||
previousMedia: prevMedia,
|
||||
});
|
||||
|
||||
const $set: Record<string, unknown> = {
|
||||
"damage.selectedParts": nextNorm,
|
||||
"media.damagedParts": nextMedia,
|
||||
"damage.expertAddedParts": mergedExpertAdded,
|
||||
};
|
||||
|
||||
await this.claimCaseDbService.findByIdAndUpdate(claimRequestId, {
|
||||
$set,
|
||||
$push: {
|
||||
history: {
|
||||
type: "EXPERT_DAMAGED_PARTS_UPDATED",
|
||||
actor: {
|
||||
actorId: new Types.ObjectId(actor.sub),
|
||||
actorName: actor.fullName,
|
||||
actorType: "damage_expert",
|
||||
},
|
||||
timestamp: new Date(),
|
||||
metadata: {
|
||||
previousSelectedParts: previous,
|
||||
selectedParts: nextNorm,
|
||||
expertAddedParts: mergedExpertAdded,
|
||||
...(damagedPartsEditSnapshot && {
|
||||
expertProfileSnapshot: damagedPartsEditSnapshot,
|
||||
}),
|
||||
},
|
||||
const push: Record<string, unknown> = {
|
||||
history: {
|
||||
type: "EXPERT_DAMAGED_PARTS_UPDATED",
|
||||
actor: {
|
||||
actorId: new Types.ObjectId(actor.sub),
|
||||
actorName: actor.fullName,
|
||||
actorType: "damage_expert",
|
||||
},
|
||||
timestamp: changedAt,
|
||||
metadata: {
|
||||
previousSelectedParts: previous,
|
||||
selectedParts: nextNorm,
|
||||
expertAddedParts: mergedExpertAdded,
|
||||
...(partSelectionRevision && {
|
||||
partSelectionRevisionId: partSelectionRevision.revisionId,
|
||||
removedParts: partSelectionRevision.removedParts,
|
||||
addedParts: partSelectionRevision.addedParts,
|
||||
}),
|
||||
...(damagedPartsEditSnapshot && {
|
||||
expertProfileSnapshot: damagedPartsEditSnapshot,
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
||||
if (partSelectionRevision) {
|
||||
push["damage.partSelectionHistory"] = partSelectionRevision;
|
||||
}
|
||||
|
||||
await this.claimCaseDbService.findByIdAndUpdate(claimRequestId, {
|
||||
$set,
|
||||
$push: push,
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -5640,6 +5716,7 @@ export class ExpertClaimService {
|
||||
selectedParts: nextNorm,
|
||||
previousSelectedParts: previous,
|
||||
expertAddedParts: mergedExpertAdded,
|
||||
partSelectionRevision,
|
||||
message: "Damaged parts updated successfully.",
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user