forked from Yara724/api
519 lines
15 KiB
TypeScript
519 lines
15 KiB
TypeScript
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<string, unknown>;
|
|
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<string, unknown> | null,
|
|
claim?: Record<string, unknown> | null,
|
|
): string | undefined {
|
|
const names = new Set<string>();
|
|
|
|
const blameDecision = (
|
|
blame?.expert as Record<string, unknown> | undefined
|
|
)?.decision as Record<string, unknown> | undefined;
|
|
const blameExpert = expertNameFromSnapshot(
|
|
blameDecision?.expertProfileSnapshot as
|
|
| { firstName?: string; lastName?: string }
|
|
| undefined,
|
|
);
|
|
if (blameExpert) names.add(blameExpert);
|
|
|
|
const evaluation = claim?.evaluation as Record<string, unknown> | undefined;
|
|
for (const key of ["damageExpertReplyFinal", "damageExpertReply"] as const) {
|
|
const reply = evaluation?.[key] as Record<string, unknown> | 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<string, unknown> | undefined,
|
|
key: string,
|
|
role?: string,
|
|
): Record<string, unknown> | undefined {
|
|
const block = inquiries?.[key] as Record<string, unknown> | undefined;
|
|
const data = block?.data as Record<string, unknown> | undefined;
|
|
if (!data) return undefined;
|
|
if (role && data[role] && typeof data[role] === "object") {
|
|
return data[role] as Record<string, unknown>;
|
|
}
|
|
return data;
|
|
}
|
|
|
|
/** Prefer mapped Tejarat fields; avoid duplicating the entire raw blob in PDF. */
|
|
function pickInquiryReportPayload(
|
|
inquiry?: Record<string, unknown>,
|
|
): Record<string, unknown> | undefined {
|
|
if (!inquiry) return undefined;
|
|
const mapped = inquiry.mapped;
|
|
if (mapped && typeof mapped === "object" && !Array.isArray(mapped)) {
|
|
return mapped as Record<string, unknown>;
|
|
}
|
|
const { raw: _raw, ...rest } = inquiry;
|
|
return Object.keys(rest).length ? rest : inquiry;
|
|
}
|
|
|
|
function licenseFieldsFromInquiry(
|
|
inquiry?: Record<string, unknown>,
|
|
): { 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<typeof resolveDamagedPartyRow>,
|
|
claim?: Record<string, unknown> | 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<typeof resolveDamagedPartyRow>,
|
|
blame?: Record<string, unknown> | 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<string, unknown> | 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<typeof resolveDamagedPartyRow>,
|
|
blame?: Record<string, unknown> | null,
|
|
claim?: Record<string, unknown> | null,
|
|
): InsurerFileReportSection {
|
|
const role = damagedParty?.role ?? PartyRole.FIRST;
|
|
const insurance = damagedParty?.insurance ?? {};
|
|
const carBodyLegacy = blame?.carBodyInsuranceDetail as
|
|
| Record<string, unknown>
|
|
| undefined;
|
|
|
|
const thirdPartyInquiry = inquiryRoleData(
|
|
blame?.inquiries as Record<string, unknown> | undefined,
|
|
"thirdParty",
|
|
role,
|
|
);
|
|
const carBodyInquiry = inquiryRoleData(
|
|
blame?.inquiries as Record<string, unknown> | undefined,
|
|
"carBody",
|
|
role,
|
|
);
|
|
const claimThirdParty = inquiryRoleData(
|
|
claim?.inquiries as Record<string, unknown> | undefined,
|
|
"thirdParty",
|
|
role,
|
|
);
|
|
const claimCarBody = inquiryRoleData(
|
|
claim?.inquiries as Record<string, unknown> | 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<typeof resolveDamagedPartyRow>,
|
|
claim?: Record<string, unknown> | null,
|
|
): InsurerFileReportSection {
|
|
const partyVehicle = damagedParty?.vehicle;
|
|
const claimVehicle = claim?.vehicle as Record<string, unknown> | 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<string, unknown> | null,
|
|
claim?: Record<string, unknown> | null,
|
|
damagedParty?: ReturnType<typeof resolveDamagedPartyRow>,
|
|
): InsurerFileReportSection {
|
|
const statement = damagedParty?.statement as
|
|
| Record<string, unknown>
|
|
| undefined;
|
|
const location = damagedParty?.location;
|
|
const snapshotAccident = (
|
|
claim?.snapshot as { accident?: Record<string, unknown> } | undefined
|
|
)?.accident;
|
|
const blameDecision = (
|
|
blame?.expert as Record<string, unknown> | undefined
|
|
)?.decision as Record<string, unknown> | undefined;
|
|
const decisionFields = blameDecision?.fields as
|
|
| Record<string, unknown>
|
|
| 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<string, unknown> | undefined)
|
|
?.damageExpertReplyFinal as Record<string, unknown> | undefined
|
|
)?.submittedAt ??
|
|
(
|
|
(claim?.evaluation as Record<string, unknown> | undefined)
|
|
?.damageExpertReply as Record<string, unknown> | undefined
|
|
)?.submittedAt,
|
|
),
|
|
},
|
|
{
|
|
label: PR.damageExpertNotes,
|
|
value: asString(
|
|
(
|
|
(claim?.evaluation as Record<string, unknown> | undefined)
|
|
?.damageExpertReplyFinal as Record<string, unknown> | undefined
|
|
)?.description ??
|
|
(
|
|
(claim?.evaluation as Record<string, unknown> | undefined)
|
|
?.damageExpertReply as Record<string, unknown> | 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<string>();
|
|
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<string, unknown>;
|
|
blame?: Record<string, unknown>;
|
|
claim?: Record<string, unknown>;
|
|
},
|
|
): 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,
|
|
};
|
|
}
|