forked from Yara724/api
Compare commits
12 Commits
cb4f02918f
...
8173764913
| Author | SHA1 | Date | |
|---|---|---|---|
| 8173764913 | |||
| c72cb265f1 | |||
| 271dc4df2a | |||
| b2ff3f574b | |||
| 769a581a51 | |||
| 466773fb2b | |||
| 37041ba19f | |||
| 0b0a1dfa13 | |||
| 073b4dec38 | |||
| c1f0d041ce | |||
| f9c60a2854 | |||
| 4310ce6398 |
File diff suppressed because it is too large
Load Diff
@@ -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.
|
||||||
|
|||||||
157
src/claim-request-management/fanavaran-claim-product.spec.ts
Normal file
157
src/claim-request-management/fanavaran-claim-product.spec.ts
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
import { BlameRequestType } from "src/Types&Enums/blame-request-management/blameRequestType.enum";
|
||||||
|
import { PartyRole } from "src/request-management/entities/schema/partyRole.enum";
|
||||||
|
import {
|
||||||
|
fanavaranClaimProductFromBlameType,
|
||||||
|
fanavaranClaimsBaseUrl,
|
||||||
|
fanavaranClaimStageUrl,
|
||||||
|
fanavaranHasDamageCaseStage,
|
||||||
|
fanavaranHullImportClaimsUrl,
|
||||||
|
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("does not POST hull damage to the missing dmg-cases resource", () => {
|
||||||
|
expect(fanavaranHasDamageCaseStage("car-body")).toBe(false);
|
||||||
|
expect(fanavaranClaimStageUrl("car-body", "damage-case", 5023617)).toBeNull();
|
||||||
|
expect(fanavaranClaimStageUrl("car-body", "attachments", 5023617)).toBe(
|
||||||
|
"https://apimanager.iraneit.com/BimeApiManager/api/BimeApi/v2.0/car/vehicle-hull-claims/5023617/files",
|
||||||
|
);
|
||||||
|
expect(fanavaranClaimStageUrl("car-body", "expertise", 5023617)).toBe(
|
||||||
|
"https://apimanager.iraneit.com/BimeApiManager/api/BimeApi/v2.0/car/vehicle-hull-claims/5023617/expertise",
|
||||||
|
);
|
||||||
|
expect(fanavaranClaimStageUrl("car-body", "culprits", 5023617)).toBe(
|
||||||
|
"https://apimanager.iraneit.com/BimeApiManager/api/BimeApi/v2.0/car/vehicle-hull-claims/5023617/culprits",
|
||||||
|
);
|
||||||
|
expect(fanavaranClaimStageUrl("third-party", "culprits", 1)).toBeNull();
|
||||||
|
expect(fanavaranHullImportClaimsUrl()).toBe(
|
||||||
|
"https://apimanager.iraneit.com/BimeApiManager/api/BimeApi/v2.0/car/vehicle-hull-import-claims",
|
||||||
|
);
|
||||||
|
expect(fanavaranClaimsBaseUrl("car-body")).not.toContain(
|
||||||
|
"vehicle-hull-import-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");
|
||||||
|
});
|
||||||
|
});
|
||||||
271
src/claim-request-management/fanavaran-claim-product.ts
Normal file
271
src/claim-request-management/fanavaran-claim-product.ts
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
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 type FanavaranClaimStage =
|
||||||
|
| "damage-case"
|
||||||
|
| "attachments"
|
||||||
|
| "expertise"
|
||||||
|
| "culprits";
|
||||||
|
|
||||||
|
/** Hull (بدنه) has no GEN.12 dmg-cases resource. Proven 2026-09-15: Feature:url(.../vehicle-hull-claims/{id}/dmg-cases) not found. Files and expertise do exist. */
|
||||||
|
export const FANAVARAN_HULL_NO_DAMAGE_CASE_REASON =
|
||||||
|
"Fanavaran vehicle-hull-claims has no dmg-cases resource. Hull is first-party (one damaged vehicle on the base claim). Continue with files and expertise.";
|
||||||
|
|
||||||
|
export const FANAVARAN_HULL_IMPORT_CLAIMS_PATH = "vehicle-hull-import-claims";
|
||||||
|
|
||||||
|
export function fanavaranHasDamageCaseStage(
|
||||||
|
product: FanavaranClaimProduct,
|
||||||
|
): boolean {
|
||||||
|
return product === "third-party";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fanavaranHullImportClaimsUrl(): string {
|
||||||
|
return `${FANAVARAN_CLAIMS_HOST}/${FANAVARAN_HULL_IMPORT_CLAIMS_PATH}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fanavaranClaimStageUrl(
|
||||||
|
product: FanavaranClaimProduct,
|
||||||
|
stage: FanavaranClaimStage,
|
||||||
|
claimId: number | string,
|
||||||
|
): string | null {
|
||||||
|
if (stage === "damage-case" && !fanavaranHasDamageCaseStage(product)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (stage === "culprits" && product !== "car-body") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const suffix =
|
||||||
|
stage === "damage-case"
|
||||||
|
? "dmg-cases"
|
||||||
|
: stage === "attachments"
|
||||||
|
? "files"
|
||||||
|
: stage === "culprits"
|
||||||
|
? "culprits"
|
||||||
|
: "expertise";
|
||||||
|
return `${fanavaranClaimsBaseUrl(product)}/${claimId}/${suffix}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
156
src/claim-request-management/fanavaran-hull-expertise.spec.ts
Normal file
156
src/claim-request-management/fanavaran-hull-expertise.spec.ts
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
import { PartyRole } from "src/request-management/entities/schema/partyRole.enum";
|
||||||
|
import {
|
||||||
|
hullExpertiseAssertFields,
|
||||||
|
collectFanavaranExpertiseReadinessWarnings,
|
||||||
|
pickHullVehicleIdentity,
|
||||||
|
toFanavaranHullExpertiseDmgSection,
|
||||||
|
toFanavaranHullExpertisePayload,
|
||||||
|
} from "./fanavaran-hull-expertise";
|
||||||
|
|
||||||
|
describe("fanavaran hull expertise", () => {
|
||||||
|
it("maps car-body inquiry identity onto GEN.06 vehicle fields", () => {
|
||||||
|
const vehicle = pickHullVehicleIdentity({
|
||||||
|
parties: [
|
||||||
|
{
|
||||||
|
role: PartyRole.FIRST,
|
||||||
|
insurance: {
|
||||||
|
carBodyInsurance: {
|
||||||
|
chassisNumber: "NAS431100K1050805",
|
||||||
|
motorNumber: "M136251767",
|
||||||
|
vin: "VIN123",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
vehicle: {
|
||||||
|
inquiry: {
|
||||||
|
carBody: {
|
||||||
|
mapped: {
|
||||||
|
platePartOne: "29",
|
||||||
|
plateLetterTitle: "د",
|
||||||
|
platePartThree: "782",
|
||||||
|
plateSerialNumber: "60",
|
||||||
|
builtYear: 1398,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(vehicle).toEqual({
|
||||||
|
motorNo: "M136251767",
|
||||||
|
chassisNo: "NAS431100K1050805",
|
||||||
|
vin: "VIN123",
|
||||||
|
plaqueNo: "29د782",
|
||||||
|
plaqueSerial: "60",
|
||||||
|
builtYear: 1398,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("builds a GEN.06 payload without ثالث expertise fields", () => {
|
||||||
|
const payload = toFanavaranHullExpertisePayload({
|
||||||
|
claimExpertId: 29,
|
||||||
|
dmgAssessmentDate: "1405/06/24",
|
||||||
|
inspectionTime: "10:33",
|
||||||
|
wage: 1000,
|
||||||
|
componentReplacementCost: 2000,
|
||||||
|
wasteValue: 0,
|
||||||
|
dropAmount: 12,
|
||||||
|
vehicleCurrentValue: 1900000000,
|
||||||
|
vehicle: {
|
||||||
|
motorNo: "M1",
|
||||||
|
chassisNo: "C1",
|
||||||
|
vin: null,
|
||||||
|
plaqueNo: "695ط61",
|
||||||
|
plaqueSerial: "87",
|
||||||
|
builtYear: 1398,
|
||||||
|
},
|
||||||
|
dmgSections: [
|
||||||
|
toFanavaranHullExpertiseDmgSection({
|
||||||
|
partId: 9,
|
||||||
|
desc: "bumper",
|
||||||
|
wasteValue: 0,
|
||||||
|
amount: 3000,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(payload.Wage).toBe(1000);
|
||||||
|
expect(payload.RepairWage).toBeUndefined();
|
||||||
|
expect(payload.DmgCaseId).toBeUndefined();
|
||||||
|
expect(payload.InspectionPlaceId).toBeUndefined();
|
||||||
|
expect(payload.DropAmountStatus).toBeUndefined();
|
||||||
|
expect(payload.DamagedVehicleCurrentPrice).toBeUndefined();
|
||||||
|
expect(payload.ClaimExpertId).toBe(29);
|
||||||
|
expect(payload.PlaqueNo).toBe("695ط61");
|
||||||
|
expect(payload.DmgSections).toEqual([
|
||||||
|
{
|
||||||
|
Count: 1,
|
||||||
|
Desc: "bumper",
|
||||||
|
WasteValue: 0,
|
||||||
|
AccessoryKindId: 9,
|
||||||
|
DmgKindId: null,
|
||||||
|
VehicleHullAccessoryId: null,
|
||||||
|
DmgSectionCosts: [
|
||||||
|
{ Caption: "bumper", Amount: 3000, DmgCostKindId: null },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not require ثالث expertise fields on a GEN.06 hull payload", () => {
|
||||||
|
const payload = toFanavaranHullExpertisePayload({
|
||||||
|
claimExpertId: 29,
|
||||||
|
dmgAssessmentDate: "1405/06/24",
|
||||||
|
inspectionTime: "10:41",
|
||||||
|
wage: 6000000,
|
||||||
|
componentReplacementCost: 10000000,
|
||||||
|
wasteValue: 0,
|
||||||
|
dropAmount: 10000,
|
||||||
|
vehicleCurrentValue: null,
|
||||||
|
vehicle: {
|
||||||
|
motorNo: "13389030167",
|
||||||
|
chassisNo: "NAAP41FD5BJ301065",
|
||||||
|
vin: "IRFC891V7D2301065",
|
||||||
|
plaqueNo: "56د394",
|
||||||
|
plaqueSerial: "66",
|
||||||
|
builtYear: 1389,
|
||||||
|
},
|
||||||
|
dmgSections: [
|
||||||
|
toFanavaranHullExpertiseDmgSection({
|
||||||
|
partId: 9,
|
||||||
|
desc: "سپر جلو",
|
||||||
|
wasteValue: 0,
|
||||||
|
amount: 11000000,
|
||||||
|
}),
|
||||||
|
toFanavaranHullExpertiseDmgSection({
|
||||||
|
partId: 11,
|
||||||
|
desc: "آينه سمت راننده",
|
||||||
|
wasteValue: 0,
|
||||||
|
amount: 5000000,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
collectFanavaranExpertiseReadinessWarnings(
|
||||||
|
payload,
|
||||||
|
hullExpertiseAssertFields("car-body"),
|
||||||
|
),
|
||||||
|
).toEqual([]);
|
||||||
|
expect(
|
||||||
|
collectFanavaranExpertiseReadinessWarnings(
|
||||||
|
payload,
|
||||||
|
hullExpertiseAssertFields("third-party"),
|
||||||
|
),
|
||||||
|
).toEqual([
|
||||||
|
"DmgCaseId is required.",
|
||||||
|
"InspectionPlaceId is required.",
|
||||||
|
"DropAmountStatus is required.",
|
||||||
|
"DmgSections[0].DmgSectionId is required.",
|
||||||
|
"DmgSections[0].AccidentLevel is required.",
|
||||||
|
"DmgSections[1].DmgSectionId is required.",
|
||||||
|
"DmgSections[1].AccidentLevel is required.",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
160
src/claim-request-management/fanavaran-hull-expertise.ts
Normal file
160
src/claim-request-management/fanavaran-hull-expertise.ts
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
import { parseFanavaranId } from "src/lookups/fanavaran-last-car-policy";
|
||||||
|
import {
|
||||||
|
fanavaranPartyInquirySources,
|
||||||
|
type FanavaranClaimProduct,
|
||||||
|
} from "./fanavaran-claim-product";
|
||||||
|
|
||||||
|
export type FanavaranHullVehicleIdentity = {
|
||||||
|
motorNo: string | null;
|
||||||
|
chassisNo: string | null;
|
||||||
|
vin: string | null;
|
||||||
|
plaqueNo: string | null;
|
||||||
|
plaqueSerial: string | null;
|
||||||
|
builtYear: number | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function pickHullVehicleIdentity(
|
||||||
|
blame?: { parties?: unknown[] } | null,
|
||||||
|
): FanavaranHullVehicleIdentity {
|
||||||
|
const party = (blame?.parties ?? [])[0] as
|
||||||
|
| Parameters<typeof fanavaranPartyInquirySources>[1]
|
||||||
|
| undefined;
|
||||||
|
const first =
|
||||||
|
(blame?.parties ?? []).find(
|
||||||
|
(row) => (row as { role?: string })?.role === "FIRST",
|
||||||
|
) ?? party;
|
||||||
|
const { mapped } = fanavaranPartyInquirySources(
|
||||||
|
"car-body",
|
||||||
|
first as Parameters<typeof fanavaranPartyInquirySources>[1],
|
||||||
|
);
|
||||||
|
const left = text(mapped.plk1 ?? mapped.Plk1);
|
||||||
|
const letter = text(mapped.plk2 ?? mapped.Plk2);
|
||||||
|
const right = text(mapped.plk3 ?? mapped.Plk3);
|
||||||
|
const serial = text(mapped.plksrl ?? mapped.PlkSrl);
|
||||||
|
const plaqueNo = [left, letter, right].filter(Boolean).join("") || null;
|
||||||
|
return {
|
||||||
|
motorNo: text(mapped.MtrNum),
|
||||||
|
chassisNo: text(mapped.ShsNum),
|
||||||
|
vin: text(mapped.VIN),
|
||||||
|
plaqueNo,
|
||||||
|
plaqueSerial: serial,
|
||||||
|
builtYear: parseFanavaranId(mapped.PrdDte),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function text(value: unknown): string | null {
|
||||||
|
if (value == null) return null;
|
||||||
|
const next = String(value).trim();
|
||||||
|
return next ? next : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toFanavaranHullExpertisePayload(input: {
|
||||||
|
claimExpertId: number;
|
||||||
|
dmgAssessmentDate: string;
|
||||||
|
inspectionTime: string;
|
||||||
|
wage: number;
|
||||||
|
componentReplacementCost: number;
|
||||||
|
wasteValue: number;
|
||||||
|
dropAmount: number;
|
||||||
|
vehicleCurrentValue: number | null;
|
||||||
|
vehicle: FanavaranHullVehicleIdentity;
|
||||||
|
dmgSections: Record<string, unknown>[];
|
||||||
|
}): Record<string, unknown> {
|
||||||
|
return {
|
||||||
|
ClaimExpertId: input.claimExpertId,
|
||||||
|
DmgAssessmentDate: input.dmgAssessmentDate,
|
||||||
|
InspectionTime: input.inspectionTime,
|
||||||
|
MotorNo: input.vehicle.motorNo,
|
||||||
|
ChassisNo: input.vehicle.chassisNo,
|
||||||
|
BuiltYear: input.vehicle.builtYear,
|
||||||
|
VIN: input.vehicle.vin,
|
||||||
|
PlaqueNo: input.vehicle.plaqueNo,
|
||||||
|
PlaqueSerial: input.vehicle.plaqueSerial,
|
||||||
|
Wage: input.wage,
|
||||||
|
ComponentReplacementCost: input.componentReplacementCost,
|
||||||
|
WasteValue: input.wasteValue,
|
||||||
|
CarryAndRescueCost: null,
|
||||||
|
RepairDuration: null,
|
||||||
|
VehicleCurrentValue: input.vehicleCurrentValue,
|
||||||
|
WreckHighestValue: null,
|
||||||
|
InspectionDeduction: null,
|
||||||
|
DmgAndWasteDesc: null,
|
||||||
|
WentDistanceByExpert: null,
|
||||||
|
IsDestruction: null,
|
||||||
|
DropAmount: input.dropAmount,
|
||||||
|
ColorId: null,
|
||||||
|
PlaqueDesignId: null,
|
||||||
|
PlaqueCityId: null,
|
||||||
|
AccidentPercent: null,
|
||||||
|
TheftCases: [],
|
||||||
|
DmgSections: input.dmgSections,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toFanavaranHullExpertiseDmgSection(input: {
|
||||||
|
partId?: unknown;
|
||||||
|
desc: string;
|
||||||
|
wasteValue: number;
|
||||||
|
amount: number;
|
||||||
|
}): Record<string, unknown> {
|
||||||
|
return {
|
||||||
|
Count: 1,
|
||||||
|
Desc: input.desc,
|
||||||
|
WasteValue: input.wasteValue,
|
||||||
|
AccessoryKindId: parseFanavaranId(input.partId),
|
||||||
|
DmgKindId: null,
|
||||||
|
VehicleHullAccessoryId: null,
|
||||||
|
DmgSectionCosts: [
|
||||||
|
{
|
||||||
|
Caption: input.desc,
|
||||||
|
Amount: input.amount,
|
||||||
|
DmgCostKindId: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hullExpertiseAssertFields(
|
||||||
|
product: FanavaranClaimProduct,
|
||||||
|
): { requireDmgCaseId: boolean; requireThirdPartyLookups: boolean } {
|
||||||
|
if (product === "car-body") {
|
||||||
|
return { requireDmgCaseId: false, requireThirdPartyLookups: false };
|
||||||
|
}
|
||||||
|
return { requireDmgCaseId: true, requireThirdPartyLookups: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function collectFanavaranExpertiseReadinessWarnings(
|
||||||
|
payload: Record<string, unknown>,
|
||||||
|
options?: { requireDmgCaseId?: boolean; requireThirdPartyLookups?: boolean },
|
||||||
|
): string[] {
|
||||||
|
const warnings: string[] = [];
|
||||||
|
const sections = Array.isArray(payload.DmgSections)
|
||||||
|
? payload.DmgSections
|
||||||
|
: [];
|
||||||
|
const thirdPartyLookups = options?.requireThirdPartyLookups !== false;
|
||||||
|
if (options?.requireDmgCaseId !== false && !payload.DmgCaseId) {
|
||||||
|
warnings.push("DmgCaseId is required.");
|
||||||
|
}
|
||||||
|
if (thirdPartyLookups) {
|
||||||
|
if (!payload.InspectionPlaceId) {
|
||||||
|
warnings.push("InspectionPlaceId is required.");
|
||||||
|
}
|
||||||
|
if (!payload.DropAmountStatus) {
|
||||||
|
warnings.push("DropAmountStatus is required.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!sections.length) {
|
||||||
|
warnings.push("At least one DmgSections row is required.");
|
||||||
|
}
|
||||||
|
for (const [index, section] of sections.entries()) {
|
||||||
|
const row = section as Record<string, unknown>;
|
||||||
|
if (!thirdPartyLookups) continue;
|
||||||
|
if (!row.DmgSectionId) {
|
||||||
|
warnings.push(`DmgSections[${index}].DmgSectionId is required.`);
|
||||||
|
}
|
||||||
|
if (!row.AccidentLevel) {
|
||||||
|
warnings.push(`DmgSections[${index}].AccidentLevel is required.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Array.from(new Set(warnings));
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
} from "@nestjs/common";
|
} from "@nestjs/common";
|
||||||
import {
|
import {
|
||||||
ApiBearerAuth,
|
ApiBearerAuth,
|
||||||
|
ApiBody,
|
||||||
ApiOperation,
|
ApiOperation,
|
||||||
ApiParam,
|
ApiParam,
|
||||||
ApiQuery,
|
ApiQuery,
|
||||||
@@ -57,7 +58,7 @@ export class FanavaranController {
|
|||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: "Preview Fanavaran base claim create payload",
|
summary: "Preview Fanavaran base claim create payload",
|
||||||
description:
|
description:
|
||||||
"Builds the GEN.03 payload from local claim/blame data. Fanavaran-sourced PolicyId is resolve-once: first call may inquire and caches on the claim; later calls reuse the cache. Auth token is shared until Asia/Tehran midnight. Pass forceRefreshPolicy=true to re-inquire.",
|
"GET has no request body. Always rebuilds GEN.03 from live policy + VIN/vehicle inquiries. Does not reuse fanavaranSync lastPayload / PolicyId / UsedId cache. Auth token is still shared until Asia/Tehran midnight.",
|
||||||
})
|
})
|
||||||
@ApiParam({
|
@ApiParam({
|
||||||
name: "client",
|
name: "client",
|
||||||
@@ -76,34 +77,30 @@ export class FanavaranController {
|
|||||||
@ApiQuery({
|
@ApiQuery({
|
||||||
name: "forceRefreshPolicy",
|
name: "forceRefreshPolicy",
|
||||||
required: false,
|
required: false,
|
||||||
description:
|
deprecated: true,
|
||||||
"When true, ignores cached PolicyId and performs a live Fanavaran policy inquiry again. Do not pass this from normal UI loads.",
|
description: "Ignored. Preview always re-inquires PolicyId.",
|
||||||
})
|
})
|
||||||
@ApiQuery({
|
@ApiQuery({
|
||||||
name: "resolvePolicy",
|
name: "resolvePolicy",
|
||||||
required: false,
|
required: false,
|
||||||
deprecated: true,
|
deprecated: true,
|
||||||
description:
|
description: "Ignored. Preview always re-inquires PolicyId.",
|
||||||
"Deprecated. Ignored for cache-busting. PolicyId is resolve-once from fanavaranSync.baseClaim.policyId; use forceRefreshPolicy=true only to re-inquire.",
|
|
||||||
})
|
})
|
||||||
async preview(
|
async preview(
|
||||||
@Param("client") client: string,
|
@Param("client") client: string,
|
||||||
@Param("claimCaseId") claimCaseId: string,
|
@Param("claimCaseId") claimCaseId: string,
|
||||||
@Query("debug") debug?: string,
|
@Query("debug") debug?: string,
|
||||||
@Query("forceRefreshPolicy") forceRefreshPolicy?: string,
|
@Query("forceRefreshPolicy") _forceRefreshPolicy?: string,
|
||||||
@Query("resolvePolicy") _resolvePolicy?: string,
|
@Query("resolvePolicy") _resolvePolicy?: string,
|
||||||
) {
|
) {
|
||||||
const clientKey = this.parseClientParam(client);
|
const clientKey = this.parseClientParam(client);
|
||||||
// IMPORTANT: resolvePolicy must NOT force a live inquiry. Older UI clients
|
|
||||||
// send resolvePolicy=true on every preview load; that used to defeat the
|
|
||||||
// PolicyId cache and re-Login Fanavaran on every click.
|
|
||||||
return await this.claimRequestManagementService.previewFanavaranSubmitV2(
|
return await this.claimRequestManagementService.previewFanavaranSubmitV2(
|
||||||
claimCaseId,
|
claimCaseId,
|
||||||
clientKey,
|
clientKey,
|
||||||
{
|
{
|
||||||
debug: debug === "1" || debug === "true",
|
debug: debug === "1" || debug === "true",
|
||||||
forceRefreshPolicy:
|
forceRefreshPolicy: true,
|
||||||
forceRefreshPolicy === "1" || forceRefreshPolicy === "true",
|
forceRefreshUsedId: true,
|
||||||
requirePolicyId: false,
|
requirePolicyId: false,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -113,7 +110,7 @@ export class FanavaranController {
|
|||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: "Submit Fanavaran base claim create request",
|
summary: "Submit Fanavaran base claim create request",
|
||||||
description:
|
description:
|
||||||
"Authenticates with the selected tenant credentials and submits the GEN.03 base claim create request. Stores returned Id as claimId and ClaimNo when present.",
|
"Swagger JSON body is optional. Always rebuilds GEN.03 from live inquiries, then POSTs that rebuilt payload. Pasting a previous preview JSON does not skip inquiry. Only user-entry keys are applied from the body (EstimateAmount, licence, dates, address, police report). PolicyId and AccidentVehicleUsedId in pasted JSON are ignored.",
|
||||||
})
|
})
|
||||||
@ApiParam({
|
@ApiParam({
|
||||||
name: "client",
|
name: "client",
|
||||||
@@ -124,6 +121,12 @@ export class FanavaranController {
|
|||||||
name: "claimCaseId",
|
name: "claimCaseId",
|
||||||
description: "Claim case MongoDB ObjectId",
|
description: "Claim case MongoDB ObjectId",
|
||||||
})
|
})
|
||||||
|
@ApiBody({
|
||||||
|
required: false,
|
||||||
|
description:
|
||||||
|
"Optional. Paste previous preview JSON if you want to tweak user-entry fields. Lookup ids in that JSON are ignored.",
|
||||||
|
schema: { type: "object", additionalProperties: true, example: {} },
|
||||||
|
})
|
||||||
async submit(
|
async submit(
|
||||||
@Param("client") client: string,
|
@Param("client") client: string,
|
||||||
@Param("claimCaseId") claimCaseId: string,
|
@Param("claimCaseId") claimCaseId: string,
|
||||||
@@ -141,7 +144,7 @@ export class FanavaranController {
|
|||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: "Preview Fanavaran damage-case payload",
|
summary: "Preview Fanavaran damage-case payload",
|
||||||
description:
|
description:
|
||||||
"Builds the GEN.12 damaged vehicle/person case payload without calling Fanavaran. Requires selected damaged parts; submit requires a Fanavaran claimId.",
|
"GET has no request body. Always rebuilds GEN.12 from live VIN/vehicle/driver inquiries. Does not reuse lastPayload / plaqueKindId / DriverId cache.",
|
||||||
})
|
})
|
||||||
@ApiParam({
|
@ApiParam({
|
||||||
name: "client",
|
name: "client",
|
||||||
@@ -167,7 +170,7 @@ export class FanavaranController {
|
|||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: "Submit Fanavaran damage-case request",
|
summary: "Submit Fanavaran damage-case request",
|
||||||
description:
|
description:
|
||||||
"Submits the GEN.12 dmg-cases request. If base claim (claimId) is missing, soft-ensures GEN.03 base claim first, then submits damage. Skips when dmgCaseId already exists.",
|
"Swagger JSON body is optional. Always rebuilds GEN.12 from live VIN/vehicle/driver inquiries, then POSTs that rebuilt payload. Pasting a previous preview JSON does not skip inquiry. Only Desc, EstimateAmount, LicenceNo, LicenceIssuDate, DriverIsOwner are taken from the body. PlaqueKindId, PlaqueSampleId, VehicleKindId, DriverId, InsuranceCorpId in pasted JSON are ignored.",
|
||||||
})
|
})
|
||||||
@ApiParam({
|
@ApiParam({
|
||||||
name: "client",
|
name: "client",
|
||||||
@@ -178,6 +181,12 @@ export class FanavaranController {
|
|||||||
name: "claimCaseId",
|
name: "claimCaseId",
|
||||||
description: "Claim case MongoDB ObjectId",
|
description: "Claim case MongoDB ObjectId",
|
||||||
})
|
})
|
||||||
|
@ApiBody({
|
||||||
|
required: false,
|
||||||
|
description:
|
||||||
|
"Optional. Paste previous preview JSON if you want to tweak Desc / licence / estimate. Lookup-filter ids in that JSON are ignored.",
|
||||||
|
schema: { type: "object", additionalProperties: true, example: {} },
|
||||||
|
})
|
||||||
async submitDamageCase(
|
async submitDamageCase(
|
||||||
@Param("client") client: string,
|
@Param("client") client: string,
|
||||||
@Param("claimCaseId") claimCaseId: string,
|
@Param("claimCaseId") claimCaseId: string,
|
||||||
@@ -247,7 +256,7 @@ export class FanavaranController {
|
|||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: "Preview Fanavaran expertise payload",
|
summary: "Preview Fanavaran expertise payload",
|
||||||
description:
|
description:
|
||||||
"Builds the GEN.08 expertise payload from the active damage expert reply, price-drop data, and Fanavaran lookup mappings without calling Fanavaran.",
|
"GET has no request body. Rebuilds GEN.08 from the active expert reply and lookups on every call. Does not reuse lastPayload cache.",
|
||||||
})
|
})
|
||||||
@ApiParam({
|
@ApiParam({
|
||||||
name: "client",
|
name: "client",
|
||||||
@@ -273,7 +282,7 @@ export class FanavaranController {
|
|||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: "Submit Fanavaran expertise request",
|
summary: "Submit Fanavaran expertise request",
|
||||||
description:
|
description:
|
||||||
"Submits the GEN.08 expertise payload for a claim with existing Fanavaran claimId and dmgCaseId. Stores returned Id as local expertiseId.",
|
"Swagger JSON body is optional. Always rebuilds GEN.08 from the local expert reply and lookups, then POSTs that rebuilt payload. Pasting a previous preview JSON does not skip rebuild. Only money/date fields are taken from the body. DmgCaseId, ClaimExpertId, and DmgSections in pasted JSON are ignored.",
|
||||||
})
|
})
|
||||||
@ApiParam({
|
@ApiParam({
|
||||||
name: "client",
|
name: "client",
|
||||||
@@ -284,6 +293,12 @@ export class FanavaranController {
|
|||||||
name: "claimCaseId",
|
name: "claimCaseId",
|
||||||
description: "Claim case MongoDB ObjectId",
|
description: "Claim case MongoDB ObjectId",
|
||||||
})
|
})
|
||||||
|
@ApiBody({
|
||||||
|
required: false,
|
||||||
|
description:
|
||||||
|
"Optional. Paste previous preview JSON if you want to tweak money/date fields. DmgCaseId / ClaimExpertId / DmgSections in that JSON are ignored.",
|
||||||
|
schema: { type: "object", additionalProperties: true, example: {} },
|
||||||
|
})
|
||||||
async submitExpertise(
|
async submitExpertise(
|
||||||
@Param("client") client: string,
|
@Param("client") client: string,
|
||||||
@Param("claimCaseId") claimCaseId: string,
|
@Param("claimCaseId") claimCaseId: string,
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
import {
|
import {
|
||||||
|
canReuseCachedFanavaranVehicleIds,
|
||||||
collectPartyVinCandidates,
|
collectPartyVinCandidates,
|
||||||
fanavaranIdOrConfigDefault,
|
fanavaranIdOrConfigDefault,
|
||||||
|
fanavaranPayloadMatchesDamagedIdentity,
|
||||||
|
fanavaranVehicleMatchesDamagedIdentity,
|
||||||
filterPoliciesByLine,
|
filterPoliciesByLine,
|
||||||
|
applyFanavaranManualPayloadOverrides,
|
||||||
|
resolveDamageCasePlaqueLookupIds,
|
||||||
insuranceLineLabel,
|
insuranceLineLabel,
|
||||||
parseLastCarPolicyInput,
|
parseLastCarPolicyInput,
|
||||||
pickFanavaranPlaqueLookupFields,
|
pickFanavaranPlaqueLookupFields,
|
||||||
@@ -10,6 +15,7 @@ import {
|
|||||||
pickVinFromPartyVehicle,
|
pickVinFromPartyVehicle,
|
||||||
plaqueLetterFromMiddleCode,
|
plaqueLetterFromMiddleCode,
|
||||||
plaqueMatchesVehicle,
|
plaqueMatchesVehicle,
|
||||||
|
selectFanavaranVehicleForDamageCase,
|
||||||
selectLastAmongCarMatches,
|
selectLastAmongCarMatches,
|
||||||
toAppPlaque,
|
toAppPlaque,
|
||||||
vehicleMatchesCar,
|
vehicleMatchesCar,
|
||||||
@@ -311,4 +317,188 @@ 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);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses national plaque 8/10 when the damaged VIN is unknown to Fanavaran", () => {
|
||||||
|
expect(resolveDamageCasePlaqueLookupIds(null)).toEqual({
|
||||||
|
kindId: 8,
|
||||||
|
sampleId: 10,
|
||||||
|
source: "national-plate-default",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps live PlaqueKindId 8 when Swagger body still has stale 15", () => {
|
||||||
|
expect(
|
||||||
|
applyFanavaranManualPayloadOverrides(
|
||||||
|
{
|
||||||
|
PlaqueKindId: 8,
|
||||||
|
PlaqueSampleId: 10,
|
||||||
|
VehicleKindId: 6704,
|
||||||
|
AccidentVehicleUsedId: 1,
|
||||||
|
Desc: "سپر جلو",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PlaqueKindId: 15,
|
||||||
|
PlaqueSampleId: 24,
|
||||||
|
Desc: "سپر جلو (edited)",
|
||||||
|
},
|
||||||
|
["Desc", "EstimateAmount", "LicenceNo"],
|
||||||
|
),
|
||||||
|
).toEqual({
|
||||||
|
PlaqueKindId: 8,
|
||||||
|
PlaqueSampleId: 10,
|
||||||
|
VehicleKindId: 6704,
|
||||||
|
AccidentVehicleUsedId: 1,
|
||||||
|
Desc: "سپر جلو (edited)",
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -262,6 +262,242 @@ 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";
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Proven Parsian national (چهار تکه) plaque codebook ids. */
|
||||||
|
export const FANAVARAN_NATIONAL_PLAQUE_KIND_ID = 8;
|
||||||
|
export const FANAVARAN_NATIONAL_PLAQUE_SAMPLE_ID = 10;
|
||||||
|
|
||||||
|
export const FANAVARAN_DAMAGE_MANUAL_OVERRIDE_KEYS = [
|
||||||
|
"Desc",
|
||||||
|
"EstimateAmount",
|
||||||
|
"LicenceNo",
|
||||||
|
"LicenceIssuDate",
|
||||||
|
"DriverIsOwner",
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export const FANAVARAN_EXPERTISE_MANUAL_OVERRIDE_KEYS = [
|
||||||
|
"RepairWage",
|
||||||
|
"ComponentReplacementCost",
|
||||||
|
"WasteValue",
|
||||||
|
"DmgAssessmentDate",
|
||||||
|
"InspectionTime",
|
||||||
|
"DamagedVehicleCurrentPrice",
|
||||||
|
"DropAmountAdditionsDeductions",
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export const FANAVARAN_BASE_MANUAL_OVERRIDE_KEYS = [
|
||||||
|
"EstimateAmount",
|
||||||
|
"CulpritLicenceNo",
|
||||||
|
"CulpritLicenceIssuDate",
|
||||||
|
"AccidentLocationAddress",
|
||||||
|
"AccidentDate",
|
||||||
|
"AnnouncementDate",
|
||||||
|
"DocReceivedDate",
|
||||||
|
"AccidentTime",
|
||||||
|
"PoliceReportDesc",
|
||||||
|
"PoliceReportSeri",
|
||||||
|
"PoliceReportSerial",
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Swagger POST body is optional user-entry tweaks only.
|
||||||
|
* Inquiry / lookup-filter ids always come from the live rebuilt payload.
|
||||||
|
*/
|
||||||
|
export function applyFanavaranManualPayloadOverrides(
|
||||||
|
built: Record<string, unknown>,
|
||||||
|
body: Record<string, unknown> | null | undefined,
|
||||||
|
keys: readonly string[],
|
||||||
|
): Record<string, unknown> {
|
||||||
|
if (!body) return built;
|
||||||
|
const next = { ...built };
|
||||||
|
for (const key of keys) {
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(body, key)) continue;
|
||||||
|
if (body[key] === undefined) continue;
|
||||||
|
next[key] = body[key];
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plaque kind/sample only from a matched Fanavaran vehicle record.
|
||||||
|
* CII/ESG inquiry, unsourced cache, and tenant defaults of 15 must not win.
|
||||||
|
* National 4-part plates fall back to 8/10 (lookup-filter safe on Parsian).
|
||||||
|
*/
|
||||||
|
export function resolveDamageCasePlaqueLookupIds(matchedVehicle: Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
> | null): {
|
||||||
|
kindId: number;
|
||||||
|
sampleId: number;
|
||||||
|
source: "vehicle-inquiry" | "national-plate-default";
|
||||||
|
} {
|
||||||
|
const plaque = pickFanavaranPlaqueLookupFields(matchedVehicle);
|
||||||
|
if (plaque.kindId != null) {
|
||||||
|
return {
|
||||||
|
kindId: plaque.kindId,
|
||||||
|
sampleId: plaque.sampleId ?? FANAVARAN_NATIONAL_PLAQUE_SAMPLE_ID,
|
||||||
|
source: "vehicle-inquiry",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
kindId: FANAVARAN_NATIONAL_PLAQUE_KIND_ID,
|
||||||
|
sampleId: FANAVARAN_NATIONAL_PLAQUE_SAMPLE_ID,
|
||||||
|
source: "national-plate-default",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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