forked from Yara724/api
Merge pull request 'fanavaran stages matching data is double checked and more controlled +' (#318) from s.hajizadeh/yara724api:main into main
Reviewed-on: Yara724/api#318
This commit is contained in:
@@ -36,6 +36,16 @@ import { CarGreenCardDbService } from "./entites/db-service/car-green-card.db.se
|
||||
import { ClaimRequestManagementDbService } from "./entites/db-service/claim-request-management.db.service";
|
||||
import { ClaimCaseDbService } from "./entites/db-service/claim-case.db.service";
|
||||
import { fanavaranClaimReferences } from "./fanavaran-claim-references";
|
||||
import {
|
||||
fanavaranClaimProductFromBlameType,
|
||||
fanavaranClaimsBaseUrl,
|
||||
fanavaranInsuranceLineIdForProduct,
|
||||
fanavaranPartyInquirySources,
|
||||
isFanavaranSubmitSupportedBlameType,
|
||||
pickCarBodyPolicyNationalCode,
|
||||
pickStoredCarBodyPolicyId,
|
||||
type FanavaranClaimProduct,
|
||||
} from "./fanavaran-claim-product";
|
||||
import { BlameRequestDbService } from "src/request-management/entities/db-service/blame-request.db.service";
|
||||
import { CreateClaimFromBlameResponseDto } from "./dto/create-claim-v2.dto";
|
||||
import {
|
||||
@@ -181,6 +191,7 @@ import {
|
||||
fanavaranSubmitPath,
|
||||
getFanavaranClientProfile,
|
||||
resolveFanavaranClientKey,
|
||||
resolveFanavaranProductContractId,
|
||||
} from "src/core/config/fanavaran-client.config";
|
||||
import { FanavaranAuditService } from "src/fanavaran/fanavaran-audit.service";
|
||||
import { FanavaranAuthService } from "src/fanavaran/fanavaran-auth.service";
|
||||
@@ -216,14 +227,21 @@ import {
|
||||
} from "./fanavaran-other-people";
|
||||
import {
|
||||
asObjectRecord,
|
||||
asVehicleInquiryRows,
|
||||
canReuseCachedFanavaranVehicleIds,
|
||||
collectPartyVinCandidates,
|
||||
completePlaqueParts,
|
||||
fanavaranIdOrConfigDefault,
|
||||
fanavaranPayloadMatchesDamagedIdentity,
|
||||
fanavaranVehicleMatchesDamagedIdentity,
|
||||
normalizeVin,
|
||||
parseFanavaranId,
|
||||
pickFanavaranPlaqueLookupFields,
|
||||
pickFanavaranVehicleFromInquiry,
|
||||
pickVehicleId,
|
||||
pickVinFromPartyVehicle,
|
||||
selectFanavaranVehicleForDamageCase,
|
||||
type FanavaranDamagedVehicleIdentity,
|
||||
} from "src/lookups/fanavaran-last-car-policy";
|
||||
|
||||
export interface FanavaranAutoSubmitResult {
|
||||
@@ -358,9 +376,8 @@ const FANAVARAN_PLATE_LETTER_CODE: Record<string, number> = {
|
||||
export class ClaimRequestManagementService {
|
||||
private readonly logger = new Logger(ClaimRequestManagementService.name);
|
||||
|
||||
// Business submit URL (auth lives in FanavaranAuthService)
|
||||
private readonly FANAVARAN_SUBMIT_URL =
|
||||
"https://apimanager.iraneit.com/BimeApiManager/api/BimeApi/v2.0/car/third-party-car-financial-claims";
|
||||
// Legacy Tejaratno third-party URL. V2 stages use fanavaranClaimsBaseUrl(product).
|
||||
private readonly FANAVARAN_SUBMIT_URL = fanavaranClaimsBaseUrl("third-party");
|
||||
|
||||
// Authentication credentials
|
||||
private readonly APP_NAME = "fanhab";
|
||||
@@ -3932,29 +3949,47 @@ export class ClaimRequestManagementService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Damaged cars often store chassis on vehicle.vin. Try every VIN-like id
|
||||
* until Fanavaran returns PlaqueKindId (required on GEN.12).
|
||||
* Damaged-car VIN inquiry: keep only rows that match VIN/plate/chassis.
|
||||
* Then GET that VehicleId so PlaqueKindId is the current version, not a
|
||||
* leftover row that merely had some PlaqueKindId.
|
||||
*/
|
||||
private async fetchFanavaranVehicleByVinCandidates(
|
||||
clientKey: FanavaranClientKey,
|
||||
vins: string[],
|
||||
identity: FanavaranDamagedVehicleIdentity,
|
||||
product: FanavaranClaimProduct = "third-party",
|
||||
): Promise<Record<string, unknown> | null> {
|
||||
let fallback: Record<string, unknown> | null = null;
|
||||
const requestOptions = {
|
||||
contractIdOverride: resolveFanavaranProductContractId(clientKey, product),
|
||||
};
|
||||
let matched: Record<string, unknown> | null = null;
|
||||
for (const vin of vins) {
|
||||
try {
|
||||
const vehicle = await this.fetchFanavaranVehicleByVin(clientKey, vin);
|
||||
if (!vehicle) {
|
||||
const inquired = await this.fanavaranLookupService.inquiryByVin(
|
||||
clientKey,
|
||||
vin,
|
||||
requestOptions,
|
||||
);
|
||||
const rows = asVehicleInquiryRows(inquired);
|
||||
if (rows.length === 0) {
|
||||
this.logger.warn(
|
||||
`[buildFanavaranDamageCasePayload] VIN inquiry for ${vin} returned no vehicle`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
const plaqueKindId = pickFanavaranPlaqueLookupFields(vehicle).kindId;
|
||||
const selected = selectFanavaranVehicleForDamageCase(rows, identity);
|
||||
if (!selected) {
|
||||
this.logger.warn(
|
||||
`[buildFanavaranDamageCasePayload] VIN inquiry ${vin} returned ${rows.length} row(s) but none matched damaged VIN/plate/chassis`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
const plaqueKindId = pickFanavaranPlaqueLookupFields(selected).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"}`,
|
||||
`[buildFanavaranDamageCasePayload] VIN inquiry ${vin} matched VehicleId=${pickVehicleId(selected) ?? "none"} VehicleKindId=${parseFanavaranId(selected.VehicleKindId) ?? "none"} UsedId=${parseFanavaranId(selected.UsedId) ?? "none"} PlaqueKindId=${plaqueKindId ?? "none"} VersionNo=${parseFanavaranId(selected.VersionNo) ?? "none"}`,
|
||||
);
|
||||
if (plaqueKindId != null) return vehicle;
|
||||
if (!fallback) fallback = vehicle;
|
||||
matched = selected;
|
||||
break;
|
||||
} catch (error) {
|
||||
this.logger.warn(
|
||||
`[buildFanavaranDamageCasePayload] VIN inquiry failed for ${vin}: ${
|
||||
@@ -3963,7 +3998,43 @@ export class ClaimRequestManagementService {
|
||||
);
|
||||
}
|
||||
}
|
||||
return fallback;
|
||||
if (!matched) return null;
|
||||
|
||||
const vehicleId = pickVehicleId(matched);
|
||||
const versionNo = parseFanavaranId(matched.VersionNo);
|
||||
if (vehicleId == null) return matched;
|
||||
|
||||
try {
|
||||
const fetched = asObjectRecord(
|
||||
await this.fanavaranLookupService.vehicleById(
|
||||
clientKey,
|
||||
vehicleId,
|
||||
versionNo ?? undefined,
|
||||
requestOptions,
|
||||
),
|
||||
);
|
||||
if (
|
||||
fetched &&
|
||||
fanavaranVehicleMatchesDamagedIdentity(fetched, identity)
|
||||
) {
|
||||
this.logger.log(
|
||||
`[buildFanavaranDamageCasePayload] vehicle GET ${vehicleId} confirmed identity PlaqueKindId=${pickFanavaranPlaqueLookupFields(fetched).kindId ?? "none"} VehicleKindId=${parseFanavaranId(fetched.VehicleKindId) ?? "none"}`,
|
||||
);
|
||||
return fetched;
|
||||
}
|
||||
if (fetched) {
|
||||
this.logger.warn(
|
||||
`[buildFanavaranDamageCasePayload] vehicle GET ${vehicleId} did not match damaged identity; keeping VIN-inquiry row`,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.warn(
|
||||
`[buildFanavaranDamageCasePayload] vehicle GET failed for VehicleId=${vehicleId}: ${
|
||||
error instanceof Error ? error.message : error
|
||||
}`,
|
||||
);
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3972,13 +4043,27 @@ export class ClaimRequestManagementService {
|
||||
private async resolveAccidentVehicleUsedFromPolicy(
|
||||
clientKey: FanavaranClientKey,
|
||||
policyId: number,
|
||||
product: FanavaranClaimProduct = "third-party",
|
||||
): Promise<{ usedId: number | null; vehicleKindId: number | null }> {
|
||||
try {
|
||||
const policy = asObjectRecord(
|
||||
await this.fanavaranLookupService.thirdPartyPolicyById(
|
||||
const requestOptions = {
|
||||
contractIdOverride: resolveFanavaranProductContractId(
|
||||
clientKey,
|
||||
policyId,
|
||||
product,
|
||||
),
|
||||
};
|
||||
const policy = asObjectRecord(
|
||||
product === "car-body"
|
||||
? await this.fanavaranLookupService.bodyPolicyById(
|
||||
clientKey,
|
||||
policyId,
|
||||
requestOptions,
|
||||
)
|
||||
: await this.fanavaranLookupService.thirdPartyPolicyById(
|
||||
clientKey,
|
||||
policyId,
|
||||
requestOptions,
|
||||
),
|
||||
);
|
||||
const vehicleId = pickVehicleId(policy?.VehicleId ?? policy?.vehicleId);
|
||||
if (vehicleId === null) {
|
||||
@@ -3992,6 +4077,7 @@ export class ClaimRequestManagementService {
|
||||
clientKey,
|
||||
vehicleId,
|
||||
versionNo ?? undefined,
|
||||
requestOptions,
|
||||
),
|
||||
);
|
||||
this.logger.log(
|
||||
@@ -4013,6 +4099,7 @@ export class ClaimRequestManagementService {
|
||||
parties: unknown[];
|
||||
guiltyPartyId?: unknown;
|
||||
policyId?: number | null;
|
||||
product?: FanavaranClaimProduct;
|
||||
}): Promise<{
|
||||
usedId: number | null;
|
||||
vehicleKindId: number | null;
|
||||
@@ -4038,6 +4125,7 @@ export class ClaimRequestManagementService {
|
||||
const fromPolicy = await this.resolveAccidentVehicleUsedFromPolicy(
|
||||
input.clientKey,
|
||||
input.policyId,
|
||||
input.product ?? "third-party",
|
||||
);
|
||||
if (fromPolicy.usedId != null) {
|
||||
return { ...fromPolicy, source: "policy-vehicle" };
|
||||
@@ -4144,14 +4232,9 @@ export class ClaimRequestManagementService {
|
||||
const person = damagedParty?.person ?? {};
|
||||
const vehicle = damagedParty?.vehicle ?? {};
|
||||
const insurance = damagedParty?.insurance ?? {};
|
||||
// Prefer nested inquiry (ESG/Tejarat) on the damaged party — not flattened insurance.*
|
||||
const inquiryRawPayload =
|
||||
vehicle?.inquiry?.raw?.data != null &&
|
||||
typeof vehicle.inquiry.raw.data === "object" &&
|
||||
!Array.isArray(vehicle.inquiry.raw.data)
|
||||
? vehicle.inquiry.raw.data
|
||||
: (vehicle?.inquiry?.raw ?? {});
|
||||
const inquiryMapped = vehicle?.inquiry?.mapped ?? inquiryRawPayload ?? {};
|
||||
const product = fanavaranClaimProductFromBlameType(input.blameCase?.type);
|
||||
const { mapped: inquiryMapped, raw: inquiryRawPayload } =
|
||||
fanavaranPartyInquirySources(product, damagedParty);
|
||||
const inquiryRaw = inquiryRawPayload ?? {};
|
||||
|
||||
const plate =
|
||||
@@ -4213,20 +4296,38 @@ export class ClaimRequestManagementService {
|
||||
]);
|
||||
const builtYearFromInquiry = builtYearRaw ? Number(builtYearRaw) || null : null;
|
||||
|
||||
const damagedVinCandidates: string[] = [];
|
||||
const seenVins = new Set<string>();
|
||||
for (const value of [
|
||||
...collectPartyVinCandidates(damagedParty),
|
||||
const damagedVin =
|
||||
this.pickPartyInquiryField(inquiryMapped, inquiryRaw, [
|
||||
"VIN",
|
||||
"vin",
|
||||
"VinNumberField",
|
||||
]),
|
||||
this.pickPartyInquiryField(inquiryMapped, inquiryRaw, [
|
||||
"ShsNum",
|
||||
"ChassisNumberField",
|
||||
"shsNam",
|
||||
]),
|
||||
]) ?? collectPartyVinCandidates(damagedParty)[0] ??
|
||||
null;
|
||||
const damagedChassis = this.pickPartyInquiryField(inquiryMapped, inquiryRaw, [
|
||||
"ShsNum",
|
||||
"ChassisNumberField",
|
||||
"shsNam",
|
||||
]);
|
||||
const damagedIdentity: FanavaranDamagedVehicleIdentity = {
|
||||
vin: damagedVin,
|
||||
chassis: damagedChassis,
|
||||
plaque: plate
|
||||
? completePlaqueParts({
|
||||
plaqueLeft: String(plate.leftDigits),
|
||||
plaqueLetter: plate.centerAlphabet,
|
||||
plaqueRight: String(plate.centerDigits),
|
||||
plaqueSerial: String(plate.ir),
|
||||
})
|
||||
: null,
|
||||
policyCINumber,
|
||||
};
|
||||
|
||||
const damagedVinCandidates: string[] = [];
|
||||
const seenVins = new Set<string>();
|
||||
for (const value of [
|
||||
...collectPartyVinCandidates(damagedParty),
|
||||
damagedVin,
|
||||
damagedChassis,
|
||||
]) {
|
||||
const vin = normalizeVin(value);
|
||||
if (!vin || seenVins.has(vin)) continue;
|
||||
@@ -4236,29 +4337,46 @@ export class ClaimRequestManagementService {
|
||||
const cachedDamage =
|
||||
(input.claimCase as any)?.fanavaranSync?.damageCase ?? {};
|
||||
const lastDamagePayload = asObjectRecord(cachedDamage.lastPayload);
|
||||
const cacheMatchesIdentity = fanavaranPayloadMatchesDamagedIdentity(
|
||||
lastDamagePayload,
|
||||
damagedIdentity,
|
||||
);
|
||||
const canReuseCachedPlaque =
|
||||
cacheMatchesIdentity &&
|
||||
canReuseCachedFanavaranVehicleIds(cachedDamage.plaqueKindSource);
|
||||
const canReuseCachedVehicleKind =
|
||||
cacheMatchesIdentity &&
|
||||
canReuseCachedFanavaranVehicleIds(cachedDamage.vehicleKindSource);
|
||||
|
||||
this.logger.log(
|
||||
`[buildFanavaranDamageCasePayload] VIN candidates=${damagedVinCandidates.join(",") || "none"}`,
|
||||
`[buildFanavaranDamageCasePayload] identity VIN=${damagedIdentity.vin ?? "none"} chassis=${damagedIdentity.chassis ?? "none"} plate=${damagedIdentity.plaque ? `${damagedIdentity.plaque.right}${damagedIdentity.plaque.letter}${damagedIdentity.plaque.left}/${damagedIdentity.plaque.serial}` : "none"} CI=${damagedIdentity.policyCINumber ?? "none"} VIN candidates=${damagedVinCandidates.join(",") || "none"}`,
|
||||
);
|
||||
const apiVehicle = await this.fetchFanavaranVehicleByVinCandidates(
|
||||
input.clientKey,
|
||||
damagedVinCandidates,
|
||||
damagedIdentity,
|
||||
product,
|
||||
);
|
||||
const apiPlaque = pickFanavaranPlaqueLookupFields(apiVehicle);
|
||||
const inquiryPlaque = pickFanavaranPlaqueLookupFields(
|
||||
asObjectRecord(inquiryMapped) ?? asObjectRecord(inquiryRaw),
|
||||
);
|
||||
const inquiryVehicle =
|
||||
asObjectRecord(inquiryMapped) ?? asObjectRecord(inquiryRaw);
|
||||
const inquiryPlaque = fanavaranVehicleMatchesDamagedIdentity(
|
||||
inquiryVehicle,
|
||||
damagedIdentity,
|
||||
)
|
||||
? pickFanavaranPlaqueLookupFields(inquiryVehicle)
|
||||
: pickFanavaranPlaqueLookupFields(null);
|
||||
const plaqueKindFromApi = this.firstPositiveFanavaranId(
|
||||
apiPlaque.kindId,
|
||||
inquiryPlaque.kindId,
|
||||
cachedDamage.plaqueKindId,
|
||||
lastDamagePayload?.PlaqueKindId,
|
||||
canReuseCachedPlaque ? cachedDamage.plaqueKindId : null,
|
||||
canReuseCachedPlaque ? lastDamagePayload?.PlaqueKindId : null,
|
||||
);
|
||||
const plaqueSampleFromApi = this.firstPositiveFanavaranId(
|
||||
apiPlaque.sampleId,
|
||||
inquiryPlaque.sampleId,
|
||||
cachedDamage.plaqueSampleId,
|
||||
lastDamagePayload?.PlaqueSampleId,
|
||||
canReuseCachedPlaque ? cachedDamage.plaqueSampleId : null,
|
||||
canReuseCachedPlaque ? lastDamagePayload?.PlaqueSampleId : null,
|
||||
);
|
||||
const plaqueKindId = fanavaranIdOrConfigDefault(
|
||||
plaqueKindFromApi,
|
||||
@@ -4268,6 +4386,10 @@ export class ClaimRequestManagementService {
|
||||
plaqueSampleFromApi,
|
||||
input.defaults.PlaqueSampleId,
|
||||
);
|
||||
const plaqueKindSource =
|
||||
plaqueKindFromApi != null ? "vehicle-inquiry" : "config-default";
|
||||
const plaqueSampleSource =
|
||||
plaqueSampleFromApi != null ? "vehicle-inquiry" : "config-default";
|
||||
if (plaqueKindFromApi == null) {
|
||||
this.logger.log(
|
||||
`[buildFanavaranDamageCasePayload] PlaqueKindId API miss; using config default ${plaqueKindId}`,
|
||||
@@ -4284,14 +4406,21 @@ export class ClaimRequestManagementService {
|
||||
const carType = input.claimCase?.vehicle?.carType as string | undefined;
|
||||
let vehicleKindId = this.firstPositiveFanavaranId(
|
||||
apiVehicle?.VehicleKindId,
|
||||
cachedDamage.vehicleKindId,
|
||||
lastDamagePayload?.VehicleKindId,
|
||||
canReuseCachedVehicleKind ? cachedDamage.vehicleKindId : null,
|
||||
canReuseCachedVehicleKind ? lastDamagePayload?.VehicleKindId : null,
|
||||
);
|
||||
let vehicleKindSource: "vehicle-inquiry" | "car-type-lookup" | null =
|
||||
parseFanavaranId(apiVehicle?.VehicleKindId) != null
|
||||
? "vehicle-inquiry"
|
||||
: canReuseCachedVehicleKind
|
||||
? "vehicle-inquiry"
|
||||
: null;
|
||||
if (vehicleKindId == null) {
|
||||
vehicleKindId = await this.resolveVehicleKindId(
|
||||
input.clientKey,
|
||||
carType,
|
||||
);
|
||||
if (vehicleKindId != null) vehicleKindSource = "car-type-lookup";
|
||||
}
|
||||
|
||||
// Prefer claim-level cache, then party.person.fanavaranDriverId, then live inquiry, then last payload
|
||||
@@ -4359,49 +4488,37 @@ export class ClaimRequestManagementService {
|
||||
}
|
||||
|
||||
const cachedUsedId =
|
||||
cachedDamage.accidentVehicleUsedId != null
|
||||
cacheMatchesIdentity && cachedDamage.accidentVehicleUsedId != null
|
||||
? Number(cachedDamage.accidentVehicleUsedId)
|
||||
: (input.claimCase as any)?.fanavaranSync?.baseClaim
|
||||
?.accidentVehicleUsedId != null
|
||||
? Number(
|
||||
(input.claimCase as any).fanavaranSync.baseClaim
|
||||
.accidentVehicleUsedId,
|
||||
)
|
||||
: null;
|
||||
const cachedUsedSource =
|
||||
cachedDamage.accidentVehicleUsedSource ??
|
||||
(input.claimCase as any)?.fanavaranSync?.baseClaim
|
||||
?.accidentVehicleUsedSource;
|
||||
: null;
|
||||
const cachedUsedSource = cachedDamage.accidentVehicleUsedSource;
|
||||
const canReuseApiUsedId =
|
||||
cachedUsedId != null &&
|
||||
cachedUsedId > 0 &&
|
||||
(cachedUsedSource === "vin-inquiry" ||
|
||||
cachedUsedSource === "policy-vehicle");
|
||||
cachedUsedSource === "vehicle-inquiry";
|
||||
let accidentVehicleUsedId: number | null = canReuseApiUsedId
|
||||
? cachedUsedId
|
||||
: null;
|
||||
let usedSource: "vin-inquiry" | "policy-vehicle" | null = canReuseApiUsedId
|
||||
? cachedUsedSource
|
||||
: null;
|
||||
if (accidentVehicleUsedId == null) {
|
||||
const resolved = await this.resolveAccidentVehicleUsedFromApis({
|
||||
clientKey: input.clientKey,
|
||||
parties: input.blameCase?.parties ?? [],
|
||||
guiltyPartyId: this.resolveGuiltyPartyIdV2(
|
||||
input.blameCase?.parties ?? [],
|
||||
input.blameCase?.expert?.decision?.guiltyPartyId,
|
||||
),
|
||||
policyId: (input.claimCase as any)?.fanavaranSync?.baseClaim?.policyId,
|
||||
});
|
||||
accidentVehicleUsedId = resolved.usedId;
|
||||
usedSource = resolved.source;
|
||||
let usedSource: "vin-inquiry" | "policy-vehicle" | "vehicle-inquiry" | null =
|
||||
canReuseApiUsedId ? "vehicle-inquiry" : null;
|
||||
if (accidentVehicleUsedId == null && apiVehicle) {
|
||||
const fromDamagedVehicle =
|
||||
await this.resolveAccidentVehicleUsedFromVehicleRecord(
|
||||
input.clientKey,
|
||||
apiVehicle,
|
||||
);
|
||||
accidentVehicleUsedId = fromDamagedVehicle.usedId;
|
||||
if (accidentVehicleUsedId != null) usedSource = "vehicle-inquiry";
|
||||
}
|
||||
if (accidentVehicleUsedId == null) {
|
||||
accidentVehicleUsedId = await this.resolveAccidentVehicleUsedFromVehicleKind(
|
||||
input.clientKey,
|
||||
vehicleKindId,
|
||||
null,
|
||||
parseFanavaranId(apiVehicle?.UsedId),
|
||||
);
|
||||
if (accidentVehicleUsedId != null && usedSource == null) {
|
||||
usedSource = apiVehicle ? "vehicle-inquiry" : null;
|
||||
}
|
||||
}
|
||||
if (accidentVehicleUsedId == null) {
|
||||
accidentVehicleUsedId = input.defaults.AccidentVehicleUsedId;
|
||||
@@ -4482,10 +4599,6 @@ export class ClaimRequestManagementService {
|
||||
|
||||
const persistedPayload = this.keepPreviousPositiveIds(lastDamagePayload, payload, [
|
||||
"DriverId",
|
||||
"PlaqueKindId",
|
||||
"PlaqueSampleId",
|
||||
"VehicleKindId",
|
||||
"AccidentVehicleUsedId",
|
||||
"InsuranceCorpId",
|
||||
]);
|
||||
|
||||
@@ -4502,14 +4615,16 @@ export class ClaimRequestManagementService {
|
||||
? { "fanavaranSync.damageCase.otherPersonId": otherPersonId }
|
||||
: {}),
|
||||
...(vehicleKindId != null
|
||||
? { "fanavaranSync.damageCase.vehicleKindId": vehicleKindId }
|
||||
: {}),
|
||||
...(plaqueKindId != null
|
||||
? { "fanavaranSync.damageCase.plaqueKindId": plaqueKindId }
|
||||
: {}),
|
||||
...(plaqueSampleId != null
|
||||
? { "fanavaranSync.damageCase.plaqueSampleId": plaqueSampleId }
|
||||
? {
|
||||
"fanavaranSync.damageCase.vehicleKindId": vehicleKindId,
|
||||
"fanavaranSync.damageCase.vehicleKindSource":
|
||||
vehicleKindSource ?? "car-type-lookup",
|
||||
}
|
||||
: {}),
|
||||
"fanavaranSync.damageCase.plaqueKindId": plaqueKindId,
|
||||
"fanavaranSync.damageCase.plaqueKindSource": plaqueKindSource,
|
||||
"fanavaranSync.damageCase.plaqueSampleId": plaqueSampleId,
|
||||
"fanavaranSync.damageCase.plaqueSampleSource": plaqueSampleSource,
|
||||
...(accidentVehicleUsedId != null
|
||||
? {
|
||||
"fanavaranSync.damageCase.accidentVehicleUsedId":
|
||||
@@ -4720,6 +4835,31 @@ export class ClaimRequestManagementService {
|
||||
return resolveFanavaranClientKey();
|
||||
}
|
||||
|
||||
private async resolveFanavaranClaimProduct(
|
||||
claimCaseId?: string,
|
||||
blameCase?: { type?: string } | null,
|
||||
): Promise<FanavaranClaimProduct> {
|
||||
if (blameCase?.type) {
|
||||
return fanavaranClaimProductFromBlameType(blameCase.type);
|
||||
}
|
||||
if (!claimCaseId) return "third-party";
|
||||
const claimCase = await this.claimCaseDbService.findById(claimCaseId);
|
||||
if (!claimCase?.blameRequestId) return "third-party";
|
||||
const blame = await this.blameRequestDbService.findById(
|
||||
String(claimCase.blameRequestId),
|
||||
);
|
||||
return fanavaranClaimProductFromBlameType(blame?.type);
|
||||
}
|
||||
|
||||
private async fanavaranClaimsUrlForClaim(
|
||||
claimCaseId?: string,
|
||||
blameCase?: { type?: string } | null,
|
||||
): Promise<string> {
|
||||
return fanavaranClaimsBaseUrl(
|
||||
await this.resolveFanavaranClaimProduct(claimCaseId, blameCase),
|
||||
);
|
||||
}
|
||||
|
||||
private async getFanavaranAuthHeaders(
|
||||
clientKey: FanavaranClientKey,
|
||||
auditSession?: FanavaranAuditSession,
|
||||
@@ -4729,9 +4869,11 @@ export class ClaimRequestManagementService {
|
||||
const locationOverride = caseId
|
||||
? await this.resolveFanavaranLocationForClaim(caseId, clientKey)
|
||||
: undefined;
|
||||
const product = await this.resolveFanavaranClaimProduct(caseId);
|
||||
return this.fanavaranAuthService.getRequestHeaders(clientKey, {
|
||||
auditSession,
|
||||
locationOverride,
|
||||
contractIdOverride: resolveFanavaranProductContractId(clientKey, product),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4793,21 +4935,31 @@ export class ClaimRequestManagementService {
|
||||
},
|
||||
logPrefix: string,
|
||||
auditSession?: FanavaranAuditSession,
|
||||
options?: { clientKey?: FanavaranClientKey; claimCaseId?: string },
|
||||
options?: {
|
||||
clientKey?: FanavaranClientKey;
|
||||
claimCaseId?: string;
|
||||
product?: FanavaranClaimProduct;
|
||||
insuranceLineId?: number;
|
||||
},
|
||||
): Promise<number | null> {
|
||||
const clientKey =
|
||||
options?.clientKey ?? this.resolveClientKeyFromAuthConfig(config);
|
||||
const policyInquiryUrl = `https://apimanager.iraneit.com/BimeApiManager/api/BimeApi/v2.0/common/Policies/inquiry-my-policies?InsuranceLineId=5&NationalCode=${nationalCodeOfInsurer}`;
|
||||
const product =
|
||||
options?.product ??
|
||||
(await this.resolveFanavaranClaimProduct(options?.claimCaseId));
|
||||
const insuranceLineId =
|
||||
options?.insuranceLineId ?? fanavaranInsuranceLineIdForProduct(product);
|
||||
const policyInquiryUrl = `https://apimanager.iraneit.com/BimeApiManager/api/BimeApi/v2.0/common/Policies/inquiry-my-policies?InsuranceLineId=${insuranceLineId}&NationalCode=${nationalCodeOfInsurer}`;
|
||||
const startedAt = Date.now();
|
||||
|
||||
const requestMeta = {
|
||||
corpId: config.corpId,
|
||||
contractId: config.contractId,
|
||||
contractId: resolveFanavaranProductContractId(clientKey, product),
|
||||
location: config.location,
|
||||
nationalCode: this.fanavaranAuditService.maskNationalCode(
|
||||
nationalCodeOfInsurer,
|
||||
),
|
||||
InsuranceLineId: 5,
|
||||
InsuranceLineId: insuranceLineId,
|
||||
};
|
||||
|
||||
if (auditSession) {
|
||||
@@ -5268,6 +5420,13 @@ export class ClaimRequestManagementService {
|
||||
: [];
|
||||
if (parties.length === 0) return null;
|
||||
|
||||
if (
|
||||
blameCase?.type === BlameRequestType.CAR_BODY ||
|
||||
blameCase?.type === "CAR_BODY"
|
||||
) {
|
||||
return resolveDamagedPartyRow(blameCase) ?? parties[0] ?? null;
|
||||
}
|
||||
|
||||
const ownerId = claimCase?.owner?.userId;
|
||||
if (ownerId) {
|
||||
const byOwner = parties.find(
|
||||
@@ -5474,15 +5633,69 @@ export class ClaimRequestManagementService {
|
||||
(claimCase as any)?.fanavaranSync?.baseClaim?.lastPayload?.PolicyId ??
|
||||
null;
|
||||
|
||||
const accidentAt =
|
||||
blameCase.type === BlameRequestType.CAR_BODY
|
||||
? ((firstParty as { statement?: { accidentDate?: Date } })?.statement
|
||||
?.accidentDate ??
|
||||
(blameCase as { createdAt?: Date }).createdAt)
|
||||
: (blameCase as { createdAt?: Date }).createdAt;
|
||||
|
||||
const payload = await this.buildFanavaranSubmitPayload({
|
||||
accidentReason: selectedAccidentReason,
|
||||
createdAt: (blameCase as { createdAt?: Date }).createdAt,
|
||||
createdAt: accidentAt,
|
||||
location: firstParty?.location,
|
||||
damageParts,
|
||||
defaults: profile.defaults,
|
||||
logPrefix,
|
||||
requirePolicyId,
|
||||
resolvePolicyId: async () => {
|
||||
const product = fanavaranClaimProductFromBlameType(blameCase.type);
|
||||
|
||||
if (cachedPolicyId != null && !forceRefreshPolicy) {
|
||||
debug.steps.policyIdFromCache = true;
|
||||
debug.values.policyId = Number(cachedPolicyId);
|
||||
return Number(cachedPolicyId);
|
||||
}
|
||||
|
||||
if (product === "car-body") {
|
||||
const storedPolicyId = pickStoredCarBodyPolicyId(blameCase);
|
||||
if (storedPolicyId != null) {
|
||||
debug.values.policyId = storedPolicyId;
|
||||
debug.steps.policyInquirySucceeded = true;
|
||||
return storedPolicyId;
|
||||
}
|
||||
|
||||
const nationalCodeOfInsurer =
|
||||
pickCarBodyPolicyNationalCode(blameCase);
|
||||
debug.steps.nationalCodeOfInsurerFound = !!nationalCodeOfInsurer;
|
||||
debug.values.nationalCodeOfInsurer = nationalCodeOfInsurer;
|
||||
if (!nationalCodeOfInsurer) {
|
||||
this.logger.warn(
|
||||
`${logPrefix} car-body policyholder national code is missing`,
|
||||
);
|
||||
debug.failureReason =
|
||||
"car-body policyholder national code is missing";
|
||||
return null;
|
||||
}
|
||||
|
||||
debug.steps.policyInquiryAttempted = true;
|
||||
const policyId = await this.getPolicyIdFromNationalCode(
|
||||
nationalCodeOfInsurer,
|
||||
profile.auth,
|
||||
logPrefix,
|
||||
auditSession,
|
||||
{ clientKey, claimCaseId, product: "car-body" },
|
||||
);
|
||||
debug.values.policyId = policyId;
|
||||
debug.steps.policyInquirySucceeded = policyId !== null;
|
||||
if (policyId === null) {
|
||||
debug.failureReason =
|
||||
debug.failureReason ??
|
||||
"car-body policy inquiry returned no PolicyId (timeout, network error, empty response, or backoff)";
|
||||
}
|
||||
return policyId;
|
||||
}
|
||||
|
||||
const guiltyPartyId = this.resolveGuiltyPartyIdV2(
|
||||
blameCase.parties ?? [],
|
||||
expertDecision?.guiltyPartyId,
|
||||
@@ -5519,20 +5732,13 @@ export class ClaimRequestManagementService {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (cachedPolicyId != null && !forceRefreshPolicy) {
|
||||
debug.steps.policyIdFromCache = true;
|
||||
debug.values.policyId = Number(cachedPolicyId);
|
||||
// Silent reuse — no Fanavaran HTTP and no audit noise on warm preview.
|
||||
return Number(cachedPolicyId);
|
||||
}
|
||||
|
||||
debug.steps.policyInquiryAttempted = true;
|
||||
const policyId = await this.getPolicyIdFromNationalCode(
|
||||
nationalCodeOfInsurer,
|
||||
profile.auth,
|
||||
logPrefix,
|
||||
auditSession,
|
||||
{ clientKey, claimCaseId },
|
||||
{ clientKey, claimCaseId, product: "third-party" },
|
||||
);
|
||||
debug.values.policyId = policyId;
|
||||
debug.steps.policyInquirySucceeded = policyId !== null;
|
||||
@@ -5545,6 +5751,17 @@ export class ClaimRequestManagementService {
|
||||
},
|
||||
});
|
||||
|
||||
const carBodyAccidentTime = (
|
||||
firstParty as { statement?: { accidentTime?: string } } | undefined
|
||||
)?.statement?.accidentTime;
|
||||
if (
|
||||
blameCase.type === BlameRequestType.CAR_BODY &&
|
||||
typeof carBodyAccidentTime === "string" &&
|
||||
carBodyAccidentTime.trim()
|
||||
) {
|
||||
payload.AccidentTime = carBodyAccidentTime.trim();
|
||||
}
|
||||
|
||||
const cachedUsedId =
|
||||
forceRefreshUsedId || forceRefreshPolicy
|
||||
? null
|
||||
@@ -5575,6 +5792,7 @@ export class ClaimRequestManagementService {
|
||||
guiltyPartyId: guiltyPartyIdForVin,
|
||||
policyId:
|
||||
typeof payload.PolicyId === "number" ? payload.PolicyId : null,
|
||||
product: fanavaranClaimProductFromBlameType(blameCase.type),
|
||||
});
|
||||
if (resolved.usedId != null) {
|
||||
payload.AccidentVehicleUsedId = resolved.usedId;
|
||||
@@ -5687,9 +5905,9 @@ export class ClaimRequestManagementService {
|
||||
if (!blameCase) {
|
||||
throw new NotFoundException("Blame case not found");
|
||||
}
|
||||
if (blameCase.type !== BlameRequestType.THIRD_PARTY) {
|
||||
if (!isFanavaranSubmitSupportedBlameType(blameCase.type)) {
|
||||
throw new BadRequestException(
|
||||
"Fanavaran damage-case submit only applies to THIRD_PARTY claims",
|
||||
"Fanavaran damage-case submit only applies to THIRD_PARTY and CAR_BODY claims",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5717,7 +5935,7 @@ export class ClaimRequestManagementService {
|
||||
claimId: claimCase.claimId ?? null,
|
||||
dmgCaseId: claimCase.dmgCaseId ?? null,
|
||||
submitUrl: claimCase.claimId
|
||||
? `${this.FANAVARAN_SUBMIT_URL}/${claimCase.claimId}/dmg-cases`
|
||||
? `${fanavaranClaimsBaseUrl(fanavaranClaimProductFromBlameType(blameCase.type))}/${claimCase.claimId}/dmg-cases`
|
||||
: null,
|
||||
warning: warnings.length ? warnings.join(" ") : undefined,
|
||||
payload,
|
||||
@@ -5881,6 +6099,7 @@ export class ClaimRequestManagementService {
|
||||
): Promise<any> {
|
||||
const { claimCase, candidates } =
|
||||
await this.collectFanavaranAttachmentCandidates(claimCaseId, clientKey);
|
||||
const claimsUrl = await this.fanavaranClaimsUrlForClaim(claimCaseId);
|
||||
|
||||
return {
|
||||
clientKey,
|
||||
@@ -5889,7 +6108,7 @@ export class ClaimRequestManagementService {
|
||||
claimNo: claimCase.claimNo ?? null,
|
||||
dmgCaseId: claimCase.dmgCaseId ?? null,
|
||||
submitUrl: claimCase.claimId
|
||||
? `${this.FANAVARAN_SUBMIT_URL}/${claimCase.claimId}/files`
|
||||
? `${claimsUrl}/${claimCase.claimId}/files`
|
||||
: null,
|
||||
warning: claimCase.claimId
|
||||
? undefined
|
||||
@@ -5984,7 +6203,7 @@ export class ClaimRequestManagementService {
|
||||
};
|
||||
}
|
||||
|
||||
const url = `${this.FANAVARAN_SUBMIT_URL}/${claimCase.claimId}/files`;
|
||||
const url = `${await this.fanavaranClaimsUrlForClaim(claimCaseId)}/${claimCase.claimId}/files`;
|
||||
const fileTypeId = profile.defaults.ClaimFileTypeId;
|
||||
|
||||
const logPrefix = `[Fanavaran ${clientKey} V2 Attachment Batch] claimCaseId=${claimCaseId}`;
|
||||
@@ -6143,7 +6362,7 @@ export class ClaimRequestManagementService {
|
||||
};
|
||||
}
|
||||
|
||||
url = `${this.FANAVARAN_SUBMIT_URL}/${claimCase.claimId}/files`;
|
||||
url = `${await this.fanavaranClaimsUrlForClaim(claimCaseId)}/${claimCase.claimId}/files`;
|
||||
content = {
|
||||
FileName: fileName,
|
||||
FileTypeId: profile.defaults.ClaimFileTypeId,
|
||||
@@ -6636,7 +6855,7 @@ export class ClaimRequestManagementService {
|
||||
expertiseId: claimCase.expertiseId ?? null,
|
||||
replyKey,
|
||||
submitUrl: claimCase.claimId
|
||||
? `${this.FANAVARAN_SUBMIT_URL}/${claimCase.claimId}/expertise`
|
||||
? `${await this.fanavaranClaimsUrlForClaim(claimCaseId)}/${claimCase.claimId}/expertise`
|
||||
: null,
|
||||
ready: warnings.length === 0,
|
||||
warnings: Array.from(new Set(warnings)),
|
||||
@@ -6721,7 +6940,7 @@ export class ClaimRequestManagementService {
|
||||
}
|
||||
);
|
||||
}
|
||||
const url = `${this.FANAVARAN_SUBMIT_URL}/${claimCase.claimId}/expertise`;
|
||||
const url = `${await this.fanavaranClaimsUrlForClaim(claimCaseId)}/${claimCase.claimId}/expertise`;
|
||||
const startedAt = Date.now();
|
||||
const auditSession: FanavaranAuditSession = {
|
||||
trackingCode: this.fanavaranAuditService.generateTrackingCode(),
|
||||
@@ -6938,7 +7157,7 @@ export class ClaimRequestManagementService {
|
||||
fanavaran: FanavaranAutoSubmitResult,
|
||||
): string {
|
||||
if (fanavaran.submitted) {
|
||||
return `${baseMessage} The initial third-party case was sent to Fanavaran successfully.`;
|
||||
return `${baseMessage} The initial Fanavaran case was sent successfully.`;
|
||||
}
|
||||
if (fanavaran.warning) {
|
||||
return `${baseMessage} ${fanavaran.warning}`;
|
||||
@@ -6964,8 +7183,8 @@ export class ClaimRequestManagementService {
|
||||
return "Blame case not found";
|
||||
}
|
||||
|
||||
if (blameCase.type !== BlameRequestType.THIRD_PARTY) {
|
||||
return "Fanavaran third-party financial submit only applies to THIRD_PARTY claims";
|
||||
if (!isFanavaranSubmitSupportedBlameType(blameCase.type)) {
|
||||
return "Fanavaran submit only applies to THIRD_PARTY and CAR_BODY claims";
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -7197,9 +7416,9 @@ export class ClaimRequestManagementService {
|
||||
if (!blameCase) {
|
||||
throw new NotFoundException("Blame case not found");
|
||||
}
|
||||
if (blameCase.type !== BlameRequestType.THIRD_PARTY) {
|
||||
if (!isFanavaranSubmitSupportedBlameType(blameCase.type)) {
|
||||
throw new BadRequestException(
|
||||
"Fanavaran damage-case submit only applies to THIRD_PARTY claims",
|
||||
"Fanavaran damage-case submit only applies to THIRD_PARTY and CAR_BODY claims",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7215,7 +7434,7 @@ export class ClaimRequestManagementService {
|
||||
// Guardrail: never send empty/null licence fields even if manual overrides arrive.
|
||||
payload.LicenceNo = this.ensureFanavaranNonEmptyLicenceNo(payload.LicenceNo);
|
||||
|
||||
const url = `${this.FANAVARAN_SUBMIT_URL}/${claimCase.claimId}/dmg-cases`;
|
||||
const url = `${fanavaranClaimsBaseUrl(fanavaranClaimProductFromBlameType(blameCase.type))}/${claimCase.claimId}/dmg-cases`;
|
||||
this.logger.log(
|
||||
`[executeFanavaranDamageCaseSubmit] claimCaseId=${claimCaseId} claimId=${claimCase.claimId} payload.DriverId=${payload.DriverId ?? "NULL"} payload keys=${Object.keys(payload).join(", ")}`,
|
||||
);
|
||||
@@ -7439,7 +7658,7 @@ export class ClaimRequestManagementService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-submit the minimum third-party Fanavaran claim as soon as the local
|
||||
* Auto-submit the minimum Fanavaran claim as soon as the local
|
||||
* claim case exists. This preserves progress in Fanavaran even if parties stop
|
||||
* later in the damage-assessment flow.
|
||||
*/
|
||||
@@ -8046,6 +8265,7 @@ export class ClaimRequestManagementService {
|
||||
auditSession,
|
||||
claimCaseId,
|
||||
);
|
||||
const submitUrl = await this.fanavaranClaimsUrlForClaim(claimCaseId);
|
||||
|
||||
const requestHeaders = {
|
||||
...headers,
|
||||
@@ -8055,7 +8275,7 @@ export class ClaimRequestManagementService {
|
||||
session: auditSession,
|
||||
step: FanavaranAuditStep.SUBMIT_CLAIM,
|
||||
status: FanavaranAuditStatus.STARTED,
|
||||
requestUrl: this.FANAVARAN_SUBMIT_URL,
|
||||
requestUrl: submitUrl,
|
||||
requestMethod: "POST",
|
||||
requestHeaders,
|
||||
requestBody: fanavaranData,
|
||||
@@ -8065,7 +8285,7 @@ export class ClaimRequestManagementService {
|
||||
|
||||
try {
|
||||
const response = await firstValueFrom(
|
||||
this.httpService.post(this.FANAVARAN_SUBMIT_URL, fanavaranData, {
|
||||
this.httpService.post(submitUrl, fanavaranData, {
|
||||
headers: requestHeaders,
|
||||
}),
|
||||
);
|
||||
@@ -8086,7 +8306,7 @@ export class ClaimRequestManagementService {
|
||||
session: auditSession,
|
||||
step: FanavaranAuditStep.SUBMIT_CLAIM,
|
||||
status: FanavaranAuditStatus.SUCCESS,
|
||||
requestUrl: this.FANAVARAN_SUBMIT_URL,
|
||||
requestUrl: submitUrl,
|
||||
requestMethod: "POST",
|
||||
httpStatus: response.status,
|
||||
requestHeaders: exchange.requestHeaders,
|
||||
@@ -8138,7 +8358,7 @@ export class ClaimRequestManagementService {
|
||||
session: auditSession,
|
||||
step: FanavaranAuditStep.SUBMIT_CLAIM,
|
||||
status: FanavaranAuditStatus.FAILURE,
|
||||
requestUrl: this.FANAVARAN_SUBMIT_URL,
|
||||
requestUrl: submitUrl,
|
||||
requestMethod: "POST",
|
||||
httpStatus: exchange.httpStatus,
|
||||
requestHeaders: exchange.requestHeaders,
|
||||
|
||||
@@ -97,14 +97,26 @@ export class FanavaranSyncStage {
|
||||
@Prop({ type: Number })
|
||||
vehicleKindId?: number;
|
||||
|
||||
/** How VehicleKindId was resolved: vehicle-inquiry | car-type-lookup */
|
||||
@Prop({ type: String })
|
||||
vehicleKindSource?: string;
|
||||
|
||||
/** Cached Fanavaran PlaqueKindId from VIN inquiry / vehicle GET. */
|
||||
@Prop({ type: Number })
|
||||
plaqueKindId?: number;
|
||||
|
||||
/** How PlaqueKindId was resolved: vehicle-inquiry | config-default */
|
||||
@Prop({ type: String })
|
||||
plaqueKindSource?: string;
|
||||
|
||||
/** Cached Fanavaran PlaqueSampleId from VIN inquiry / vehicle GET. */
|
||||
@Prop({ type: Number })
|
||||
plaqueSampleId?: number;
|
||||
|
||||
/** How PlaqueSampleId was resolved: vehicle-inquiry | config-default */
|
||||
@Prop({ type: String })
|
||||
plaqueSampleSource?: string;
|
||||
|
||||
/**
|
||||
* Cached Fanavaran AccidentVehicleUsedId, resolved from VIN inquiry / vehicle GET
|
||||
* UsedId (lookup-filter safe). Not the Mongo tenant default.
|
||||
|
||||
133
src/claim-request-management/fanavaran-claim-product.spec.ts
Normal file
133
src/claim-request-management/fanavaran-claim-product.spec.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import { BlameRequestType } from "src/Types&Enums/blame-request-management/blameRequestType.enum";
|
||||
import { PartyRole } from "src/request-management/entities/schema/partyRole.enum";
|
||||
import {
|
||||
fanavaranClaimProductFromBlameType,
|
||||
fanavaranClaimsBaseUrl,
|
||||
fanavaranPartyInquirySources,
|
||||
isFanavaranSubmitSupportedBlameType,
|
||||
pickCarBodyPolicyNationalCode,
|
||||
pickStoredCarBodyPolicyId,
|
||||
} from "./fanavaran-claim-product";
|
||||
|
||||
describe("fanavaran claim product", () => {
|
||||
it("maps CAR_BODY blame files onto the hull claims resource", () => {
|
||||
expect(fanavaranClaimProductFromBlameType(BlameRequestType.CAR_BODY)).toBe(
|
||||
"car-body",
|
||||
);
|
||||
expect(fanavaranClaimsBaseUrl("car-body")).toBe(
|
||||
"https://apimanager.iraneit.com/BimeApiManager/api/BimeApi/v2.0/car/vehicle-hull-claims",
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps THIRD_PARTY on the financial third-party claims resource", () => {
|
||||
expect(
|
||||
fanavaranClaimProductFromBlameType(BlameRequestType.THIRD_PARTY),
|
||||
).toBe("third-party");
|
||||
expect(fanavaranClaimsBaseUrl("third-party")).toBe(
|
||||
"https://apimanager.iraneit.com/BimeApiManager/api/BimeApi/v2.0/car/third-party-car-financial-claims",
|
||||
);
|
||||
});
|
||||
|
||||
it("allows Fanavaran submit for both car products", () => {
|
||||
expect(isFanavaranSubmitSupportedBlameType("THIRD_PARTY")).toBe(true);
|
||||
expect(isFanavaranSubmitSupportedBlameType("CAR_BODY")).toBe(true);
|
||||
expect(isFanavaranSubmitSupportedBlameType("OTHER")).toBe(false);
|
||||
});
|
||||
|
||||
it("reuses the car-body inquiry PolicyId without another policy list call", () => {
|
||||
const policyId = pickStoredCarBodyPolicyId({
|
||||
parties: [
|
||||
{
|
||||
role: PartyRole.FIRST,
|
||||
insurance: { carBodyInsurance: { policyId: 15292336 } },
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(policyId).toBe(15292336);
|
||||
});
|
||||
|
||||
it("reads PolicyId from nested car-body inquiry raw when the flat field is missing", () => {
|
||||
const policyId = pickStoredCarBodyPolicyId({
|
||||
parties: [
|
||||
{
|
||||
role: PartyRole.FIRST,
|
||||
vehicle: {
|
||||
inquiry: {
|
||||
carBody: {
|
||||
mapped: {},
|
||||
raw: { policyId: "15292336", policy: { PolicyId: 99 } },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(policyId).toBe(15292336);
|
||||
});
|
||||
|
||||
it("uses the first-party car-body policyholder national code", () => {
|
||||
const nationalCode = pickCarBodyPolicyNationalCode({
|
||||
parties: [
|
||||
{
|
||||
role: PartyRole.FIRST,
|
||||
person: { nationalCodeOfInsurer: "0012345678" },
|
||||
insurance: {
|
||||
carBodyInsurance: { ownerNationalCode: "0098765432" },
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(nationalCode).toBe("0012345678");
|
||||
});
|
||||
|
||||
it("flattens car-body inquiry fields onto the damage-case aliases", () => {
|
||||
const { mapped } = fanavaranPartyInquirySources("car-body", {
|
||||
insurance: {
|
||||
carBodyInsurance: {
|
||||
policyNumber: "70019846985",
|
||||
chassisNumber: "IRNKAEK4150012345",
|
||||
},
|
||||
},
|
||||
vehicle: {
|
||||
inquiry: {
|
||||
carBody: {
|
||||
mapped: {
|
||||
policyId: 15292336,
|
||||
policyNumber: "70019846985",
|
||||
chassisNumber: "IRNKAEK4150012345",
|
||||
motorNumber: "M123",
|
||||
vin: "IRNKAEK4150012345",
|
||||
StartDate: "1405/05/18",
|
||||
EndDate: "1406/05/18",
|
||||
builtYear: 1402,
|
||||
platePartOne: "29",
|
||||
plateLetterTitle: "د",
|
||||
platePartThree: "782",
|
||||
plateSerialNumber: "44",
|
||||
},
|
||||
raw: {
|
||||
policy: { CINumber: "70019846985" },
|
||||
vehicle: { ChassisNo: "IRNKAEK4150012345" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(mapped.policyId).toBe(15292336);
|
||||
expect(mapped.PrntCmpDocNo).toBe("70019846985");
|
||||
expect(mapped.ShsNum).toBe("IRNKAEK4150012345");
|
||||
expect(mapped.MtrNum).toBe("M123");
|
||||
expect(mapped.VIN).toBe("IRNKAEK4150012345");
|
||||
expect(mapped.HBgnDte).toBe("1405/05/18");
|
||||
expect(mapped.HEndDte).toBe("1406/05/18");
|
||||
expect(mapped.PrdDte).toBe(1402);
|
||||
expect(mapped.plk1).toBe("29");
|
||||
expect(mapped.plk2).toBe("د");
|
||||
expect(mapped.plk3).toBe("782");
|
||||
expect(mapped.plksrl).toBe("44");
|
||||
});
|
||||
});
|
||||
227
src/claim-request-management/fanavaran-claim-product.ts
Normal file
227
src/claim-request-management/fanavaran-claim-product.ts
Normal file
@@ -0,0 +1,227 @@
|
||||
import { BlameRequestType } from "src/Types&Enums/blame-request-management/blameRequestType.enum";
|
||||
import { PartyRole } from "src/request-management/entities/schema/partyRole.enum";
|
||||
import {
|
||||
FANAVARAN_CAR_BODY_LINE_ID,
|
||||
FANAVARAN_THIRD_PARTY_LINE_ID,
|
||||
asObjectRecord,
|
||||
parseFanavaranId,
|
||||
type FanavaranCarPolicyProduct,
|
||||
} from "src/lookups/fanavaran-last-car-policy";
|
||||
|
||||
export type FanavaranClaimProduct = FanavaranCarPolicyProduct;
|
||||
|
||||
const FANAVARAN_CLAIMS_HOST =
|
||||
"https://apimanager.iraneit.com/BimeApiManager/api/BimeApi/v2.0/car";
|
||||
|
||||
export function fanavaranClaimProductFromBlameType(
|
||||
type?: string | null,
|
||||
): FanavaranClaimProduct {
|
||||
return type === BlameRequestType.CAR_BODY || type === "CAR_BODY"
|
||||
? "car-body"
|
||||
: "third-party";
|
||||
}
|
||||
|
||||
export function isFanavaranSubmitSupportedBlameType(
|
||||
type?: string | null,
|
||||
): boolean {
|
||||
return (
|
||||
type === BlameRequestType.THIRD_PARTY ||
|
||||
type === "THIRD_PARTY" ||
|
||||
type === BlameRequestType.CAR_BODY ||
|
||||
type === "CAR_BODY"
|
||||
);
|
||||
}
|
||||
|
||||
export function fanavaranClaimsResourcePath(
|
||||
product: FanavaranClaimProduct,
|
||||
): string {
|
||||
return product === "car-body"
|
||||
? "vehicle-hull-claims"
|
||||
: "third-party-car-financial-claims";
|
||||
}
|
||||
|
||||
export function fanavaranClaimsBaseUrl(
|
||||
product: FanavaranClaimProduct,
|
||||
): string {
|
||||
return `${FANAVARAN_CLAIMS_HOST}/${fanavaranClaimsResourcePath(product)}`;
|
||||
}
|
||||
|
||||
export function fanavaranInsuranceLineIdForProduct(
|
||||
product: FanavaranClaimProduct,
|
||||
): number {
|
||||
return product === "car-body"
|
||||
? FANAVARAN_CAR_BODY_LINE_ID
|
||||
: FANAVARAN_THIRD_PARTY_LINE_ID;
|
||||
}
|
||||
|
||||
type BlamePartyForCarBodyPolicy = {
|
||||
role?: string;
|
||||
person?: { nationalCodeOfInsurer?: unknown };
|
||||
insurance?: {
|
||||
carBodyInsurance?: {
|
||||
policyId?: unknown;
|
||||
ownerNationalCode?: unknown;
|
||||
insurerNationalCode?: unknown;
|
||||
policyNumber?: unknown;
|
||||
chassisNumber?: unknown;
|
||||
motorNumber?: unknown;
|
||||
vin?: unknown;
|
||||
startDate?: unknown;
|
||||
endDate?: unknown;
|
||||
};
|
||||
};
|
||||
vehicle?: {
|
||||
inquiry?: {
|
||||
mapped?: Record<string, unknown>;
|
||||
raw?: Record<string, unknown> | { data?: Record<string, unknown> };
|
||||
carBody?: {
|
||||
mapped?: Record<string, unknown>;
|
||||
raw?: Record<string, unknown> & {
|
||||
policyId?: unknown;
|
||||
policy?: Record<string, unknown>;
|
||||
vehicle?: Record<string, unknown>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
function firstCarBodyParty(
|
||||
blame?: { parties?: BlamePartyForCarBodyPolicy[] } | null,
|
||||
): BlamePartyForCarBodyPolicy | null {
|
||||
const parties = blame?.parties ?? [];
|
||||
return (
|
||||
parties.find((party) => party?.role === PartyRole.FIRST) ??
|
||||
parties[0] ??
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
function nonEmptyText(value: unknown): string | null {
|
||||
if (value == null) return null;
|
||||
const text = String(value).trim();
|
||||
return text ? text : null;
|
||||
}
|
||||
|
||||
export function pickStoredCarBodyPolicyId(
|
||||
blame?: { parties?: BlamePartyForCarBodyPolicy[] } | null,
|
||||
): number | null {
|
||||
const party = firstCarBodyParty(blame);
|
||||
const carBody = party?.vehicle?.inquiry?.carBody;
|
||||
const raw = asObjectRecord(carBody?.raw) ?? {};
|
||||
const mapped = asObjectRecord(carBody?.mapped) ?? {};
|
||||
const rawPolicy = asObjectRecord(raw.policy);
|
||||
return parseFanavaranId(
|
||||
party?.insurance?.carBodyInsurance?.policyId ??
|
||||
mapped.policyId ??
|
||||
raw.policyId ??
|
||||
rawPolicy?.PolicyId,
|
||||
);
|
||||
}
|
||||
|
||||
export function pickCarBodyPolicyNationalCode(
|
||||
blame?: { parties?: BlamePartyForCarBodyPolicy[] } | null,
|
||||
): string | null {
|
||||
const party = firstCarBodyParty(blame);
|
||||
return (
|
||||
nonEmptyText(party?.person?.nationalCodeOfInsurer) ??
|
||||
nonEmptyText(party?.insurance?.carBodyInsurance?.ownerNationalCode) ??
|
||||
nonEmptyText(party?.insurance?.carBodyInsurance?.insurerNationalCode)
|
||||
);
|
||||
}
|
||||
|
||||
export function fanavaranPartyInquirySources(
|
||||
product: FanavaranClaimProduct,
|
||||
party?: BlamePartyForCarBodyPolicy | null,
|
||||
): {
|
||||
mapped: Record<string, any>;
|
||||
raw: Record<string, any>;
|
||||
} {
|
||||
const inquiry = party?.vehicle?.inquiry ?? {};
|
||||
if (product !== "car-body") {
|
||||
const mapped = asObjectRecord(inquiry.mapped) ?? {};
|
||||
const rawValue = inquiry.raw as
|
||||
| Record<string, unknown>
|
||||
| { data?: Record<string, unknown> }
|
||||
| undefined;
|
||||
const raw =
|
||||
asObjectRecord(
|
||||
rawValue &&
|
||||
typeof rawValue === "object" &&
|
||||
"data" in rawValue &&
|
||||
rawValue.data != null &&
|
||||
typeof rawValue.data === "object" &&
|
||||
!Array.isArray(rawValue.data)
|
||||
? rawValue.data
|
||||
: rawValue,
|
||||
) ?? {};
|
||||
return { mapped, raw };
|
||||
}
|
||||
|
||||
const carBody = inquiry.carBody ?? {};
|
||||
const mapped = asObjectRecord(carBody.mapped) ?? {};
|
||||
const raw = asObjectRecord(carBody.raw) ?? {};
|
||||
const policy = asObjectRecord(raw.policy) ?? {};
|
||||
const vehicle = asObjectRecord(raw.vehicle) ?? {};
|
||||
const insurance = party?.insurance?.carBodyInsurance ?? {};
|
||||
|
||||
const policyNumber =
|
||||
mapped.policyNumber ??
|
||||
insurance.policyNumber ??
|
||||
policy.CINumber ??
|
||||
policy.PolicyNo;
|
||||
const chassisNumber =
|
||||
mapped.chassisNumber ?? insurance.chassisNumber ?? vehicle.ChassisNo;
|
||||
const motorNumber =
|
||||
mapped.motorNumber ?? insurance.motorNumber ?? vehicle.MotorNo;
|
||||
const vin = mapped.vin ?? insurance.vin ?? vehicle.VIN ?? vehicle.ChassisNo;
|
||||
const beginDate =
|
||||
mapped.StartDate ?? mapped.startDate ?? insurance.startDate ?? policy.BeginDate;
|
||||
const endDate =
|
||||
mapped.EndDate ?? mapped.endDate ?? insurance.endDate ?? policy.EndDate;
|
||||
const builtYear = mapped.builtYear ?? vehicle.BuiltYear;
|
||||
const plaque = asObjectRecord(vehicle.plaque) ?? {};
|
||||
const plk1 =
|
||||
mapped.platePartOne ?? plaque.leftTwoDigits ?? vehicle.PlaqueLeftNo;
|
||||
const plk2 =
|
||||
mapped.plateLetterTitle ?? plaque.serialLetter;
|
||||
const plk3 =
|
||||
mapped.platePartThree ?? plaque.threeDigits ?? vehicle.PlaqueRightNo;
|
||||
const plksrl =
|
||||
mapped.plateSerialNumber ??
|
||||
plaque.rightTwoDigits ??
|
||||
vehicle.PlaqueSerial;
|
||||
|
||||
return {
|
||||
mapped: {
|
||||
...mapped,
|
||||
...insurance,
|
||||
policyId:
|
||||
mapped.policyId ??
|
||||
insurance.policyId ??
|
||||
raw.policyId ??
|
||||
policy.PolicyId,
|
||||
PrntCmpDocNo: policyNumber,
|
||||
PlcyUnqCod: policy.PolicyUnqCod ?? mapped.policyNumber,
|
||||
ShsNum: chassisNumber,
|
||||
MtrNum: motorNumber,
|
||||
VIN: vin,
|
||||
HBgnDte: beginDate,
|
||||
HEndDte: endDate,
|
||||
PrdDte: builtYear,
|
||||
plk1,
|
||||
Plk1: plk1,
|
||||
plk2,
|
||||
Plk2: plk2,
|
||||
plk3,
|
||||
Plk3: plk3,
|
||||
plksrl,
|
||||
PlkSrl: plksrl,
|
||||
},
|
||||
raw: {
|
||||
...policy,
|
||||
...vehicle,
|
||||
...raw,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
import {
|
||||
canReuseCachedFanavaranVehicleIds,
|
||||
collectPartyVinCandidates,
|
||||
fanavaranIdOrConfigDefault,
|
||||
fanavaranPayloadMatchesDamagedIdentity,
|
||||
fanavaranVehicleMatchesDamagedIdentity,
|
||||
filterPoliciesByLine,
|
||||
insuranceLineLabel,
|
||||
parseLastCarPolicyInput,
|
||||
@@ -10,6 +13,7 @@ import {
|
||||
pickVinFromPartyVehicle,
|
||||
plaqueLetterFromMiddleCode,
|
||||
plaqueMatchesVehicle,
|
||||
selectFanavaranVehicleForDamageCase,
|
||||
selectLastAmongCarMatches,
|
||||
toAppPlaque,
|
||||
vehicleMatchesCar,
|
||||
@@ -311,4 +315,154 @@ describe("fanavaran last car policy", () => {
|
||||
it("returns null when there are no matches", () => {
|
||||
expect(selectLastAmongCarMatches([], today)).toBeNull();
|
||||
});
|
||||
|
||||
const damagedIdentity = {
|
||||
vin: "IRPC941V2BD798656",
|
||||
chassis: "NAS431100E5798656",
|
||||
plaque: { left: "59", letter: "ی", right: "419", serial: "78" },
|
||||
policyCINumber: "16040446424",
|
||||
};
|
||||
|
||||
const culpritVehicle = {
|
||||
Id: 475449,
|
||||
VIN: "IRFC891V7D2301065",
|
||||
ChassisNo: "NAAP41FD5BJ301065",
|
||||
VehicleKindId: 6953,
|
||||
VersionNo: 3,
|
||||
PlaqueKindId: 8,
|
||||
PlaqueSampleId: 10,
|
||||
PlaqueLeftNo: "56",
|
||||
PlaqueMiddleCodeId: 5,
|
||||
PlaqueRightNo: "394",
|
||||
PlaqueSerial: "66",
|
||||
PlaqueNo: "394د56",
|
||||
};
|
||||
|
||||
it("does not treat the culprit vehicle as the damaged car", () => {
|
||||
expect(
|
||||
fanavaranVehicleMatchesDamagedIdentity(culpritVehicle, damagedIdentity),
|
||||
).toBe(false);
|
||||
expect(
|
||||
selectFanavaranVehicleForDamageCase(
|
||||
[
|
||||
culpritVehicle,
|
||||
{ Id: 9, VIN: "OTHER", PlaqueKindId: 15, PlaqueSampleId: 24 },
|
||||
],
|
||||
damagedIdentity,
|
||||
),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("ignores a PlaqueKindId 15 row that does not share VIN or plate", () => {
|
||||
expect(
|
||||
selectFanavaranVehicleForDamageCase(
|
||||
[
|
||||
{ Id: 1, VIN: "IRPC941V2BD798656" },
|
||||
{
|
||||
Id: 2,
|
||||
VIN: "IRFC901V119493683",
|
||||
PlaqueKindId: 15,
|
||||
PlaqueSampleId: 24,
|
||||
PlaqueLeftNo: "62",
|
||||
PlaqueMiddleCodeId: 14,
|
||||
PlaqueRightNo: "278",
|
||||
PlaqueSerial: "50",
|
||||
},
|
||||
],
|
||||
damagedIdentity,
|
||||
),
|
||||
).toEqual({ Id: 1, VIN: "IRPC941V2BD798656" });
|
||||
});
|
||||
|
||||
it("requires plate agreement when the inquiry row has plaque parts", () => {
|
||||
expect(
|
||||
selectFanavaranVehicleForDamageCase(
|
||||
[
|
||||
{
|
||||
Id: 1,
|
||||
VIN: "IRPC941V2BD798656",
|
||||
PlaqueKindId: 15,
|
||||
PlaqueLeftNo: "56",
|
||||
PlaqueMiddleCodeId: 5,
|
||||
PlaqueRightNo: "394",
|
||||
PlaqueSerial: "66",
|
||||
},
|
||||
],
|
||||
damagedIdentity,
|
||||
),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("picks the newest matching version of the damaged VIN and plate", () => {
|
||||
expect(
|
||||
selectFanavaranVehicleForDamageCase(
|
||||
[
|
||||
{
|
||||
Id: 10,
|
||||
VIN: "IRPC941V2BD798656",
|
||||
ChassisNo: "NAS431100E5798656",
|
||||
VersionNo: 1,
|
||||
PlaqueKindId: 15,
|
||||
PlaqueSampleId: 24,
|
||||
PlaqueLeftNo: "59",
|
||||
PlaqueMiddleCodeId: 16,
|
||||
PlaqueRightNo: "419",
|
||||
PlaqueSerial: "78",
|
||||
},
|
||||
{
|
||||
Id: 10,
|
||||
VIN: "IRPC941V2BD798656",
|
||||
ChassisNo: "NAS431100E5798656",
|
||||
VersionNo: 3,
|
||||
PlaqueKindId: 8,
|
||||
PlaqueSampleId: 10,
|
||||
PlaqueLeftNo: "59",
|
||||
PlaqueMiddleCodeId: 16,
|
||||
PlaqueRightNo: "419",
|
||||
PlaqueSerial: "78",
|
||||
},
|
||||
],
|
||||
damagedIdentity,
|
||||
)?.PlaqueKindId,
|
||||
).toBe(8);
|
||||
});
|
||||
|
||||
it("rejects cached payload ids from a different VIN, plate, or CI number", () => {
|
||||
expect(
|
||||
fanavaranPayloadMatchesDamagedIdentity(
|
||||
{
|
||||
VIN: "IRFC891V7D2301065",
|
||||
ChassisNo: "NAAP41FD5BJ301065",
|
||||
PolicyCINumber: "16018466143",
|
||||
PlaqueKindId: 8,
|
||||
PlaqueLeftNo: "56",
|
||||
PlaqueMiddleCodeId: 5,
|
||||
PlaqueRightNo: "394",
|
||||
PlaqueSerial: "66",
|
||||
},
|
||||
damagedIdentity,
|
||||
),
|
||||
).toBe(false);
|
||||
expect(canReuseCachedFanavaranVehicleIds("config-default")).toBe(false);
|
||||
expect(canReuseCachedFanavaranVehicleIds("vehicle-inquiry")).toBe(true);
|
||||
expect(canReuseCachedFanavaranVehicleIds(undefined)).toBe(false);
|
||||
});
|
||||
|
||||
it("accepts cache only when VIN, plate, and CI number all agree", () => {
|
||||
expect(
|
||||
fanavaranPayloadMatchesDamagedIdentity(
|
||||
{
|
||||
VIN: "IRPC941V2BD798656",
|
||||
ChassisNo: "NAS431100E5798656",
|
||||
PolicyCINumber: "16040446424",
|
||||
PlaqueLeftNo: "59",
|
||||
PlaqueMiddleCodeId: 16,
|
||||
PlaqueRightNo: "419",
|
||||
PlaqueSerial: "78",
|
||||
PlaqueKindId: 8,
|
||||
},
|
||||
damagedIdentity,
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -262,6 +262,159 @@ export function pickFanavaranVehicleFromInquiry(
|
||||
return asObjectRecord(inquired);
|
||||
}
|
||||
|
||||
export type FanavaranDamagedVehicleIdentity = {
|
||||
vin?: string | null;
|
||||
chassis?: string | null;
|
||||
plaque?: FanavaranPlaqueParts | null;
|
||||
policyCINumber?: string | null;
|
||||
vehicleKindId?: number | null;
|
||||
};
|
||||
|
||||
export function asVehicleInquiryRows(
|
||||
inquired: unknown,
|
||||
): Record<string, unknown>[] {
|
||||
if (Array.isArray(inquired)) {
|
||||
return inquired
|
||||
.map((row) => asObjectRecord(row))
|
||||
.filter((row): row is Record<string, unknown> => row != null);
|
||||
}
|
||||
const one = asObjectRecord(inquired);
|
||||
return one ? [one] : [];
|
||||
}
|
||||
|
||||
export function normalizePolicyCINumber(value: unknown): string {
|
||||
return toEnglishDigits(value).replace(/\s+/g, "").trim();
|
||||
}
|
||||
|
||||
function vehicleHasPlaqueParts(vehicle: Record<string, unknown>): boolean {
|
||||
return Boolean(
|
||||
asPlaqueText(vehicle.PlaqueLeftNo ?? vehicle.plaqueLeftNo) &&
|
||||
(parseFanavaranId(
|
||||
vehicle.PlaqueMiddleCodeId ?? vehicle.plaqueMiddleCodeId,
|
||||
) != null ||
|
||||
String(vehicle.PlaqueLetter ?? vehicle.plaqueLetter ?? "").trim()) &&
|
||||
asPlaqueText(vehicle.PlaqueRightNo ?? vehicle.plaqueRightNo) &&
|
||||
asPlaqueText(vehicle.PlaqueSerial ?? vehicle.plaqueSerial),
|
||||
);
|
||||
}
|
||||
|
||||
function vinEquals(left: unknown, right: unknown): boolean {
|
||||
const a = normalizeVin(left);
|
||||
const b = normalizeVin(right);
|
||||
return Boolean(a && b && a === b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same physical car: VIN and plaque must not contradict the damaged-party
|
||||
* inquiry. A row that only "has PlaqueKindId" is not enough.
|
||||
*/
|
||||
export function fanavaranVehicleMatchesDamagedIdentity(
|
||||
vehicle: Record<string, unknown> | null,
|
||||
identity: FanavaranDamagedVehicleIdentity,
|
||||
): boolean {
|
||||
if (!vehicle) return false;
|
||||
|
||||
const identityVin = normalizeVin(identity.vin);
|
||||
const identityChassis = normalizeVin(identity.chassis);
|
||||
const vehicleVin = pickVehicleVin(vehicle);
|
||||
const vehicleChassis = normalizeVin(
|
||||
vehicle.ChassisNo ?? vehicle.chassisNo ?? vehicle.ShsNum,
|
||||
);
|
||||
|
||||
const vinMatched = Boolean(
|
||||
identityVin &&
|
||||
(vinEquals(vehicleVin, identityVin) ||
|
||||
vinEquals(vehicleChassis, identityVin)),
|
||||
);
|
||||
const chassisMatched = Boolean(
|
||||
identityChassis &&
|
||||
(vinEquals(vehicleVin, identityChassis) ||
|
||||
vinEquals(vehicleChassis, identityChassis)),
|
||||
);
|
||||
|
||||
if (identityVin && vehicleVin && !vinMatched && !chassisMatched) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const plaqueMatched = Boolean(
|
||||
identity.plaque && plaqueMatchesVehicle(identity.plaque, vehicle),
|
||||
);
|
||||
if (identity.plaque && vehicleHasPlaqueParts(vehicle) && !plaqueMatched) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return vinMatched || chassisMatched || plaqueMatched;
|
||||
}
|
||||
|
||||
export function fanavaranPayloadMatchesDamagedIdentity(
|
||||
payload: Record<string, unknown> | null,
|
||||
identity: FanavaranDamagedVehicleIdentity,
|
||||
): boolean {
|
||||
if (!payload) return false;
|
||||
|
||||
const payloadVin = normalizeVin(payload.VIN ?? payload.vin);
|
||||
const identityVin = normalizeVin(identity.vin);
|
||||
if (payloadVin && identityVin && payloadVin !== identityVin) return false;
|
||||
|
||||
const payloadChassis = normalizeVin(
|
||||
payload.ChassisNo ?? payload.chassisNo,
|
||||
);
|
||||
const identityChassis = normalizeVin(identity.chassis);
|
||||
if (payloadChassis && identityChassis && payloadChassis !== identityChassis) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const payloadCI = normalizePolicyCINumber(payload.PolicyCINumber);
|
||||
const identityCI = normalizePolicyCINumber(identity.policyCINumber);
|
||||
if (payloadCI && identityCI && payloadCI !== identityCI) return false;
|
||||
|
||||
if (identity.plaque && vehicleHasPlaqueParts(payload)) {
|
||||
if (!plaqueMatchesVehicle(identity.plaque, payload)) return false;
|
||||
}
|
||||
|
||||
const vinMatched = Boolean(payloadVin && identityVin && payloadVin === identityVin);
|
||||
const chassisMatched = Boolean(
|
||||
payloadChassis && identityChassis && payloadChassis === identityChassis,
|
||||
);
|
||||
const ciMatched = Boolean(payloadCI && identityCI && payloadCI === identityCI);
|
||||
const plaqueMatched = Boolean(
|
||||
identity.plaque && plaqueMatchesVehicle(identity.plaque, payload),
|
||||
);
|
||||
return vinMatched || chassisMatched || ciMatched || plaqueMatched;
|
||||
}
|
||||
|
||||
export function canReuseCachedFanavaranVehicleIds(source: unknown): boolean {
|
||||
return source === "vehicle-inquiry" || source === "vin-inquiry" || source === "vehicle-get";
|
||||
}
|
||||
|
||||
/**
|
||||
* Pick the VIN-inquiry / vehicle-GET row that is the damaged car.
|
||||
* Newest VersionNo wins among matches; PlaqueKindId is a tie-break only.
|
||||
*/
|
||||
export function selectFanavaranVehicleForDamageCase(
|
||||
inquired: unknown,
|
||||
identity: FanavaranDamagedVehicleIdentity,
|
||||
): Record<string, unknown> | null {
|
||||
const matches = asVehicleInquiryRows(inquired).filter((row) =>
|
||||
fanavaranVehicleMatchesDamagedIdentity(row, identity),
|
||||
);
|
||||
if (matches.length === 0) return null;
|
||||
|
||||
return matches.reduce((current, candidate) => {
|
||||
const currentVersion = parseFanavaranId(current.VersionNo) ?? 0;
|
||||
const candidateVersion = parseFanavaranId(candidate.VersionNo) ?? 0;
|
||||
if (candidateVersion !== currentVersion) {
|
||||
return candidateVersion > currentVersion ? candidate : current;
|
||||
}
|
||||
const currentHasKind =
|
||||
pickFanavaranPlaqueLookupFields(current).kindId != null;
|
||||
const candidateHasKind =
|
||||
pickFanavaranPlaqueLookupFields(candidate).kindId != null;
|
||||
if (candidateHasKind && !currentHasKind) return candidate;
|
||||
return current;
|
||||
});
|
||||
}
|
||||
|
||||
export function pickVinFromPartyVehicle(party: {
|
||||
vehicle?: { vin?: unknown; inquiry?: unknown };
|
||||
} | null | undefined): string | null {
|
||||
|
||||
Reference in New Issue
Block a user