forked from Yara724/api
updated the fucking mf fanavaran
This commit is contained in:
@@ -197,12 +197,17 @@ import {
|
|||||||
} from "./fanavaran-accident-vehicle-used";
|
} from "./fanavaran-accident-vehicle-used";
|
||||||
import {
|
import {
|
||||||
asPartyInquiryRows,
|
asPartyInquiryRows,
|
||||||
|
collectPersonBirthdays,
|
||||||
|
collectPersonNationalCodes,
|
||||||
|
parseJalaliDateParts,
|
||||||
pickPersonBirthday,
|
pickPersonBirthday,
|
||||||
pickPersonNationalCode,
|
pickPersonNationalCode,
|
||||||
selectFanavaranDriverId,
|
selectFanavaranDriverId,
|
||||||
} from "./fanavaran-driver-inquiry";
|
} from "./fanavaran-driver-inquiry";
|
||||||
import {
|
import {
|
||||||
asObjectRecord,
|
asObjectRecord,
|
||||||
|
collectPartyVinCandidates,
|
||||||
|
normalizeVin,
|
||||||
parseFanavaranId,
|
parseFanavaranId,
|
||||||
pickFanavaranPlaqueLookupFields,
|
pickFanavaranPlaqueLookupFields,
|
||||||
pickFanavaranVehicleFromInquiry,
|
pickFanavaranVehicleFromInquiry,
|
||||||
@@ -3934,6 +3939,41 @@ export class ClaimRequestManagementService {
|
|||||||
return pickFanavaranVehicleFromInquiry(inquired, vehicleId);
|
return pickFanavaranVehicleFromInquiry(inquired, vehicleId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Damaged cars often store chassis on vehicle.vin. Try every VIN-like id
|
||||||
|
* until Fanavaran returns PlaqueKindId (required on GEN.12).
|
||||||
|
*/
|
||||||
|
private async fetchFanavaranVehicleByVinCandidates(
|
||||||
|
clientKey: FanavaranClientKey,
|
||||||
|
vins: string[],
|
||||||
|
): Promise<Record<string, unknown> | null> {
|
||||||
|
let fallback: Record<string, unknown> | null = null;
|
||||||
|
for (const vin of vins) {
|
||||||
|
try {
|
||||||
|
const vehicle = await this.fetchFanavaranVehicleByVin(clientKey, vin);
|
||||||
|
if (!vehicle) {
|
||||||
|
this.logger.warn(
|
||||||
|
`[buildFanavaranDamageCasePayload] VIN inquiry for ${vin} returned no vehicle`,
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const plaqueKindId = pickFanavaranPlaqueLookupFields(vehicle).kindId;
|
||||||
|
this.logger.log(
|
||||||
|
`[buildFanavaranDamageCasePayload] VIN inquiry ${vin} VehicleId=${pickVehicleId(vehicle) ?? "none"} VehicleKindId=${parseFanavaranId(vehicle.VehicleKindId) ?? "none"} UsedId=${parseFanavaranId(vehicle.UsedId) ?? "none"} PlaqueKindId=${plaqueKindId ?? "none"}`,
|
||||||
|
);
|
||||||
|
if (plaqueKindId != null) return vehicle;
|
||||||
|
if (!fallback) fallback = vehicle;
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.warn(
|
||||||
|
`[buildFanavaranDamageCasePayload] VIN inquiry failed for ${vin}: ${
|
||||||
|
error instanceof Error ? error.message : error
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fallback when VIN is missing: policy GET → vehicle GET → UsedId/VehicleKindId.
|
* Fallback when VIN is missing: policy GET → vehicle GET → UsedId/VehicleKindId.
|
||||||
*/
|
*/
|
||||||
@@ -4016,43 +4056,9 @@ export class ClaimRequestManagementService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private parseJalaliBirthday(
|
private parseJalaliBirthday(
|
||||||
birthday: string | number | null | undefined,
|
birthday: string | number | Record<string, unknown> | null | undefined,
|
||||||
): { year: number; month: number; day: number } | null {
|
): { year: number; month: number; day: number } | null {
|
||||||
if (birthday == null) return null;
|
return parseJalaliDateParts(birthday);
|
||||||
const str = String(birthday).trim();
|
|
||||||
|
|
||||||
// Compact format: "13640610" → year=1364, month=06, day=10
|
|
||||||
if (/^\d{8}$/.test(str)) {
|
|
||||||
const year = parseInt(str.slice(0, 4), 10);
|
|
||||||
const month = parseInt(str.slice(4, 6), 10);
|
|
||||||
const day = parseInt(str.slice(6, 8), 10);
|
|
||||||
if (isNaN(year) || isNaN(month) || isNaN(day)) {
|
|
||||||
this.logger.warn(
|
|
||||||
`parseJalaliBirthday: failed to parse compact format "${str}" - year=${year}, month=${month}, day=${day}`,
|
|
||||||
);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return { year, month, day };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delimited formats: "1364/06/10" or "1364-06-10"
|
|
||||||
const parts = str.split(/[/\-]/);
|
|
||||||
if (parts.length < 3) {
|
|
||||||
this.logger.warn(
|
|
||||||
`parseJalaliBirthday: insufficient parts in "${str}" - got ${parts.length} parts`,
|
|
||||||
);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const year = parseInt(parts[0], 10);
|
|
||||||
const month = parseInt(parts[1], 10);
|
|
||||||
const day = parseInt(parts[2], 10);
|
|
||||||
if (isNaN(year) || isNaN(month) || isNaN(day)) {
|
|
||||||
this.logger.warn(
|
|
||||||
`parseJalaliBirthday: failed to parse delimited format "${str}" - year=${year}, month=${month}, day=${day}`,
|
|
||||||
);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return { year, month, day };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async resolveDriverFanavaranId(
|
private async resolveDriverFanavaranId(
|
||||||
@@ -4213,38 +4219,72 @@ export class ClaimRequestManagementService {
|
|||||||
]);
|
]);
|
||||||
const builtYearFromInquiry = builtYearRaw ? Number(builtYearRaw) || null : null;
|
const builtYearFromInquiry = builtYearRaw ? Number(builtYearRaw) || null : null;
|
||||||
|
|
||||||
const damagedVin =
|
const damagedVinCandidates: string[] = [];
|
||||||
pickVinFromPartyVehicle(damagedParty) ??
|
const seenVins = new Set<string>();
|
||||||
|
for (const value of [
|
||||||
|
...collectPartyVinCandidates(damagedParty),
|
||||||
this.pickPartyInquiryField(inquiryMapped, inquiryRaw, [
|
this.pickPartyInquiryField(inquiryMapped, inquiryRaw, [
|
||||||
"VIN",
|
"VIN",
|
||||||
"vin",
|
"vin",
|
||||||
"VinNumberField",
|
"VinNumberField",
|
||||||
]);
|
]),
|
||||||
let apiVehicle: Record<string, unknown> | null = null;
|
this.pickPartyInquiryField(inquiryMapped, inquiryRaw, [
|
||||||
if (damagedVin) {
|
"ShsNum",
|
||||||
try {
|
"ChassisNumberField",
|
||||||
apiVehicle = await this.fetchFanavaranVehicleByVin(
|
"shsNam",
|
||||||
|
]),
|
||||||
|
]) {
|
||||||
|
const vin = normalizeVin(value);
|
||||||
|
if (!vin || seenVins.has(vin)) continue;
|
||||||
|
seenVins.add(vin);
|
||||||
|
damagedVinCandidates.push(vin);
|
||||||
|
}
|
||||||
|
const cachedDamage =
|
||||||
|
(input.claimCase as any)?.fanavaranSync?.damageCase ?? {};
|
||||||
|
const lastDamagePayload = asObjectRecord(cachedDamage.lastPayload);
|
||||||
|
|
||||||
|
this.logger.log(
|
||||||
|
`[buildFanavaranDamageCasePayload] VIN candidates=${damagedVinCandidates.join(",") || "none"}`,
|
||||||
|
);
|
||||||
|
const apiVehicle = await this.fetchFanavaranVehicleByVinCandidates(
|
||||||
input.clientKey,
|
input.clientKey,
|
||||||
damagedVin,
|
damagedVinCandidates,
|
||||||
);
|
);
|
||||||
} catch (error) {
|
|
||||||
this.logger.warn(
|
|
||||||
`[buildFanavaranDamageCasePayload] VIN inquiry failed for ${damagedVin}: ${
|
|
||||||
error instanceof Error ? error.message : error
|
|
||||||
}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const apiPlaque = pickFanavaranPlaqueLookupFields(apiVehicle);
|
const apiPlaque = pickFanavaranPlaqueLookupFields(apiVehicle);
|
||||||
|
const inquiryPlaque = pickFanavaranPlaqueLookupFields(
|
||||||
|
asObjectRecord(inquiryMapped) ?? asObjectRecord(inquiryRaw),
|
||||||
|
);
|
||||||
|
const plaqueKindId = this.firstPositiveFanavaranId(
|
||||||
|
apiPlaque.kindId,
|
||||||
|
inquiryPlaque.kindId,
|
||||||
|
cachedDamage.plaqueKindId,
|
||||||
|
lastDamagePayload?.PlaqueKindId,
|
||||||
|
);
|
||||||
|
const plaqueSampleId = this.firstPositiveFanavaranId(
|
||||||
|
apiPlaque.sampleId,
|
||||||
|
inquiryPlaque.sampleId,
|
||||||
|
cachedDamage.plaqueSampleId,
|
||||||
|
lastDamagePayload?.PlaqueSampleId,
|
||||||
|
);
|
||||||
|
if (apiPlaque.kindId == null && plaqueKindId != null) {
|
||||||
|
this.logger.log(
|
||||||
|
`[buildFanavaranDamageCasePayload] PlaqueKindId API miss; using DB value ${plaqueKindId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (apiPlaque.sampleId == null && plaqueSampleId != null) {
|
||||||
|
this.logger.log(
|
||||||
|
`[buildFanavaranDamageCasePayload] PlaqueSampleId API miss; using DB value ${plaqueSampleId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
const builtYear =
|
const builtYear =
|
||||||
parseFanavaranId(apiVehicle?.BuiltYear) ?? builtYearFromInquiry;
|
parseFanavaranId(apiVehicle?.BuiltYear) ?? builtYearFromInquiry;
|
||||||
|
|
||||||
const carType = input.claimCase?.vehicle?.carType as string | undefined;
|
const carType = input.claimCase?.vehicle?.carType as string | undefined;
|
||||||
const cachedDamage = (input.claimCase as any)?.fanavaranSync?.damageCase ?? {};
|
let vehicleKindId = this.firstPositiveFanavaranId(
|
||||||
let vehicleKindId = parseFanavaranId(apiVehicle?.VehicleKindId);
|
apiVehicle?.VehicleKindId,
|
||||||
if (vehicleKindId == null && cachedDamage.vehicleKindId != null) {
|
cachedDamage.vehicleKindId,
|
||||||
vehicleKindId = Number(cachedDamage.vehicleKindId);
|
lastDamagePayload?.VehicleKindId,
|
||||||
}
|
);
|
||||||
if (vehicleKindId == null) {
|
if (vehicleKindId == null) {
|
||||||
vehicleKindId = await this.resolveVehicleKindId(
|
vehicleKindId = await this.resolveVehicleKindId(
|
||||||
input.clientKey,
|
input.clientKey,
|
||||||
@@ -4252,20 +4292,23 @@ export class ClaimRequestManagementService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prefer claim-level cache, then party.person.fanavaranDriverId, then live inquiry
|
// Prefer claim-level cache, then party.person.fanavaranDriverId, then live inquiry, then last payload
|
||||||
let driverFanavaranId =
|
let driverFanavaranId = this.firstPositiveFanavaranId(
|
||||||
cachedDamage.driverId ?? person.fanavaranDriverId ?? null;
|
cachedDamage.driverId,
|
||||||
|
person.fanavaranDriverId,
|
||||||
|
lastDamagePayload?.DriverId,
|
||||||
|
);
|
||||||
|
|
||||||
if (driverFanavaranId) {
|
if (driverFanavaranId) {
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
`[buildFanavaranDamageCasePayload] Using CACHED DriverId=${driverFanavaranId}`,
|
`[buildFanavaranDamageCasePayload] Using CACHED DriverId=${driverFanavaranId}`,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
driverFanavaranId = await this.resolveDriverFanavaranId(
|
driverFanavaranId = await this.resolveDriverFanavaranIdFromBlameCase(
|
||||||
input.clientKey,
|
input.clientKey,
|
||||||
pickPersonNationalCode(person),
|
input.blameCase?.parties ?? [],
|
||||||
pickPersonBirthday(person),
|
person,
|
||||||
person.driverIsInsurer,
|
input.claimCase,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Persist the resolved ID back to the blame case party for future use
|
// Persist the resolved ID back to the blame case party for future use
|
||||||
@@ -4386,7 +4429,7 @@ export class ClaimRequestManagementService {
|
|||||||
]) ?? (typeof apiVehicle?.MotorNo === "string" ? apiVehicle.MotorNo : null),
|
]) ?? (typeof apiVehicle?.MotorNo === "string" ? apiVehicle.MotorNo : null),
|
||||||
OwnerId: null,
|
OwnerId: null,
|
||||||
PlaqueCityId: apiPlaque.cityId,
|
PlaqueCityId: apiPlaque.cityId,
|
||||||
PlaqueKindId: apiPlaque.kindId,
|
PlaqueKindId: plaqueKindId,
|
||||||
PlaqueLeftNo:
|
PlaqueLeftNo:
|
||||||
apiPlaque.leftNo ?? (plate ? String(plate.leftDigits) : null),
|
apiPlaque.leftNo ?? (plate ? String(plate.leftDigits) : null),
|
||||||
PlaqueMiddleCodeId:
|
PlaqueMiddleCodeId:
|
||||||
@@ -4398,7 +4441,7 @@ export class ClaimRequestManagementService {
|
|||||||
this.formatFanavaranPlateNo(plate),
|
this.formatFanavaranPlateNo(plate),
|
||||||
PlaqueRightNo:
|
PlaqueRightNo:
|
||||||
apiPlaque.rightNo ?? (plate ? String(plate.centerDigits) : null),
|
apiPlaque.rightNo ?? (plate ? String(plate.centerDigits) : null),
|
||||||
PlaqueSampleId: apiPlaque.sampleId,
|
PlaqueSampleId: plaqueSampleId,
|
||||||
PlaqueSerial: apiPlaque.serial ?? (plate ? String(plate.ir) : null),
|
PlaqueSerial: apiPlaque.serial ?? (plate ? String(plate.ir) : null),
|
||||||
PolicyNo: policyNo,
|
PolicyNo: policyNo,
|
||||||
PreviousPolicyEndDate: endDate ?? "",
|
PreviousPolicyEndDate: endDate ?? "",
|
||||||
@@ -4413,11 +4456,20 @@ export class ClaimRequestManagementService {
|
|||||||
PolicyCINumber: policyCINumber,
|
PolicyCINumber: policyCINumber,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const persistedPayload = this.keepPreviousPositiveIds(lastDamagePayload, payload, [
|
||||||
|
"DriverId",
|
||||||
|
"PlaqueKindId",
|
||||||
|
"PlaqueSampleId",
|
||||||
|
"VehicleKindId",
|
||||||
|
"AccidentVehicleUsedId",
|
||||||
|
"InsuranceCorpId",
|
||||||
|
]);
|
||||||
|
|
||||||
// Persist Fanavaran-sourced IDs + last payload for reuse on later preview/submit
|
// Persist Fanavaran-sourced IDs + last payload for reuse on later preview/submit
|
||||||
if (input.claimCase?._id) {
|
if (input.claimCase?._id) {
|
||||||
await this.claimCaseDbService.findByIdAndUpdate(String(input.claimCase._id), {
|
await this.claimCaseDbService.findByIdAndUpdate(String(input.claimCase._id), {
|
||||||
$set: {
|
$set: {
|
||||||
"fanavaranSync.damageCase.lastPayload": payload,
|
"fanavaranSync.damageCase.lastPayload": persistedPayload,
|
||||||
"fanavaranSync.damageCase.lastPayloadBuiltAt": new Date(),
|
"fanavaranSync.damageCase.lastPayloadBuiltAt": new Date(),
|
||||||
...(driverFanavaranId != null
|
...(driverFanavaranId != null
|
||||||
? { "fanavaranSync.damageCase.driverId": driverFanavaranId }
|
? { "fanavaranSync.damageCase.driverId": driverFanavaranId }
|
||||||
@@ -4425,11 +4477,11 @@ export class ClaimRequestManagementService {
|
|||||||
...(vehicleKindId != null
|
...(vehicleKindId != null
|
||||||
? { "fanavaranSync.damageCase.vehicleKindId": vehicleKindId }
|
? { "fanavaranSync.damageCase.vehicleKindId": vehicleKindId }
|
||||||
: {}),
|
: {}),
|
||||||
...(apiPlaque.kindId != null
|
...(plaqueKindId != null
|
||||||
? { "fanavaranSync.damageCase.plaqueKindId": apiPlaque.kindId }
|
? { "fanavaranSync.damageCase.plaqueKindId": plaqueKindId }
|
||||||
: {}),
|
: {}),
|
||||||
...(apiPlaque.sampleId != null
|
...(plaqueSampleId != null
|
||||||
? { "fanavaranSync.damageCase.plaqueSampleId": apiPlaque.sampleId }
|
? { "fanavaranSync.damageCase.plaqueSampleId": plaqueSampleId }
|
||||||
: {}),
|
: {}),
|
||||||
...(accidentVehicleUsedId != null
|
...(accidentVehicleUsedId != null
|
||||||
? {
|
? {
|
||||||
@@ -4450,7 +4502,7 @@ export class ClaimRequestManagementService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return payload;
|
return persistedPayload;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -4909,6 +4961,121 @@ export class ClaimRequestManagementService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private firstPositiveFanavaranId(...values: unknown[]): number | null {
|
||||||
|
for (const value of values) {
|
||||||
|
const id = parseFanavaranId(value);
|
||||||
|
if (id != null) return id;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private keepPreviousPositiveIds(
|
||||||
|
previous: Record<string, unknown> | null,
|
||||||
|
next: Record<string, unknown>,
|
||||||
|
keys: string[],
|
||||||
|
): Record<string, unknown> {
|
||||||
|
const merged = { ...previous, ...next };
|
||||||
|
for (const key of keys) {
|
||||||
|
if (this.firstPositiveFanavaranId(next[key]) != null) continue;
|
||||||
|
const previousId = this.firstPositiveFanavaranId(previous?.[key]);
|
||||||
|
if (previousId != null) {
|
||||||
|
merged[key] = previousId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return merged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async resolveDriverFanavaranIdFromPerson(
|
||||||
|
clientKey: FanavaranClientKey,
|
||||||
|
person: {
|
||||||
|
driverIsInsurer?: boolean;
|
||||||
|
nationalCodeOfDriver?: unknown;
|
||||||
|
nationalCodeOfInsurer?: unknown;
|
||||||
|
nationalCode?: unknown;
|
||||||
|
driverBirthday?: unknown;
|
||||||
|
birthday?: unknown;
|
||||||
|
insurerBirthday?: unknown;
|
||||||
|
} | null | undefined,
|
||||||
|
): Promise<number | null> {
|
||||||
|
const codes = collectPersonNationalCodes(person);
|
||||||
|
const birthdays = collectPersonBirthdays(person);
|
||||||
|
|
||||||
|
for (const nationalCode of codes) {
|
||||||
|
for (const birthday of birthdays) {
|
||||||
|
const driverId = await this.resolveDriverFanavaranId(
|
||||||
|
clientKey,
|
||||||
|
nationalCode,
|
||||||
|
birthday,
|
||||||
|
person?.driverIsInsurer,
|
||||||
|
);
|
||||||
|
if (driverId != null) return driverId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async resolveDriverFanavaranIdFromBlameCase(
|
||||||
|
clientKey: FanavaranClientKey,
|
||||||
|
parties: any[],
|
||||||
|
preferredPerson: any,
|
||||||
|
claimCase?: any,
|
||||||
|
): Promise<number | null> {
|
||||||
|
const people = [
|
||||||
|
preferredPerson,
|
||||||
|
...parties.map((party) => party?.person),
|
||||||
|
].filter(Boolean);
|
||||||
|
|
||||||
|
this.logger.log(
|
||||||
|
`[resolveDriverFanavaranIdFromBlameCase] trying ${people.length} people then leftover codes`,
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const person of people) {
|
||||||
|
const driverId = await this.resolveDriverFanavaranIdFromPerson(
|
||||||
|
clientKey,
|
||||||
|
person,
|
||||||
|
);
|
||||||
|
if (driverId != null) return driverId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const leftoverCodes = [
|
||||||
|
...(claimCase?.money?.nationalCodeOfInsurer
|
||||||
|
? collectPersonNationalCodes({
|
||||||
|
nationalCodeOfInsurer: claimCase.money.nationalCodeOfInsurer,
|
||||||
|
})
|
||||||
|
: []),
|
||||||
|
];
|
||||||
|
const leftoverBirthdays: Array<string | number> = [];
|
||||||
|
const seenBirthdays = new Set<string>();
|
||||||
|
for (const person of people) {
|
||||||
|
for (const birthday of collectPersonBirthdays(person)) {
|
||||||
|
const parsed = parseJalaliDateParts(birthday);
|
||||||
|
const key = parsed
|
||||||
|
? `${parsed.year}-${parsed.month}-${parsed.day}`
|
||||||
|
: String(birthday);
|
||||||
|
if (seenBirthdays.has(key)) continue;
|
||||||
|
seenBirthdays.add(key);
|
||||||
|
leftoverBirthdays.push(birthday);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.log(
|
||||||
|
`[resolveDriverFanavaranIdFromBlameCase] leftover codes=${leftoverCodes.join(",") || "none"} birthdays=${leftoverBirthdays.join(",") || "none"}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const nationalCode of leftoverCodes) {
|
||||||
|
for (const birthday of leftoverBirthdays) {
|
||||||
|
const driverId = await this.resolveDriverFanavaranId(
|
||||||
|
clientKey,
|
||||||
|
nationalCode,
|
||||||
|
birthday,
|
||||||
|
preferredPerson?.driverIsInsurer,
|
||||||
|
);
|
||||||
|
if (driverId != null) return driverId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* زیاندیده for GEN.12: claim owner when linked to a blame party,
|
* زیاندیده for GEN.12: claim owner when linked to a blame party,
|
||||||
* else the party that is not the guilty one.
|
* else the party that is not the guilty one.
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
asPartyInquiryRows,
|
asPartyInquiryRows,
|
||||||
|
collectPersonNationalCodes,
|
||||||
parseJalaliDateParts,
|
parseJalaliDateParts,
|
||||||
pickPersonBirthday,
|
pickPersonBirthday,
|
||||||
pickPersonNationalCode,
|
pickPersonNationalCode,
|
||||||
@@ -24,8 +25,17 @@ describe("selectFanavaranDriverId", () => {
|
|||||||
expect(selectFanavaranDriverId([{ Id: 20, RoleId: 166 }], true)).toBe(20);
|
expect(selectFanavaranDriverId([{ Id: 20, RoleId: 166 }], true)).toBe(20);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("unwraps a wrapped Fanavaran payload", () => {
|
it("picks 4773521 from a real Fanavaran unique-identifier response", () => {
|
||||||
expect(selectFanavaranDriverId({ value: rows }, true)).toBe(10);
|
expect(
|
||||||
|
selectFanavaranDriverId(
|
||||||
|
[
|
||||||
|
{ Id: 4773521, RoleId: 161 },
|
||||||
|
{ Id: 4773521, RoleId: 163 },
|
||||||
|
{ Id: 4773521, RoleId: 166 },
|
||||||
|
],
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
).toBe(4773521);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -51,11 +61,16 @@ describe("pickPersonNationalCode / birthday", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("asPartyInquiryRows", () => {
|
describe("asPartyInquiryRows", () => {
|
||||||
it("parses compact Jalali birthday 13480313", () => {
|
it("normalizes Persian digits and compact insurer birthday 13640628", () => {
|
||||||
expect(parseJalaliDateParts("13480313")).toEqual({
|
expect(
|
||||||
year: 1348,
|
collectPersonNationalCodes({
|
||||||
month: 3,
|
nationalCodeOfInsurer: "۰۰۸۰۰۸۶۵۱۹",
|
||||||
day: 13,
|
}),
|
||||||
|
).toEqual(["0080086519"]);
|
||||||
|
expect(parseJalaliDateParts(13640628)).toEqual({
|
||||||
|
year: 1364,
|
||||||
|
month: 6,
|
||||||
|
day: 28,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
|
import { toEnglishDigits } from "src/lookups/fanavaran-last-car-policy";
|
||||||
|
|
||||||
export const FANAVARAN_INSURER_ROLE_ID = 161;
|
export const FANAVARAN_INSURER_ROLE_ID = 161;
|
||||||
|
export const FANAVARAN_PLAQUE_OWNER_ROLE_ID = 163;
|
||||||
export const FANAVARAN_DRIVER_ROLE_ID = 166;
|
export const FANAVARAN_DRIVER_ROLE_ID = 166;
|
||||||
|
|
||||||
export type FanavaranPartyInquiryRow = {
|
export type FanavaranPartyInquiryRow = {
|
||||||
@@ -42,61 +45,109 @@ export function selectFanavaranDriverId(
|
|||||||
const parties = asPartyInquiryRows(rows);
|
const parties = asPartyInquiryRows(rows);
|
||||||
if (parties.length === 0) return null;
|
if (parties.length === 0) return null;
|
||||||
|
|
||||||
const preferredRole = driverIsInsurer
|
const preferredRoles = driverIsInsurer
|
||||||
? FANAVARAN_INSURER_ROLE_ID
|
? [FANAVARAN_INSURER_ROLE_ID, FANAVARAN_DRIVER_ROLE_ID, FANAVARAN_PLAQUE_OWNER_ROLE_ID]
|
||||||
: FANAVARAN_DRIVER_ROLE_ID;
|
: [FANAVARAN_DRIVER_ROLE_ID, FANAVARAN_INSURER_ROLE_ID, FANAVARAN_PLAQUE_OWNER_ROLE_ID];
|
||||||
const preferred = parties.find(
|
|
||||||
(row) => asPositiveId(row.RoleId) === preferredRole,
|
|
||||||
);
|
|
||||||
if (preferred) return asPositiveId(preferred.Id);
|
|
||||||
|
|
||||||
const fallbackRole = driverIsInsurer
|
for (const roleId of preferredRoles) {
|
||||||
? FANAVARAN_DRIVER_ROLE_ID
|
const match = parties.find((row) => asPositiveId(row.RoleId) === roleId);
|
||||||
: FANAVARAN_INSURER_ROLE_ID;
|
if (match) return asPositiveId(match.Id);
|
||||||
const fallback = parties.find(
|
}
|
||||||
(row) => asPositiveId(row.RoleId) === fallbackRole,
|
|
||||||
);
|
|
||||||
if (fallback) return asPositiveId(fallback.Id);
|
|
||||||
|
|
||||||
return asPositiveId(parties[0].Id);
|
return asPositiveId(parties[0].Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function normalizeNationalCode(value: unknown): string | null {
|
||||||
|
const digits = toEnglishDigits(value).replace(/\D/g, "");
|
||||||
|
if (digits.length === 10) return digits;
|
||||||
|
if (digits.length === 8 || digits.length === 9) return digits.padStart(10, "0");
|
||||||
|
return digits.length > 0 ? digits : null;
|
||||||
|
}
|
||||||
|
|
||||||
export function pickPersonNationalCode(person: {
|
export function pickPersonNationalCode(person: {
|
||||||
driverIsInsurer?: boolean;
|
driverIsInsurer?: boolean;
|
||||||
nationalCodeOfDriver?: unknown;
|
nationalCodeOfDriver?: unknown;
|
||||||
nationalCodeOfInsurer?: unknown;
|
nationalCodeOfInsurer?: unknown;
|
||||||
|
nationalCode?: unknown;
|
||||||
} | null | undefined): string | null {
|
} | null | undefined): string | null {
|
||||||
if (!person) return null;
|
if (!person) return null;
|
||||||
const driver = String(person.nationalCodeOfDriver ?? "").trim();
|
const driver = normalizeNationalCode(person.nationalCodeOfDriver);
|
||||||
const insurer = String(person.nationalCodeOfInsurer ?? "").trim();
|
const insurer = normalizeNationalCode(person.nationalCodeOfInsurer);
|
||||||
|
const generic = normalizeNationalCode(person.nationalCode);
|
||||||
if (person.driverIsInsurer) {
|
if (person.driverIsInsurer) {
|
||||||
return insurer || driver || null;
|
return insurer || driver || generic;
|
||||||
}
|
}
|
||||||
return driver || insurer || null;
|
return driver || insurer || generic;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function pickPersonBirthday(person: {
|
export function pickPersonBirthday(person: {
|
||||||
|
driverIsInsurer?: boolean;
|
||||||
driverBirthday?: unknown;
|
driverBirthday?: unknown;
|
||||||
birthday?: unknown;
|
birthday?: unknown;
|
||||||
insurerBirthday?: unknown;
|
insurerBirthday?: unknown;
|
||||||
} | null | undefined): string | number | null {
|
} | null | undefined): string | number | null {
|
||||||
if (!person) return null;
|
if (!person) return null;
|
||||||
for (const value of [
|
const ordered = person.driverIsInsurer
|
||||||
person.driverBirthday,
|
? [person.insurerBirthday, person.birthday, person.driverBirthday]
|
||||||
person.birthday,
|
: [person.driverBirthday, person.birthday, person.insurerBirthday];
|
||||||
person.insurerBirthday,
|
for (const value of ordered) {
|
||||||
]) {
|
|
||||||
if (value == null || String(value).trim() === "") continue;
|
if (value == null || String(value).trim() === "") continue;
|
||||||
return value as string | number;
|
if (parseJalaliDateParts(value)) return value as string | number;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function collectPersonNationalCodes(
|
||||||
|
person: {
|
||||||
|
nationalCodeOfDriver?: unknown;
|
||||||
|
nationalCodeOfInsurer?: unknown;
|
||||||
|
nationalCode?: unknown;
|
||||||
|
} | null | undefined,
|
||||||
|
): string[] {
|
||||||
|
const codes = [
|
||||||
|
normalizeNationalCode(person?.nationalCodeOfDriver),
|
||||||
|
normalizeNationalCode(person?.nationalCodeOfInsurer),
|
||||||
|
normalizeNationalCode(person?.nationalCode),
|
||||||
|
].filter((value): value is string => !!value);
|
||||||
|
return [...new Set(codes)];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function collectPersonBirthdays(person: {
|
||||||
|
driverBirthday?: unknown;
|
||||||
|
birthday?: unknown;
|
||||||
|
insurerBirthday?: unknown;
|
||||||
|
} | null | undefined): Array<string | number> {
|
||||||
|
const values = [
|
||||||
|
person?.insurerBirthday,
|
||||||
|
person?.birthday,
|
||||||
|
person?.driverBirthday,
|
||||||
|
].filter((value) => value != null && String(value).trim() !== "");
|
||||||
|
const unique: Array<string | number> = [];
|
||||||
|
const seen = new Set<string>();
|
||||||
|
for (const value of values) {
|
||||||
|
const parsed = parseJalaliDateParts(value);
|
||||||
|
const key = parsed
|
||||||
|
? `${parsed.year}-${parsed.month}-${parsed.day}`
|
||||||
|
: String(value);
|
||||||
|
if (seen.has(key)) continue;
|
||||||
|
seen.add(key);
|
||||||
|
unique.push(value as string | number);
|
||||||
|
}
|
||||||
|
return unique;
|
||||||
|
}
|
||||||
|
|
||||||
export function parseJalaliDateParts(
|
export function parseJalaliDateParts(
|
||||||
birthday: string | number | null | undefined,
|
birthday: unknown,
|
||||||
): { year: number; month: number; day: number } | null {
|
): { year: number; month: number; day: number } | null {
|
||||||
if (birthday == null) return null;
|
if (birthday == null) return null;
|
||||||
const str = String(birthday).trim();
|
if (typeof birthday === "object") {
|
||||||
|
const year = Number(toEnglishDigits((birthday as any).year ?? (birthday as any).BirthYear));
|
||||||
|
const month = Number(toEnglishDigits((birthday as any).month ?? (birthday as any).BirthMonth));
|
||||||
|
const day = Number(toEnglishDigits((birthday as any).day ?? (birthday as any).BirthDay));
|
||||||
|
if (year > 0 && month > 0 && day > 0) return { year, month, day };
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const str = toEnglishDigits(birthday).trim();
|
||||||
if (/^\d{8}$/.test(str)) {
|
if (/^\d{8}$/.test(str)) {
|
||||||
const year = parseInt(str.slice(0, 4), 10);
|
const year = parseInt(str.slice(0, 4), 10);
|
||||||
const month = parseInt(str.slice(4, 6), 10);
|
const month = parseInt(str.slice(4, 6), 10);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
collectPartyVinCandidates,
|
||||||
filterPoliciesByLine,
|
filterPoliciesByLine,
|
||||||
insuranceLineLabel,
|
insuranceLineLabel,
|
||||||
parseLastCarPolicyInput,
|
parseLastCarPolicyInput,
|
||||||
@@ -114,6 +115,39 @@ describe("fanavaran last car policy", () => {
|
|||||||
).toBe("NAS451100P4934023");
|
).toBe("NAS451100P4934023");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("does not let chassis stored on vehicle.vin beat the inquiry VIN", () => {
|
||||||
|
const party = {
|
||||||
|
vehicle: {
|
||||||
|
vin: "NAAN01CA9BK493683",
|
||||||
|
inquiry: {
|
||||||
|
mapped: {
|
||||||
|
VIN: "IRFC901V119493683",
|
||||||
|
ChassisNo: "NAAN01CA9BK493683",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
expect(pickVinFromPartyVehicle(party)).toBe("IRFC901V119493683");
|
||||||
|
expect(collectPartyVinCandidates(party)).toEqual([
|
||||||
|
"IRFC901V119493683",
|
||||||
|
"NAAN01CA9BK493683",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("prefers the VIN-inquiry row that already has PlaqueKindId", () => {
|
||||||
|
expect(
|
||||||
|
pickFanavaranVehicleFromInquiry([
|
||||||
|
{ Id: 1, VIN: "IRFC901V119493683" },
|
||||||
|
{ Id: 2, VIN: "IRFC901V119493683", PlaqueKindId: 15, PlaqueSampleId: 24 },
|
||||||
|
]),
|
||||||
|
).toEqual({
|
||||||
|
Id: 2,
|
||||||
|
VIN: "IRFC901V119493683",
|
||||||
|
PlaqueKindId: 15,
|
||||||
|
PlaqueSampleId: 24,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("reads plaque kind and sample from VIN inquiry for the lookup filter", () => {
|
it("reads plaque kind and sample from VIN inquiry for the lookup filter", () => {
|
||||||
expect(
|
expect(
|
||||||
pickFanavaranPlaqueLookupFields({
|
pickFanavaranPlaqueLookupFields({
|
||||||
|
|||||||
@@ -243,6 +243,12 @@ export function pickFanavaranVehicleFromInquiry(
|
|||||||
const match = inquired.find((row) => pickVehicleId(row) === vehicleId);
|
const match = inquired.find((row) => pickVehicleId(row) === vehicleId);
|
||||||
if (match) return asObjectRecord(match);
|
if (match) return asObjectRecord(match);
|
||||||
}
|
}
|
||||||
|
const withPlaqueKind = inquired.find(
|
||||||
|
(row) =>
|
||||||
|
parseFanavaranId(asObjectRecord(row)?.PlaqueKindId) != null ||
|
||||||
|
parseFanavaranId(asObjectRecord(row)?.plaqueKindId) != null,
|
||||||
|
);
|
||||||
|
if (withPlaqueKind) return asObjectRecord(withPlaqueKind);
|
||||||
return asObjectRecord(inquired[0]);
|
return asObjectRecord(inquired[0]);
|
||||||
}
|
}
|
||||||
return asObjectRecord(inquired);
|
return asObjectRecord(inquired);
|
||||||
@@ -251,15 +257,51 @@ export function pickFanavaranVehicleFromInquiry(
|
|||||||
export function pickVinFromPartyVehicle(party: {
|
export function pickVinFromPartyVehicle(party: {
|
||||||
vehicle?: { vin?: unknown; inquiry?: unknown };
|
vehicle?: { vin?: unknown; inquiry?: unknown };
|
||||||
} | null | undefined): string | null {
|
} | null | undefined): string | null {
|
||||||
const direct = normalizeVin(party?.vehicle?.vin);
|
return collectPartyVinCandidates(party)[0] ?? null;
|
||||||
if (direct) return direct;
|
}
|
||||||
|
|
||||||
const inquiry = party?.vehicle?.inquiry;
|
/**
|
||||||
const nested = asObjectRecord(inquiry);
|
* VIN first (mapped/raw VIN fields), then party.vehicle.vin, then chassis.
|
||||||
const mapped = pickVehicleVin(asObjectRecord(nested?.mapped));
|
* Damaged cars often store chassis on `vehicle.vin` which must not win over the real VIN.
|
||||||
if (mapped) return mapped;
|
*/
|
||||||
const raw = pickVehicleVin(asObjectRecord(nested?.raw ?? inquiry));
|
export function collectPartyVinCandidates(party: {
|
||||||
return raw || null;
|
vehicle?: { vin?: unknown; inquiry?: unknown };
|
||||||
|
} | null | undefined): string[] {
|
||||||
|
const inquiry = asObjectRecord(party?.vehicle?.inquiry);
|
||||||
|
const mapped = asObjectRecord(inquiry?.mapped);
|
||||||
|
const rawData = asObjectRecord(
|
||||||
|
asObjectRecord(inquiry?.raw)?.data ?? inquiry?.raw ?? inquiry,
|
||||||
|
);
|
||||||
|
const values = [
|
||||||
|
mapped?.VIN,
|
||||||
|
mapped?.Vin,
|
||||||
|
mapped?.vin,
|
||||||
|
mapped?.VinNumberField,
|
||||||
|
rawData?.VIN,
|
||||||
|
rawData?.Vin,
|
||||||
|
rawData?.vin,
|
||||||
|
rawData?.VinNumberField,
|
||||||
|
party?.vehicle?.vin,
|
||||||
|
mapped?.ChassisNo,
|
||||||
|
mapped?.chassisNo,
|
||||||
|
mapped?.ShsNum,
|
||||||
|
mapped?.shsNam,
|
||||||
|
mapped?.ChassisNumberField,
|
||||||
|
rawData?.ChassisNo,
|
||||||
|
rawData?.chassisNo,
|
||||||
|
rawData?.ShsNum,
|
||||||
|
rawData?.shsNam,
|
||||||
|
rawData?.ChassisNumberField,
|
||||||
|
];
|
||||||
|
const unique: string[] = [];
|
||||||
|
const seen = new Set<string>();
|
||||||
|
for (const value of values) {
|
||||||
|
const vin = normalizeVin(value);
|
||||||
|
if (!vin || seen.has(vin)) continue;
|
||||||
|
seen.add(vin);
|
||||||
|
unique.push(vin);
|
||||||
|
}
|
||||||
|
return unique;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FanavaranPlaqueLookupFields = {
|
export type FanavaranPlaqueLookupFields = {
|
||||||
|
|||||||
Reference in New Issue
Block a user