forked from Yara724/api
update the lookups list
This commit is contained in:
@@ -2,7 +2,10 @@ import {
|
||||
filterPoliciesByLine,
|
||||
insuranceLineLabel,
|
||||
parseLastCarPolicyInput,
|
||||
pickFanavaranPlaqueLookupFields,
|
||||
pickFanavaranVehicleFromInquiry,
|
||||
pickVehicleId,
|
||||
pickVinFromPartyVehicle,
|
||||
plaqueLetterFromMiddleCode,
|
||||
plaqueMatchesVehicle,
|
||||
selectLastAmongCarMatches,
|
||||
@@ -84,6 +87,57 @@ describe("fanavaran last car policy", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("picks the inquiry row matching policy VehicleId and VIN from the party", () => {
|
||||
const inquired = [
|
||||
{ Id: 1, UsedId: 1, VehicleKindId: 10 },
|
||||
{
|
||||
Id: 3379978,
|
||||
UsedId: 84,
|
||||
VehicleKindId: 5950,
|
||||
VIN: "NAS451100P4934023",
|
||||
},
|
||||
];
|
||||
expect(pickFanavaranVehicleFromInquiry(inquired, 3379978)).toEqual(
|
||||
inquired[1],
|
||||
);
|
||||
expect(
|
||||
pickVinFromPartyVehicle({
|
||||
vehicle: { vin: "nas451100p4934023" },
|
||||
}),
|
||||
).toBe("NAS451100P4934023");
|
||||
expect(
|
||||
pickVinFromPartyVehicle({
|
||||
vehicle: {
|
||||
inquiry: { mapped: { VIN: "NAS451100P4934023" } },
|
||||
},
|
||||
}),
|
||||
).toBe("NAS451100P4934023");
|
||||
});
|
||||
|
||||
it("reads plaque kind and sample from VIN inquiry for the lookup filter", () => {
|
||||
expect(
|
||||
pickFanavaranPlaqueLookupFields({
|
||||
PlaqueKindId: 15,
|
||||
PlaqueSampleId: 24,
|
||||
PlaqueCityId: null,
|
||||
PlaqueLeftNo: "62",
|
||||
PlaqueMiddleCodeId: 14,
|
||||
PlaqueRightNo: "278",
|
||||
PlaqueSerial: "50",
|
||||
PlaqueNo: "278و62",
|
||||
}),
|
||||
).toEqual({
|
||||
kindId: 15,
|
||||
sampleId: 24,
|
||||
cityId: null,
|
||||
leftNo: "62",
|
||||
middleCodeId: 14,
|
||||
rightNo: "278",
|
||||
serial: "50",
|
||||
plaqueNo: "278و62",
|
||||
});
|
||||
});
|
||||
|
||||
it("matches VIN against ChassisNo when VIN is empty", () => {
|
||||
expect(
|
||||
vehicleMatchesCar(
|
||||
|
||||
@@ -233,6 +233,71 @@ export function pickVehicleId(source: unknown): number | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Prefer the inquiry row whose Id matches the policy VehicleId. */
|
||||
export function pickFanavaranVehicleFromInquiry(
|
||||
inquired: unknown,
|
||||
vehicleId?: number | null,
|
||||
): Record<string, unknown> | null {
|
||||
if (Array.isArray(inquired)) {
|
||||
if (vehicleId != null) {
|
||||
const match = inquired.find((row) => pickVehicleId(row) === vehicleId);
|
||||
if (match) return asObjectRecord(match);
|
||||
}
|
||||
return asObjectRecord(inquired[0]);
|
||||
}
|
||||
return asObjectRecord(inquired);
|
||||
}
|
||||
|
||||
export function pickVinFromPartyVehicle(party: {
|
||||
vehicle?: { vin?: unknown; inquiry?: unknown };
|
||||
} | null | undefined): string | null {
|
||||
const direct = normalizeVin(party?.vehicle?.vin);
|
||||
if (direct) return direct;
|
||||
|
||||
const inquiry = party?.vehicle?.inquiry;
|
||||
const nested = asObjectRecord(inquiry);
|
||||
const mapped = pickVehicleVin(asObjectRecord(nested?.mapped));
|
||||
if (mapped) return mapped;
|
||||
const raw = pickVehicleVin(asObjectRecord(nested?.raw ?? inquiry));
|
||||
return raw || null;
|
||||
}
|
||||
|
||||
export type FanavaranPlaqueLookupFields = {
|
||||
kindId: number | null;
|
||||
sampleId: number | null;
|
||||
cityId: number | null;
|
||||
leftNo: string | null;
|
||||
middleCodeId: number | null;
|
||||
rightNo: string | null;
|
||||
serial: string | null;
|
||||
plaqueNo: string | null;
|
||||
};
|
||||
|
||||
function asPlaqueText(value: unknown): string | null {
|
||||
const text = normalizePlaquePart(value);
|
||||
return text || null;
|
||||
}
|
||||
|
||||
/** Plaque lookup-filter fields from VIN inquiry / vehicle GET. */
|
||||
export function pickFanavaranPlaqueLookupFields(
|
||||
vehicle: Record<string, unknown> | null,
|
||||
): FanavaranPlaqueLookupFields {
|
||||
return {
|
||||
kindId: parseFanavaranId(vehicle?.PlaqueKindId ?? vehicle?.plaqueKindId),
|
||||
sampleId: parseFanavaranId(
|
||||
vehicle?.PlaqueSampleId ?? vehicle?.plaqueSampleId,
|
||||
),
|
||||
cityId: parseFanavaranId(vehicle?.PlaqueCityId ?? vehicle?.plaqueCityId),
|
||||
leftNo: asPlaqueText(vehicle?.PlaqueLeftNo ?? vehicle?.plaqueLeftNo),
|
||||
middleCodeId: parseFanavaranId(
|
||||
vehicle?.PlaqueMiddleCodeId ?? vehicle?.plaqueMiddleCodeId,
|
||||
),
|
||||
rightNo: asPlaqueText(vehicle?.PlaqueRightNo ?? vehicle?.plaqueRightNo),
|
||||
serial: asPlaqueText(vehicle?.PlaqueSerial ?? vehicle?.plaqueSerial),
|
||||
plaqueNo: asPlaqueText(vehicle?.PlaqueNo ?? vehicle?.plaqueNo),
|
||||
};
|
||||
}
|
||||
|
||||
function plaqueNumberEquals(left: unknown, right: unknown): boolean {
|
||||
const a = normalizePlaquePart(left);
|
||||
const b = normalizePlaquePart(right);
|
||||
|
||||
@@ -331,7 +331,7 @@ export class LookupsController {
|
||||
@ApiOperation({
|
||||
summary: "Fanavaran vehicle inquiry by VIN",
|
||||
description:
|
||||
"Returns vehicle details from Fanavaran for the given VIN number via car/vehicles/inquiry-by-vin.",
|
||||
"GET car/vehicles/inquiry-by-vin. Response includes UsedId (AccidentVehicleUsedId), VehicleKindId, plus vehicleKind/used lookup rows. Prefer this over vehicle GET when you already have the VIN.",
|
||||
})
|
||||
@ApiQuery({
|
||||
name: "vin",
|
||||
@@ -346,6 +346,42 @@ export class LookupsController {
|
||||
return await this.lookupsService.inquiryByVin(vin);
|
||||
}
|
||||
|
||||
@Get("inquiry-by-unique-identifier")
|
||||
@ApiOperation({
|
||||
summary: "Fanavaran party inquiry by national code and birthday",
|
||||
description:
|
||||
"GET common/parties/inquiry-by-unique-identifier. Use this to resolve GEN.12 DriverId. Pass birthday as 13480313 or 1348/03/13, or birthYear+birthMonth+birthDay.",
|
||||
})
|
||||
@ApiQuery({ name: "nationalCode", example: "0012345678" })
|
||||
@ApiQuery({
|
||||
name: "birthday",
|
||||
required: false,
|
||||
example: "13480313",
|
||||
description: "Jalali birthday compact (13480313) or delimited (1348/03/13)",
|
||||
})
|
||||
@ApiQuery({ name: "birthYear", required: false, example: 1348 })
|
||||
@ApiQuery({ name: "birthMonth", required: false, example: 3 })
|
||||
@ApiQuery({ name: "birthDay", required: false, example: 13 })
|
||||
@ApiOkResponse({
|
||||
description: "Fanavaran parties plus picked driverId / insurerId",
|
||||
schema: { type: "object" },
|
||||
})
|
||||
async inquiryByUniqueIdentifier(
|
||||
@Query("nationalCode") nationalCode?: string,
|
||||
@Query("birthday") birthday?: string,
|
||||
@Query("birthYear") birthYear?: string,
|
||||
@Query("birthMonth") birthMonth?: string,
|
||||
@Query("birthDay") birthDay?: string,
|
||||
) {
|
||||
return await this.lookupsService.inquiryByUniqueIdentifier({
|
||||
nationalCode,
|
||||
birthday,
|
||||
birthYear,
|
||||
birthMonth,
|
||||
birthDay,
|
||||
});
|
||||
}
|
||||
|
||||
@Get("fanavaran")
|
||||
@ApiOperation({
|
||||
summary: "List configured Fanavaran remote lookups",
|
||||
@@ -627,4 +663,41 @@ export class LookupsController {
|
||||
async bodyPolicyById(@Param("policyId", ParseIntPipe) policyId: number) {
|
||||
return await this.lookupsService.bodyPolicyById(policyId);
|
||||
}
|
||||
|
||||
@Get("vehicle/:vehicleId")
|
||||
@ApiOperation({
|
||||
summary: "Fanavaran vehicle by id",
|
||||
description:
|
||||
"GET car/vehicles/{vehicleId} after a third-party/hull policy returns VehicleId. Pass the policy VehicleVersionNo when you need that exact version; omit it for the latest version. Response includes vehicleKind (VehicleGroupId) and used (UsedId) lookup rows.",
|
||||
})
|
||||
@ApiParam({
|
||||
name: "vehicleId",
|
||||
description: "Fanavaran vehicle id from the policy VehicleId field",
|
||||
example: 3379978,
|
||||
})
|
||||
@ApiQuery({
|
||||
name: "versionNo",
|
||||
required: false,
|
||||
description:
|
||||
"Policy VehicleVersionNo. Omit to fetch the latest vehicle version.",
|
||||
example: 2,
|
||||
})
|
||||
@ApiOkResponse({
|
||||
description:
|
||||
"Vehicle record plus vehicleKind, used, plaque, and color placeholders",
|
||||
schema: { type: "object" },
|
||||
})
|
||||
async vehicleById(
|
||||
@Param("vehicleId", ParseIntPipe) vehicleId: number,
|
||||
@Query("versionNo") versionNo?: string,
|
||||
) {
|
||||
const parsedVersion =
|
||||
versionNo == null || String(versionNo).trim() === ""
|
||||
? undefined
|
||||
: Number(versionNo);
|
||||
return await this.lookupsService.vehicleById(
|
||||
vehicleId,
|
||||
Number.isFinite(parsedVersion) ? parsedVersion : undefined,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,10 @@ import {
|
||||
TEJARAT_STATIC_ACCIDENT_FILES,
|
||||
} from "src/fanavaran/fanavaran-lookup.config";
|
||||
import { FanavaranLookupService } from "src/fanavaran/fanavaran-lookup.service";
|
||||
import {
|
||||
parseJalaliDateParts,
|
||||
selectFanavaranDriverId,
|
||||
} from "src/claim-request-management/fanavaran-driver-inquiry";
|
||||
import { LookupDbService } from "./entities/db-service/lookup.db.service";
|
||||
import {
|
||||
asObjectRecord,
|
||||
@@ -176,9 +180,81 @@ export class LookupsService {
|
||||
return await this.getClientRemoteLookup("dmg-business-line");
|
||||
}
|
||||
|
||||
async inquiryByUniqueIdentifier(query: {
|
||||
nationalCode?: string;
|
||||
birthday?: string;
|
||||
birthYear?: string | number;
|
||||
birthMonth?: string | number;
|
||||
birthDay?: string | number;
|
||||
}): Promise<unknown> {
|
||||
const nationalCode = String(query.nationalCode ?? "").trim();
|
||||
if (!nationalCode) {
|
||||
throw new BadRequestException("nationalCode is required");
|
||||
}
|
||||
|
||||
const fromParts = {
|
||||
year: Number(query.birthYear),
|
||||
month: Number(query.birthMonth),
|
||||
day: Number(query.birthDay),
|
||||
};
|
||||
const parsed =
|
||||
Number.isFinite(fromParts.year) &&
|
||||
fromParts.year > 0 &&
|
||||
Number.isFinite(fromParts.month) &&
|
||||
fromParts.month > 0 &&
|
||||
Number.isFinite(fromParts.day) &&
|
||||
fromParts.day > 0
|
||||
? fromParts
|
||||
: parseJalaliDateParts(query.birthday);
|
||||
|
||||
if (!parsed) {
|
||||
throw new BadRequestException(
|
||||
"birthday (e.g. 13480313) or birthYear+birthMonth+birthDay is required",
|
||||
);
|
||||
}
|
||||
|
||||
const clientKey = this.activeClientKey();
|
||||
const rows = await this.fanavaranLookupService.inquiryByUniqueIdentifier(
|
||||
clientKey,
|
||||
{
|
||||
nationalCode,
|
||||
birthYear: parsed.year,
|
||||
birthMonth: parsed.month,
|
||||
birthDay: parsed.day,
|
||||
},
|
||||
);
|
||||
return {
|
||||
nationalCode,
|
||||
birthday: parsed,
|
||||
driverId: selectFanavaranDriverId(rows, undefined),
|
||||
insurerId: selectFanavaranDriverId(rows, true),
|
||||
parties: rows,
|
||||
};
|
||||
}
|
||||
|
||||
async inquiryByVin(vin: string): Promise<unknown> {
|
||||
const clientKey = this.activeClientKey();
|
||||
return this.fanavaranLookupService.inquiryByVin(clientKey, vin);
|
||||
const raw = await this.fanavaranLookupService.inquiryByVin(clientKey, vin);
|
||||
if (Array.isArray(raw)) {
|
||||
if (raw.length === 0) {
|
||||
throw new NotFoundException(
|
||||
`Fanavaran vehicle for VIN ${vin} was not found.`,
|
||||
);
|
||||
}
|
||||
return Promise.all(
|
||||
raw.map(async (item) => {
|
||||
const vehicle = asObjectRecord(item);
|
||||
return vehicle ? this.enrichVehicle(vehicle) : item;
|
||||
}),
|
||||
);
|
||||
}
|
||||
const vehicle = asObjectRecord(raw);
|
||||
if (!vehicle) {
|
||||
throw new NotFoundException(
|
||||
`Fanavaran vehicle for VIN ${vin} was not found.`,
|
||||
);
|
||||
}
|
||||
return this.enrichVehicle(vehicle);
|
||||
}
|
||||
|
||||
async myPolicies(
|
||||
@@ -500,11 +576,19 @@ export class LookupsService {
|
||||
|
||||
async vehicleById(vehicleId: number, versionNo?: number): Promise<unknown> {
|
||||
const clientKey = this.activeClientKey();
|
||||
return this.fanavaranLookupService.vehicleById(
|
||||
clientKey,
|
||||
vehicleId,
|
||||
versionNo,
|
||||
const vehicle = asObjectRecord(
|
||||
await this.fanavaranLookupService.vehicleById(
|
||||
clientKey,
|
||||
vehicleId,
|
||||
versionNo,
|
||||
),
|
||||
);
|
||||
if (!vehicle) {
|
||||
throw new NotFoundException(
|
||||
`Fanavaran vehicle ${vehicleId} was not found.`,
|
||||
);
|
||||
}
|
||||
return this.enrichVehicle(vehicle);
|
||||
}
|
||||
|
||||
async customerById(customerId: number): Promise<unknown> {
|
||||
|
||||
145
src/lookups/lookups.service.vehicle.spec.ts
Normal file
145
src/lookups/lookups.service.vehicle.spec.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
import { NotFoundException } from "@nestjs/common";
|
||||
import { LookupsService } from "./lookups.service";
|
||||
|
||||
describe("LookupsService.vehicleById", () => {
|
||||
const originalClient = process.env.FANAVARAN_CLIENT;
|
||||
|
||||
beforeAll(() => {
|
||||
process.env.FANAVARAN_CLIENT = "parsian";
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
process.env.FANAVARAN_CLIENT = originalClient;
|
||||
});
|
||||
|
||||
function createService(lookup: Record<string, jest.Mock>) {
|
||||
return new LookupsService({} as any, lookup as any);
|
||||
}
|
||||
|
||||
it("returns the vehicle with kind, used, and plaque lookups", async () => {
|
||||
const lookup = {
|
||||
vehicleById: jest.fn().mockResolvedValue({
|
||||
Id: 3379978,
|
||||
VehicleKindId: 5904,
|
||||
UsedId: 36,
|
||||
VIN: "IRNKAEK4150012345",
|
||||
PlaqueLeftNo: "12",
|
||||
PlaqueMiddleCodeId: 2,
|
||||
PlaqueRightNo: "345",
|
||||
PlaqueSerial: "67",
|
||||
VersionNo: 2,
|
||||
}),
|
||||
getRemoteLookup: jest.fn().mockImplementation((_key, url: string) => {
|
||||
if (url.includes("vehicle-kinds")) {
|
||||
return Promise.resolve([
|
||||
{ Id: 5904, Caption: "بنز", VehicleGroupId: 3, IsActive: 1 },
|
||||
]);
|
||||
}
|
||||
if (url.includes("vehicle-use-types")) {
|
||||
return Promise.resolve([
|
||||
{ Id: 36, Caption: "حمل مواد سريع الاشتعال", VehicleGroupId: 3, IsActive: 1 },
|
||||
]);
|
||||
}
|
||||
return Promise.resolve([]);
|
||||
}),
|
||||
};
|
||||
|
||||
const service = createService(lookup);
|
||||
const result = (await service.vehicleById(3379978, 2)) as Record<
|
||||
string,
|
||||
unknown
|
||||
>;
|
||||
|
||||
expect(lookup.vehicleById).toHaveBeenCalledWith("parsian", 3379978, 2);
|
||||
expect(result.Id).toBe(3379978);
|
||||
expect(result.vehicleKind).toEqual({
|
||||
Id: 5904,
|
||||
Caption: "بنز",
|
||||
VehicleGroupId: 3,
|
||||
IsActive: 1,
|
||||
});
|
||||
expect(result.used).toEqual({
|
||||
Id: 36,
|
||||
Caption: "حمل مواد سريع الاشتعال",
|
||||
VehicleGroupId: 3,
|
||||
IsActive: 1,
|
||||
});
|
||||
expect(result.plaque).toEqual({
|
||||
leftTwoDigits: "12",
|
||||
serialLetter: "ب",
|
||||
threeDigits: "345",
|
||||
rightTwoDigits: "67",
|
||||
});
|
||||
});
|
||||
|
||||
it("enriches inquiry-by-vin rows with used and vehicleKind", async () => {
|
||||
const lookup = {
|
||||
inquiryByVin: jest.fn().mockResolvedValue([
|
||||
{
|
||||
Id: 3379978,
|
||||
UsedId: 84,
|
||||
VehicleKindId: 5950,
|
||||
VIN: "NAS451100P4934023",
|
||||
},
|
||||
]),
|
||||
getRemoteLookup: jest.fn().mockImplementation((_key, url: string) => {
|
||||
if (url.includes("vehicle-kinds")) {
|
||||
return Promise.resolve([
|
||||
{ Id: 5950, Caption: "کامیون", VehicleGroupId: 3, IsActive: 1 },
|
||||
]);
|
||||
}
|
||||
if (url.includes("vehicle-use-types")) {
|
||||
return Promise.resolve([
|
||||
{ Id: 84, Caption: "حمل بار", VehicleGroupId: 3, IsActive: 1 },
|
||||
]);
|
||||
}
|
||||
return Promise.resolve([]);
|
||||
}),
|
||||
};
|
||||
|
||||
const service = createService(lookup);
|
||||
const result = (await service.inquiryByVin("NAS451100P4934023")) as Array<
|
||||
Record<string, unknown>
|
||||
>;
|
||||
|
||||
expect(lookup.inquiryByVin).toHaveBeenCalledWith(
|
||||
"parsian",
|
||||
"NAS451100P4934023",
|
||||
);
|
||||
expect(result[0].UsedId).toBe(84);
|
||||
expect(result[0].used).toEqual({
|
||||
Id: 84,
|
||||
Caption: "حمل بار",
|
||||
VehicleGroupId: 3,
|
||||
IsActive: 1,
|
||||
});
|
||||
expect(result[0].vehicleKind).toEqual({
|
||||
Id: 5950,
|
||||
Caption: "کامیون",
|
||||
VehicleGroupId: 3,
|
||||
IsActive: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it("unwraps an array payload and 404s when Fanavaran returns nothing", async () => {
|
||||
const lookup = {
|
||||
vehicleById: jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce([{ Id: 3379978, VehicleKindId: 1 }])
|
||||
.mockResolvedValueOnce([]),
|
||||
getRemoteLookup: jest.fn().mockResolvedValue([]),
|
||||
};
|
||||
|
||||
const service = createService(lookup);
|
||||
const unwrapped = (await service.vehicleById(3379978)) as Record<
|
||||
string,
|
||||
unknown
|
||||
>;
|
||||
expect(unwrapped.Id).toBe(3379978);
|
||||
expect(unwrapped.color).toBeNull();
|
||||
|
||||
await expect(service.vehicleById(1)).rejects.toBeInstanceOf(
|
||||
NotFoundException,
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user