Complete inquiry participant flow coverage

This commit is contained in:
SepehrYahyaee
2026-09-13 11:29:43 +03:30
parent d060b6d9e2
commit b5b18114d8
8 changed files with 698 additions and 167 deletions

View File

@@ -36,6 +36,8 @@ type ReportParty = ReportRecord & {
vehicle?: ReportRecord;
statement?: ReportRecord;
location?: { lat?: number; lon?: number };
participants?: ReportRecord[];
participantRoles?: ReportRecord;
};
function asString(value: unknown): string | undefined {
@@ -66,7 +68,8 @@ function formatBirthDate(value: unknown): string | undefined {
function formatDateTime(value: unknown): string | undefined {
if (value === undefined || value === null || value === "") return undefined;
const date = value instanceof Date ? value : new Date(value as string | number);
const date =
value instanceof Date ? value : new Date(value as string | number);
if (Number.isNaN(date.getTime())) return asString(value);
const [d, t] = toJalaliDateAndTime(date);
return `${d} ${t}`;
@@ -126,13 +129,13 @@ function flattenObject(
if (Array.isArray(obj)) {
const value = normalizeListValue(obj);
return value
? [{ label: persianFieldPath(prefix || "items"), value }]
: [];
return value ? [{ label: persianFieldPath(prefix || "items"), value }] : [];
}
if (typeof obj !== "object") {
return [{ label: persianFieldPath(prefix || "value"), value: asString(obj) }];
return [
{ label: persianFieldPath(prefix || "value"), value: asString(obj) },
];
}
const rows: InsurerFileReportField[] = [];
@@ -160,7 +163,9 @@ function flattenObject(
return rows;
}
function dedupeFields(fields: InsurerFileReportField[]): InsurerFileReportField[] {
function dedupeFields(
fields: InsurerFileReportField[],
): InsurerFileReportField[] {
const seen = new Set<string>();
const out: InsurerFileReportField[] = [];
@@ -180,7 +185,8 @@ function filterEmptySections(
sections: Array<InsurerFileReportSection | undefined>,
): InsurerFileReportSection[] {
return sections.filter(
(section): section is InsurerFileReportSection => !!section && section.fields.length > 0,
(section): section is InsurerFileReportSection =>
!!section && section.fields.length > 0,
);
}
@@ -204,7 +210,9 @@ function expertNameFromSnapshot(snapshot?: {
lastName?: string;
}): string | undefined {
if (!snapshot) return undefined;
const name = [snapshot.firstName, snapshot.lastName].filter(Boolean).join(" ");
const name = [snapshot.firstName, snapshot.lastName]
.filter(Boolean)
.join(" ");
return name || undefined;
}
@@ -214,9 +222,8 @@ function collectExpertNames(
): string | undefined {
const names = new Set<string>();
const blameDecision = (
blame?.expert as Record<string, unknown> | undefined
)?.decision as Record<string, unknown> | undefined;
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 }
@@ -280,13 +287,16 @@ function resolveReportBlameContext(
(claim?.blameFileContext as Record<string, unknown> | undefined)
?.blameRequestType ??
(snapshot.accident as Record<string, unknown> | undefined)?.type,
blameStatus: (claim?.blameFileContext as Record<string, unknown> | undefined)
?.blameStatus,
blameStatus: (
claim?.blameFileContext as Record<string, unknown> | undefined
)?.blameStatus,
parties: snapshot.parties,
};
}
function getPartyRole(party: ReportParty | null | undefined): string | undefined {
function getPartyRole(
party: ReportParty | null | undefined,
): string | undefined {
const role = party?.role;
return typeof role === "string" ? role : undefined;
}
@@ -318,7 +328,8 @@ function sameParty(
second: ReportParty | null | undefined,
): boolean {
if (!first || !second) return false;
const firstUserId = first.person?.userId != null ? String(first.person.userId) : "";
const firstUserId =
first.person?.userId != null ? String(first.person.userId) : "";
const secondUserId =
second.person?.userId != null ? String(second.person.userId) : "";
if (firstUserId && secondUserId) return firstUserId === secondUserId;
@@ -330,7 +341,9 @@ function resolveEvaluationReply(
): Record<string, unknown> | undefined {
const evaluation = claim?.evaluation as Record<string, unknown> | undefined;
return (
(evaluation?.damageExpertReplyFinal as Record<string, unknown> | undefined) ??
(evaluation?.damageExpertReplyFinal as
| Record<string, unknown>
| undefined) ??
(evaluation?.damageExpertReply as Record<string, unknown> | undefined)
);
}
@@ -375,10 +388,18 @@ function buildThirdPartyInsuranceFields(
const role = getPartyRole(party);
const direct = (party?.insurance ?? {}) as Record<string, unknown>;
const blameInquiry = pickInquiryReportPayload(
inquiryRoleData(blame?.inquiries as Record<string, unknown> | undefined, "thirdParty", role),
inquiryRoleData(
blame?.inquiries as Record<string, unknown> | undefined,
"thirdParty",
role,
),
);
const claimInquiry = pickInquiryReportPayload(
inquiryRoleData(claim?.inquiries as Record<string, unknown> | undefined, "thirdParty", role),
inquiryRoleData(
claim?.inquiries as Record<string, unknown> | undefined,
"thirdParty",
role,
),
);
return [
@@ -462,12 +483,23 @@ function buildCarBodyInsuranceFields(
party?.insurance?.carBody ??
{}) as Record<string, unknown>;
const blameInquiry = pickInquiryReportPayload(
inquiryRoleData(blame?.inquiries as Record<string, unknown> | undefined, "carBody", role),
inquiryRoleData(
blame?.inquiries as Record<string, unknown> | undefined,
"carBody",
role,
),
);
const claimInquiry = pickInquiryReportPayload(
inquiryRoleData(claim?.inquiries as Record<string, unknown> | undefined, "carBody", role),
inquiryRoleData(
claim?.inquiries as Record<string, unknown> | undefined,
"carBody",
role,
),
);
const legacy = (blame?.carBodyInsuranceDetail ?? {}) as Record<string, unknown>;
const legacy = (blame?.carBodyInsuranceDetail ?? {}) as Record<
string,
unknown
>;
return [
{
@@ -556,7 +588,10 @@ function buildPartyOwnerSection(
{
label: PR.nationalCode,
value: options?.useClaimOwnerFallback
? firstDefined(person?.nationalCodeOfInsurer, money?.nationalCodeOfInsurer)
? firstDefined(
person?.nationalCodeOfInsurer,
money?.nationalCodeOfInsurer,
)
: firstDefined(person?.nationalCodeOfInsurer, person?.nationalCode),
},
{
@@ -572,9 +607,55 @@ function buildPartyOwnerSection(
]);
}
function licenseFieldsFromInquiry(
inquiry?: Record<string, unknown>,
): { licenseType?: string; licenseDate?: string } {
function buildParticipantRolesSection(
title: string,
party: ReportParty | null | undefined,
): InsurerFileReportSection | undefined {
const participants = Array.isArray(party?.participants)
? party.participants
: [];
const assignments = party?.participantRoles;
if (!assignments || participants.length === 0) return undefined;
const roleLabels: Record<string, string> = {
driver: PR.driverRole,
vehicleOwner: PR.vehicleOwnerRole,
thirdPartyPolicyholder: PR.thirdPartyPolicyholderRole,
carBodyPolicyholder: PR.carBodyPolicyholderRole,
};
const fields = Object.entries(assignments).map(([role, participantId]) => {
const participant = participants.find(
(candidate) => String(candidate.participantId) === String(participantId),
);
const value = participant?.unknown
? PR.unknown
: [
asString(participant?.fullName),
participant?.nationalCode
? `${PR.nationalCode}: ${asString(participant.nationalCode)}`
: undefined,
participant?.birthday
? `${PR.birthDate}: ${formatBirthDate(participant.birthday)}`
: undefined,
participant?.licenseNumber
? `${PR.licenseNumber}: ${asString(participant.licenseNumber)}`
: undefined,
]
.filter(Boolean)
.join("، ");
return {
label: roleLabels[role] ?? persianFieldPath(role),
value: value || PR.empty,
};
});
return buildSection(title, fields, false);
}
function licenseFieldsFromInquiry(inquiry?: Record<string, unknown>): {
licenseType?: string;
licenseDate?: string;
} {
if (!inquiry) return {};
return {
licenseType: firstDefined(
@@ -700,7 +781,9 @@ function buildCaseTimelineSection(
},
{
label: PR.evaluationRegisteredAt,
value: formatDateTime(resolveEvaluationSubmittedAt(claim, evaluationReply)),
value: formatDateTime(
resolveEvaluationSubmittedAt(claim, evaluationReply),
),
},
]);
}
@@ -709,10 +792,14 @@ function buildFanavaranCodesSection(
claim?: Record<string, unknown> | null,
): InsurerFileReportSection | undefined {
if (!claim) return undefined;
const sync = (claim.fanavaranSync as Record<string, unknown> | undefined) ?? {};
const baseClaim = (sync.baseClaim as Record<string, unknown> | undefined) ?? {};
const damageCase = (sync.damageCase as Record<string, unknown> | undefined) ?? {};
const expertise = (sync.expertise as Record<string, unknown> | undefined) ?? {};
const sync =
(claim.fanavaranSync as Record<string, unknown> | undefined) ?? {};
const baseClaim =
(sync.baseClaim as Record<string, unknown> | undefined) ?? {};
const damageCase =
(sync.damageCase as Record<string, unknown> | undefined) ?? {};
const expertise =
(sync.expertise as Record<string, unknown> | undefined) ?? {};
return buildSection(PR.fanavaranSection, [
{
@@ -725,7 +812,11 @@ function buildFanavaranCodesSection(
},
{
label: PR.fanavaranDamageCaseId,
value: firstDefined(claim.dmgCaseId, damageCase.dmgCaseId, expertise.dmgCaseId),
value: firstDefined(
claim.dmgCaseId,
damageCase.dmgCaseId,
expertise.dmgCaseId,
),
},
{
label: PR.fanavaranExpertiseId,
@@ -824,10 +915,19 @@ function buildEvaluationPartsSection(
const prefix = `${PR.part} ${PERSIAN_NUMBER_FORMATTER.format(index + 1)}`;
return [
{ label: `${prefix} / ${PR.partName}`, value: evaluationPartName(part) },
{ label: `${prefix} / ${PR.damageType}`, value: asString(part.typeOfDamage) },
{
label: `${prefix} / ${PR.damageType}`,
value: asString(part.typeOfDamage),
},
{ label: `${prefix} / ${PR.partPrice}`, value: formatToman(part.price) },
{ label: `${prefix} / ${PR.repairSalary}`, value: formatToman(part.salary) },
{ label: `${prefix} / ${PR.totalPayment}`, value: formatToman(part.totalPayment) },
{
label: `${prefix} / ${PR.repairSalary}`,
value: formatToman(part.salary),
},
{
label: `${prefix} / ${PR.totalPayment}`,
value: formatToman(part.totalPayment),
},
{
label: `${prefix} / ${PR.factorNeeded}`,
value:
@@ -835,7 +935,10 @@ function buildEvaluationPartsSection(
? persianStatus(part.factorNeeded)
: undefined,
},
{ label: `${prefix} / ${PR.daghi}`, value: evaluationDaghiValue(part.daghi) },
{
label: `${prefix} / ${PR.daghi}`,
value: evaluationDaghiValue(part.daghi),
},
];
});
@@ -852,10 +955,11 @@ function buildAccidentReportSection(
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 blameDecision = (blame?.expert as Record<string, unknown> | undefined)
?.decision as Record<string, unknown> | undefined;
const decisionFields = blameDecision?.fields as
| Record<string, unknown>
| undefined;
return buildSection(PR.accidentSection, [
{
@@ -868,7 +972,8 @@ function buildAccidentReportSection(
},
{
label: PR.accidentTime,
value: asString(statement?.accidentTime) ?? asString(snapshotAccident?.time),
value:
asString(statement?.accidentTime) ?? asString(snapshotAccident?.time),
},
{
label: PR.experts,
@@ -914,7 +1019,8 @@ function buildAccidentReportSection(
{
label: PR.accidentWay,
value: asString(
(decisionFields?.accidentWay as { label?: string } | undefined)?.label ??
(decisionFields?.accidentWay as { label?: string } | undefined)
?.label ??
(
snapshotAccident?.classification as {
accidentWay?: { label?: string };
@@ -925,7 +1031,8 @@ function buildAccidentReportSection(
{
label: PR.accidentReason,
value: asString(
(decisionFields?.accidentReason as { label?: string } | undefined)?.label ??
(decisionFields?.accidentReason as { label?: string } | undefined)
?.label ??
(
snapshotAccident?.classification as {
accidentReason?: { label?: string };
@@ -936,7 +1043,8 @@ function buildAccidentReportSection(
{
label: PR.accidentType,
value: asString(
(decisionFields?.accidentType as { label?: string } | undefined)?.label ??
(decisionFields?.accidentType as { label?: string } | undefined)
?.label ??
(
snapshotAccident?.classification as {
accidentType?: { label?: string };
@@ -968,12 +1076,17 @@ export function buildInsurerFileReport(file: {
: null;
const isCarBody =
String((blameContext?.type as string | undefined) ?? "") === "CAR_BODY";
const includeGuiltySections = !!guiltyParty && !(isCarBody && sameParty(damagedParty, guiltyParty));
const includeGuiltySections =
!!guiltyParty && !(isCarBody && sameParty(damagedParty, guiltyParty));
const claimVehicle = claim?.vehicle as Record<string, unknown> | undefined;
const sections = filterEmptySections([
buildCaseTimelineSection(overview, claim),
buildParticipantRolesSection(PR.damagedParticipantsSection, damagedParty),
includeGuiltySections
? buildParticipantRolesSection(PR.guiltyParticipantsSection, guiltyParty)
: undefined,
buildPartyOwnerSection(PR.ownerSection, damagedParty, {
claim,
useClaimOwnerFallback: true,
@@ -1003,7 +1116,11 @@ export function buildInsurerFileReport(file: {
buildCarBodyInsuranceFields(guiltyParty, blameContext, claim),
)
: undefined,
buildPartyVehicleSection(PR.damagedVehicleSection, damagedParty, claimVehicle),
buildPartyVehicleSection(
PR.damagedVehicleSection,
damagedParty,
claimVehicle,
),
includeGuiltySections
? buildPartyVehicleSection(PR.guiltyVehicleSection, guiltyParty)
: undefined,