import { resolveDamagedPartyRow } from "src/helpers/blame-damaged-party"; import { toJalaliDateAndTime } from "src/helpers/date-jalali"; import { PartyRole } from "src/request-management/entities/schema/partyRole.enum"; import { InsurerFileReportField, InsurerFileReportSection, InsurerFileReportViewModel, } from "./case-expert-report.types"; import { PR, persianFieldPath, persianStatus } from "./persian-report-labels"; const SKIP_FLATTEN_KEYS = new Set([ "_id", "__v", "history", "workflow", "evidence", "confirmation", ]); function asString(value: unknown): string | undefined { if (value === undefined || value === null || value === "") return undefined; if (value instanceof Date) { const [d, t] = toJalaliDateAndTime(value); return `${d} ${t}`; } if (typeof value === "object") { if (typeof (value as { toString?: () => string }).toString === "function") { const s = String(value); if (s !== "[object Object]") return s; } return undefined; } return String(value); } function formatBirthDate(value: unknown): string | undefined { if (value === undefined || value === null || value === "") return undefined; const raw = String(value); if (/^\d{8}$/.test(raw)) { return `${raw.slice(0, 4)}/${raw.slice(4, 6)}/${raw.slice(6, 8)}`; } return raw; } function flattenObject( obj: unknown, prefix = "", depth = 0, ): InsurerFileReportField[] { if (obj == null) return []; if (depth > 4) { return [{ label: persianFieldPath(prefix), value: asString(obj) }]; } if (Array.isArray(obj)) { if (!obj.length) return []; return [ { label: persianFieldPath(prefix || "items"), value: obj.map((item) => asString(item) ?? JSON.stringify(item)).join("، "), }, ]; } if (typeof obj !== "object") { return [{ label: persianFieldPath(prefix || "value"), value: asString(obj) }]; } const rows: InsurerFileReportField[] = []; const objRecord = obj as Record; const hasMapped = objRecord.mapped != null && typeof objRecord.mapped === "object"; for (const [key, value] of Object.entries(objRecord)) { if (SKIP_FLATTEN_KEYS.has(key)) continue; if (key === "raw" && hasMapped) continue; if (value === undefined || value === null || value === "") continue; const path = prefix ? `${prefix}.${key}` : key; if ( typeof value === "object" && !Array.isArray(value) && !(value instanceof Date) ) { rows.push(...flattenObject(value, path, depth + 1)); } else { rows.push({ label: persianFieldPath(path), value: asString(value) }); } } return rows; } function expertNameFromSnapshot(snapshot?: { firstName?: string; lastName?: string; }): string | undefined { if (!snapshot) return undefined; const name = [snapshot.firstName, snapshot.lastName].filter(Boolean).join(" "); return name || undefined; } function collectExpertNames( blame?: Record | null, claim?: Record | null, ): string | undefined { const names = new Set(); const blameDecision = ( blame?.expert as Record | undefined )?.decision as Record | undefined; const blameExpert = expertNameFromSnapshot( blameDecision?.expertProfileSnapshot as | { firstName?: string; lastName?: string } | undefined, ); if (blameExpert) names.add(blameExpert); const evaluation = claim?.evaluation as Record | undefined; for (const key of ["damageExpertReplyFinal", "damageExpertReply"] as const) { const reply = evaluation?.[key] as Record | undefined; if (!reply) continue; const actor = (reply.actorDetail as { actorName?: string } | undefined) ?.actorName; if (actor) names.add(actor); const snap = expertNameFromSnapshot( reply.expertProfileSnapshot as | { firstName?: string; lastName?: string } | undefined, ); if (snap) names.add(snap); } return names.size ? [...names].join(", ") : undefined; } function inquiryRoleData( inquiries: Record | undefined, key: string, role?: string, ): Record | undefined { const block = inquiries?.[key] as Record | undefined; const data = block?.data as Record | undefined; if (!data) return undefined; if (role && data[role] && typeof data[role] === "object") { return data[role] as Record; } return data; } /** Prefer mapped Tejarat fields; avoid duplicating the entire raw blob in PDF. */ function pickInquiryReportPayload( inquiry?: Record, ): Record | undefined { if (!inquiry) return undefined; const mapped = inquiry.mapped; if (mapped && typeof mapped === "object" && !Array.isArray(mapped)) { return mapped as Record; } const { raw: _raw, ...rest } = inquiry; return Object.keys(rest).length ? rest : inquiry; } function licenseFieldsFromInquiry( inquiry?: Record, ): { licenseType?: string; licenseDate?: string } { if (!inquiry) return {}; const candidates = [ inquiry.LicenseType, inquiry.licenseType, inquiry.Type, inquiry.type, inquiry.LicenseCategory, inquiry.licenseCategory, ]; const licenseType = candidates.find((v) => v != null && v !== ""); const dateCandidates = [ inquiry.IssueDate, inquiry.issueDate, inquiry.LicenseIssueDate, inquiry.licenseIssueDate, inquiry.ExpireDate, inquiry.expireDate, ]; const licenseDate = dateCandidates.find((v) => v != null && v !== ""); return { licenseType: asString(licenseType), licenseDate: asString(licenseDate), }; } function buildOwnerSection( damagedParty: ReturnType, claim?: Record | null, ): InsurerFileReportSection { const person = damagedParty?.person; const money = claim?.money as | { sheba?: string; nationalCodeOfInsurer?: string } | undefined; const claimOwner = claim?.owner as { fullName?: string } | undefined; return { title: PR.ownerSection, fields: [ { label: PR.name, value: person?.fullName ?? claimOwner?.fullName }, { label: PR.phone, value: person?.phoneNumber }, { label: PR.nationalCode, value: person?.nationalCodeOfInsurer ?? money?.nationalCodeOfInsurer, }, { label: PR.birthDate, value: formatBirthDate(person?.insurerBirthday ?? person?.birthday), }, { label: PR.sheba, value: money?.sheba }, ], }; } function buildDriverSection( damagedParty: ReturnType, blame?: Record | null, ): InsurerFileReportSection | undefined { const person = damagedParty?.person; if (!person || person.driverIsInsurer !== false) return undefined; const role = damagedParty?.role ?? PartyRole.FIRST; const licenseInquiry = inquiryRoleData( blame?.inquiries as Record | undefined, "drivingLicence", role, ); const { licenseType, licenseDate } = licenseFieldsFromInquiry(licenseInquiry); return { title: PR.driverSection, fields: [ { label: PR.name, value: person.fullName }, { label: PR.licenseType, value: licenseType ?? (person.driverLicense ? PR.driverLicense : undefined), }, { label: PR.licenseDate, value: licenseDate ?? person.driverLicense, }, { label: PR.phone, value: person.phoneNumber }, { label: PR.nationalCode, value: person.nationalCodeOfDriver }, { label: PR.birthDate, value: formatBirthDate(person.driverBirthday) }, { label: PR.licenseNumber, value: person.driverLicense }, ], }; } function buildInsuranceSection( damagedParty: ReturnType, blame?: Record | null, claim?: Record | null, ): InsurerFileReportSection { const role = damagedParty?.role ?? PartyRole.FIRST; const insurance = damagedParty?.insurance ?? {}; const carBodyLegacy = blame?.carBodyInsuranceDetail as | Record | undefined; const thirdPartyInquiry = inquiryRoleData( blame?.inquiries as Record | undefined, "thirdParty", role, ); const carBodyInquiry = inquiryRoleData( blame?.inquiries as Record | undefined, "carBody", role, ); const claimThirdParty = inquiryRoleData( claim?.inquiries as Record | undefined, "thirdParty", role, ); const claimCarBody = inquiryRoleData( claim?.inquiries as Record | undefined, "carBody", role, ); const fields: InsurerFileReportField[] = [ ...flattenObject(insurance, "party.insurance"), ...flattenObject( (insurance as { carBodyInsurance?: unknown }).carBodyInsurance, "party.insurance.carBodyInsurance", ), ...flattenObject( pickInquiryReportPayload(thirdPartyInquiry), "inquiry.thirdParty", ), ...flattenObject(pickInquiryReportPayload(carBodyInquiry), "inquiry.carBody"), ...flattenObject( pickInquiryReportPayload(claimThirdParty), "claim.inquiry.thirdParty", ), ...flattenObject( pickInquiryReportPayload(claimCarBody), "claim.inquiry.carBody", ), ...flattenObject(carBodyLegacy, "blame.carBodyInsuranceDetail"), ]; const deduped = dedupeFields(fields); return { title: PR.insuranceSection, fields: deduped.length ? deduped : [{ label: PR.data, value: PR.empty }], }; } function buildVehicleSection( damagedParty: ReturnType, claim?: Record | null, ): InsurerFileReportSection { const partyVehicle = damagedParty?.vehicle; const claimVehicle = claim?.vehicle as Record | undefined; const fields = dedupeFields([ ...flattenObject(claimVehicle, "claim.vehicle"), ...flattenObject(partyVehicle, "party.vehicle"), ]); return { title: PR.vehicleSection, fields: fields.length ? fields : [{ label: PR.data, value: PR.empty }], }; } function buildAccidentReportSection( blame?: Record | null, claim?: Record | null, damagedParty?: ReturnType, ): InsurerFileReportSection { const statement = damagedParty?.statement as | Record | undefined; const location = damagedParty?.location; const snapshotAccident = ( claim?.snapshot as { accident?: Record } | undefined )?.accident; const blameDecision = ( blame?.expert as Record | undefined )?.decision as Record | undefined; const decisionFields = blameDecision?.fields as | Record | undefined; const accidentDate = asString(statement?.accidentDate) ?? asString(snapshotAccident?.date) ?? asString(blame?.createdAtFormatted) ?? asString(blame?.createdAt); const accidentTime = asString(statement?.accidentTime) ?? asString(snapshotAccident?.time); const fields: InsurerFileReportField[] = [ { label: PR.date, value: accidentDate }, { label: PR.time, value: accidentTime }, { label: PR.experts, value: collectExpertNames(blame, claim), }, { label: PR.location, value: location?.lat != null && location?.lon != null ? `${location.lat}، ${location.lon}` : undefined, }, { label: PR.weather, value: asString(statement?.weatherCondition) ?? asString(snapshotAccident?.weatherCondition), }, { label: PR.road, value: asString(statement?.roadCondition) ?? asString(snapshotAccident?.roadCondition), }, { label: PR.light, value: asString(statement?.lightCondition) ?? asString(snapshotAccident?.lightCondition), }, { label: PR.blameStatus, value: persianStatus(blame?.blameStatus), }, { label: PR.claimStatus, value: persianStatus(claim?.claimStatus), }, { label: PR.expertDecision, value: asString(blameDecision?.description) }, { label: PR.accidentWay, value: asString( (decisionFields?.accidentWay as { label?: string } | undefined)?.label ?? ( snapshotAccident?.classification as { accidentWay?: { label?: string }; } )?.accidentWay?.label, ), }, { label: PR.accidentReason, value: asString( (decisionFields?.accidentReason as { label?: string } | undefined) ?.label ?? ( snapshotAccident?.classification as { accidentReason?: { label?: string }; } )?.accidentReason?.label, ), }, { label: PR.accidentType, value: asString( (decisionFields?.accidentType as { label?: string } | undefined)?.label ?? ( snapshotAccident?.classification as { accidentType?: { label?: string }; } )?.accidentType?.label, ), }, { label: PR.damageExpertDate, value: asString( ( (claim?.evaluation as Record | undefined) ?.damageExpertReplyFinal as Record | undefined )?.submittedAt ?? ( (claim?.evaluation as Record | undefined) ?.damageExpertReply as Record | undefined )?.submittedAt, ), }, { label: PR.damageExpertNotes, value: asString( ( (claim?.evaluation as Record | undefined) ?.damageExpertReplyFinal as Record | undefined )?.description ?? ( (claim?.evaluation as Record | undefined) ?.damageExpertReply as Record | undefined )?.description, ), }, { label: PR.partyDescription, value: asString(statement?.description), }, ]; return { title: PR.accidentSection, fields: dedupeFields(fields), }; } function dedupeFields(fields: InsurerFileReportField[]): InsurerFileReportField[] { const seen = new Set(); const out: InsurerFileReportField[] = []; for (const field of fields) { const value = asString(field.value); if (!value) continue; const key = `${field.label}::${value}`; if (seen.has(key)) continue; seen.add(key); out.push({ label: field.label, value }); } return out; } export function buildInsurerFileReport( file: { overview?: Record; blame?: Record; claim?: Record; }, ): InsurerFileReportViewModel { const overview = file.overview ?? {}; const blame = file.blame ?? null; const claim = file.claim ?? null; const damagedParty = blame ? resolveDamagedPartyRow(blame) : null; const sections: InsurerFileReportSection[] = [ buildOwnerSection(damagedParty, claim), ]; const driverSection = buildDriverSection(damagedParty, blame); if (driverSection) sections.push(driverSection); sections.push( buildInsuranceSection(damagedParty, blame, claim), buildVehicleSection(damagedParty, claim), buildAccidentReportSection(blame, claim, damagedParty), ); return { title: PR.reportTitle, publicId: asString(overview.publicId) ?? PR.empty, requestNo: asString(overview.requestNo), sections, }; }