1
0
forked from Yara724/api

resolved conflicts and maintained new features

This commit is contained in:
SepehrYahyaee
2026-04-18 17:41:45 +03:30
26 changed files with 2091 additions and 548 deletions

View File

@@ -4,6 +4,7 @@ import { join } from "node:path";
import { HttpService } from "@nestjs/axios";
import {
BadRequestException,
ConflictException,
ForbiddenException,
HttpException,
HttpStatus,
@@ -49,7 +50,9 @@ import { ClaimRequiredDocumentDbService } from "src/claim-request-management/ent
import { FactorStatus } from "src/Types&Enums/claim-request-management/factor-status.enum";
import { DaghiOption } from "src/Types&Enums/claim-request-management/daghi-option.enum";
import { BranchDbService } from "src/client/entities/db-service/branch.db.service";
import { ClaimSubmitResendV2Dto } from "./dto/expert-claim-v2.dto";
import { BlameRequestDbService } from "src/request-management/entities/db-service/blame-request.db.service";
import { FactorValidationV2Dto } from "./dto/factor-validation.dto";
@Injectable()
export class ExpertClaimService {
@@ -162,14 +165,14 @@ export class ExpertClaimService {
private readonly claimVideoCaptureDbService: VideoCaptureDbService,
private readonly httpService: HttpService,
private readonly claimCaseDbService: ClaimCaseDbService,
private readonly blameRequestDbService: BlameRequestDbService,
private readonly sandHubService: SandHubService,
private readonly damageExpertDbService: DamageExpertDbService,
private readonly clientDbService: ClientDbService,
private readonly claimFactorsImageDbService: ClaimFactorsImageDbService,
private readonly claimRequiredDocumentDbService: ClaimRequiredDocumentDbService,
private readonly branchDbService: BranchDbService,
private readonly blameRequestDbService: BlameRequestDbService,
) {}
) { }
/** Map blame party `Vehicle` (name/model/type) to expert panel shape. */
private expertVehicleFromPartyVehicle(v: any): {
@@ -604,7 +607,7 @@ export class ExpertClaimService {
async calculatePriceDrop(request: any, carPartsAi: any, query?: any) {
const requestId = request?._id?.toString() || 'unknown';
try {
// Step 1: Check SandHub data
if (!request?.blameFile?.sanHubId) {
@@ -662,7 +665,7 @@ export class ExpertClaimService {
} else if (severityValues.length > 0) {
// Only fetch car prices if we have severity values to calculate
const carPrices = await this.fetchCarPrices();
if (!carPrices || carPrices.length === 0) {
this.logger.error(
`[PriceDrop] Request ${requestId}: Failed to fetch car prices or car prices list is empty. Cannot find matching car.`,
@@ -672,9 +675,9 @@ export class ExpertClaimService {
`[PriceDrop] Request ${requestId}: Fetched ${carPrices.length} car prices. Searching for: "${request.carDetail.carName}"`,
);
const closestCar = this.findClosestCar(
request.carDetail.carName,
carPrices,
const closestCar = this.findClosestCar(
request.carDetail.carName,
carPrices,
);
if (!closestCar) {
@@ -688,11 +691,11 @@ export class ExpertClaimService {
} else {
this.logger.debug(
`[PriceDrop] Request ${requestId}: Found matching car "${closestCar.carName}" with marketPrice: ${closestCar.marketPrice}`,
);
autoCalculatedData = {
);
autoCalculatedData = {
carPrice: closestCar.marketPrice,
carValue: severityValues,
};
carValue: severityValues,
};
}
}
}
@@ -730,10 +733,10 @@ export class ExpertClaimService {
}
// Step 6: Save to database
await this.claimRequestManagementDbService.findOneAndUpdate(
{ _id: new Types.ObjectId(request._id) },
{ $set: { priceDrop: finalPriceDropData } },
);
await this.claimRequestManagementDbService.findOneAndUpdate(
{ _id: new Types.ObjectId(request._id) },
{ $set: { priceDrop: finalPriceDropData } },
);
this.logger.log(
`[PriceDrop] Request ${requestId}: Price drop calculation completed and saved.`,
@@ -801,7 +804,7 @@ export class ExpertClaimService {
const severityValue =
this.priceDropPart[originalPriceDropKey]?.[
damagedPartInfo.severity
damagedPartInfo.severity
];
if (severityValue !== undefined) {
@@ -1098,13 +1101,13 @@ export class ExpertClaimService {
private isCurrentUserAllowed(request: any, currentUser: any): boolean {
// Check if locked by current user and lock is still active
const isLockedByCurrentUser =
const isLockedByCurrentUser =
String(request?.actorLocked?.actorId) === currentUser.sub &&
request.lockFile;
if (!isLockedByCurrentUser) {
return false;
}
}
// Also check if lock has expired (unlockTime has passed)
if (request.unlockTime) {
@@ -1116,7 +1119,7 @@ export class ExpertClaimService {
return true;
}
}
return true;
}
@@ -1124,7 +1127,7 @@ export class ExpertClaimService {
if (!request.lockFile || request.claimStatus !== ReqClaimStatus.ReviewRequest) {
return false;
}
// Check if lock has expired
if (request.unlockTime) {
const unlockTime = new Date(request.unlockTime).getTime();
@@ -1134,15 +1137,15 @@ export class ExpertClaimService {
return false;
}
}
// If currentUser is provided, allow access if they are the one who locked it
if (currentUser) {
const isLockedByCurrentUser =
const isLockedByCurrentUser =
String(request?.actorLocked?.actorId) === currentUser.sub;
// Return false (not locked) if locked by current user, true if locked by someone else
return !isLockedByCurrentUser;
}
// If no currentUser provided, treat as locked
return true;
}
@@ -1165,18 +1168,18 @@ export class ExpertClaimService {
) {
throw new ForbiddenException("Access denied to this request");
}
// Check if lock has expired (unlockTime has passed)
if (request?.unlockTime && !request?.objection) {
const unlockTime = new Date(request.unlockTime).getTime();
const now = Date.now();
if (now >= unlockTime) {
throw new ForbiddenException("Your time has expired");
}
throw new ForbiddenException("Your time has expired");
}
} else if (request?.unlockTime == null && !request?.objection) {
throw new ForbiddenException("Your time has expired");
}
if (!request.lockFile && !request?.objection) {
throw new ForbiddenException(
"For submit reply you must lock the request",
@@ -1385,6 +1388,66 @@ export class ExpertClaimService {
return request;
}
async streamServiceV2(requestId, query, res, header) {
const request = await this.claimCaseDbService.findById(requestId);
console.log(request)
// const blameCase = await this.blameCaseDbService
return request;
// let videoPath: string = "";
// switch (query) {
// case "accident":
// videoPath = await this.getAccidentVideoPath(
// String(
// request?.blameFile?.firstPartyDetails?.firstPartyFile
// ?.firstPartyVideoId,
// ),
// );
// break;
// case "car-capture":
// videoPath = await this.getCarCapture(requestId);
// break;
// default:
// throw new NotFoundException("Invalid query type");
// }
// if (!videoPath) throw new NotFoundException("video_not_found");
// const absolutePath = join(process.cwd(), videoPath.replace(/^\/+/, ""));
// if (!existsSync(absolutePath)) {
// throw new NotFoundException(
// `Video file not found at path: ${absolutePath}`,
// );
// }
// const { size } = statSync(absolutePath);
// const range = header.range;
// if (range) {
// const [startStr, endStr] = range.replace(/bytes=/, "").split("-");
// const start = parseInt(startStr, 10);
// const end = endStr ? parseInt(endStr, 10) : size - 1;
// const chunkSize = end - start + 1;
// const file = createReadStream(absolutePath, { start, end });
// res.writeHead(206, {
// "Content-Range": `bytes ${start}-${end}/${size}`,
// "Accept-Ranges": "bytes",
// "Content-Length": chunkSize,
// "Content-Type": "video/mp4",
// });
// file.pipe(res);
// } else {
// res.writeHead(200, {
// "Content-Length": size,
// "Content-Type": "video/mp4",
// });
// createReadStream(absolutePath).pipe(res);
// }
}
async streamService(requestId, query, res, header) {
const request =
await this.claimRequestManagementDbService.findOne(requestId);
@@ -1658,6 +1721,210 @@ export class ExpertClaimService {
};
}
/**
* V2 (ClaimCase): validate user-uploaded repair factors on the active expert reply.
* Preconditions: all `factorNeeded` parts have `factorLink`; case is UNDER_REVIEW at EXPERT_COST_EVALUATION.
* — All approved → same as non-factor path: claimStatus APPROVED, step INSURER_REVIEW.
* — Any rejected (expert repriced) → case COMPLETED with expert amounts (damage-expert finalization).
*/
async validateClaimFactorsV2(
claimRequestId: string,
body: FactorValidationV2Dto,
actor: { sub: string; fullName?: string; clientKey?: string },
) {
const claim = await this.claimCaseDbService.findById(claimRequestId);
if (!claim) {
throw new NotFoundException("Claim request not found");
}
assertClaimCaseForTenant(claim, actor);
if (
claim.status !== ClaimCaseStatus.WAITING_FOR_INSURER_APPROVAL ||
claim.claimStatus !== ClaimStatus.UNDER_REVIEW ||
claim.workflow?.currentStep !== ClaimWorkflowStep.EXPERT_COST_EVALUATION
) {
throw new BadRequestException(
"Claim is not awaiting expert factor validation (UNDER_REVIEW at EXPERT_COST_EVALUATION).",
);
}
const replyField = claim.evaluation?.damageExpertReplyFinal
? "damageExpertReplyFinal"
: "damageExpertReply";
const finalReply =
replyField === "damageExpertReplyFinal"
? claim.evaluation?.damageExpertReplyFinal
: claim.evaluation?.damageExpertReply;
if (!finalReply?.parts?.length) {
throw new BadRequestException("No expert reply found in this claim.");
}
const requiredFactors = finalReply.parts.filter((p) => p.factorNeeded);
if (requiredFactors.length === 0) {
throw new BadRequestException("No parts require factor validation.");
}
if (!requiredFactors.every((p) => !!p.factorLink)) {
throw new BadRequestException(
"Not all required factors have been uploaded yet.",
);
}
const $set: Record<string, unknown> = {};
for (const decision of body.decisions) {
const partIndex = finalReply.parts.findIndex(
(p) => String(p.partId) === String(decision.partId),
);
if (partIndex === -1) {
this.logger.warn(
`Part ${decision.partId} not found in claim ${claimRequestId}, skipping.`,
);
continue;
}
const part = finalReply.parts[partIndex];
if (!part.factorNeeded) {
this.logger.warn(
`Part ${decision.partId} is not factor-needed, skipping.`,
);
continue;
}
const base = `evaluation.${replyField}.parts.${partIndex}`;
$set[`${base}.factorStatus`] = decision.status;
$set[`${base}.rejectionReason`] = decision.rejectionReason ?? null;
if (decision.price !== undefined) {
$set[`${base}.price`] = decision.price;
}
if (decision.salary !== undefined) {
$set[`${base}.salary`] = decision.salary;
}
if (decision.totalPayment !== undefined) {
$set[`${base}.totalPayment`] = decision.totalPayment;
}
if (decision.status === FactorStatus.REJECTED) {
const hasOverride =
decision.totalPayment != null ||
(decision.price != null && decision.salary != null);
if (!hasOverride) {
throw new BadRequestException(
`Part ${decision.partId}: rejected factors require expert totalPayment (or both price and salary).`,
);
}
}
}
if (Object.keys($set).length === 0) {
throw new BadRequestException("No valid factor decisions to apply.");
}
await this.claimCaseDbService.findByIdAndUpdate(claimRequestId, {
$set,
});
const updatedClaim = await this.claimCaseDbService.findById(claimRequestId);
const updatedReply =
replyField === "damageExpertReplyFinal"
? updatedClaim!.evaluation!.damageExpertReplyFinal!
: updatedClaim!.evaluation!.damageExpertReply!;
const reqParts = updatedReply.parts.filter((p) => p.factorNeeded);
const anyRejected = reqParts.some(
(p) => p.factorStatus === FactorStatus.REJECTED,
);
const anyStillPending = reqParts.some(
(p) => !p.factorStatus || p.factorStatus === FactorStatus.PENDING,
);
const baseMessage = "Factor validation updated.";
if (anyStillPending) {
return {
message: `${baseMessage} Some factors are still pending review.`,
claimRequestId,
claimStatus: updatedClaim!.claimStatus,
caseStatus: updatedClaim!.status,
pendingFactorValidation: true,
};
}
const PRICE_CAP = 30_000_000;
let totalPrice = 0;
for (const part of updatedReply.parts || []) {
const parsed = this.parsePersianNumber(String(part.totalPayment ?? "0"));
if (!isNaN(parsed)) {
totalPrice += parsed;
}
}
if (totalPrice > PRICE_CAP) {
throw new BadRequestException({
message: `Total price (${totalPrice.toLocaleString()}) exceeds the cap of ${PRICE_CAP.toLocaleString()}`,
error: "PRICE_CAP_ERROR",
totalPrice,
priceCap: PRICE_CAP,
});
}
const historyActor = {
actorId: new Types.ObjectId(actor.sub),
actorName: actor.fullName,
actorType: "damage_expert",
};
if (anyRejected) {
await this.claimCaseDbService.findByIdAndUpdate(claimRequestId, {
$set: {
status: ClaimCaseStatus.COMPLETED,
claimStatus: ClaimStatus.APPROVED,
"workflow.currentStep": ClaimWorkflowStep.CLAIM_COMPLETED,
"workflow.nextStep": ClaimWorkflowStep.CLAIM_COMPLETED,
},
$push: {
history: {
type: "FACTORS_VALIDATED_REJECTED_EXPERT_FINALIZED",
actor: historyActor,
timestamp: new Date(),
metadata: { replyField },
},
},
});
return {
message:
"Factors reviewed. Rejected parts were repriced by the expert; the claim is completed.",
claimRequestId,
claimStatus: ClaimStatus.APPROVED,
caseStatus: ClaimCaseStatus.COMPLETED,
outcome: "REJECTED_REPRICED_COMPLETED",
};
}
await this.claimCaseDbService.findByIdAndUpdate(claimRequestId, {
$set: {
claimStatus: ClaimStatus.APPROVED,
"workflow.currentStep": ClaimWorkflowStep.INSURER_REVIEW,
"workflow.nextStep": ClaimWorkflowStep.CLAIM_COMPLETED,
},
$push: {
history: {
type: "FACTORS_VALIDATED_ALL_APPROVED",
actor: historyActor,
timestamp: new Date(),
metadata: { replyField },
},
},
});
return {
message:
"All factors approved. The claim proceeds to insurer review (same as non-factor expert reply).",
claimRequestId,
claimStatus: ClaimStatus.APPROVED,
caseStatus: ClaimCaseStatus.WAITING_FOR_INSURER_APPROVAL,
outcome: "ALL_APPROVED_INSURER_REVIEW",
};
}
async inPersonVisit(requestId: string, actorDetail: any) {
const request =
await this.claimRequestManagementDbService.findOne(requestId);
@@ -1768,6 +2035,80 @@ export class ExpertClaimService {
};
}
async submitResendDocsV2(
claimRequestId: string,
reply: ClaimSubmitResendV2Dto,
actor: any,
) {
const claim = await this.claimCaseDbService.findById(claimRequestId);
if (!claim) {
throw new NotFoundException('Claim request not found');
}
assertClaimCaseForTenant(claim, actor);
if (claim.status !== ClaimCaseStatus.EXPERT_REVIEWING) {
throw new BadRequestException(
`Claim is not in EXPERT_REVIEWING state. Current status: ${claim.status}`,
);
}
if (!claim.workflow?.locked) {
throw new ForbiddenException(
'You must lock the claim before submitting a resend request',
);
}
if (claim.workflow.lockedBy?.actorId?.toString() !== actor.sub) {
throw new ForbiddenException('This claim is locked by another expert');
}
const existingResend = claim.evaluation?.damageExpertResend;
const resendHasContent =
existingResend &&
(String(existingResend.resendDescription || '').trim() !== '' ||
(existingResend.resendDocuments?.length ?? 0) > 0 ||
(existingResend.resendCarParts?.length ?? 0) > 0);
if (resendHasContent) {
throw new ConflictException('A resend request already exists for this claim');
}
await this.claimCaseDbService.findByIdAndUpdate(claimRequestId, {
$set: {
claimStatus: ClaimStatus.NEEDS_REVISION,
'workflow.locked': false,
'evaluation.damageExpertResend': {
resendDescription: reply.resendDescription,
resendDocuments: reply.resendDocuments ?? [],
resendCarParts: reply.resendCarParts ?? [],
},
},
$push: {
history: {
type: 'EXPERT_RESEND_REQUESTED',
actor: {
actorId: new Types.ObjectId(actor.sub),
actorName: actor.fullName,
actorType: 'damage_expert',
},
timestamp: new Date(),
metadata: {
documentCount: reply.resendDocuments?.length ?? 0,
carPartCount: reply.resendCarParts?.length ?? 0,
},
},
},
});
return {
claimRequestId,
claimStatus: ClaimStatus.NEEDS_REVISION,
message:
'Resend request recorded. The damaged party can review requirements and submit an objection or updated materials.',
};
}
/**
* V2: Submit damage expert reply for a claim.
*
@@ -1831,6 +2172,23 @@ export class ExpertClaimService {
const needsFactorUpload = reply.parts?.some((p) => p.factorNeeded === true) ?? false;
const objectionSubmitted = !!claim.evaluation?.objection?.submittedAt;
const hasFinalReply = !!claim.evaluation?.damageExpertReplyFinal;
if (objectionSubmitted && hasFinalReply) {
throw new ConflictException(
'A final expert reply after objection already exists for this claim.',
);
}
const isFinalReplyAfterObjection = objectionSubmitted && !hasFinalReply;
const replyField = isFinalReplyAfterObjection
? 'damageExpertReplyFinal'
: 'damageExpertReply';
const completedStep = isFinalReplyAfterObjection
? ClaimWorkflowStep.EXPERT_FINAL_REPLY
: ClaimWorkflowStep.EXPERT_DAMAGE_ASSESSMENT;
const replyPayload = {
description: reply.description,
parts: reply.parts,
@@ -1850,7 +2208,7 @@ export class ExpertClaimService {
? ClaimStatus.NEEDS_REVISION
: ClaimStatus.APPROVED;
await this.claimCaseDbService.findByIdAndUpdate(claimRequestId, {
const updatePayload: Record<string, unknown> = {
status: nextCaseStatus,
claimStatus: nextClaimStatus,
'workflow.locked': false,
@@ -1858,18 +2216,30 @@ export class ExpertClaimService {
'workflow.nextStep': needsFactorUpload
? ClaimWorkflowStep.INSURER_REVIEW
: ClaimWorkflowStep.CLAIM_COMPLETED,
'evaluation.damageExpertReply': replyPayload,
[`evaluation.${replyField}`]: replyPayload,
$push: {
'workflow.completedSteps': ClaimWorkflowStep.EXPERT_DAMAGE_ASSESSMENT,
'workflow.completedSteps': completedStep,
history: {
event: 'EXPERT_REPLY_SUBMITTED',
performedBy: actor.sub,
performedByName: actor.fullName,
performedAt: new Date(),
note: `Expert reply submitted. factorNeeded=${needsFactorUpload}`,
type: isFinalReplyAfterObjection
? 'EXPERT_FINAL_REPLY_SUBMITTED'
: 'EXPERT_REPLY_SUBMITTED',
actor: {
actorId: new Types.ObjectId(actor.sub),
actorName: actor.fullName,
actorType: 'damage_expert',
},
timestamp: new Date(),
metadata: {
factorNeeded: needsFactorUpload,
partsCount: reply.parts?.length ?? 0,
isFinalReplyAfterObjection,
replyField,
},
},
},
});
};
await this.claimCaseDbService.findByIdAndUpdate(claimRequestId, updatePayload);
return {
claimRequestId,
@@ -1877,6 +2247,7 @@ export class ExpertClaimService {
claimStatus: nextClaimStatus,
currentStep: nextStep,
factorNeeded: needsFactorUpload,
isFinalReplyAfterObjection,
};
}
@@ -1949,10 +2320,10 @@ export class ExpertClaimService {
/**
* V2: Get claim list for damage expert
*
* Returns claims that are:
* 1. WAITING_FOR_DAMAGE_EXPERT status AND not locked (available for anyone to pick)
* 2. WAITING_FOR_DAMAGE_EXPERT status AND not locked (claimStatus PENDING)
* 3. Any status locked by THIS expert (their own in-progress work)
* Returns claims that are (then filtered to the actor's insurer via `claimCaseTouchesClient`):
* 1. WAITING_FOR_DAMAGE_EXPERT and not locked (open queue)
* 2. Locked by this expert (in-progress)
* 3. WAITING_FOR_INSURER_APPROVAL + UNDER_REVIEW at EXPERT_COST_EVALUATION (factor validation queue)
*/
async getClaimListV2(actor: any): Promise<GetClaimListV2ResponseDto> {
requireActorClientKey(actor);
@@ -1970,6 +2341,12 @@ export class ExpertClaimService {
'workflow.locked': true,
'workflow.lockedBy.actorId': new Types.ObjectId(actorId),
},
// User uploaded all factors; expert must approve/reject (unlocked queue)
{
status: ClaimCaseStatus.WAITING_FOR_INSURER_APPROVAL,
claimStatus: ClaimStatus.UNDER_REVIEW,
'workflow.currentStep': ClaimWorkflowStep.EXPERT_COST_EVALUATION,
},
],
});
@@ -1996,6 +2373,10 @@ export class ExpertClaimService {
);
const list = filtered.map((c) => {
const awaitingFactorValidation =
c.status === ClaimCaseStatus.WAITING_FOR_INSURER_APPROVAL &&
c.claimStatus === ClaimStatus.UNDER_REVIEW &&
c.workflow?.currentStep === ClaimWorkflowStep.EXPERT_COST_EVALUATION;
const v = this.vehicleForExpertFromClaimAndBlameMap(c, blameById);
const blame = c.blameRequestId
? blameById.get(c.blameRequestId.toString())
@@ -2022,6 +2403,7 @@ export class ExpertClaimService {
: undefined,
...fileCtx,
createdAt: c.createdAt,
awaitingFactorValidation,
};
}) as ClaimListItemV2Dto[];
@@ -2032,15 +2414,14 @@ export class ExpertClaimService {
* V2: Get claim detail for damage expert
*
* Validations:
* - Claim must exist
* - Claim status must be WAITING_FOR_DAMAGE_EXPERT
* - If claim is locked, only the locking expert can access it
* - Claim must exist and belong to the actor's insurer (`assertClaimCaseForTenant`)
* - Allowed when picking up/reviewing: WAITING_FOR_DAMAGE_EXPERT (if locked, only the locking expert)
* - Or when validating factors: WAITING_FOR_INSURER_APPROVAL + UNDER_REVIEW at EXPERT_COST_EVALUATION
*/
async getClaimDetailV2(
claimRequestId: string,
actor: any,
): Promise<ClaimDetailV2ResponseDto> {
requireActorClientKey(actor);
const actorId = actor.sub;
const claim = await this.claimCaseDbService.findById(claimRequestId);
@@ -2050,14 +2431,22 @@ export class ExpertClaimService {
assertClaimCaseForTenant(claim, actor);
if (claim.status !== ClaimCaseStatus.WAITING_FOR_DAMAGE_EXPERT) {
const isPickupReview =
claim.status === ClaimCaseStatus.WAITING_FOR_DAMAGE_EXPERT;
const isFactorValidationPending =
claim.status === ClaimCaseStatus.WAITING_FOR_INSURER_APPROVAL &&
claim.claimStatus === ClaimStatus.UNDER_REVIEW &&
claim.workflow?.currentStep === ClaimWorkflowStep.EXPERT_COST_EVALUATION;
if (!isPickupReview && !isFactorValidationPending) {
throw new ForbiddenException(
`This claim is not available for expert review. Current status: ${claim.status}`,
);
}
// If locked by someone else, deny access
// If locked by someone else, deny access (initial pickup / assessment only)
if (
isPickupReview &&
claim.workflow?.locked &&
claim.workflow.lockedBy?.actorId?.toString() !== actorId
) {
@@ -2146,16 +2535,16 @@ export class ExpertClaimService {
locked: claim.workflow?.locked || false,
lockedBy: claim.workflow?.lockedBy
? {
actorId: claim.workflow.lockedBy.actorId?.toString(),
actorName: claim.workflow.lockedBy.actorName,
lockedAt: (claim.workflow as any).lockedAt?.toISOString(),
}
actorId: claim.workflow.lockedBy.actorId?.toString(),
actorName: claim.workflow.lockedBy.actorName,
lockedAt: (claim.workflow as any).lockedAt?.toISOString(),
}
: undefined,
owner: claim.owner
? {
userId: claim.owner.userId?.toString(),
fullName: claim.owner.fullName,
}
userId: claim.owner.userId?.toString(),
fullName: claim.owner.fullName,
}
: undefined,
vehicle: vehiclePayload,
...blameFileContext,
@@ -2169,6 +2558,13 @@ export class ExpertClaimService {
: undefined,
carAngles,
damagedParts,
awaitingFactorValidation: isFactorValidationPending,
evaluation: isFactorValidationPending
? {
damageExpertReply: (claim as any).evaluation?.damageExpertReply,
damageExpertReplyFinal: (claim as any).evaluation?.damageExpertReplyFinal,
}
: undefined,
createdAt: (claim as any).createdAt,
updatedAt: (claim as any).updatedAt,
};