add blame in claim details plus auto expert decision bug fixed

This commit is contained in:
2026-04-19 11:57:18 +03:30
parent c5b2d7b520
commit 08a4d754c1
5 changed files with 393 additions and 8 deletions

View File

@@ -20,6 +20,7 @@ import { DamageImageDbService } from "src/claim-request-management/entites/db-se
import { VideoCaptureDbService } from "src/claim-request-management/entites/db-service/video-capture.db.service";
import { ClientDbService } from "src/client/entities/db-service/client.db.service";
import { buildFileLink } from "src/helpers/urlCreator";
import { toJalaliDateAndTime } from "src/helpers/date-jalali";
import { BlameDocumentDbService } from "src/request-management/entities/db-service/blame-document.db.service";
import { BlameVideoDbService } from "src/request-management/entities/db-service/blame-video.db.service";
import { BlameVoiceDbService } from "src/request-management/entities/db-service/blame.voice.db.service";
@@ -53,6 +54,10 @@ import { BranchDbService } from "src/client/entities/db-service/branch.db.servic
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";
import {
buildMutualAgreementExpertDecision,
enrichBlamePartiesForAgreementView,
} from "src/helpers/blame-party-agreement-decision";
@Injectable()
export class ExpertClaimService {
@@ -2410,6 +2415,119 @@ export class ExpertClaimService {
return { list, total: list.length };
}
/**
* Load linked blame case for damage-expert claim detail (same enrichment as expert-blame `findOneV2`:
* party evidence `videoUrl` / `voiceUrls`, Jalali date strings).
*/
private async buildBlameCaseSnapshotForClaimDetail(
blameRequestId: string,
): Promise<Record<string, unknown> | null> {
const doc = await this.blameRequestDbService.findByIdWithoutHistory(
blameRequestId,
);
if (!doc) {
return null;
}
const docRec = doc as Record<string, unknown>;
const built = buildMutualAgreementExpertDecision(docRec);
const existingDecision = (docRec.expert as Record<string, unknown> | undefined)
?.decision as Record<string, unknown> | undefined;
if (built && !existingDecision?.guiltyPartyId) {
const prevExpert =
typeof docRec.expert === "object" && docRec.expert !== null
? { ...(docRec.expert as Record<string, unknown>) }
: {};
const expert = { ...prevExpert, decision: built };
await this.blameRequestDbService.findByIdAndUpdate(blameRequestId, {
$set: { expert },
});
docRec.expert = expert;
}
const parties = (doc.parties ?? []) as Array<{
evidence?: {
videoId?: string | number;
voices?: (string | number)[];
videoUrl?: string;
voiceUrls?: string[];
};
}>;
for (const party of parties) {
if (!party.evidence) continue;
const evidence = party.evidence as Record<string, unknown>;
if (evidence.videoId) {
const videoDoc = await this.blameVideoDbService.findById(
String(evidence.videoId),
);
if (videoDoc?.path) {
evidence.videoUrl = buildFileLink(videoDoc.path);
}
}
if (evidence.voices && Array.isArray(evidence.voices)) {
const voiceUrls: string[] = [];
for (const voiceId of evidence.voices) {
const voiceDoc = await this.blameVoiceDbService.findById(
String(voiceId),
);
if (voiceDoc?.path) {
voiceUrls.push(buildFileLink(voiceDoc.path));
}
}
evidence.voiceUrls = voiceUrls;
}
}
const createdAt = doc.createdAt
? new Date(doc.createdAt as string | number)
: new Date();
const updatedAt = doc.updatedAt
? new Date(doc.updatedAt as string | number)
: new Date();
const [createdDate, createdTime] = toJalaliDateAndTime(createdAt);
const [updatedDate, updatedTime] = toJalaliDateAndTime(updatedAt);
(doc as Record<string, unknown>).createdAtFormatted =
`${createdDate} ${createdTime}`;
(doc as Record<string, unknown>).updatedAtFormatted =
`${updatedDate} ${updatedTime}`;
docRec.parties = enrichBlamePartiesForAgreementView(docRec);
return docRec;
}
/** JSON-safe `expert.decision` for API responses (ObjectIds → string). */
private serializeBlameExpertDecisionForClaimDetail(
decision: unknown,
): Record<string, unknown> | undefined {
if (!decision || typeof decision !== "object") {
return undefined;
}
const d = decision as Record<string, unknown>;
const guilty = d.guiltyPartyId;
const decidedBy = d.decidedByExpertId;
const decidedAt = d.decidedAt;
return {
...d,
guiltyPartyId:
guilty != null && typeof (guilty as { toString?: () => string }).toString === "function"
? String(guilty)
: guilty,
decidedByExpertId:
decidedBy != null &&
typeof (decidedBy as { toString?: () => string }).toString === "function"
? String(decidedBy)
: decidedBy,
decidedAt:
decidedAt instanceof Date
? decidedAt.toISOString()
: decidedAt,
};
}
/**
* V2: Get claim detail for damage expert
*
@@ -2499,6 +2617,9 @@ export class ExpertClaimService {
};
}
// --- Combine both branches of the conflict, preserving necessary logic from both ---
// 1. Vehicle payload logic from "upstream"
let vehiclePayload = claim.vehicle as any;
const hasClaimVehicle =
vehiclePayload &&
@@ -2508,23 +2629,73 @@ export class ExpertClaimService {
vehiclePayload.plate);
let blameLean: any = null;
if (claim.blameRequestId) {
const rows = (await this.blameRequestDbService.find(
{ _id: new Types.ObjectId(claim.blameRequestId.toString()) },
{ lean: true, select: "type parties expert.decision" },
)) as any[];
blameLean = rows[0] ?? null;
// 2. Video capture and blame case db queries from "stashed"
const videoCaptureIdRaw = (claim.media as any)?.videoCaptureId;
const blameRequestIdStr = claim.blameRequestId?.toString();
const [
videoCaptureRow,
blameCase,
blameLeanRows
] = await Promise.all([
videoCaptureIdRaw
? this.claimVideoCaptureDbService.findById(String(videoCaptureIdRaw))
: Promise.resolve(null),
blameRequestIdStr
? this.buildBlameCaseSnapshotForClaimDetail(blameRequestIdStr)
: Promise.resolve(null),
claim.blameRequestId
? this.blameRequestDbService.find(
{ _id: new Types.ObjectId(claim.blameRequestId.toString()) },
{ lean: true, select: "type parties expert.decision" },
)
: Promise.resolve([]),
]);
if (Array.isArray(blameLeanRows) && blameLeanRows.length) {
blameLean = blameLeanRows[0] ?? null;
}
// If claim vehicle not found and blameLean exists, replace vehiclePayload
if (!hasClaimVehicle && blameLean) {
const fromBlame = this.damagedPartyVehicleFromBlame(blameLean, claim);
if (fromBlame) vehiclePayload = fromBlame;
}
// 3. Blame file context logic from "upstream"
const blameFileContext = blameLean
? this.blameFileContextForExpert(blameLean)
: {};
// 4. videoCapture logic from "stashed"
let videoCapture: ClaimDetailV2ResponseDto['videoCapture'] = undefined;
if (videoCaptureRow) {
const vc = videoCaptureRow as any;
videoCapture = {
id: String(vc._id ?? videoCaptureIdRaw),
fileName: vc.fileName,
path: vc.path,
url: vc.path ? buildFileLink(vc.path) : undefined,
};
}
const blameExpertDecision = blameCase
? this.serializeBlameExpertDecisionForClaimDetail(
(blameCase.expert as Record<string, unknown> | undefined)?.decision,
)
: undefined;
const money = (claim as { money?: { sheba?: string; nationalCodeOfInsurer?: string } })
.money;
const moneyPayload =
money && (money.sheba != null || money.nationalCodeOfInsurer != null)
? {
sheba: money.sheba,
nationalCodeOfInsurer: money.nationalCodeOfInsurer,
}
: undefined;
return {
claimRequestId: claim._id.toString(),
publicId: claim.publicId,
@@ -2550,6 +2721,7 @@ export class ExpertClaimService {
...blameFileContext,
blameRequestId: claim.blameRequestId?.toString(),
blameRequestNo: claim.blameRequestNo,
money: moneyPayload,
selectedParts: claim.damage?.selectedParts,
otherParts: claim.damage?.otherParts,
requiredDocuments:
@@ -2565,6 +2737,9 @@ export class ExpertClaimService {
damageExpertReplyFinal: (claim as any).evaluation?.damageExpertReplyFinal,
}
: undefined,
videoCapture,
blameCase: blameCase ?? undefined,
blameExpertDecision,
createdAt: (claim as any).createdAt,
updatedAt: (claim as any).updatedAt,
};