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

@@ -70,6 +70,26 @@ describe("buildInsurerFileReport", () => {
}, },
{ {
role: "SECOND", role: "SECOND",
participants: [
{
participantId: "DRIVER",
fullName: "راننده نمونه",
nationalCode: "0012345678",
birthday: "1370/01/01",
licenseNumber: "LIC-1",
},
{
participantId: "VEHICLE_OWNER",
fullName: "زیان دیده نمونه",
nationalCode: "1234567890",
birthday: "1370/01/01",
},
],
participantRoles: {
driver: "DRIVER",
vehicleOwner: "VEHICLE_OWNER",
thirdPartyPolicyholder: "VEHICLE_OWNER",
},
person: { person: {
userId: "damaged-user-id", userId: "damaged-user-id",
fullName: "زیان دیده نمونه", fullName: "زیان دیده نمونه",
@@ -165,61 +185,89 @@ describe("buildInsurerFileReport", () => {
}, },
}); });
expect(getFieldValue(report, PR.ownerSection, PR.name)).toBe("زیان دیده نمونه"); expect(getFieldValue(report, PR.ownerSection, PR.name)).toBe(
expect(getFieldValue(report, PR.guiltyOwnerSection, PR.name)).toBe("مقصر نمونه"); "زیان دیده نمونه",
expect(getFieldValue(report, PR.guiltyOwnerSection, PR.phone)).toBe("09120000000"); );
expect(
getFieldValue(report, PR.damagedParticipantsSection, PR.driverRole),
).toContain("راننده نمونه");
expect(
getFieldValue(
report,
PR.damagedParticipantsSection,
PR.thirdPartyPolicyholderRole,
),
).toContain("1234567890");
expect(getFieldValue(report, PR.guiltyOwnerSection, PR.name)).toBe(
"مقصر نمونه",
);
expect(getFieldValue(report, PR.guiltyOwnerSection, PR.phone)).toBe(
"09120000000",
);
expect(getFieldValue(report, PR.guiltyOwnerSection, PR.nationalCode)).toBe( expect(getFieldValue(report, PR.guiltyOwnerSection, PR.nationalCode)).toBe(
"0987654321", "0987654321",
); );
expect(getFieldValue(report, PR.damagedThirdPartyInsuranceSection, PR.policyNumber)).toBe(
"TP-DAMAGED-001",
);
expect(getFieldValue(report, PR.guiltyThirdPartyInsuranceSection, PR.policyNumber)).toBe(
"TP-GUILTY-001",
);
expect(getFieldValue(report, PR.damagedCarBodyInsuranceSection, PR.policyNumber)).toBe(
"CB-DAMAGED-001",
);
expect(getFieldValue(report, PR.guiltyCarBodyInsuranceSection, PR.policyNumber)).toBe(
"CB-GUILTY-001",
);
expect(getFieldValue(report, PR.damagedVehicleSection, "خودرو / نام خودرو")).toBe(
"207",
);
expect(getFieldValue(report, PR.guiltyVehicleSection, "خودرو / نام خودرو")).toBe(
"206",
);
expect(getFieldValue(report, PR.damagedStatementSection, PR.partyDescription)).toBe(
"توضیحات زیان‌دیده",
);
expect(getFieldValue(report, PR.damagedStatementSection, PR.claimsDamage)).toBe(
"بله",
);
expect( expect(
getFieldValue(report, PR.damagedStatementSection, PR.acceptsExpertOpinion), getFieldValue(
report,
PR.damagedThirdPartyInsuranceSection,
PR.policyNumber,
),
).toBe("TP-DAMAGED-001");
expect(
getFieldValue(
report,
PR.guiltyThirdPartyInsuranceSection,
PR.policyNumber,
),
).toBe("TP-GUILTY-001");
expect(
getFieldValue(report, PR.damagedCarBodyInsuranceSection, PR.policyNumber),
).toBe("CB-DAMAGED-001");
expect(
getFieldValue(report, PR.guiltyCarBodyInsuranceSection, PR.policyNumber),
).toBe("CB-GUILTY-001");
expect(
getFieldValue(report, PR.damagedVehicleSection, "خودرو / نام خودرو"),
).toBe("207");
expect(
getFieldValue(report, PR.guiltyVehicleSection, "خودرو / نام خودرو"),
).toBe("206");
expect(
getFieldValue(report, PR.damagedStatementSection, PR.partyDescription),
).toBe("توضیحات زیان‌دیده");
expect(
getFieldValue(report, PR.damagedStatementSection, PR.claimsDamage),
).toBe("بله");
expect(
getFieldValue(
report,
PR.damagedStatementSection,
PR.acceptsExpertOpinion,
),
).toBe("خیر"); ).toBe("خیر");
expect(getFieldValue(report, PR.guiltyStatementSection, PR.partyDescription)).toBe( expect(
"توضیحات مقصر", getFieldValue(report, PR.guiltyStatementSection, PR.partyDescription),
); ).toBe("توضیحات مقصر");
expect(getFieldValue(report, PR.guiltyStatementSection, PR.admitsGuilt)).toBe( expect(
"بله", getFieldValue(report, PR.guiltyStatementSection, PR.admitsGuilt),
); ).toBe("بله");
expect( expect(
getFieldValue(report, PR.guiltyStatementSection, PR.acceptsExpertOpinion), getFieldValue(report, PR.guiltyStatementSection, PR.acceptsExpertOpinion),
).toBe("بله"); ).toBe("بله");
expect(getFieldValue(report, PR.fanavaranSection, PR.fanavaranClaimNo)).toBe( expect(
"111", getFieldValue(report, PR.fanavaranSection, PR.fanavaranClaimNo),
); ).toBe("111");
expect( expect(
getFieldValue(report, PR.fanavaranSection, PR.fanavaranExpertiseId), getFieldValue(report, PR.fanavaranSection, PR.fanavaranExpertiseId),
).toBe("444"); ).toBe("444");
expect(getFieldValue(report, PR.fanavaranSection, PR.fanavaranPolicyId)).toBe( expect(
"555", getFieldValue(report, PR.fanavaranSection, PR.fanavaranPolicyId),
); ).toBe("555");
expect(getFieldValue(report, PR.timelineSection, PR.fileRegisteredAt)).toBe( expect(getFieldValue(report, PR.timelineSection, PR.fileRegisteredAt)).toBe(
"1405/05/19 13:23", "1405/05/19 13:23",
@@ -228,15 +276,15 @@ describe("buildInsurerFileReport", () => {
getFieldValue(report, PR.timelineSection, PR.evaluationRegisteredAt), getFieldValue(report, PR.timelineSection, PR.evaluationRegisteredAt),
).toBe("1405/05/19 16:00"); ).toBe("1405/05/19 16:00");
expect(getFieldValue(report, PR.evaluationSection, PR.evaluationResult)).toBe( expect(
"تأیید شده", getFieldValue(report, PR.evaluationSection, PR.evaluationResult),
); ).toBe("تأیید شده");
expect(getFieldValue(report, PR.evaluationSection, PR.evaluationExpert)).toBe( expect(
"کارشناس خسارت نمونه", getFieldValue(report, PR.evaluationSection, PR.evaluationExpert),
); ).toBe("کارشناس خسارت نمونه");
expect(getFieldValue(report, PR.evaluationSection, PR.evaluationResponse)).toBe( expect(
"نیاز به تعویض سپر جلو", getFieldValue(report, PR.evaluationSection, PR.evaluationResponse),
); ).toBe("نیاز به تعویض سپر جلو");
expect(getFieldValue(report, "قیمت قطعات", "قطعه ۱ / نام قطعه")).toBe( expect(getFieldValue(report, "قیمت قطعات", "قطعه ۱ / نام قطعه")).toBe(
"سپر جلو", "سپر جلو",
); );
@@ -345,9 +393,9 @@ describe("buildInsurerFileReport", () => {
}); });
const fields = report.sections.flatMap((section) => section.fields); const fields = report.sections.flatMap((section) => section.fields);
expect(fields.find((field) => field.label === "خودرو / خودرو نو")?.value).toBe( expect(
"بله", fields.find((field) => field.label === "خودرو / خودرو نو")?.value,
); ).toBe("بله");
expect(fields.some((field) => field.value === "خیر")).toBe(true); expect(fields.some((field) => field.value === "خیر")).toBe(true);
expect(fields.some((field) => field.value === "true")).toBe(false); expect(fields.some((field) => field.value === "true")).toBe(false);
expect(fields.some((field) => field.value === "false")).toBe(false); expect(fields.some((field) => field.value === "false")).toBe(false);
@@ -372,10 +420,12 @@ describe("buildInsurerFileReport", () => {
}); });
const fields = report.sections.flatMap((section) => section.fields); const fields = report.sections.flatMap((section) => section.fields);
expect(fields.some((field) => String(field.value).includes("ESG"))).toBe(false); expect(fields.some((field) => String(field.value).includes("ESG"))).toBe(
expect(fields.some((field) => String(field.value).includes("TEJARAT"))).toBe(
false, false,
); );
expect(
fields.some((field) => String(field.value).includes("TEJARAT")),
).toBe(false);
expect(fields.some((field) => field.label.includes("منبع"))).toBe(false); expect(fields.some((field) => field.label.includes("منبع"))).toBe(false);
}); });

View File

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

View File

@@ -5,6 +5,8 @@ export const PR = {
empty: "-", empty: "-",
ownerSection: "مالک خودروی زیان دیده", ownerSection: "مالک خودروی زیان دیده",
guiltyOwnerSection: "مالک خودروی مقصر", guiltyOwnerSection: "مالک خودروی مقصر",
damagedParticipantsSection: "اشخاص و نقش‌های خودروی زیان‌دیده",
guiltyParticipantsSection: "اشخاص و نقش‌های خودروی مقصر",
driverSection: "راننده خودروی زیان دیده", driverSection: "راننده خودروی زیان دیده",
damagedThirdPartyInsuranceSection: "بیمه شخص ثالث زیان‌دیده", damagedThirdPartyInsuranceSection: "بیمه شخص ثالث زیان‌دیده",
damagedCarBodyInsuranceSection: "بیمه بدنه زیان‌دیده", damagedCarBodyInsuranceSection: "بیمه بدنه زیان‌دیده",
@@ -61,6 +63,11 @@ export const PR = {
claimsDamage: "ادعای خسارت", claimsDamage: "ادعای خسارت",
acceptsExpertOpinion: "پذیرش نظر کارشناس", acceptsExpertOpinion: "پذیرش نظر کارشناس",
partyRole: "نقش طرف", partyRole: "نقش طرف",
driverRole: "راننده",
vehicleOwnerRole: "مالک وسیله نقلیه",
thirdPartyPolicyholderRole: "بیمه‌گذار شخص ثالث",
carBodyPolicyholderRole: "بیمه‌گذار بدنه",
unknown: "نامشخص",
data: "اطلاعات", data: "اطلاعات",
date: "تاریخ", date: "تاریخ",
time: "زمان", time: "زمان",

View File

@@ -24,9 +24,8 @@ export enum VehicleRegistrationState {
export class InquiryPlateDto { export class InquiryPlateDto {
@ApiProperty({ example: "44" }) @ApiProperty({ example: "44" })
@IsString()
@IsNotEmpty() @IsNotEmpty()
leftDigits: string; leftDigits: string | number;
@ApiProperty({ example: "ب" }) @ApiProperty({ example: "ب" })
@IsString() @IsString()
@@ -34,14 +33,12 @@ export class InquiryPlateDto {
centerAlphabet: string; centerAlphabet: string;
@ApiProperty({ example: "111" }) @ApiProperty({ example: "111" })
@IsString()
@IsNotEmpty() @IsNotEmpty()
centerDigits: string; centerDigits: string | number;
@ApiProperty({ example: "22" }) @ApiProperty({ example: "22" })
@IsString()
@IsNotEmpty() @IsNotEmpty()
ir: string; ir: string | number;
} }
export class InquiryParticipantInputDto { export class InquiryParticipantInputDto {

View File

@@ -146,6 +146,22 @@ export class FirstPartyDetail {
@Prop({ type: Boolean }) @Prop({ type: Boolean })
firstPartyCertificate: boolean | null; firstPartyCertificate: boolean | null;
@Prop({ type: [MongooseSchema.Types.Mixed] })
participants?: Record<string, unknown>[];
@Prop({ type: MongooseSchema.Types.Mixed })
participantRoles?: Record<string, string>;
@Prop({ type: MongooseSchema.Types.Mixed })
participantInquiries?: Record<string, unknown>;
@Prop({ type: MongooseSchema.Types.Mixed })
policyInquiry?: Record<string, unknown>;
@Prop() registrationState?: string;
@Prop() previousPlateId?: string;
@Prop() vehicleVin?: string;
} }
export class SecondPartyDetail { export class SecondPartyDetail {
@@ -178,6 +194,22 @@ export class SecondPartyDetail {
@Prop({ type: SecondPartyFile }) @Prop({ type: SecondPartyFile })
secondPartyFiles?: SecondPartyFile; secondPartyFiles?: SecondPartyFile;
@Prop({ type: [MongooseSchema.Types.Mixed] })
participants?: Record<string, unknown>[];
@Prop({ type: MongooseSchema.Types.Mixed })
participantRoles?: Record<string, string>;
@Prop({ type: MongooseSchema.Types.Mixed })
participantInquiries?: Record<string, unknown>;
@Prop({ type: MongooseSchema.Types.Mixed })
policyInquiry?: Record<string, unknown>;
@Prop() registrationState?: string;
@Prop() previousPlateId?: string;
@Prop() vehicleVin?: string;
} }
export class LocationDto { export class LocationDto {

View File

@@ -421,6 +421,41 @@ describe("inquiry participant resolver", () => {
}); });
}); });
it("does not use the previous plate after a transport or provider outage", async () => {
const currentPlate = {
leftDigits: "44",
centerAlphabet: "ب",
centerDigits: "111",
ir: "22",
};
const query = jest.fn().mockRejectedValue(
Object.assign(new Error("upstream timeout"), {
code: "ETIMEDOUT",
}),
);
await expect(
runPlateInquiryWithFallback({
vehicle: resolveInquiryVehicle({
registrationState: VehicleRegistrationState.RECENTLY_TRANSFERRED,
currentPlate,
previousPlate: {
leftDigits: "55",
centerAlphabet: "ج",
centerDigits: "222",
ir: "33",
},
vin: "NAAM01E15HK123456",
}),
fallbackCurrentPlate: currentPlate,
query,
isUsable: () => false,
mappedValue: () => ({}),
}),
).rejects.toThrow("upstream timeout");
expect(query).toHaveBeenCalledTimes(1);
});
it("allows an explicitly unknown policyholder only for a damaged-party submission", () => { it("allows an explicitly unknown policyholder only for a damaged-party submission", () => {
const input = { const input = {
driver: { driver: {

View File

@@ -391,12 +391,31 @@ export function assertPreviousPlateInquiryMatchesVin(
} }
} }
export function isPolicyNotFoundError(error: unknown): boolean {
const candidate = error as Record<string, any> | null;
const status = candidate?.status ?? candidate?.response?.status;
if (Number(status) === 404) return true;
const code = String(
candidate?.code ?? candidate?.response?.data?.code ?? "",
).toUpperCase();
if (["NOT_FOUND", "POLICY_NOT_FOUND", "NO_POLICY"].includes(code)) {
return true;
}
const message = String(
candidate?.message ?? candidate?.response?.data?.message ?? error ?? "",
);
return /\bnot[ -]?found\b|\bno (?:relevant )?policy\b|یافت نشد|فاقد بیمه(?:نامه)?/i.test(
message,
);
}
export async function runPlateInquiryWithFallback<T>(options: { export async function runPlateInquiryWithFallback<T>(options: {
vehicle?: ResolvedInquiryVehicle; vehicle?: ResolvedInquiryVehicle;
fallbackCurrentPlate: InquiryVehicleInputDto["currentPlate"]; fallbackCurrentPlate: InquiryVehicleInputDto["currentPlate"];
query: (plate: InquiryVehicleInputDto["currentPlate"]) => Promise<T>; query: (plate: InquiryVehicleInputDto["currentPlate"]) => Promise<T>;
isUsable: (value: T) => boolean; isUsable: (value: T) => boolean;
mappedValue: (value: T) => Record<string, any>; mappedValue: (value: T) => Record<string, any>;
shouldFallbackOnError?: (error: unknown) => boolean;
}): Promise<{ }): Promise<{
value: T; value: T;
plateKind: "CURRENT" | "PREVIOUS"; plateKind: "CURRENT" | "PREVIOUS";
@@ -462,6 +481,15 @@ export async function runPlateInquiryWithFallback<T>(options: {
error: error instanceof Error ? error.message : String(error), error: error instanceof Error ? error.message : String(error),
}); });
} }
if (
!isLast &&
!(options.shouldFallbackOnError ?? isPolicyNotFoundError)(error)
) {
if (typeof error === "object" && error != null) {
(error as { attempts?: typeof attempts }).attempts = attempts;
}
throw error;
}
if (isLast) { if (isLast) {
if (typeof error === "object" && error != null) { if (typeof error === "object" && error != null) {
(error as { attempts?: typeof attempts }).attempts = attempts; (error as { attempts?: typeof attempts }).attempts = attempts;

View File

@@ -542,6 +542,44 @@ export class RequestManagementService {
} }
} }
/** Inquiry bundle for the still-routable legacy RequestManagement model. */
private async collectLegacyParticipantInquiries(
submission: NormalizedInquirySubmission<Record<string, any>>,
): Promise<Record<string, unknown> | undefined> {
if (submission.participants.legacy) return undefined;
const personal: Record<string, unknown> = {};
for (const participant of submission.participants.participants) {
if (participant.unknown) continue;
personal[participant.participantId] =
await this.sandHubService.getPersonalInquiry(
participant.nationalCode,
participant.birthday,
);
}
const plate = submission.vehicle?.currentPlate ?? submission.dto.plate;
const ownership =
submission.vehicleOwner && plate
? await this.sandHubService.getCarOwnershipInfo(
plate,
submission.vehicleOwner.nationalCode,
)
: undefined;
const drivingLicence =
submission.driver.hasDrivingLicense === false
? {
skipped: true,
reason: "Driver declared no driving licence",
}
: await this.sandHubService.getDrivingLicenseInfo(
submission.driver.nationalCode,
submission.driver.licenseNumber!,
);
return { personal, ownership, drivingLicence };
}
private getPartyIndex(req: any, role: PartyRole): number { private getPartyIndex(req: any, role: PartyRole): number {
return Array.isArray(req.parties) return Array.isArray(req.parties)
? req.parties.findIndex((p) => p?.role === role) ? req.parties.findIndex((p) => p?.role === role)
@@ -3271,6 +3309,9 @@ export class RequestManagementService {
user: any, user: any,
body: AddPlateDto, body: AddPlateDto,
partyType: "firstParty" | "secondParty", partyType: "firstParty" | "secondParty",
inquirySubmission?: NormalizedInquirySubmission<Record<string, any>>,
policyInquiry?: Record<string, any>,
participantInquiries?: Record<string, unknown>,
) { ) {
if (request.type === "THIRD_PARTY" && partyType === "firstParty") { if (request.type === "THIRD_PARTY" && partyType === "firstParty") {
this.sandHubService.assertInsuranceMatchesDeployment( this.sandHubService.assertInsuranceMatchesDeployment(
@@ -3282,12 +3323,16 @@ export class RequestManagementService {
const clientName = sandHubReport?.CompanyName; const clientName = sandHubReport?.CompanyName;
const companyCode = sandHubReport?.CompanyCode; const companyCode = sandHubReport?.CompanyCode;
const client = await this.clientService.findOrCreateClientByCompanyCode( const policyholderUnknown =
inquirySubmission?.thirdPartyPolicyholder.unknown === true;
const client = clientName
? await this.clientService.findOrCreateClientByCompanyCode(
companyCode, companyCode,
clientName, clientName,
); )
: null;
if (!client) { if (!client && !policyholderUnknown) {
throw new HttpException("Client not found", HttpStatus.CONFLICT); throw new HttpException("Client not found", HttpStatus.CONFLICT);
} }
@@ -3313,23 +3358,27 @@ export class RequestManagementService {
} }
try { try {
if (client) {
await this.userDbService.findOneAndUpdate( await this.userDbService.findOneAndUpdate(
{ _id: user.sub }, { _id: user.sub },
{ clientKey: client["_doc"]._id }, { clientKey: (client as any)["_doc"]?._id ?? (client as any)._id },
); );
}
const sandHubDocData = { const sandHubDocData = {
...sandHubReport, ...sandHubReport,
plate: body.plate, plate: body.plate,
nationalCode: body.nationalCodeOfInsurer, nationalCode: body.nationalCodeOfInsurer,
}; };
const sandHubDoc = await this.sandHubService.sandHubDocumentCreator( const sandHubDoc = policyInquiry?.skipped
? null
: await this.sandHubService.sandHubDocumentCreator(
user.sub, user.sub,
request["_doc"]._id, request["_doc"]._id,
sandHubDocData, sandHubDocData,
); );
if (partyType === "firstParty") { if (partyType === "firstParty" && sandHubDoc) {
await this.requestManagementDbService.findAndUpdate( await this.requestManagementDbService.findAndUpdate(
{ _id: request._id }, { _id: request._id },
{ {
@@ -3352,8 +3401,6 @@ export class RequestManagementService {
body.driverBirthday, body.driverBirthday,
[`${partyDetails}.${partyType}CarDetail.carType`]: `${sandHubReport.UsageField} / ${sandHubReport.MapUsageName || "-"}`, [`${partyDetails}.${partyType}CarDetail.carType`]: `${sandHubReport.UsageField} / ${sandHubReport.MapUsageName || "-"}`,
[`${partyDetails}.${partyType}CarDetail.isNewCar`]: body.isNewCar, [`${partyDetails}.${partyType}CarDetail.isNewCar`]: body.isNewCar,
[`${partyDetails}.${partyType}Client.clientId`]: client["_doc"]._id,
[`${partyDetails}.${partyType}Client.clientName`]: clientName,
[`${partyDetails}.${partyType}Certificate`]: body.userNoCertificate, [`${partyDetails}.${partyType}Certificate`]: body.userNoCertificate,
[`${partyDetails}.${partyType}InsuranceDetail.insuranceId`]: [`${partyDetails}.${partyType}InsuranceDetail.insuranceId`]:
sandHubReport.LastCompanyDocumentNumber, sandHubReport.LastCompanyDocumentNumber,
@@ -3367,9 +3414,35 @@ export class RequestManagementService {
sandHubReport.EndDate, sandHubReport.EndDate,
}; };
if (client) {
setFields[`${partyDetails}.${partyType}Client.clientId`] =
(client as any)["_doc"]?._id ?? (client as any)._id;
setFields[`${partyDetails}.${partyType}Client.clientName`] = clientName;
}
if (inquirySubmission && !inquirySubmission.participants.legacy) {
setFields[`${partyDetails}.participants`] =
inquirySubmission.participants.participants;
setFields[`${partyDetails}.participantRoles`] =
inquirySubmission.participants.roles;
setFields[`${partyDetails}.participantInquiries`] =
participantInquiries ?? {};
setFields[`${partyDetails}.policyInquiry`] = policyInquiry ?? {};
setFields[`${partyDetails}.registrationState`] =
inquirySubmission.vehicle?.registrationState;
setFields[`${partyDetails}.previousPlateId`] = inquirySubmission.vehicle
?.previousPlate
? this.plateToPlateIdString(inquirySubmission.vehicle.previousPlate)
: undefined;
setFields[`${partyDetails}.vehicleVin`] =
inquirySubmission.vehicle?.vin;
}
// For CAR_BODY type, persist the provider response and its mapped fields. // For CAR_BODY type, persist the provider response and its mapped fields.
if (request.type === "CAR_BODY" && partyType === "firstParty") { if (request.type === "CAR_BODY" && partyType === "firstParty") {
const carBodyInquiry = await this.sandHubService.getCarBodyInquiry({ const carBodyInquiry =
inquirySubmission && !inquirySubmission.participants.legacy
? await this.getCarBodyPlateInquiry(inquirySubmission)
: await this.sandHubService.getCarBodyInquiry({
nationalCodeOfInsurer: body.nationalCodeOfInsurer, nationalCodeOfInsurer: body.nationalCodeOfInsurer,
plate: body.plate, plate: body.plate,
} as any); } as any);
@@ -3400,6 +3473,18 @@ export class RequestManagementService {
carBodyInquiry.raw; carBodyInquiry.raw;
setFields["firstPartyDetails.firstPartyCarBodyInsuranceDetail.source"] = setFields["firstPartyDetails.firstPartyCarBodyInsuranceDetail.source"] =
carBodyInquiry.source; carBodyInquiry.source;
if (inquirySubmission && !inquirySubmission.participants.legacy) {
setFields[`${partyDetails}.policyInquiry`] = {
thirdParty: policyInquiry,
carBody: {
source: carBodyInquiry.source,
plateKind: carBodyInquiry.plateKind,
attempts: carBodyInquiry.attempts,
raw: carBodyInquiry.raw,
mapped: carBodyInquiry.mapped,
},
};
}
setFields[`${partyDetails}.${partyType}Client.clientId`] = setFields[`${partyDetails}.${partyType}Client.clientId`] =
await this.resolveCarBodyPolicyClientId(carBodyInfo); await this.resolveCarBodyPolicyClientId(carBodyInfo);
} }
@@ -3456,6 +3541,13 @@ export class RequestManagementService {
(request.nextStep === StepsEnum.F_addPlate || (request.nextStep === StepsEnum.F_addPlate ||
request.currentStep === StepsEnum.F_addPlate) && request.currentStep === StepsEnum.F_addPlate) &&
request.firstPartyDetails.firstPartyPhoneNumber === user.username; request.firstPartyDetails.firstPartyPhoneNumber === user.username;
const partyRole = isFirstParty ? PartyRole.FIRST : PartyRole.SECOND;
const inquirySubmission = this.normalizeInquiryInput(
request.type as BlameRequestType,
body,
partyRole,
);
body = inquirySubmission.dto as unknown as AddPlateDto;
if (!isFirstParty) { if (!isFirstParty) {
const firstPartyDetails = request.firstPartyDetails as any; const firstPartyDetails = request.firstPartyDetails as any;
@@ -3482,10 +3574,23 @@ export class RequestManagementService {
// TODO: Activate // TODO: Activate
// --- Proceed with existing logic if the check passes --- // --- Proceed with existing logic if the check passes ---
// 1) Main third-party/block inquiry (existing behavior) // 1) Main third-party/block inquiry (existing behavior)
const sanHubResponse = await this.sandHubService.getSandHubResponse({ const policyInquiry = inquirySubmission.participants.legacy
? await (async () => {
const response = await this.sandHubService.getSandHubResponse({
plate: body.plate, plate: body.plate,
nationalCodeOfInsurer: body.nationalCodeOfInsurer, nationalCodeOfInsurer: body.nationalCodeOfInsurer,
}); });
return {
raw: response,
mapped: response["_doc"] || response,
plateKind: "CURRENT",
attempts: [{ plateKind: "CURRENT", succeeded: true, usable: true }],
};
})()
: await this.getThirdPartyPlateInquiry(inquirySubmission);
const sanHubResponse = policyInquiry.mapped;
const participantInquiries =
await this.collectLegacyParticipantInquiries(inquirySubmission);
// if (sanHubResponse.Error) { // if (sanHubResponse.Error) {
// throw new HttpException( // throw new HttpException(
@@ -3520,6 +3625,9 @@ export class RequestManagementService {
user, user,
body, body,
isFirstParty ? "firstParty" : "secondParty", isFirstParty ? "firstParty" : "secondParty",
inquirySubmission,
policyInquiry,
participantInquiries,
); );
} }
@@ -6292,7 +6400,7 @@ export class RequestManagementService {
return none; return none;
} }
/** Party payload for user blame detail: no national codes, licenses, phone, birthdays, or vehicle inquiry blobs. */ /** Party payload for user blame detail: role links are visible, but participant PII and inquiry blobs remain private. */
private async sanitizePartyForBlameUserView( private async sanitizePartyForBlameUserView(
party: any, party: any,
): Promise<Record<string, unknown>> { ): Promise<Record<string, unknown>> {
@@ -6311,12 +6419,22 @@ export class RequestManagementService {
const vehicle = party.vehicle const vehicle = party.vehicle
? { ? {
plateId: party.vehicle.plateId, plateId: party.vehicle.plateId,
previousPlateId: party.vehicle.previousPlateId,
registrationState: party.vehicle.registrationState,
name: party.vehicle.name, name: party.vehicle.name,
model: party.vehicle.model, model: party.vehicle.model,
type: party.vehicle.type, type: party.vehicle.type,
isNew: party.vehicle.isNew, isNew: party.vehicle.isNew,
} }
: undefined; : undefined;
const participants = Array.isArray(party.participants)
? party.participants.map((participant: any) => ({
participantId: participant.participantId,
fullName: participant.fullName,
hasDrivingLicense: participant.hasDrivingLicense,
unknown: participant.unknown,
}))
: undefined;
const evidenceRaw = party.evidence as Record<string, unknown> | undefined; const evidenceRaw = party.evidence as Record<string, unknown> | undefined;
const evidence: Record<string, unknown> | undefined = evidenceRaw const evidence: Record<string, unknown> | undefined = evidenceRaw
? { ...evidenceRaw } ? { ...evidenceRaw }
@@ -6350,6 +6468,8 @@ export class RequestManagementService {
return { return {
role: party.role, role: party.role,
participants,
participantRoles: party.participantRoles,
person: safePerson, person: safePerson,
carBodyFirstForm: party.carBodyFirstForm, carBodyFirstForm: party.carBodyFirstForm,
location: party.location, location: party.location,
@@ -6689,10 +6809,26 @@ export class RequestManagementService {
); );
} }
const carBodyInquiry = await this.getCarBodyPlateInquiry(inquirySubmission); let carBodyInquiry: any;
try {
carBodyInquiry = await this.getCarBodyPlateInquiry(inquirySubmission);
} catch (error: any) {
this.recordPartyCaseInquiryStatus(
req,
"carBody",
PartyRole.FIRST,
false,
{ attempts: error?.attempts ?? [] },
error,
);
await (req as any).save();
this.throwCarBodyInquiryFailure(error);
}
const carBodyInfo = carBodyInquiry.mapped as any; const carBodyInfo = carBodyInquiry.mapped as any;
this.recordPartyCaseInquiryStatus(req, "carBody", PartyRole.FIRST, true, { this.recordPartyCaseInquiryStatus(req, "carBody", PartyRole.FIRST, true, {
source: carBodyInquiry.source, source: carBodyInquiry.source,
plateKind: carBodyInquiry.plateKind,
attempts: carBodyInquiry.attempts,
raw: carBodyInquiry.raw, raw: carBodyInquiry.raw,
mapped: carBodyInfo, mapped: carBodyInfo,
}); });
@@ -7669,13 +7805,30 @@ export class RequestManagementService {
stepsToAdd.push(StepsEnum.S_addDescription); stepsToAdd.push(StepsEnum.S_addDescription);
// Process first party plate with SandHub lookup // Process first party plate with SandHub lookup
const firstPartyPlate = formData.firstPartyPlate; const firstInquirySubmission = this.normalizeInquiryInput(
BlameRequestType.THIRD_PARTY,
formData.firstPartyPlate,
PartyRole.FIRST,
);
const firstPartyPlate = firstInquirySubmission.dto;
try { try {
const sandHubResponse = await this.sandHubService.getSandHubResponse({ const firstPolicyInquiry = firstInquirySubmission.participants.legacy
? await (async () => {
const response = await this.sandHubService.getSandHubResponse({
plate: firstPartyPlate.plate, plate: firstPartyPlate.plate,
nationalCodeOfInsurer: firstPartyPlate.nationalCodeOfInsurer, nationalCodeOfInsurer: firstPartyPlate.nationalCodeOfInsurer,
}); });
const sandHubReport = sandHubResponse["_doc"] || sandHubResponse; return {
raw: response,
mapped: response["_doc"] || response,
plateKind: "CURRENT",
attempts: [
{ plateKind: "CURRENT", succeeded: true, usable: true },
],
};
})()
: await this.getThirdPartyPlateInquiry(firstInquirySubmission);
const sandHubReport = firstPolicyInquiry.mapped;
this.sandHubService.assertInsuranceMatchesDeployment( this.sandHubService.assertInsuranceMatchesDeployment(
sandHubReport, sandHubReport,
"THIRD_PARTY", "THIRD_PARTY",
@@ -7727,6 +7880,26 @@ export class RequestManagementService {
startDate: sandHubReport?.IssueDate || sandHubReport?.StartDate, startDate: sandHubReport?.IssueDate || sandHubReport?.StartDate,
endDate: sandHubReport?.EndDate, endDate: sandHubReport?.EndDate,
}; };
if (!firstInquirySubmission.participants.legacy) {
firstPartyDetails.participants =
firstInquirySubmission.participants.participants;
firstPartyDetails.participantRoles =
firstInquirySubmission.participants.roles;
firstPartyDetails.participantInquiries =
await this.collectLegacyParticipantInquiries(
firstInquirySubmission,
);
firstPartyDetails.policyInquiry = firstPolicyInquiry;
firstPartyDetails.registrationState =
firstInquirySubmission.vehicle?.registrationState;
firstPartyDetails.previousPlateId = firstInquirySubmission.vehicle
?.previousPlate
? this.plateToPlateIdString(
firstInquirySubmission.vehicle.previousPlate,
)
: undefined;
firstPartyDetails.vehicleVin = firstInquirySubmission.vehicle?.vin;
}
// Update the firstPartyDetails in the payload with all plate data // Update the firstPartyDetails in the payload with all plate data
updatePayload.$set.firstPartyDetails = firstPartyDetails; updatePayload.$set.firstPartyDetails = firstPartyDetails;
@@ -7742,26 +7915,47 @@ export class RequestManagementService {
// Process second party plate // Process second party plate
try { try {
const secondPartyPlate = formData.secondParty.plate; const secondInquirySubmission = this.normalizeInquiryInput(
const sandHubResponse = await this.sandHubService.getSandHubResponse({ BlameRequestType.THIRD_PARTY,
formData.secondParty.plate,
PartyRole.SECOND,
);
const secondPartyPlate = secondInquirySubmission.dto;
const secondPolicyInquiry = secondInquirySubmission.participants.legacy
? await (async () => {
const response = await this.sandHubService.getSandHubResponse({
plate: secondPartyPlate.plate, plate: secondPartyPlate.plate,
nationalCodeOfInsurer: secondPartyPlate.nationalCodeOfInsurer, nationalCodeOfInsurer: secondPartyPlate.nationalCodeOfInsurer,
}); });
const sandHubReport = sandHubResponse["_doc"] || sandHubResponse; return {
raw: response,
mapped: response["_doc"] || response,
plateKind: "CURRENT",
attempts: [
{ plateKind: "CURRENT", succeeded: true, usable: true },
],
};
})()
: await this.getThirdPartyPlateInquiry(secondInquirySubmission);
const sandHubReport = secondPolicyInquiry.mapped;
const clientName = const clientName =
sandHubReport?.CompanyName || sandHubReport?.LastCompanyName; sandHubReport?.CompanyName || sandHubReport?.LastCompanyName;
const companyCode = sandHubReport?.CompanyCode; const companyCode = sandHubReport?.CompanyCode;
// Try to find client by company code first (more reliable) // Try to find client by company code first (more reliable)
const client = companyCode const policyholderUnknown =
secondInquirySubmission.thirdPartyPolicyholder.unknown === true;
const client = clientName
? companyCode
? await this.clientService.findOrCreateClientByCompanyCode( ? await this.clientService.findOrCreateClientByCompanyCode(
companyCode, companyCode,
clientName, clientName,
) )
: await this.clientService.findOne({ clientName: clientName }); : await this.clientService.findOne({ clientName: clientName })
: null;
if (!client) { if (!client && !policyholderUnknown) {
throw new NotFoundException( throw new NotFoundException(
`Client not found for company: ${clientName}${companyCode ? ` (code: ${companyCode})` : ""}`, `Client not found for company: ${clientName}${companyCode ? ` (code: ${companyCode})` : ""}`,
); );
@@ -7778,10 +7972,12 @@ export class RequestManagementService {
carType: `${sandHubReport?.UsageField || ""} / ${sandHubReport?.MapUsageName || "-"}`, carType: `${sandHubReport?.UsageField || ""} / ${sandHubReport?.MapUsageName || "-"}`,
isNewCar: secondPartyPlate.isNewCar, isNewCar: secondPartyPlate.isNewCar,
}; };
if (client) {
secondPartyDetails.secondPartyClient = { secondPartyDetails.secondPartyClient = {
clientId: (client as any)["_doc"]?._id || (client as any)._id, clientId: (client as any)["_doc"]?._id || (client as any)._id,
clientName: clientName, clientName: clientName,
}; };
}
secondPartyDetails.secondPartyCertificate = secondPartyDetails.secondPartyCertificate =
secondPartyPlate.userNoCertificate; secondPartyPlate.userNoCertificate;
secondPartyDetails.secondPartyInsuranceDetail = { secondPartyDetails.secondPartyInsuranceDetail = {
@@ -7795,6 +7991,26 @@ export class RequestManagementService {
startDate: sandHubReport?.IssueDate || sandHubReport?.StartDate, startDate: sandHubReport?.IssueDate || sandHubReport?.StartDate,
endDate: sandHubReport?.EndDate, endDate: sandHubReport?.EndDate,
}; };
if (!secondInquirySubmission.participants.legacy) {
secondPartyDetails.participants =
secondInquirySubmission.participants.participants;
secondPartyDetails.participantRoles =
secondInquirySubmission.participants.roles;
secondPartyDetails.participantInquiries =
await this.collectLegacyParticipantInquiries(
secondInquirySubmission,
);
secondPartyDetails.policyInquiry = secondPolicyInquiry;
secondPartyDetails.registrationState =
secondInquirySubmission.vehicle?.registrationState;
secondPartyDetails.previousPlateId = secondInquirySubmission.vehicle
?.previousPlate
? this.plateToPlateIdString(
secondInquirySubmission.vehicle.previousPlate,
)
: undefined;
secondPartyDetails.vehicleVin = secondInquirySubmission.vehicle?.vin;
}
// Update the secondPartyDetails in the payload // Update the secondPartyDetails in the payload
updatePayload.$set.secondPartyDetails = secondPartyDetails; updatePayload.$set.secondPartyDetails = secondPartyDetails;
@@ -8014,13 +8230,31 @@ export class RequestManagementService {
stepsToAdd.push(StepsEnum.F_addDescription); stepsToAdd.push(StepsEnum.F_addDescription);
// Process first party plate with SandHub lookup // Process first party plate with SandHub lookup
const firstPartyPlate = formData.firstPartyPlate; const firstInquirySubmission = this.normalizeInquiryInput(
BlameRequestType.CAR_BODY,
formData.firstPartyPlate,
PartyRole.FIRST,
);
const firstPartyPlate = firstInquirySubmission.dto;
try { try {
const sandHubResponse = await this.sandHubService.getSandHubResponse({ const thirdPartyPolicyInquiry = firstInquirySubmission.participants
.legacy
? await (async () => {
const response = await this.sandHubService.getSandHubResponse({
plate: firstPartyPlate.plate, plate: firstPartyPlate.plate,
nationalCodeOfInsurer: firstPartyPlate.nationalCodeOfInsurer, nationalCodeOfInsurer: firstPartyPlate.nationalCodeOfInsurer,
}); });
const sandHubReport = sandHubResponse["_doc"] || sandHubResponse; return {
raw: response,
mapped: response["_doc"] || response,
plateKind: "CURRENT",
attempts: [
{ plateKind: "CURRENT", succeeded: true, usable: true },
],
};
})()
: await this.getThirdPartyPlateInquiry(firstInquirySubmission);
const sandHubReport = thirdPartyPolicyInquiry.mapped;
const clientName = const clientName =
sandHubReport?.CompanyName || sandHubReport?.LastCompanyName; sandHubReport?.CompanyName || sandHubReport?.LastCompanyName;
@@ -8070,10 +8304,12 @@ export class RequestManagementService {
}; };
// CAR_BODY specific insurance info // CAR_BODY specific insurance info
const carBodyInquiry = await this.sandHubService.getCarBodyInquiry({ const carBodyInquiry = firstInquirySubmission.participants.legacy
? await this.sandHubService.getCarBodyInquiry({
nationalCodeOfInsurer: firstPartyPlate.nationalCodeOfInsurer, nationalCodeOfInsurer: firstPartyPlate.nationalCodeOfInsurer,
plate: firstPartyPlate.plate, plate: firstPartyPlate.plate,
} as any); } as any)
: await this.getCarBodyPlateInquiry(firstInquirySubmission);
const carBodyInfo = carBodyInquiry.mapped as any; const carBodyInfo = carBodyInquiry.mapped as any;
firstPartyDetails.firstPartyCarBodyInsuranceDetail = { firstPartyDetails.firstPartyCarBodyInsuranceDetail = {
@@ -8085,6 +8321,35 @@ export class RequestManagementService {
raw: carBodyInquiry.raw, raw: carBodyInquiry.raw,
source: carBodyInquiry.source, source: carBodyInquiry.source,
}; };
if (!firstInquirySubmission.participants.legacy) {
firstPartyDetails.participants =
firstInquirySubmission.participants.participants;
firstPartyDetails.participantRoles =
firstInquirySubmission.participants.roles;
firstPartyDetails.participantInquiries =
await this.collectLegacyParticipantInquiries(
firstInquirySubmission,
);
firstPartyDetails.policyInquiry = {
thirdParty: thirdPartyPolicyInquiry,
carBody: {
source: carBodyInquiry.source,
plateKind: carBodyInquiry.plateKind,
attempts: carBodyInquiry.attempts,
raw: carBodyInquiry.raw,
mapped: carBodyInquiry.mapped,
},
};
firstPartyDetails.registrationState =
firstInquirySubmission.vehicle?.registrationState;
firstPartyDetails.previousPlateId = firstInquirySubmission.vehicle
?.previousPlate
? this.plateToPlateIdString(
firstInquirySubmission.vehicle.previousPlate,
)
: undefined;
firstPartyDetails.vehicleVin = firstInquirySubmission.vehicle?.vin;
}
firstPartyDetails.firstPartyClient.clientId = firstPartyDetails.firstPartyClient.clientId =
await this.resolveCarBodyPolicyClientId(carBodyInfo); await this.resolveCarBodyPolicyClientId(carBodyInfo);