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 { ClaimRequestManagementDbService } from "./entites/db-service/claim-request-management.db.service";
|
||||||
import { ClaimCaseDbService } from "./entites/db-service/claim-case.db.service";
|
import { ClaimCaseDbService } from "./entites/db-service/claim-case.db.service";
|
||||||
import { fanavaranClaimReferences } from "./fanavaran-claim-references";
|
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 { BlameRequestDbService } from "src/request-management/entities/db-service/blame-request.db.service";
|
||||||
import { CreateClaimFromBlameResponseDto } from "./dto/create-claim-v2.dto";
|
import { CreateClaimFromBlameResponseDto } from "./dto/create-claim-v2.dto";
|
||||||
import {
|
import {
|
||||||
@@ -181,6 +191,7 @@ import {
|
|||||||
fanavaranSubmitPath,
|
fanavaranSubmitPath,
|
||||||
getFanavaranClientProfile,
|
getFanavaranClientProfile,
|
||||||
resolveFanavaranClientKey,
|
resolveFanavaranClientKey,
|
||||||
|
resolveFanavaranProductContractId,
|
||||||
} from "src/core/config/fanavaran-client.config";
|
} from "src/core/config/fanavaran-client.config";
|
||||||
import { FanavaranAuditService } from "src/fanavaran/fanavaran-audit.service";
|
import { FanavaranAuditService } from "src/fanavaran/fanavaran-audit.service";
|
||||||
import { FanavaranAuthService } from "src/fanavaran/fanavaran-auth.service";
|
import { FanavaranAuthService } from "src/fanavaran/fanavaran-auth.service";
|
||||||
@@ -216,14 +227,21 @@ import {
|
|||||||
} from "./fanavaran-other-people";
|
} from "./fanavaran-other-people";
|
||||||
import {
|
import {
|
||||||
asObjectRecord,
|
asObjectRecord,
|
||||||
|
asVehicleInquiryRows,
|
||||||
|
canReuseCachedFanavaranVehicleIds,
|
||||||
collectPartyVinCandidates,
|
collectPartyVinCandidates,
|
||||||
|
completePlaqueParts,
|
||||||
fanavaranIdOrConfigDefault,
|
fanavaranIdOrConfigDefault,
|
||||||
|
fanavaranPayloadMatchesDamagedIdentity,
|
||||||
|
fanavaranVehicleMatchesDamagedIdentity,
|
||||||
normalizeVin,
|
normalizeVin,
|
||||||
parseFanavaranId,
|
parseFanavaranId,
|
||||||
pickFanavaranPlaqueLookupFields,
|
pickFanavaranPlaqueLookupFields,
|
||||||
pickFanavaranVehicleFromInquiry,
|
pickFanavaranVehicleFromInquiry,
|
||||||
pickVehicleId,
|
pickVehicleId,
|
||||||
pickVinFromPartyVehicle,
|
pickVinFromPartyVehicle,
|
||||||
|
selectFanavaranVehicleForDamageCase,
|
||||||
|
type FanavaranDamagedVehicleIdentity,
|
||||||
} from "src/lookups/fanavaran-last-car-policy";
|
} from "src/lookups/fanavaran-last-car-policy";
|
||||||
|
|
||||||
export interface FanavaranAutoSubmitResult {
|
export interface FanavaranAutoSubmitResult {
|
||||||
@@ -358,9 +376,8 @@ const FANAVARAN_PLATE_LETTER_CODE: Record<string, number> = {
|
|||||||
export class ClaimRequestManagementService {
|
export class ClaimRequestManagementService {
|
||||||
private readonly logger = new Logger(ClaimRequestManagementService.name);
|
private readonly logger = new Logger(ClaimRequestManagementService.name);
|
||||||
|
|
||||||
// Business submit URL (auth lives in FanavaranAuthService)
|
// Legacy Tejaratno third-party URL. V2 stages use fanavaranClaimsBaseUrl(product).
|
||||||
private readonly FANAVARAN_SUBMIT_URL =
|
private readonly FANAVARAN_SUBMIT_URL = fanavaranClaimsBaseUrl("third-party");
|
||||||
"https://apimanager.iraneit.com/BimeApiManager/api/BimeApi/v2.0/car/third-party-car-financial-claims";
|
|
||||||
|
|
||||||
// Authentication credentials
|
// Authentication credentials
|
||||||
private readonly APP_NAME = "fanhab";
|
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
|
* Damaged-car VIN inquiry: keep only rows that match VIN/plate/chassis.
|
||||||
* until Fanavaran returns PlaqueKindId (required on GEN.12).
|
* Then GET that VehicleId so PlaqueKindId is the current version, not a
|
||||||
|
* leftover row that merely had some PlaqueKindId.
|
||||||
*/
|
*/
|
||||||
private async fetchFanavaranVehicleByVinCandidates(
|
private async fetchFanavaranVehicleByVinCandidates(
|
||||||
clientKey: FanavaranClientKey,
|
clientKey: FanavaranClientKey,
|
||||||
vins: string[],
|
vins: string[],
|
||||||
|
identity: FanavaranDamagedVehicleIdentity,
|
||||||
|
product: FanavaranClaimProduct = "third-party",
|
||||||
): Promise<Record<string, unknown> | null> {
|
): 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) {
|
for (const vin of vins) {
|
||||||
try {
|
try {
|
||||||
const vehicle = await this.fetchFanavaranVehicleByVin(clientKey, vin);
|
const inquired = await this.fanavaranLookupService.inquiryByVin(
|
||||||
if (!vehicle) {
|
clientKey,
|
||||||
|
vin,
|
||||||
|
requestOptions,
|
||||||
|
);
|
||||||
|
const rows = asVehicleInquiryRows(inquired);
|
||||||
|
if (rows.length === 0) {
|
||||||
this.logger.warn(
|
this.logger.warn(
|
||||||
`[buildFanavaranDamageCasePayload] VIN inquiry for ${vin} returned no vehicle`,
|
`[buildFanavaranDamageCasePayload] VIN inquiry for ${vin} returned no vehicle`,
|
||||||
);
|
);
|
||||||
continue;
|
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(
|
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;
|
matched = selected;
|
||||||
if (!fallback) fallback = vehicle;
|
break;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.warn(
|
this.logger.warn(
|
||||||
`[buildFanavaranDamageCasePayload] VIN inquiry failed for ${vin}: ${
|
`[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(
|
private async resolveAccidentVehicleUsedFromPolicy(
|
||||||
clientKey: FanavaranClientKey,
|
clientKey: FanavaranClientKey,
|
||||||
policyId: number,
|
policyId: number,
|
||||||
|
product: FanavaranClaimProduct = "third-party",
|
||||||
): Promise<{ usedId: number | null; vehicleKindId: number | null }> {
|
): Promise<{ usedId: number | null; vehicleKindId: number | null }> {
|
||||||
try {
|
try {
|
||||||
const policy = asObjectRecord(
|
const requestOptions = {
|
||||||
await this.fanavaranLookupService.thirdPartyPolicyById(
|
contractIdOverride: resolveFanavaranProductContractId(
|
||||||
clientKey,
|
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);
|
const vehicleId = pickVehicleId(policy?.VehicleId ?? policy?.vehicleId);
|
||||||
if (vehicleId === null) {
|
if (vehicleId === null) {
|
||||||
@@ -3992,6 +4077,7 @@ export class ClaimRequestManagementService {
|
|||||||
clientKey,
|
clientKey,
|
||||||
vehicleId,
|
vehicleId,
|
||||||
versionNo ?? undefined,
|
versionNo ?? undefined,
|
||||||
|
requestOptions,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
@@ -4013,6 +4099,7 @@ export class ClaimRequestManagementService {
|
|||||||
parties: unknown[];
|
parties: unknown[];
|
||||||
guiltyPartyId?: unknown;
|
guiltyPartyId?: unknown;
|
||||||
policyId?: number | null;
|
policyId?: number | null;
|
||||||
|
product?: FanavaranClaimProduct;
|
||||||
}): Promise<{
|
}): Promise<{
|
||||||
usedId: number | null;
|
usedId: number | null;
|
||||||
vehicleKindId: number | null;
|
vehicleKindId: number | null;
|
||||||
@@ -4038,6 +4125,7 @@ export class ClaimRequestManagementService {
|
|||||||
const fromPolicy = await this.resolveAccidentVehicleUsedFromPolicy(
|
const fromPolicy = await this.resolveAccidentVehicleUsedFromPolicy(
|
||||||
input.clientKey,
|
input.clientKey,
|
||||||
input.policyId,
|
input.policyId,
|
||||||
|
input.product ?? "third-party",
|
||||||
);
|
);
|
||||||
if (fromPolicy.usedId != null) {
|
if (fromPolicy.usedId != null) {
|
||||||
return { ...fromPolicy, source: "policy-vehicle" };
|
return { ...fromPolicy, source: "policy-vehicle" };
|
||||||
@@ -4144,14 +4232,9 @@ export class ClaimRequestManagementService {
|
|||||||
const person = damagedParty?.person ?? {};
|
const person = damagedParty?.person ?? {};
|
||||||
const vehicle = damagedParty?.vehicle ?? {};
|
const vehicle = damagedParty?.vehicle ?? {};
|
||||||
const insurance = damagedParty?.insurance ?? {};
|
const insurance = damagedParty?.insurance ?? {};
|
||||||
// Prefer nested inquiry (ESG/Tejarat) on the damaged party — not flattened insurance.*
|
const product = fanavaranClaimProductFromBlameType(input.blameCase?.type);
|
||||||
const inquiryRawPayload =
|
const { mapped: inquiryMapped, raw: inquiryRawPayload } =
|
||||||
vehicle?.inquiry?.raw?.data != null &&
|
fanavaranPartyInquirySources(product, damagedParty);
|
||||||
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 inquiryRaw = inquiryRawPayload ?? {};
|
const inquiryRaw = inquiryRawPayload ?? {};
|
||||||
|
|
||||||
const plate =
|
const plate =
|
||||||
@@ -4213,20 +4296,38 @@ export class ClaimRequestManagementService {
|
|||||||
]);
|
]);
|
||||||
const builtYearFromInquiry = builtYearRaw ? Number(builtYearRaw) || null : null;
|
const builtYearFromInquiry = builtYearRaw ? Number(builtYearRaw) || null : null;
|
||||||
|
|
||||||
const damagedVinCandidates: string[] = [];
|
const damagedVin =
|
||||||
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",
|
||||||
]),
|
]) ?? collectPartyVinCandidates(damagedParty)[0] ??
|
||||||
this.pickPartyInquiryField(inquiryMapped, inquiryRaw, [
|
null;
|
||||||
"ShsNum",
|
const damagedChassis = this.pickPartyInquiryField(inquiryMapped, inquiryRaw, [
|
||||||
"ChassisNumberField",
|
"ShsNum",
|
||||||
"shsNam",
|
"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);
|
const vin = normalizeVin(value);
|
||||||
if (!vin || seenVins.has(vin)) continue;
|
if (!vin || seenVins.has(vin)) continue;
|
||||||
@@ -4236,29 +4337,46 @@ export class ClaimRequestManagementService {
|
|||||||
const cachedDamage =
|
const cachedDamage =
|
||||||
(input.claimCase as any)?.fanavaranSync?.damageCase ?? {};
|
(input.claimCase as any)?.fanavaranSync?.damageCase ?? {};
|
||||||
const lastDamagePayload = asObjectRecord(cachedDamage.lastPayload);
|
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(
|
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(
|
const apiVehicle = await this.fetchFanavaranVehicleByVinCandidates(
|
||||||
input.clientKey,
|
input.clientKey,
|
||||||
damagedVinCandidates,
|
damagedVinCandidates,
|
||||||
|
damagedIdentity,
|
||||||
|
product,
|
||||||
);
|
);
|
||||||
const apiPlaque = pickFanavaranPlaqueLookupFields(apiVehicle);
|
const apiPlaque = pickFanavaranPlaqueLookupFields(apiVehicle);
|
||||||
const inquiryPlaque = pickFanavaranPlaqueLookupFields(
|
const inquiryVehicle =
|
||||||
asObjectRecord(inquiryMapped) ?? asObjectRecord(inquiryRaw),
|
asObjectRecord(inquiryMapped) ?? asObjectRecord(inquiryRaw);
|
||||||
);
|
const inquiryPlaque = fanavaranVehicleMatchesDamagedIdentity(
|
||||||
|
inquiryVehicle,
|
||||||
|
damagedIdentity,
|
||||||
|
)
|
||||||
|
? pickFanavaranPlaqueLookupFields(inquiryVehicle)
|
||||||
|
: pickFanavaranPlaqueLookupFields(null);
|
||||||
const plaqueKindFromApi = this.firstPositiveFanavaranId(
|
const plaqueKindFromApi = this.firstPositiveFanavaranId(
|
||||||
apiPlaque.kindId,
|
apiPlaque.kindId,
|
||||||
inquiryPlaque.kindId,
|
inquiryPlaque.kindId,
|
||||||
cachedDamage.plaqueKindId,
|
canReuseCachedPlaque ? cachedDamage.plaqueKindId : null,
|
||||||
lastDamagePayload?.PlaqueKindId,
|
canReuseCachedPlaque ? lastDamagePayload?.PlaqueKindId : null,
|
||||||
);
|
);
|
||||||
const plaqueSampleFromApi = this.firstPositiveFanavaranId(
|
const plaqueSampleFromApi = this.firstPositiveFanavaranId(
|
||||||
apiPlaque.sampleId,
|
apiPlaque.sampleId,
|
||||||
inquiryPlaque.sampleId,
|
inquiryPlaque.sampleId,
|
||||||
cachedDamage.plaqueSampleId,
|
canReuseCachedPlaque ? cachedDamage.plaqueSampleId : null,
|
||||||
lastDamagePayload?.PlaqueSampleId,
|
canReuseCachedPlaque ? lastDamagePayload?.PlaqueSampleId : null,
|
||||||
);
|
);
|
||||||
const plaqueKindId = fanavaranIdOrConfigDefault(
|
const plaqueKindId = fanavaranIdOrConfigDefault(
|
||||||
plaqueKindFromApi,
|
plaqueKindFromApi,
|
||||||
@@ -4268,6 +4386,10 @@ export class ClaimRequestManagementService {
|
|||||||
plaqueSampleFromApi,
|
plaqueSampleFromApi,
|
||||||
input.defaults.PlaqueSampleId,
|
input.defaults.PlaqueSampleId,
|
||||||
);
|
);
|
||||||
|
const plaqueKindSource =
|
||||||
|
plaqueKindFromApi != null ? "vehicle-inquiry" : "config-default";
|
||||||
|
const plaqueSampleSource =
|
||||||
|
plaqueSampleFromApi != null ? "vehicle-inquiry" : "config-default";
|
||||||
if (plaqueKindFromApi == null) {
|
if (plaqueKindFromApi == null) {
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
`[buildFanavaranDamageCasePayload] PlaqueKindId API miss; using config default ${plaqueKindId}`,
|
`[buildFanavaranDamageCasePayload] PlaqueKindId API miss; using config default ${plaqueKindId}`,
|
||||||
@@ -4284,14 +4406,21 @@ export class ClaimRequestManagementService {
|
|||||||
const carType = input.claimCase?.vehicle?.carType as string | undefined;
|
const carType = input.claimCase?.vehicle?.carType as string | undefined;
|
||||||
let vehicleKindId = this.firstPositiveFanavaranId(
|
let vehicleKindId = this.firstPositiveFanavaranId(
|
||||||
apiVehicle?.VehicleKindId,
|
apiVehicle?.VehicleKindId,
|
||||||
cachedDamage.vehicleKindId,
|
canReuseCachedVehicleKind ? cachedDamage.vehicleKindId : null,
|
||||||
lastDamagePayload?.VehicleKindId,
|
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) {
|
if (vehicleKindId == null) {
|
||||||
vehicleKindId = await this.resolveVehicleKindId(
|
vehicleKindId = await this.resolveVehicleKindId(
|
||||||
input.clientKey,
|
input.clientKey,
|
||||||
carType,
|
carType,
|
||||||
);
|
);
|
||||||
|
if (vehicleKindId != null) vehicleKindSource = "car-type-lookup";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prefer claim-level cache, then party.person.fanavaranDriverId, then live inquiry, then last payload
|
// Prefer claim-level cache, then party.person.fanavaranDriverId, then live inquiry, then last payload
|
||||||
@@ -4359,49 +4488,37 @@ export class ClaimRequestManagementService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cachedUsedId =
|
const cachedUsedId =
|
||||||
cachedDamage.accidentVehicleUsedId != null
|
cacheMatchesIdentity && cachedDamage.accidentVehicleUsedId != null
|
||||||
? Number(cachedDamage.accidentVehicleUsedId)
|
? Number(cachedDamage.accidentVehicleUsedId)
|
||||||
: (input.claimCase as any)?.fanavaranSync?.baseClaim
|
: null;
|
||||||
?.accidentVehicleUsedId != null
|
const cachedUsedSource = cachedDamage.accidentVehicleUsedSource;
|
||||||
? Number(
|
|
||||||
(input.claimCase as any).fanavaranSync.baseClaim
|
|
||||||
.accidentVehicleUsedId,
|
|
||||||
)
|
|
||||||
: null;
|
|
||||||
const cachedUsedSource =
|
|
||||||
cachedDamage.accidentVehicleUsedSource ??
|
|
||||||
(input.claimCase as any)?.fanavaranSync?.baseClaim
|
|
||||||
?.accidentVehicleUsedSource;
|
|
||||||
const canReuseApiUsedId =
|
const canReuseApiUsedId =
|
||||||
cachedUsedId != null &&
|
cachedUsedId != null &&
|
||||||
cachedUsedId > 0 &&
|
cachedUsedId > 0 &&
|
||||||
(cachedUsedSource === "vin-inquiry" ||
|
cachedUsedSource === "vehicle-inquiry";
|
||||||
cachedUsedSource === "policy-vehicle");
|
|
||||||
let accidentVehicleUsedId: number | null = canReuseApiUsedId
|
let accidentVehicleUsedId: number | null = canReuseApiUsedId
|
||||||
? cachedUsedId
|
? cachedUsedId
|
||||||
: null;
|
: null;
|
||||||
let usedSource: "vin-inquiry" | "policy-vehicle" | null = canReuseApiUsedId
|
let usedSource: "vin-inquiry" | "policy-vehicle" | "vehicle-inquiry" | null =
|
||||||
? cachedUsedSource
|
canReuseApiUsedId ? "vehicle-inquiry" : null;
|
||||||
: null;
|
if (accidentVehicleUsedId == null && apiVehicle) {
|
||||||
if (accidentVehicleUsedId == null) {
|
const fromDamagedVehicle =
|
||||||
const resolved = await this.resolveAccidentVehicleUsedFromApis({
|
await this.resolveAccidentVehicleUsedFromVehicleRecord(
|
||||||
clientKey: input.clientKey,
|
input.clientKey,
|
||||||
parties: input.blameCase?.parties ?? [],
|
apiVehicle,
|
||||||
guiltyPartyId: this.resolveGuiltyPartyIdV2(
|
);
|
||||||
input.blameCase?.parties ?? [],
|
accidentVehicleUsedId = fromDamagedVehicle.usedId;
|
||||||
input.blameCase?.expert?.decision?.guiltyPartyId,
|
if (accidentVehicleUsedId != null) usedSource = "vehicle-inquiry";
|
||||||
),
|
|
||||||
policyId: (input.claimCase as any)?.fanavaranSync?.baseClaim?.policyId,
|
|
||||||
});
|
|
||||||
accidentVehicleUsedId = resolved.usedId;
|
|
||||||
usedSource = resolved.source;
|
|
||||||
}
|
}
|
||||||
if (accidentVehicleUsedId == null) {
|
if (accidentVehicleUsedId == null) {
|
||||||
accidentVehicleUsedId = await this.resolveAccidentVehicleUsedFromVehicleKind(
|
accidentVehicleUsedId = await this.resolveAccidentVehicleUsedFromVehicleKind(
|
||||||
input.clientKey,
|
input.clientKey,
|
||||||
vehicleKindId,
|
vehicleKindId,
|
||||||
null,
|
parseFanavaranId(apiVehicle?.UsedId),
|
||||||
);
|
);
|
||||||
|
if (accidentVehicleUsedId != null && usedSource == null) {
|
||||||
|
usedSource = apiVehicle ? "vehicle-inquiry" : null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (accidentVehicleUsedId == null) {
|
if (accidentVehicleUsedId == null) {
|
||||||
accidentVehicleUsedId = input.defaults.AccidentVehicleUsedId;
|
accidentVehicleUsedId = input.defaults.AccidentVehicleUsedId;
|
||||||
@@ -4482,10 +4599,6 @@ export class ClaimRequestManagementService {
|
|||||||
|
|
||||||
const persistedPayload = this.keepPreviousPositiveIds(lastDamagePayload, payload, [
|
const persistedPayload = this.keepPreviousPositiveIds(lastDamagePayload, payload, [
|
||||||
"DriverId",
|
"DriverId",
|
||||||
"PlaqueKindId",
|
|
||||||
"PlaqueSampleId",
|
|
||||||
"VehicleKindId",
|
|
||||||
"AccidentVehicleUsedId",
|
|
||||||
"InsuranceCorpId",
|
"InsuranceCorpId",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -4502,14 +4615,16 @@ export class ClaimRequestManagementService {
|
|||||||
? { "fanavaranSync.damageCase.otherPersonId": otherPersonId }
|
? { "fanavaranSync.damageCase.otherPersonId": otherPersonId }
|
||||||
: {}),
|
: {}),
|
||||||
...(vehicleKindId != null
|
...(vehicleKindId != null
|
||||||
? { "fanavaranSync.damageCase.vehicleKindId": vehicleKindId }
|
? {
|
||||||
: {}),
|
"fanavaranSync.damageCase.vehicleKindId": vehicleKindId,
|
||||||
...(plaqueKindId != null
|
"fanavaranSync.damageCase.vehicleKindSource":
|
||||||
? { "fanavaranSync.damageCase.plaqueKindId": plaqueKindId }
|
vehicleKindSource ?? "car-type-lookup",
|
||||||
: {}),
|
}
|
||||||
...(plaqueSampleId != null
|
|
||||||
? { "fanavaranSync.damageCase.plaqueSampleId": plaqueSampleId }
|
|
||||||
: {}),
|
: {}),
|
||||||
|
"fanavaranSync.damageCase.plaqueKindId": plaqueKindId,
|
||||||
|
"fanavaranSync.damageCase.plaqueKindSource": plaqueKindSource,
|
||||||
|
"fanavaranSync.damageCase.plaqueSampleId": plaqueSampleId,
|
||||||
|
"fanavaranSync.damageCase.plaqueSampleSource": plaqueSampleSource,
|
||||||
...(accidentVehicleUsedId != null
|
...(accidentVehicleUsedId != null
|
||||||
? {
|
? {
|
||||||
"fanavaranSync.damageCase.accidentVehicleUsedId":
|
"fanavaranSync.damageCase.accidentVehicleUsedId":
|
||||||
@@ -4720,6 +4835,31 @@ export class ClaimRequestManagementService {
|
|||||||
return resolveFanavaranClientKey();
|
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(
|
private async getFanavaranAuthHeaders(
|
||||||
clientKey: FanavaranClientKey,
|
clientKey: FanavaranClientKey,
|
||||||
auditSession?: FanavaranAuditSession,
|
auditSession?: FanavaranAuditSession,
|
||||||
@@ -4729,9 +4869,11 @@ export class ClaimRequestManagementService {
|
|||||||
const locationOverride = caseId
|
const locationOverride = caseId
|
||||||
? await this.resolveFanavaranLocationForClaim(caseId, clientKey)
|
? await this.resolveFanavaranLocationForClaim(caseId, clientKey)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
const product = await this.resolveFanavaranClaimProduct(caseId);
|
||||||
return this.fanavaranAuthService.getRequestHeaders(clientKey, {
|
return this.fanavaranAuthService.getRequestHeaders(clientKey, {
|
||||||
auditSession,
|
auditSession,
|
||||||
locationOverride,
|
locationOverride,
|
||||||
|
contractIdOverride: resolveFanavaranProductContractId(clientKey, product),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4793,21 +4935,31 @@ export class ClaimRequestManagementService {
|
|||||||
},
|
},
|
||||||
logPrefix: string,
|
logPrefix: string,
|
||||||
auditSession?: FanavaranAuditSession,
|
auditSession?: FanavaranAuditSession,
|
||||||
options?: { clientKey?: FanavaranClientKey; claimCaseId?: string },
|
options?: {
|
||||||
|
clientKey?: FanavaranClientKey;
|
||||||
|
claimCaseId?: string;
|
||||||
|
product?: FanavaranClaimProduct;
|
||||||
|
insuranceLineId?: number;
|
||||||
|
},
|
||||||
): Promise<number | null> {
|
): Promise<number | null> {
|
||||||
const clientKey =
|
const clientKey =
|
||||||
options?.clientKey ?? this.resolveClientKeyFromAuthConfig(config);
|
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 startedAt = Date.now();
|
||||||
|
|
||||||
const requestMeta = {
|
const requestMeta = {
|
||||||
corpId: config.corpId,
|
corpId: config.corpId,
|
||||||
contractId: config.contractId,
|
contractId: resolveFanavaranProductContractId(clientKey, product),
|
||||||
location: config.location,
|
location: config.location,
|
||||||
nationalCode: this.fanavaranAuditService.maskNationalCode(
|
nationalCode: this.fanavaranAuditService.maskNationalCode(
|
||||||
nationalCodeOfInsurer,
|
nationalCodeOfInsurer,
|
||||||
),
|
),
|
||||||
InsuranceLineId: 5,
|
InsuranceLineId: insuranceLineId,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (auditSession) {
|
if (auditSession) {
|
||||||
@@ -5268,6 +5420,13 @@ export class ClaimRequestManagementService {
|
|||||||
: [];
|
: [];
|
||||||
if (parties.length === 0) return null;
|
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;
|
const ownerId = claimCase?.owner?.userId;
|
||||||
if (ownerId) {
|
if (ownerId) {
|
||||||
const byOwner = parties.find(
|
const byOwner = parties.find(
|
||||||
@@ -5474,15 +5633,69 @@ export class ClaimRequestManagementService {
|
|||||||
(claimCase as any)?.fanavaranSync?.baseClaim?.lastPayload?.PolicyId ??
|
(claimCase as any)?.fanavaranSync?.baseClaim?.lastPayload?.PolicyId ??
|
||||||
null;
|
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({
|
const payload = await this.buildFanavaranSubmitPayload({
|
||||||
accidentReason: selectedAccidentReason,
|
accidentReason: selectedAccidentReason,
|
||||||
createdAt: (blameCase as { createdAt?: Date }).createdAt,
|
createdAt: accidentAt,
|
||||||
location: firstParty?.location,
|
location: firstParty?.location,
|
||||||
damageParts,
|
damageParts,
|
||||||
defaults: profile.defaults,
|
defaults: profile.defaults,
|
||||||
logPrefix,
|
logPrefix,
|
||||||
requirePolicyId,
|
requirePolicyId,
|
||||||
resolvePolicyId: async () => {
|
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(
|
const guiltyPartyId = this.resolveGuiltyPartyIdV2(
|
||||||
blameCase.parties ?? [],
|
blameCase.parties ?? [],
|
||||||
expertDecision?.guiltyPartyId,
|
expertDecision?.guiltyPartyId,
|
||||||
@@ -5519,20 +5732,13 @@ export class ClaimRequestManagementService {
|
|||||||
return null;
|
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;
|
debug.steps.policyInquiryAttempted = true;
|
||||||
const policyId = await this.getPolicyIdFromNationalCode(
|
const policyId = await this.getPolicyIdFromNationalCode(
|
||||||
nationalCodeOfInsurer,
|
nationalCodeOfInsurer,
|
||||||
profile.auth,
|
profile.auth,
|
||||||
logPrefix,
|
logPrefix,
|
||||||
auditSession,
|
auditSession,
|
||||||
{ clientKey, claimCaseId },
|
{ clientKey, claimCaseId, product: "third-party" },
|
||||||
);
|
);
|
||||||
debug.values.policyId = policyId;
|
debug.values.policyId = policyId;
|
||||||
debug.steps.policyInquirySucceeded = policyId !== null;
|
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 =
|
const cachedUsedId =
|
||||||
forceRefreshUsedId || forceRefreshPolicy
|
forceRefreshUsedId || forceRefreshPolicy
|
||||||
? null
|
? null
|
||||||
@@ -5575,6 +5792,7 @@ export class ClaimRequestManagementService {
|
|||||||
guiltyPartyId: guiltyPartyIdForVin,
|
guiltyPartyId: guiltyPartyIdForVin,
|
||||||
policyId:
|
policyId:
|
||||||
typeof payload.PolicyId === "number" ? payload.PolicyId : null,
|
typeof payload.PolicyId === "number" ? payload.PolicyId : null,
|
||||||
|
product: fanavaranClaimProductFromBlameType(blameCase.type),
|
||||||
});
|
});
|
||||||
if (resolved.usedId != null) {
|
if (resolved.usedId != null) {
|
||||||
payload.AccidentVehicleUsedId = resolved.usedId;
|
payload.AccidentVehicleUsedId = resolved.usedId;
|
||||||
@@ -5687,9 +5905,9 @@ export class ClaimRequestManagementService {
|
|||||||
if (!blameCase) {
|
if (!blameCase) {
|
||||||
throw new NotFoundException("Blame case not found");
|
throw new NotFoundException("Blame case not found");
|
||||||
}
|
}
|
||||||
if (blameCase.type !== BlameRequestType.THIRD_PARTY) {
|
if (!isFanavaranSubmitSupportedBlameType(blameCase.type)) {
|
||||||
throw new BadRequestException(
|
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,
|
claimId: claimCase.claimId ?? null,
|
||||||
dmgCaseId: claimCase.dmgCaseId ?? null,
|
dmgCaseId: claimCase.dmgCaseId ?? null,
|
||||||
submitUrl: claimCase.claimId
|
submitUrl: claimCase.claimId
|
||||||
? `${this.FANAVARAN_SUBMIT_URL}/${claimCase.claimId}/dmg-cases`
|
? `${fanavaranClaimsBaseUrl(fanavaranClaimProductFromBlameType(blameCase.type))}/${claimCase.claimId}/dmg-cases`
|
||||||
: null,
|
: null,
|
||||||
warning: warnings.length ? warnings.join(" ") : undefined,
|
warning: warnings.length ? warnings.join(" ") : undefined,
|
||||||
payload,
|
payload,
|
||||||
@@ -5881,6 +6099,7 @@ export class ClaimRequestManagementService {
|
|||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
const { claimCase, candidates } =
|
const { claimCase, candidates } =
|
||||||
await this.collectFanavaranAttachmentCandidates(claimCaseId, clientKey);
|
await this.collectFanavaranAttachmentCandidates(claimCaseId, clientKey);
|
||||||
|
const claimsUrl = await this.fanavaranClaimsUrlForClaim(claimCaseId);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
clientKey,
|
clientKey,
|
||||||
@@ -5889,7 +6108,7 @@ export class ClaimRequestManagementService {
|
|||||||
claimNo: claimCase.claimNo ?? null,
|
claimNo: claimCase.claimNo ?? null,
|
||||||
dmgCaseId: claimCase.dmgCaseId ?? null,
|
dmgCaseId: claimCase.dmgCaseId ?? null,
|
||||||
submitUrl: claimCase.claimId
|
submitUrl: claimCase.claimId
|
||||||
? `${this.FANAVARAN_SUBMIT_URL}/${claimCase.claimId}/files`
|
? `${claimsUrl}/${claimCase.claimId}/files`
|
||||||
: null,
|
: null,
|
||||||
warning: claimCase.claimId
|
warning: claimCase.claimId
|
||||||
? undefined
|
? 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 fileTypeId = profile.defaults.ClaimFileTypeId;
|
||||||
|
|
||||||
const logPrefix = `[Fanavaran ${clientKey} V2 Attachment Batch] claimCaseId=${claimCaseId}`;
|
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 = {
|
content = {
|
||||||
FileName: fileName,
|
FileName: fileName,
|
||||||
FileTypeId: profile.defaults.ClaimFileTypeId,
|
FileTypeId: profile.defaults.ClaimFileTypeId,
|
||||||
@@ -6636,7 +6855,7 @@ export class ClaimRequestManagementService {
|
|||||||
expertiseId: claimCase.expertiseId ?? null,
|
expertiseId: claimCase.expertiseId ?? null,
|
||||||
replyKey,
|
replyKey,
|
||||||
submitUrl: claimCase.claimId
|
submitUrl: claimCase.claimId
|
||||||
? `${this.FANAVARAN_SUBMIT_URL}/${claimCase.claimId}/expertise`
|
? `${await this.fanavaranClaimsUrlForClaim(claimCaseId)}/${claimCase.claimId}/expertise`
|
||||||
: null,
|
: null,
|
||||||
ready: warnings.length === 0,
|
ready: warnings.length === 0,
|
||||||
warnings: Array.from(new Set(warnings)),
|
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 startedAt = Date.now();
|
||||||
const auditSession: FanavaranAuditSession = {
|
const auditSession: FanavaranAuditSession = {
|
||||||
trackingCode: this.fanavaranAuditService.generateTrackingCode(),
|
trackingCode: this.fanavaranAuditService.generateTrackingCode(),
|
||||||
@@ -6938,7 +7157,7 @@ export class ClaimRequestManagementService {
|
|||||||
fanavaran: FanavaranAutoSubmitResult,
|
fanavaran: FanavaranAutoSubmitResult,
|
||||||
): string {
|
): string {
|
||||||
if (fanavaran.submitted) {
|
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) {
|
if (fanavaran.warning) {
|
||||||
return `${baseMessage} ${fanavaran.warning}`;
|
return `${baseMessage} ${fanavaran.warning}`;
|
||||||
@@ -6964,8 +7183,8 @@ export class ClaimRequestManagementService {
|
|||||||
return "Blame case not found";
|
return "Blame case not found";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (blameCase.type !== BlameRequestType.THIRD_PARTY) {
|
if (!isFanavaranSubmitSupportedBlameType(blameCase.type)) {
|
||||||
return "Fanavaran third-party financial submit only applies to THIRD_PARTY claims";
|
return "Fanavaran submit only applies to THIRD_PARTY and CAR_BODY claims";
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -7197,9 +7416,9 @@ export class ClaimRequestManagementService {
|
|||||||
if (!blameCase) {
|
if (!blameCase) {
|
||||||
throw new NotFoundException("Blame case not found");
|
throw new NotFoundException("Blame case not found");
|
||||||
}
|
}
|
||||||
if (blameCase.type !== BlameRequestType.THIRD_PARTY) {
|
if (!isFanavaranSubmitSupportedBlameType(blameCase.type)) {
|
||||||
throw new BadRequestException(
|
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.
|
// Guardrail: never send empty/null licence fields even if manual overrides arrive.
|
||||||
payload.LicenceNo = this.ensureFanavaranNonEmptyLicenceNo(payload.LicenceNo);
|
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(
|
this.logger.log(
|
||||||
`[executeFanavaranDamageCaseSubmit] claimCaseId=${claimCaseId} claimId=${claimCase.claimId} payload.DriverId=${payload.DriverId ?? "NULL"} payload keys=${Object.keys(payload).join(", ")}`,
|
`[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
|
* claim case exists. This preserves progress in Fanavaran even if parties stop
|
||||||
* later in the damage-assessment flow.
|
* later in the damage-assessment flow.
|
||||||
*/
|
*/
|
||||||
@@ -8046,6 +8265,7 @@ export class ClaimRequestManagementService {
|
|||||||
auditSession,
|
auditSession,
|
||||||
claimCaseId,
|
claimCaseId,
|
||||||
);
|
);
|
||||||
|
const submitUrl = await this.fanavaranClaimsUrlForClaim(claimCaseId);
|
||||||
|
|
||||||
const requestHeaders = {
|
const requestHeaders = {
|
||||||
...headers,
|
...headers,
|
||||||
@@ -8055,7 +8275,7 @@ export class ClaimRequestManagementService {
|
|||||||
session: auditSession,
|
session: auditSession,
|
||||||
step: FanavaranAuditStep.SUBMIT_CLAIM,
|
step: FanavaranAuditStep.SUBMIT_CLAIM,
|
||||||
status: FanavaranAuditStatus.STARTED,
|
status: FanavaranAuditStatus.STARTED,
|
||||||
requestUrl: this.FANAVARAN_SUBMIT_URL,
|
requestUrl: submitUrl,
|
||||||
requestMethod: "POST",
|
requestMethod: "POST",
|
||||||
requestHeaders,
|
requestHeaders,
|
||||||
requestBody: fanavaranData,
|
requestBody: fanavaranData,
|
||||||
@@ -8065,7 +8285,7 @@ export class ClaimRequestManagementService {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await firstValueFrom(
|
const response = await firstValueFrom(
|
||||||
this.httpService.post(this.FANAVARAN_SUBMIT_URL, fanavaranData, {
|
this.httpService.post(submitUrl, fanavaranData, {
|
||||||
headers: requestHeaders,
|
headers: requestHeaders,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -8086,7 +8306,7 @@ export class ClaimRequestManagementService {
|
|||||||
session: auditSession,
|
session: auditSession,
|
||||||
step: FanavaranAuditStep.SUBMIT_CLAIM,
|
step: FanavaranAuditStep.SUBMIT_CLAIM,
|
||||||
status: FanavaranAuditStatus.SUCCESS,
|
status: FanavaranAuditStatus.SUCCESS,
|
||||||
requestUrl: this.FANAVARAN_SUBMIT_URL,
|
requestUrl: submitUrl,
|
||||||
requestMethod: "POST",
|
requestMethod: "POST",
|
||||||
httpStatus: response.status,
|
httpStatus: response.status,
|
||||||
requestHeaders: exchange.requestHeaders,
|
requestHeaders: exchange.requestHeaders,
|
||||||
@@ -8138,7 +8358,7 @@ export class ClaimRequestManagementService {
|
|||||||
session: auditSession,
|
session: auditSession,
|
||||||
step: FanavaranAuditStep.SUBMIT_CLAIM,
|
step: FanavaranAuditStep.SUBMIT_CLAIM,
|
||||||
status: FanavaranAuditStatus.FAILURE,
|
status: FanavaranAuditStatus.FAILURE,
|
||||||
requestUrl: this.FANAVARAN_SUBMIT_URL,
|
requestUrl: submitUrl,
|
||||||
requestMethod: "POST",
|
requestMethod: "POST",
|
||||||
httpStatus: exchange.httpStatus,
|
httpStatus: exchange.httpStatus,
|
||||||
requestHeaders: exchange.requestHeaders,
|
requestHeaders: exchange.requestHeaders,
|
||||||
|
|||||||
@@ -97,14 +97,26 @@ export class FanavaranSyncStage {
|
|||||||
@Prop({ type: Number })
|
@Prop({ type: Number })
|
||||||
vehicleKindId?: 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. */
|
/** Cached Fanavaran PlaqueKindId from VIN inquiry / vehicle GET. */
|
||||||
@Prop({ type: Number })
|
@Prop({ type: Number })
|
||||||
plaqueKindId?: number;
|
plaqueKindId?: number;
|
||||||
|
|
||||||
|
/** How PlaqueKindId was resolved: vehicle-inquiry | config-default */
|
||||||
|
@Prop({ type: String })
|
||||||
|
plaqueKindSource?: string;
|
||||||
|
|
||||||
/** Cached Fanavaran PlaqueSampleId from VIN inquiry / vehicle GET. */
|
/** Cached Fanavaran PlaqueSampleId from VIN inquiry / vehicle GET. */
|
||||||
@Prop({ type: Number })
|
@Prop({ type: Number })
|
||||||
plaqueSampleId?: 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
|
* Cached Fanavaran AccidentVehicleUsedId, resolved from VIN inquiry / vehicle GET
|
||||||
* UsedId (lookup-filter safe). Not the Mongo tenant default.
|
* 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 {
|
import {
|
||||||
|
canReuseCachedFanavaranVehicleIds,
|
||||||
collectPartyVinCandidates,
|
collectPartyVinCandidates,
|
||||||
fanavaranIdOrConfigDefault,
|
fanavaranIdOrConfigDefault,
|
||||||
|
fanavaranPayloadMatchesDamagedIdentity,
|
||||||
|
fanavaranVehicleMatchesDamagedIdentity,
|
||||||
filterPoliciesByLine,
|
filterPoliciesByLine,
|
||||||
insuranceLineLabel,
|
insuranceLineLabel,
|
||||||
parseLastCarPolicyInput,
|
parseLastCarPolicyInput,
|
||||||
@@ -10,6 +13,7 @@ import {
|
|||||||
pickVinFromPartyVehicle,
|
pickVinFromPartyVehicle,
|
||||||
plaqueLetterFromMiddleCode,
|
plaqueLetterFromMiddleCode,
|
||||||
plaqueMatchesVehicle,
|
plaqueMatchesVehicle,
|
||||||
|
selectFanavaranVehicleForDamageCase,
|
||||||
selectLastAmongCarMatches,
|
selectLastAmongCarMatches,
|
||||||
toAppPlaque,
|
toAppPlaque,
|
||||||
vehicleMatchesCar,
|
vehicleMatchesCar,
|
||||||
@@ -311,4 +315,154 @@ describe("fanavaran last car policy", () => {
|
|||||||
it("returns null when there are no matches", () => {
|
it("returns null when there are no matches", () => {
|
||||||
expect(selectLastAmongCarMatches([], today)).toBeNull();
|
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);
|
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: {
|
export function pickVinFromPartyVehicle(party: {
|
||||||
vehicle?: { vin?: unknown; inquiry?: unknown };
|
vehicle?: { vin?: unknown; inquiry?: unknown };
|
||||||
} | null | undefined): string | null {
|
} | null | undefined): string | null {
|
||||||
|
|||||||
Reference in New Issue
Block a user