forked from Yara724/api
Merge pull request 'Added extra fields for insurer' (#57) from s.yahyaee/yara724-api:main into main
Reviewed-on: Yara724/api#57
This commit is contained in:
@@ -98,6 +98,7 @@ import { JwtModule } from "@nestjs/jwt";
|
|||||||
DamageImageDbService,
|
DamageImageDbService,
|
||||||
VideoCaptureDbService,
|
VideoCaptureDbService,
|
||||||
ClaimRequiredDocumentDbService,
|
ClaimRequiredDocumentDbService,
|
||||||
|
ClaimSignDbService,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class ClaimRequestManagementModule {}
|
export class ClaimRequestManagementModule {}
|
||||||
|
|||||||
@@ -29,6 +29,28 @@ import { ExpertFileActivityType } from "src/users/entities/schema/expert-file-ac
|
|||||||
import { HashService } from "src/utils/hash/hash.service";
|
import { HashService } from "src/utils/hash/hash.service";
|
||||||
import { RoleEnum } from "src/Types&Enums/role.enum";
|
import { RoleEnum } from "src/Types&Enums/role.enum";
|
||||||
import { UserType } from "src/Types&Enums/userType.enum";
|
import { UserType } from "src/Types&Enums/userType.enum";
|
||||||
|
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";
|
||||||
|
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 { enrichBlamePartiesForAgreementView } from "src/helpers/blame-party-agreement-decision";
|
||||||
|
import { BlameRequestType } from "src/Types&Enums/blame-request-management/blameRequestType.enum";
|
||||||
|
import { PartyRole } from "src/request-management/entities/schema/partyRole.enum";
|
||||||
|
import {
|
||||||
|
getClaimCarAngleCaptureBlob,
|
||||||
|
getDamagedPartCaptureBlob,
|
||||||
|
hasDamagedPartCapture,
|
||||||
|
type ClaimCarAngleKey,
|
||||||
|
} from "src/helpers/claim-car-angle-media";
|
||||||
|
import {
|
||||||
|
catalogLikeKeyFromPart,
|
||||||
|
normalizeDamageSelectedParts,
|
||||||
|
type DamageSelectedPartV2,
|
||||||
|
} from "src/helpers/outer-damage-parts";
|
||||||
|
import { ClaimVehicleTypeV2 } from "src/static/outer-car-parts-catalog";
|
||||||
|
import { ClaimSignDbService } from "src/claim-request-management/entites/db-service/claim-sign.db.service";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ExpertInsurerService {
|
export class ExpertInsurerService {
|
||||||
@@ -40,6 +62,11 @@ export class ExpertInsurerService {
|
|||||||
private readonly claimCaseDbService: ClaimCaseDbService,
|
private readonly claimCaseDbService: ClaimCaseDbService,
|
||||||
private readonly branchDbService: BranchDbService,
|
private readonly branchDbService: BranchDbService,
|
||||||
private readonly hashService: HashService,
|
private readonly hashService: HashService,
|
||||||
|
private readonly blameVideoDbService: BlameVideoDbService,
|
||||||
|
private readonly blameVoiceDbService: BlameVoiceDbService,
|
||||||
|
private readonly videoCaptureDbService: VideoCaptureDbService,
|
||||||
|
private readonly clientDbService: ClientDbService,
|
||||||
|
private readonly claimSignDbService: ClaimSignDbService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
private getClientId(actorOrId: any): Types.ObjectId {
|
private getClientId(actorOrId: any): Types.ObjectId {
|
||||||
@@ -225,6 +252,445 @@ export class ExpertInsurerService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private serializeBlameExpertDecisionForInsurer(
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Same semantics as damage-expert claim detail `blameFileContext`. */
|
||||||
|
private blameFileContextForInsurer(blame: Record<string, unknown> | null): {
|
||||||
|
blameRequestType?: BlameRequestType;
|
||||||
|
carBodyFirstForm?: { car?: boolean; object?: boolean };
|
||||||
|
blameStatus?: string;
|
||||||
|
} {
|
||||||
|
if (!blame?.type) return {};
|
||||||
|
const blameRequestType = blame.type as BlameRequestType;
|
||||||
|
const out: {
|
||||||
|
blameRequestType?: BlameRequestType;
|
||||||
|
carBodyFirstForm?: { car?: boolean; object?: boolean };
|
||||||
|
blameStatus?: string;
|
||||||
|
} = { blameRequestType };
|
||||||
|
out.blameStatus = blame.blameStatus as string | undefined;
|
||||||
|
if (blameRequestType !== BlameRequestType.CAR_BODY) return out;
|
||||||
|
|
||||||
|
const parties = blame.parties;
|
||||||
|
if (!Array.isArray(parties) || parties.length === 0) return out;
|
||||||
|
|
||||||
|
const first =
|
||||||
|
(parties as any[]).find((p: any) => p?.role === PartyRole.FIRST) ?? (parties as any[])[0];
|
||||||
|
const cbf = first?.carBodyFirstForm;
|
||||||
|
delete out.blameStatus;
|
||||||
|
if (cbf && typeof cbf === "object") {
|
||||||
|
out.carBodyFirstForm = {
|
||||||
|
...(typeof cbf.car === "boolean" ? { car: cbf.car } : {}),
|
||||||
|
...(typeof cbf.object === "boolean" ? { object: cbf.object } : {}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async clientNamesByClientIds(
|
||||||
|
ids: Array<string | undefined | null>,
|
||||||
|
): Promise<Record<string, { persian: string; english: string }>> {
|
||||||
|
const unique = [
|
||||||
|
...new Set(
|
||||||
|
ids
|
||||||
|
.filter((x): x is string => !!x && Types.ObjectId.isValid(String(x)))
|
||||||
|
.map(String),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
const out: Record<string, { persian: string; english: string }> = {};
|
||||||
|
await Promise.all(
|
||||||
|
unique.map(async (id) => {
|
||||||
|
const doc = await this.clientDbService.find({
|
||||||
|
_id: new Types.ObjectId(id),
|
||||||
|
});
|
||||||
|
if (doc?.clientName) out[id] = doc.clientName;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapPersonForInsurer(
|
||||||
|
person: any,
|
||||||
|
clientNames: Record<string, { persian: string; english: string }>,
|
||||||
|
) {
|
||||||
|
if (!person || typeof person !== "object") return person;
|
||||||
|
const cid =
|
||||||
|
person.clientId?.toString?.() ?? (person.clientId != null ? String(person.clientId) : undefined);
|
||||||
|
return {
|
||||||
|
...person,
|
||||||
|
userId: person.userId?.toString?.() ?? undefined,
|
||||||
|
clientId: cid,
|
||||||
|
clientName: cid ? clientNames[cid] : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapPartyForInsurerFull(
|
||||||
|
party: any,
|
||||||
|
clientNames: Record<string, { persian: string; english: string }>,
|
||||||
|
) {
|
||||||
|
if (!party || typeof party !== "object") return party;
|
||||||
|
return {
|
||||||
|
...party,
|
||||||
|
person: this.mapPersonForInsurer(party.person, clientNames),
|
||||||
|
vehicle: this.sanitizeVehicleInquiry(party.vehicle),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Public URL for a stored relative path or pass-through if already absolute. */
|
||||||
|
private linkFromPathOrUrl(stored: string | undefined): string | undefined {
|
||||||
|
if (stored == null || stored === "") return undefined;
|
||||||
|
const s = String(stored);
|
||||||
|
if (/^https?:\/\//i.test(s)) return s;
|
||||||
|
return buildFileLink(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
private enrichSignatureRecord(
|
||||||
|
sig: Record<string, unknown> | undefined,
|
||||||
|
): Record<string, unknown> | undefined {
|
||||||
|
if (!sig || typeof sig !== "object") return sig;
|
||||||
|
const fileUrl = sig.fileUrl != null ? String(sig.fileUrl) : undefined;
|
||||||
|
const link = this.linkFromPathOrUrl(fileUrl);
|
||||||
|
return { ...sig, link };
|
||||||
|
}
|
||||||
|
|
||||||
|
private enrichPartiesConfirmationSignatures(parties: any[]): void {
|
||||||
|
if (!Array.isArray(parties)) return;
|
||||||
|
for (const p of parties) {
|
||||||
|
const sig = p?.confirmation?.signature;
|
||||||
|
if (sig && typeof sig === "object") {
|
||||||
|
p.confirmation = {
|
||||||
|
...p.confirmation,
|
||||||
|
signature: this.enrichSignatureRecord(sig as Record<string, unknown>),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private insurerDamagedPartRowFromCapture(
|
||||||
|
sp: DamageSelectedPartV2,
|
||||||
|
index: number,
|
||||||
|
damagedPartsDataExpert: unknown,
|
||||||
|
selectedNormExpert: DamageSelectedPartV2[],
|
||||||
|
) {
|
||||||
|
const ck = sp.catalogKey ?? catalogLikeKeyFromPart(sp);
|
||||||
|
const cap = getDamagedPartCaptureBlob(
|
||||||
|
damagedPartsDataExpert,
|
||||||
|
ck,
|
||||||
|
selectedNormExpert,
|
||||||
|
) as { url?: string; path?: string; fileName?: string } | undefined;
|
||||||
|
const url = cap?.url || (cap?.path ? buildFileLink(cap.path) : undefined);
|
||||||
|
const link = url;
|
||||||
|
return {
|
||||||
|
index,
|
||||||
|
id: sp.id,
|
||||||
|
name: sp.name,
|
||||||
|
side: sp.side,
|
||||||
|
label_fa: sp.label_fa,
|
||||||
|
catalogKey: sp.catalogKey,
|
||||||
|
captured: hasDamagedPartCapture(damagedPartsDataExpert, ck, selectedNormExpert),
|
||||||
|
path: cap?.path,
|
||||||
|
fileName: cap?.fileName,
|
||||||
|
url,
|
||||||
|
link,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private sanitizeDamageForInsurerDetail(damage: unknown): unknown {
|
||||||
|
if (!damage || typeof damage !== "object") return damage;
|
||||||
|
const d = { ...(damage as Record<string, unknown>) };
|
||||||
|
delete d.selectedOuterParts;
|
||||||
|
delete d.selectedPartIds;
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
private stripCarTypeFromPartyVehicles(parties: any[]): any[] {
|
||||||
|
if (!Array.isArray(parties)) return parties;
|
||||||
|
return parties.map((p) => {
|
||||||
|
const v = p?.vehicle;
|
||||||
|
if (!v || typeof v !== "object") return p;
|
||||||
|
const { carType: _omit, ...rest } = v as Record<string, unknown>;
|
||||||
|
const nextV = Object.keys(rest).length ? rest : undefined;
|
||||||
|
return { ...p, vehicle: nextV };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async claimSignLinkFromId(signDetailId: unknown): Promise<string | undefined> {
|
||||||
|
if (signDetailId == null || signDetailId === "") return undefined;
|
||||||
|
const id = String(signDetailId);
|
||||||
|
if (!Types.ObjectId.isValid(id)) return undefined;
|
||||||
|
const doc = await this.claimSignDbService.findOne({
|
||||||
|
_id: new Types.ObjectId(id),
|
||||||
|
});
|
||||||
|
const path = (doc as any)?.path;
|
||||||
|
return path ? buildFileLink(String(path)) : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async enrichClaimEvaluationForInsurer(
|
||||||
|
evaluation: Record<string, unknown> | undefined,
|
||||||
|
): Promise<Record<string, unknown> | undefined> {
|
||||||
|
if (!evaluation || typeof evaluation !== "object") return evaluation;
|
||||||
|
const ev = JSON.parse(JSON.stringify(evaluation)) as Record<string, unknown>;
|
||||||
|
|
||||||
|
const enrichReply = async (reply: any) => {
|
||||||
|
if (!reply || typeof reply !== "object") return;
|
||||||
|
const uc = reply.userComment;
|
||||||
|
if (uc?.signDetailId != null) {
|
||||||
|
const signLink = await this.claimSignLinkFromId(uc.signDetailId);
|
||||||
|
reply.userComment = { ...uc, signLink };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
await enrichReply(ev.damageExpertReply);
|
||||||
|
await enrichReply(ev.damageExpertReplyFinal);
|
||||||
|
|
||||||
|
const enrichApproval = async (key: string) => {
|
||||||
|
const o = ev[key] as Record<string, unknown> | undefined;
|
||||||
|
if (o?.signDetailId != null) {
|
||||||
|
const signLink = await this.claimSignLinkFromId(o.signDetailId);
|
||||||
|
ev[key] = { ...o, signLink };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
await enrichApproval("ownerInsurerApproval");
|
||||||
|
await enrichApproval("ownerPricedPartsApproval");
|
||||||
|
|
||||||
|
return ev;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async enrichBlamePartyEvidenceUrls(parties: any[]): Promise<void> {
|
||||||
|
if (!Array.isArray(parties)) return;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async buildInsurerBlameDetail(
|
||||||
|
blameLean: Record<string, unknown>,
|
||||||
|
): Promise<Record<string, unknown>> {
|
||||||
|
const doc: Record<string, unknown> = { ...blameLean };
|
||||||
|
const parties = (Array.isArray(doc.parties) ? doc.parties : []) as any[];
|
||||||
|
await this.enrichBlamePartyEvidenceUrls(parties);
|
||||||
|
doc.parties = enrichBlamePartiesForAgreementView(doc);
|
||||||
|
|
||||||
|
const mergedParties = doc.parties as any[];
|
||||||
|
const clientIds = mergedParties.map((p) => p?.person?.clientId?.toString?.()).filter(Boolean);
|
||||||
|
const clientNames = await this.clientNamesByClientIds(clientIds);
|
||||||
|
doc.parties = mergedParties.map((p) => this.mapPartyForInsurerFull(p, clientNames));
|
||||||
|
this.enrichPartiesConfirmationSignatures(doc.parties as any[]);
|
||||||
|
|
||||||
|
if (doc.expert && typeof doc.expert === "object") {
|
||||||
|
const ex = { ...(doc.expert as Record<string, unknown>) };
|
||||||
|
const serialized = this.serializeBlameExpertDecisionForInsurer(ex.decision);
|
||||||
|
if (serialized !== undefined) ex.decision = serialized;
|
||||||
|
doc.expert = ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
const createdAt = doc.createdAt
|
||||||
|
? new Date(doc.createdAt as string | number | Date)
|
||||||
|
: new Date();
|
||||||
|
const updatedAt = doc.updatedAt
|
||||||
|
? new Date(doc.updatedAt as string | number | Date)
|
||||||
|
: new Date();
|
||||||
|
const [cd, ct] = toJalaliDateAndTime(createdAt);
|
||||||
|
const [ud, ut] = toJalaliDateAndTime(updatedAt);
|
||||||
|
doc.createdAtFormatted = `${cd} ${ct}`;
|
||||||
|
doc.updatedAtFormatted = `${ud} ${ut}`;
|
||||||
|
|
||||||
|
delete doc.history;
|
||||||
|
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async buildInsurerClaimDetail(
|
||||||
|
claim: Record<string, unknown>,
|
||||||
|
blameForContext: Record<string, unknown> | null,
|
||||||
|
): Promise<Record<string, unknown>> {
|
||||||
|
const requiredDocs = claim.requiredDocuments as any;
|
||||||
|
const requiredDocumentsStatus: Record<string, any> = {};
|
||||||
|
if (requiredDocs) {
|
||||||
|
const keys =
|
||||||
|
requiredDocs instanceof Map
|
||||||
|
? Array.from(requiredDocs.keys())
|
||||||
|
: Object.keys(requiredDocs);
|
||||||
|
for (const k of keys as string[]) {
|
||||||
|
const row = requiredDocs instanceof Map ? requiredDocs.get(k) : requiredDocs[k];
|
||||||
|
requiredDocumentsStatus[k] = {
|
||||||
|
uploaded: !!row?.uploaded,
|
||||||
|
fileId: row?.fileId?.toString?.(),
|
||||||
|
fileName: row?.fileName,
|
||||||
|
fileUrl: row?.filePath ? buildFileLink(row.filePath) : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const carAnglesData = (claim.media as any)?.carAngles;
|
||||||
|
const damagedPartsDataForAngles = (claim.media as any)?.damagedParts;
|
||||||
|
const carAngles: Record<string, { captured: boolean; url?: string }> = {};
|
||||||
|
for (const k of ["front", "back", "left", "right"] as ClaimCarAngleKey[]) {
|
||||||
|
const cap = getClaimCarAngleCaptureBlob(
|
||||||
|
carAnglesData,
|
||||||
|
damagedPartsDataForAngles,
|
||||||
|
k,
|
||||||
|
) as { url?: string; path?: string } | undefined;
|
||||||
|
carAngles[k] = {
|
||||||
|
captured: !!cap,
|
||||||
|
url: cap?.url || (cap?.path ? buildFileLink(cap.path) : undefined),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const carTypeExpert = (claim.vehicle as any)?.carType as ClaimVehicleTypeV2 | undefined;
|
||||||
|
const selectedNormExpert = normalizeDamageSelectedParts(
|
||||||
|
(claim.damage as any)?.selectedParts,
|
||||||
|
carTypeExpert,
|
||||||
|
(claim.damage as any)?.selectedOuterParts,
|
||||||
|
);
|
||||||
|
const damagedPartsDataExpert = (claim.media as any)?.damagedParts;
|
||||||
|
const damagedParts = selectedNormExpert.map((sp, index) =>
|
||||||
|
this.insurerDamagedPartRowFromCapture(
|
||||||
|
sp,
|
||||||
|
index,
|
||||||
|
damagedPartsDataExpert,
|
||||||
|
selectedNormExpert,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const selectedPartsNormalized = selectedNormExpert.map((sp, index) =>
|
||||||
|
this.insurerDamagedPartRowFromCapture(
|
||||||
|
sp,
|
||||||
|
index,
|
||||||
|
damagedPartsDataExpert,
|
||||||
|
selectedNormExpert,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const videoCaptureIdRaw = (claim.media as any)?.videoCaptureId;
|
||||||
|
let videoCapture:
|
||||||
|
| { id: string; fileName?: string; path?: string; url?: string }
|
||||||
|
| undefined;
|
||||||
|
if (videoCaptureIdRaw) {
|
||||||
|
const vc = (await this.videoCaptureDbService.findById(
|
||||||
|
String(videoCaptureIdRaw),
|
||||||
|
)) as any;
|
||||||
|
if (vc) {
|
||||||
|
videoCapture = {
|
||||||
|
id: String(vc._id ?? videoCaptureIdRaw),
|
||||||
|
fileName: vc.fileName,
|
||||||
|
path: vc.path,
|
||||||
|
url: vc.path ? buildFileLink(vc.path) : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const evaluation = claim.evaluation as Record<string, unknown> | undefined;
|
||||||
|
const owner = claim.owner as any;
|
||||||
|
const ownerOut = owner
|
||||||
|
? {
|
||||||
|
...owner,
|
||||||
|
userId: owner.userId?.toString?.(),
|
||||||
|
userClientKey: owner.userClientKey?.toString?.(),
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
let snapshotOut = claim.snapshot as any;
|
||||||
|
if (snapshotOut && Array.isArray(snapshotOut.parties)) {
|
||||||
|
const snapIds = snapshotOut.parties
|
||||||
|
.map((p: any) => p?.person?.clientId?.toString?.())
|
||||||
|
.filter(Boolean);
|
||||||
|
const snapNames = await this.clientNamesByClientIds(snapIds);
|
||||||
|
const snapParties = this.stripCarTypeFromPartyVehicles(
|
||||||
|
snapshotOut.parties.map((p: any) =>
|
||||||
|
this.mapPartyForInsurerFull(p, snapNames),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
this.enrichPartiesConfirmationSignatures(snapParties);
|
||||||
|
snapshotOut = {
|
||||||
|
...snapshotOut,
|
||||||
|
parties: snapParties,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const evaluationEnriched = await this.enrichClaimEvaluationForInsurer(evaluation);
|
||||||
|
const vehicleSanitized = claim.vehicle
|
||||||
|
? this.sanitizeVehicleInquiry(claim.vehicle as any)
|
||||||
|
: undefined;
|
||||||
|
const vehicleOut = vehicleSanitized
|
||||||
|
? (() => {
|
||||||
|
const { carType: _omit, ...rest } = vehicleSanitized as Record<string, unknown>;
|
||||||
|
return rest;
|
||||||
|
})()
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
claimRequestId: (claim._id as any)?.toString?.(),
|
||||||
|
publicId: claim.publicId,
|
||||||
|
status: claim.status,
|
||||||
|
claimStatus: claim.claimStatus,
|
||||||
|
blameDocumentResendPending: claim.blameDocumentResendPending,
|
||||||
|
workflow: claim.workflow,
|
||||||
|
owner: ownerOut,
|
||||||
|
vehicle: vehicleOut,
|
||||||
|
blameRequestId: (claim as any).blameRequestId?.toString?.(),
|
||||||
|
blameRequestNo: claim.blameRequestNo,
|
||||||
|
blameFileContext: blameForContext
|
||||||
|
? this.blameFileContextForInsurer(blameForContext)
|
||||||
|
: undefined,
|
||||||
|
money: claim.money,
|
||||||
|
damage: this.sanitizeDamageForInsurerDetail(claim.damage),
|
||||||
|
media: claim.media,
|
||||||
|
requiredDocuments:
|
||||||
|
Object.keys(requiredDocumentsStatus).length > 0
|
||||||
|
? requiredDocumentsStatus
|
||||||
|
: undefined,
|
||||||
|
carAngles,
|
||||||
|
damagedParts,
|
||||||
|
selectedPartsNormalized,
|
||||||
|
videoCapture,
|
||||||
|
evaluation: evaluationEnriched,
|
||||||
|
userRating: claim.userRating,
|
||||||
|
insurerRating: (evaluationEnriched as any)?.rating,
|
||||||
|
snapshot: snapshotOut,
|
||||||
|
createdAt: claim.createdAt,
|
||||||
|
updatedAt: claim.updatedAt,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private getCombinedFileScore(file: any): number | null {
|
private getCombinedFileScore(file: any): number | null {
|
||||||
const insurerRating = file?.rating;
|
const insurerRating = file?.rating;
|
||||||
const userRating = file?.userRating;
|
const userRating = file?.userRating;
|
||||||
@@ -655,6 +1121,7 @@ export class ExpertInsurerService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const id = this.getClientId(insurerId);
|
const id = this.getClientId(insurerId);
|
||||||
|
const idStr = String(id);
|
||||||
const [blameFiles, claimFiles] = await Promise.all([
|
const [blameFiles, claimFiles] = await Promise.all([
|
||||||
this.getClientBlameFiles(id),
|
this.getClientBlameFiles(id),
|
||||||
this.getClientClaimFiles(id),
|
this.getClientClaimFiles(id),
|
||||||
@@ -667,12 +1134,31 @@ export class ExpertInsurerService {
|
|||||||
throw new NotFoundException("File not found for this publicId");
|
throw new NotFoundException("File not found for this publicId");
|
||||||
}
|
}
|
||||||
|
|
||||||
const parties = Array.isArray((blame as any)?.parties) ? (blame as any).parties : [];
|
let blameFull: Record<string, unknown> | null = null;
|
||||||
const firstParty = parties.find((p: any) => p?.role === "FIRST");
|
if (blame) {
|
||||||
const secondParty = parties.find((p: any) => p?.role === "SECOND");
|
blameFull = (await this.blameRequestDbService.findByIdWithoutHistory(
|
||||||
const claimSnapshotParties = Array.isArray((claim as any)?.snapshot?.parties)
|
String((blame as any)._id),
|
||||||
? (claim as any).snapshot.parties
|
)) as Record<string, unknown> | null;
|
||||||
: undefined;
|
}
|
||||||
|
|
||||||
|
let claimFull: Record<string, unknown> | null = null;
|
||||||
|
if (claim) {
|
||||||
|
const rows = (await this.claimCaseDbService.find(
|
||||||
|
{ _id: new Types.ObjectId(String((claim as any)._id)) },
|
||||||
|
{ lean: true },
|
||||||
|
)) as Record<string, unknown>[];
|
||||||
|
claimFull = rows[0] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let blameForClaimContext: Record<string, unknown> | null = blameFull;
|
||||||
|
if (!blameForClaimContext && claimFull?.blameRequestId) {
|
||||||
|
const linked = (await this.blameRequestDbService.findByIdWithoutHistory(
|
||||||
|
String(claimFull.blameRequestId),
|
||||||
|
)) as Record<string, unknown> | null;
|
||||||
|
if (linked && blameCaseTouchesClient(linked, idStr)) {
|
||||||
|
blameForClaimContext = linked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const overview = {
|
const overview = {
|
||||||
publicId,
|
publicId,
|
||||||
@@ -681,97 +1167,21 @@ export class ExpertInsurerService {
|
|||||||
(claim as any)?.requestNo ||
|
(claim as any)?.requestNo ||
|
||||||
(blame as any)?.requestNumber ||
|
(blame as any)?.requestNumber ||
|
||||||
(claim as any)?.requestNumber,
|
(claim as any)?.requestNumber,
|
||||||
type: (blame as any)?.type,
|
type: (blame as any)?.type ?? (blameFull as any)?.type,
|
||||||
hasClaim: !!claim,
|
hasClaim: !!claim,
|
||||||
blameStatus: (blame as any)?.status,
|
blameStatus: (blame as any)?.status ?? (blameFull as any)?.status,
|
||||||
claimStatus: (claim as any)?.status,
|
claimStatus: (claim as any)?.status,
|
||||||
claimReviewStatus: (claim as any)?.claimStatus,
|
claimReviewStatus: (claim as any)?.claimStatus,
|
||||||
createdAt: (blame as any)?.createdAt ?? (claim as any)?.createdAt,
|
createdAt: (blame as any)?.createdAt ?? (claim as any)?.createdAt,
|
||||||
updatedAt: (claim as any)?.updatedAt ?? (blame as any)?.updatedAt,
|
updatedAt: (claim as any)?.updatedAt ?? (blame as any)?.updatedAt,
|
||||||
};
|
};
|
||||||
|
|
||||||
const blameDetails = blame
|
const blameDetails = blameFull
|
||||||
? {
|
? await this.buildInsurerBlameDetail(blameFull)
|
||||||
requestId:
|
|
||||||
(blame as any)?._id?.toString?.() || String((blame as any)?._id || ""),
|
|
||||||
requestNo:
|
|
||||||
(blame as any).requestNo || String((blame as any).requestNumber || ""),
|
|
||||||
status: (blame as any).status,
|
|
||||||
blameStatus: (blame as any).blameStatus,
|
|
||||||
workflow: (blame as any).workflow,
|
|
||||||
parties: {
|
|
||||||
first: firstParty
|
|
||||||
? {
|
|
||||||
fullName: firstParty?.person?.fullName,
|
|
||||||
phoneNumber: firstParty?.person?.phoneNumber,
|
|
||||||
role: firstParty?.role,
|
|
||||||
vehicle: this.sanitizeVehicleInquiry(firstParty?.vehicle),
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
second: secondParty
|
|
||||||
? {
|
|
||||||
fullName: secondParty?.person?.fullName,
|
|
||||||
phoneNumber: secondParty?.person?.phoneNumber,
|
|
||||||
role: secondParty?.role,
|
|
||||||
vehicle: this.sanitizeVehicleInquiry(secondParty?.vehicle),
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
},
|
|
||||||
expert: (blame as any).expert
|
|
||||||
? {
|
|
||||||
assignedExpertId: (blame as any).expert?.assignedExpertId,
|
|
||||||
decision: (blame as any).expert?.decision,
|
|
||||||
resend: (blame as any).expert?.resend,
|
|
||||||
rating: (blame as any).expert?.rating,
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
createdAt: (blame as any).createdAt,
|
|
||||||
updatedAt: (blame as any).updatedAt,
|
|
||||||
}
|
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
// Keep claim section claim-specific to avoid duplicating shared data already in blame/overview.
|
const claimDetails = claimFull
|
||||||
const claimDetails = claim
|
? await this.buildInsurerClaimDetail(claimFull, blameForClaimContext)
|
||||||
? {
|
|
||||||
requestId:
|
|
||||||
(claim as any)?._id?.toString?.() || String((claim as any)?._id || ""),
|
|
||||||
requestNo:
|
|
||||||
(claim as any).requestNo || String((claim as any).requestNumber || ""),
|
|
||||||
status: (claim as any).status,
|
|
||||||
claimStatus: (claim as any).claimStatus,
|
|
||||||
owner: (claim as any).owner,
|
|
||||||
vehicle: (claim as any).vehicle,
|
|
||||||
money: (claim as any).money,
|
|
||||||
damage: (claim as any).damage,
|
|
||||||
media: (claim as any).media,
|
|
||||||
requiredDocuments: (claim as any).requiredDocuments,
|
|
||||||
snapshot: (claim as any).snapshot
|
|
||||||
? {
|
|
||||||
...(claim as any).snapshot,
|
|
||||||
parties: claimSnapshotParties?.map((p: any) =>
|
|
||||||
this.sanitizePartyForInsurerView(p),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
workflow: {
|
|
||||||
currentStep: (claim as any).currentStep,
|
|
||||||
nextStep: (claim as any).nextStep,
|
|
||||||
},
|
|
||||||
evaluation: {
|
|
||||||
damageExpertReply: (claim as any).damageExpertReply,
|
|
||||||
damageExpertReplyFinal: (claim as any).damageExpertReplyFinal,
|
|
||||||
damageExpertResend: (claim as any).damageExpertResend,
|
|
||||||
objection: (claim as any).objection,
|
|
||||||
priceDrop: (claim as any).priceDrop,
|
|
||||||
visitLocation: (claim as any).visitLocation,
|
|
||||||
factors: (claim as any).evaluation?.factors,
|
|
||||||
},
|
|
||||||
userResendDocuments: (claim as any).userResendDocuments,
|
|
||||||
userRating: (claim as any).userRating,
|
|
||||||
insurerRating: (claim as any).rating,
|
|
||||||
createdAt: (claim as any).createdAt,
|
|
||||||
updatedAt: (claim as any).updatedAt,
|
|
||||||
}
|
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user