forked from Yara724/api
add blame in claim details plus auto expert decision bug fixed
This commit is contained in:
@@ -98,6 +98,37 @@ export class ClaimDetailV2ResponseDto {
|
||||
damageExpertReplyFinal?: unknown;
|
||||
};
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description:
|
||||
'Car walk-around video from `claim-video-capture` (resolved from `media.videoCaptureId`).',
|
||||
})
|
||||
videoCapture?: {
|
||||
id: string;
|
||||
url?: string;
|
||||
path?: string;
|
||||
fileName?: string;
|
||||
};
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description:
|
||||
'Linked blame case (`blameCases`), same shape as expert-blame detail: parties with video/voice URLs, workflow, expert, formatted dates.',
|
||||
})
|
||||
blameCase?: Record<string, unknown>;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description:
|
||||
'Same as `blameCase.expert.decision` when present: guilty party id, description, accident fields, decidedAt.',
|
||||
})
|
||||
blameExpertDecision?: Record<string, unknown>;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'Claim payout / banking metadata from the claim file (e.g. Sheba).',
|
||||
})
|
||||
money?: {
|
||||
sheba?: string;
|
||||
nationalCodeOfInsurer?: string;
|
||||
};
|
||||
|
||||
@ApiProperty()
|
||||
createdAt: string;
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -48,7 +48,7 @@ export class ExpertClaimV2Controller {
|
||||
@ApiOperation({
|
||||
summary: "Get claim request detail for damage expert",
|
||||
description:
|
||||
"Returns full claim details including captured images, required documents status, and damage selections. Allowed when status is WAITING_FOR_DAMAGE_EXPERT (if locked, only the locking expert) or when awaiting factor validation (WAITING_FOR_INSURER_APPROVAL + UNDER_REVIEW + EXPERT_COST_EVALUATION); in the latter case `evaluation` includes the active expert reply for factors.",
|
||||
"Returns full claim details including captured images, required documents, damage selections, `videoCapture` (from claim-video-capture via media.videoCaptureId), and `blameCase` (linked blameCases document with party video/voice URLs like expert-blame detail). Allowed when status is WAITING_FOR_DAMAGE_EXPERT (if locked, only the locking expert) or when awaiting factor validation.",
|
||||
})
|
||||
@ApiParam({ name: "claimRequestId" })
|
||||
async getClaimDetailV2(
|
||||
|
||||
Reference in New Issue
Block a user