Files
yara724-api/src/claim-request-management/fanavaran-party-roles.ts
s.hajizadeh fc1c117b93 Map Fanavaran people and licences by party role; allow unlisted base-info lookups.
Resolve driver, owner, and policyholder from participants when they differ, prefer real licence / driverLicenseDate over dummies, and fall back unknown /lookups/fanavaran/{name} to car/base-info/{name} (including vehicle-groups).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-26 16:57:30 +03:30

218 lines
7.1 KiB
TypeScript

import { InquiryParticipantRole } from "src/common/dto/inquiry-participants.dto";
import { participantForStoredPartyRole } from "src/request-management/inquiry-participant-resolver";
import {
normalizeNationalCode,
parseJalaliDateParts,
} from "./fanavaran-driver-inquiry";
import type { FanavaranClaimProduct } from "./fanavaran-claim-product";
/** Fanavaran dummy licence issue date when the user did not supply one. */
export const FANAVARAN_DUMMY_LICENCE_ISSU_DATE = "1394/10/13";
export type FanavaranRoleIdentity = {
nationalCode: string | null;
birthday: string | number | null;
/** Digits-only licence number when known. */
licenseNo: string | null;
licenseType: string | null;
/**
* Jalali `YYYY/MM/DD` from `driverLicenseDate` when present.
* Not a persisted model field — read opportunistically from party/person/participant.
*/
licenseIssuDate: string | null;
};
type PartyLike = {
participants?: Array<Record<string, any>>;
participantRoles?: Record<string, string | undefined>;
person?: Record<string, any> | null;
};
function nonEmptyText(value: unknown): string | null {
if (value == null) return null;
const text = String(value).trim();
if (!text || text.toLowerCase() === "null" || text.toLowerCase() === "undefined") {
return null;
}
return text;
}
/** Digits-only licence number, or null when empty / non-digit. */
export function extractLicenceDigits(value: unknown): string | null {
const text = nonEmptyText(value);
if (!text) return null;
const digits = text.replace(/[^\d]/g, "");
return digits || null;
}
/**
* Normalize a stored `driverLicenseDate` (or similar) to Fanavaran Jalali `YYYY/MM/DD`.
* Returns null when missing / unparseable so callers can fall back to the dummy.
*/
export function formatFanavaranLicenceIssuDate(value: unknown): string | null {
const parsed = parseJalaliDateParts(value);
if (!parsed) return null;
const month = String(parsed.month).padStart(2, "0");
const day = String(parsed.day).padStart(2, "0");
return `${parsed.year}/${month}/${day}`;
}
function readDriverLicenseDate(
...sources: Array<Record<string, any> | null | undefined>
): string | null {
for (const source of sources) {
if (!source) continue;
const formatted = formatFanavaranLicenceIssuDate(
source.driverLicenseDate ?? source.licenseDate ?? source.licenceIssuDate,
);
if (formatted) return formatted;
}
return null;
}
function fromParticipant(
participant: Record<string, any> | undefined,
): FanavaranRoleIdentity {
if (!participant) {
return {
nationalCode: null,
birthday: null,
licenseNo: null,
licenseType: null,
licenseIssuDate: null,
};
}
return {
nationalCode: normalizeNationalCode(participant.nationalCode),
birthday: nonEmptyText(participant.birthday) ?? participant.birthday ?? null,
licenseNo: extractLicenceDigits(participant.licenseNumber),
licenseType: nonEmptyText(participant.licenseType),
licenseIssuDate: readDriverLicenseDate(participant),
};
}
/**
* Damaged/guilty party DRIVER: participants first, then legacy person.* fields.
* Licence number prefers real user values; callers fall back to the Fanavaran dummy.
*/
export function resolveDriverFromParty(party: PartyLike | null | undefined): FanavaranRoleIdentity {
const fromRole = fromParticipant(
participantForStoredPartyRole(
party ?? {},
InquiryParticipantRole.DRIVER,
) as Record<string, any> | undefined,
);
const person = party?.person ?? undefined;
return {
nationalCode:
fromRole.nationalCode ??
normalizeNationalCode(person?.nationalCodeOfDriver) ??
normalizeNationalCode(person?.nationalCode),
birthday:
(fromRole.birthday != null && String(fromRole.birthday).trim() !== ""
? fromRole.birthday
: null) ??
nonEmptyText(person?.driverBirthday) ??
nonEmptyText(person?.birthday) ??
person?.driverBirthday ??
person?.birthday ??
null,
licenseNo:
fromRole.licenseNo ??
extractLicenceDigits(person?.driverLicense) ??
extractLicenceDigits(person?.insurerLicense),
licenseType: fromRole.licenseType ?? nonEmptyText(person?.licenseType),
licenseIssuDate:
fromRole.licenseIssuDate ?? readDriverLicenseDate(person),
};
}
/** Vehicle owner for OwnerId / DriverIsOwner / Sheba national-code routing. */
export function resolveOwnerFromParty(party: PartyLike | null | undefined): FanavaranRoleIdentity {
const fromRole = fromParticipant(
participantForStoredPartyRole(
party ?? {},
InquiryParticipantRole.VEHICLE_OWNER,
) as Record<string, any> | undefined,
);
if (fromRole.nationalCode || fromRole.birthday) {
return fromRole;
}
// Legacy parties had no dedicated owner fields.
return {
nationalCode: null,
birthday: null,
licenseNo: null,
licenseType: null,
licenseIssuDate: null,
};
}
/**
* Policyholder national code for GEN.03 PolicyId inquiry:
* THIRD_PARTY → third-party policyholder; CAR_BODY → car-body policyholder.
*/
export function resolvePolicyholderFromParty(
party: PartyLike | null | undefined,
product: FanavaranClaimProduct,
): FanavaranRoleIdentity {
const role =
product === "car-body"
? InquiryParticipantRole.CAR_BODY_POLICYHOLDER
: InquiryParticipantRole.THIRD_PARTY_POLICYHOLDER;
const fromRole = fromParticipant(
participantForStoredPartyRole(party ?? {}, role) as
| Record<string, any>
| undefined,
);
const person = party?.person ?? undefined;
return {
nationalCode:
fromRole.nationalCode ??
normalizeNationalCode(person?.nationalCodeOfInsurer) ??
normalizeNationalCode(person?.nationalCode),
birthday:
(fromRole.birthday != null && String(fromRole.birthday).trim() !== ""
? fromRole.birthday
: null) ??
nonEmptyText(person?.insurerBirthday) ??
person?.insurerBirthday ??
null,
licenseNo:
fromRole.licenseNo ?? extractLicenceDigits(person?.insurerLicense),
licenseType: fromRole.licenseType ?? null,
licenseIssuDate: fromRole.licenseIssuDate,
};
}
/**
* GEN.12 `DriverIsOwner`: driver national code equals vehicle-owner national code.
* Falls back to legacy `person.driverIsInsurer` when owner role is absent.
*/
export function resolveDriverIsOwner(party: PartyLike | null | undefined): boolean {
const driver = resolveDriverFromParty(party);
const owner = resolveOwnerFromParty(party);
if (driver.nationalCode && owner.nationalCode) {
return driver.nationalCode === owner.nationalCode;
}
return party?.person?.driverIsInsurer === true;
}
/** Licence number for Fanavaran; never empty — uses `dummy` when user value is missing. */
export function resolveFanavaranLicenceNo(
identity: Pick<FanavaranRoleIdentity, "licenseNo">,
dummy: string,
): string {
return identity.licenseNo || dummy;
}
/** Licence issue date for Fanavaran; uses dummy when `driverLicenseDate` is absent. */
export function resolveFanavaranLicenceIssuDate(
identity: Pick<FanavaranRoleIdentity, "licenseIssuDate">,
dummy: string = FANAVARAN_DUMMY_LICENCE_ISSU_DATE,
): string {
return identity.licenseIssuDate || dummy;
}