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>
This commit is contained in:
2026-09-26 16:42:21 +03:30
parent 4f8fe04380
commit fc1c117b93
9 changed files with 590 additions and 44 deletions

View File

@@ -262,7 +262,16 @@ import {
pickPersonBirthday,
pickPersonNationalCode,
selectFanavaranDriverId,
selectFanavaranOwnerId,
} from "./fanavaran-driver-inquiry";
import {
resolveDriverFromParty,
resolveDriverIsOwner,
resolveFanavaranLicenceIssuDate,
resolveFanavaranLicenceNo,
resolveOwnerFromParty,
resolvePolicyholderFromParty,
} from "./fanavaran-party-roles";
import {
buildFanavaranOtherPeoplePayload,
pickFanavaranRecordId,
@@ -3676,23 +3685,23 @@ export class ClaimRequestManagementService {
/**
* Resolve licence number to send to Fanavaran.
* Preference order:
* - use the first non-empty value from DB (driverLicense/insurerLicense)
* - else use the configured 10-digit dummy
* - driver participant / person.driverLicense (and insurerLicense as legacy)
* - else the configured 10-digit dummy
*/
private resolveFanavaranLicenceNoFromParty(input: {
driverIsInsurer?: boolean;
driverLicense?: unknown;
insurerLicense?: unknown;
/** Optional already-resolved digits from participants. */
resolvedLicenseNo?: string | null;
}): string {
const { driverIsInsurer, driverLicense, insurerLicense } = input;
const fromResolved = this.extractNonEmptyLicenceDigits(
input.resolvedLicenseNo,
);
if (fromResolved) return fromResolved;
// When driverIsInsurer === true, UI usually mirrors insurer/driver values,
// but DB can still miss one of the two fields — prefer both.
const candidates = driverIsInsurer
? [driverLicense, insurerLicense]
: [driverLicense, insurerLicense];
for (const c of candidates) {
const { driverLicense, insurerLicense } = input;
for (const c of [driverLicense, insurerLicense]) {
const digits = this.extractNonEmptyLicenceDigits(c);
if (digits) return digits;
}
@@ -4298,6 +4307,36 @@ export class ClaimRequestManagementService {
}
}
/** Best-effort OwnerId from parties inquiry (no GEN.44 create). */
private async resolveOwnerFanavaranId(
clientKey: FanavaranClientKey,
nationalCode: string | null | undefined,
birthday: string | number | null | undefined,
): Promise<number | null> {
if (!nationalCode) return null;
const parsed = this.parseJalaliBirthday(birthday);
if (!parsed) return null;
try {
const response = await this.fanavaranLookupService.inquiryByUniqueIdentifier(
clientKey,
{
nationalCode,
birthYear: parsed.year,
birthMonth: parsed.month,
birthDay: parsed.day,
},
);
return selectFanavaranOwnerId(response);
} catch (error) {
this.logger.warn(
`[resolveOwnerFanavaranId] inquiry failed for nationalCode=${nationalCode}: ${
error instanceof Error ? error.message : error
}`,
);
return null;
}
}
/** GEN.03 hull: TotalPremium + parties inquiry (AccidentCulpritId) before hull field whitelist. */
private async enrichCarBodyHullBaseClaimPayload(input: {
clientKey: FanavaranClientKey;
@@ -4357,13 +4396,14 @@ export class ClaimRequestManagementService {
}
| undefined;
const driverIdentity = resolveDriverFromParty(input.firstParty as any);
const nationalCode =
driverIdentity.nationalCode ??
pickPersonNationalCode(person) ??
pickCarBodyPolicyNationalCode(
input.blameCase as { parties?: unknown[] },
);
const birthday = pickPersonBirthday(person);
const driverIsInsurer = person?.driverIsInsurer ?? true;
const birthday = driverIdentity.birthday ?? pickPersonBirthday(person);
let accidentCulpritId: number | null = null;
if (nationalCode && birthday) {
@@ -4371,7 +4411,7 @@ export class ClaimRequestManagementService {
input.clientKey,
nationalCode,
birthday,
driverIsInsurer,
false,
);
}
if (accidentCulpritId == null) {
@@ -4386,6 +4426,13 @@ export class ClaimRequestManagementService {
input.payload.AccidentCulpritId = accidentCulpritId;
}
input.payload.CulpritLicenceNo = resolveFanavaranLicenceNo(
driverIdentity,
FANAVARAN_DUMMY_LICENCE_NO,
);
input.payload.CulpritLicenceIssuDate =
resolveFanavaranLicenceIssuDate(driverIdentity);
input.payload.CustomerFaultPercent =
FANAVARAN_DEFAULT_HULL_CUSTOMER_FAULT_PERCENT;
@@ -4619,6 +4666,9 @@ export class ClaimRequestManagementService {
const person = damagedParty?.person ?? {};
const vehicle = damagedParty?.vehicle ?? {};
const insurance = damagedParty?.insurance ?? {};
const driverIdentity = resolveDriverFromParty(damagedParty);
const ownerIdentity = resolveOwnerFromParty(damagedParty);
const driverIsOwner = resolveDriverIsOwner(damagedParty);
const product = fanavaranClaimProductFromBlameType(input.blameCase?.type);
const { mapped: inquiryMapped, raw: inquiryRawPayload } =
fanavaranPartyInquirySources(product, damagedParty);
@@ -4630,12 +4680,14 @@ export class ClaimRequestManagementService {
this.logger.log(
`[buildFanavaranDamageCasePayload] damagedParty resolved: userId=${damagedParty?.person?.userId ?? "NONE"}, ` +
`nationalCode=${pickPersonNationalCode(person) ?? "MISSING"}, ` +
`driverNationalCode=${driverIdentity.nationalCode ?? "MISSING"}, ` +
`ownerNationalCode=${ownerIdentity.nationalCode ?? "MISSING"}, ` +
`nationalCodeOfDriver=${person.nationalCodeOfDriver ?? "MISSING"}, ` +
`nationalCodeOfInsurer=${person.nationalCodeOfInsurer ?? "MISSING"}, ` +
`driverBirthday=${pickPersonBirthday(person) ?? "MISSING"}, ` +
`driverIsInsurer=${person.driverIsInsurer ?? "MISSING"}, ` +
`driverLicense=${person.driverLicense ?? "MISSING"}`,
`driverBirthday=${driverIdentity.birthday ?? "MISSING"}, ` +
`driverIsOwner=${driverIsOwner}, ` +
`driverLicense=${driverIdentity.licenseNo ?? person.driverLicense ?? "MISSING"}, ` +
`driverLicenseDate=${driverIdentity.licenseIssuDate ?? "MISSING"}`,
);
if (!damagedParty) {
this.logger.warn(
@@ -4761,12 +4813,23 @@ export class ClaimRequestManagementService {
if (vehicleKindId != null) vehicleKindSource = "car-type-lookup";
}
let driverFanavaranId = await this.resolveDriverFanavaranIdFromBlameCase(
input.clientKey,
input.blameCase?.parties ?? [],
person,
input.claimCase,
);
let driverFanavaranId: number | null = null;
if (driverIdentity.nationalCode && driverIdentity.birthday) {
driverFanavaranId = await this.resolveDriverFanavaranId(
input.clientKey,
driverIdentity.nationalCode,
driverIdentity.birthday,
false,
);
}
if (driverFanavaranId == null) {
driverFanavaranId = await this.resolveDriverFanavaranIdFromBlameCase(
input.clientKey,
input.blameCase?.parties ?? [],
person,
input.claimCase,
);
}
let otherPersonId = this.firstPositiveFanavaranId(cachedDamage.otherPersonId);
if (driverFanavaranId) {
@@ -4781,7 +4844,13 @@ export class ClaimRequestManagementService {
} else if (input.registerMissingPerson) {
const registered = await this.registerFanavaranOtherPerson({
clientKey: input.clientKey,
person,
person: {
...person,
nationalCodeOfDriver:
driverIdentity.nationalCode ?? person.nationalCodeOfDriver,
driverBirthday: driverIdentity.birthday ?? person.driverBirthday,
driverIsInsurer: false,
},
claimCaseId: input.claimCase?._id
? String(input.claimCase._id)
: undefined,
@@ -4792,6 +4861,21 @@ export class ClaimRequestManagementService {
}
}
let ownerFanavaranId: number | null = null;
if (
ownerIdentity.nationalCode &&
ownerIdentity.nationalCode === driverIdentity.nationalCode &&
driverFanavaranId != null
) {
ownerFanavaranId = driverFanavaranId;
} else if (ownerIdentity.nationalCode && ownerIdentity.birthday) {
ownerFanavaranId = await this.resolveOwnerFanavaranId(
input.clientKey,
ownerIdentity.nationalCode,
ownerIdentity.birthday,
);
}
if (driverFanavaranId && input.blameCase?._id && damagedParty) {
const partyIndex = (input.blameCase.parties ?? []).findIndex(
(p: any) => p === damagedParty ||
@@ -4844,7 +4928,7 @@ export class ClaimRequestManagementService {
}
this.logger.log(
`[buildFanavaranDamageCasePayload] Final DriverId=${driverFanavaranId ?? "NULL"} for nationalCode=${pickPersonNationalCode(person) ?? "MISSING"}, ` +
`[buildFanavaranDamageCasePayload] Final DriverId=${driverFanavaranId ?? "NULL"} OwnerId=${ownerFanavaranId ?? "NULL"} for driverNationalCode=${driverIdentity.nationalCode ?? "MISSING"}, ` +
`PolicyNo=${policyNo ?? "NULL"}, PolicyCINumber=${policyCINumber ?? "NULL"}`,
);
@@ -4866,16 +4950,17 @@ export class ClaimRequestManagementService {
EstimateAmount: FANAVARAN_PROVISIONAL_ESTIMATE_AMOUNT,
FaultPercent: input.defaults.FaultPercent,
InsuranceCorpId: insuranceCorpId,
DriverIsOwner: person.driverIsInsurer ? 1 : input.defaults.DriverIsOwner,
DriverIsOwner: driverIsOwner ? 1 : 0,
LicenceCityId: null,
LicenceCountryId: null,
LicenceForeignCityName: null,
LicenceIssuDate: person.driverBirthday ?? "1394/10/13",
LicenceIssuDate: resolveFanavaranLicenceIssuDate(driverIdentity),
// Must never be empty/null; prefer stored DB value, else use dummy.
LicenceNo: this.resolveFanavaranLicenceNoFromParty({
driverIsInsurer: person.driverIsInsurer,
driverLicense: person.driverLicense,
insurerLicense: person.insurerLicense,
resolvedLicenseNo: driverIdentity.licenseNo,
}),
LicenceTypeId: input.defaults.CulpritLicenceTypeId,
MotorNo:
@@ -4884,7 +4969,7 @@ export class ClaimRequestManagementService {
"EngineNumberField",
"mtrnum",
]) ?? (typeof apiVehicle?.MotorNo === "string" ? apiVehicle.MotorNo : null),
OwnerId: null,
OwnerId: ownerFanavaranId,
PlaqueCityId: apiPlaque.cityId,
PlaqueKindId: plaqueKindId,
PlaqueLeftNo:
@@ -5819,6 +5904,8 @@ export class ClaimRequestManagementService {
parties: Array<{
role?: PartyRole;
person?: { userId?: Types.ObjectId; nationalCodeOfInsurer?: string };
participants?: Array<Record<string, any>>;
participantRoles?: Record<string, string | undefined>;
statement?: { admitsGuilt?: boolean };
}>,
guiltyPartyId?: Types.ObjectId | string | null,
@@ -5830,8 +5917,13 @@ export class ClaimRequestManagementService {
const guiltyParty = parties.find(
(party) => party.person?.userId?.toString() === guiltyPartyId.toString(),
);
if (!guiltyParty) return null;
return guiltyParty?.person?.nationalCodeOfInsurer ?? null;
return (
resolvePolicyholderFromParty(guiltyParty, "third-party").nationalCode ??
guiltyParty.person?.nationalCodeOfInsurer ??
null
);
}
private resolveGuiltyPartyIdV2(
@@ -6110,6 +6202,31 @@ export class ClaimRequestManagementService {
},
});
// GEN.03 CulpritLicence*: guilty-party DRIVER for THIRD_PARTY, FIRST-party DRIVER for CAR_BODY.
{
const culpritParty =
blameCase.type === BlameRequestType.CAR_BODY
? firstParty
: (() => {
const guiltyPartyId = this.resolveGuiltyPartyIdV2(
blameCase.parties ?? [],
expertDecision?.guiltyPartyId,
);
return (blameCase.parties ?? []).find(
(party: any) =>
String(party?.person?.userId ?? "") ===
String(guiltyPartyId ?? ""),
);
})();
const culpritDriver = resolveDriverFromParty(culpritParty as any);
payload.CulpritLicenceNo = resolveFanavaranLicenceNo(
culpritDriver,
FANAVARAN_DUMMY_LICENCE_NO,
);
payload.CulpritLicenceIssuDate =
resolveFanavaranLicenceIssuDate(culpritDriver);
}
const carBodyAccidentTime = (
firstParty as { statement?: { accidentTime?: string } } | undefined
)?.statement?.accidentTime;