diff --git a/src/fanavaran/fanavaran-lookup.service.ts b/src/fanavaran/fanavaran-lookup.service.ts index a6e6263..fad58c3 100644 --- a/src/fanavaran/fanavaran-lookup.service.ts +++ b/src/fanavaran/fanavaran-lookup.service.ts @@ -291,9 +291,10 @@ export class FanavaranLookupService { async customerById( clientKey: FanavaranClientKey, customerId: number, + options?: { contractIdOverride?: string }, ): Promise { const url = `${FANAVARAN_LOOKUP_BASE_URL}/common/customers/${customerId}`; - return this.fetchFromFanavaran(clientKey, url); + return this.fetchFromFanavaran(clientKey, url, options); } async inquiryByUniqueIdentifier( diff --git a/src/lookups/fanavaran-last-car-policy.spec.ts b/src/lookups/fanavaran-last-car-policy.spec.ts index cbeded5..57d2866 100644 --- a/src/lookups/fanavaran-last-car-policy.spec.ts +++ b/src/lookups/fanavaran-last-car-policy.spec.ts @@ -1,9 +1,12 @@ import { filterPoliciesByLine, + insuranceLineLabel, parseLastCarPolicyInput, pickVehicleId, + plaqueLetterFromMiddleCode, plaqueMatchesVehicle, selectLastAmongCarMatches, + toAppPlaque, vehicleMatchesCar, } from "./fanavaran-last-car-policy"; @@ -40,6 +43,28 @@ describe("fanavaran last car policy", () => { }); }); + it("maps insurance line ids to CAR_BODY and THIRD_PARTY", () => { + expect(insuranceLineLabel(4)).toBe("CAR_BODY"); + expect(insuranceLineLabel(5)).toBe("THIRD_PARTY"); + }); + + it("converts PlaqueMiddleCodeId 5 to د and builds app plaque parts", () => { + expect(plaqueLetterFromMiddleCode(5)).toBe("د"); + expect( + toAppPlaque({ + PlaqueLeftNo: "29", + PlaqueMiddleCodeId: 5, + PlaqueRightNo: "782", + PlaqueSerial: "60", + }), + ).toEqual({ + leftTwoDigits: "29", + serialLetter: "د", + threeDigits: "782", + rightTwoDigits: "60", + }); + }); + it("keeps only the requested insurance line", () => { const rows = filterPoliciesByLine( [ diff --git a/src/lookups/fanavaran-last-car-policy.ts b/src/lookups/fanavaran-last-car-policy.ts index 74d6707..c69eab8 100644 --- a/src/lookups/fanavaran-last-car-policy.ts +++ b/src/lookups/fanavaran-last-car-policy.ts @@ -94,6 +94,52 @@ export function insuranceLineIdForProduct( : FANAVARAN_THIRD_PARTY_LINE_ID; } +export function insuranceLineLabel( + insuranceLineId: number, +): "CAR_BODY" | "THIRD_PARTY" | null { + if (insuranceLineId === FANAVARAN_CAR_BODY_LINE_ID) return "CAR_BODY"; + if (insuranceLineId === FANAVARAN_THIRD_PARTY_LINE_ID) return "THIRD_PARTY"; + return null; +} + +export function plaqueLetterFromMiddleCode(code: unknown): string | null { + const id = parseFanavaranId(code); + if (id === null) return null; + const found = Object.entries(FANAVARAN_PLATE_LETTER_CODE).find( + ([, value]) => value === id, + ); + return found?.[0] ?? null; +} + +export type AppPlaque = { + leftTwoDigits: string; + serialLetter: string; + threeDigits: string; + rightTwoDigits: string; +}; + +export function toAppPlaque( + vehicle: Record | null, +): AppPlaque | null { + if (!vehicle) return null; + const leftTwoDigits = normalizePlaquePart( + vehicle.PlaqueLeftNo ?? vehicle.plaqueLeftNo, + ); + const threeDigits = normalizePlaquePart( + vehicle.PlaqueRightNo ?? vehicle.plaqueRightNo, + ); + const rightTwoDigits = normalizePlaquePart( + vehicle.PlaqueSerial ?? vehicle.plaqueSerial, + ); + const serialLetter = plaqueLetterFromMiddleCode( + vehicle.PlaqueMiddleCodeId ?? vehicle.plaqueMiddleCodeId, + ); + if (!leftTwoDigits || !serialLetter || !threeDigits || !rightTwoDigits) { + return null; + } + return { leftTwoDigits, serialLetter, threeDigits, rightTwoDigits }; +} + export function toEnglishDigits(value: unknown): string { return String(value ?? "") .split("") diff --git a/src/lookups/lookups.service.last-car-policy.spec.ts b/src/lookups/lookups.service.last-car-policy.spec.ts index 5ee56e9..e407d27 100644 --- a/src/lookups/lookups.service.last-car-policy.spec.ts +++ b/src/lookups/lookups.service.last-car-policy.spec.ts @@ -58,6 +58,7 @@ describe("LookupsService.findLastProcessedCarPolicy", () => { }), ), getRemoteLookup: jest.fn().mockResolvedValue([]), + customerById: jest.fn().mockResolvedValue(null), }; const service = createService(lookup); @@ -68,10 +69,15 @@ describe("LookupsService.findLastProcessedCarPolicy", () => { expect(result.policyId).toBe(20); expect(result.product).toBe("third-party"); + expect(result.insuranceLine).toBe("THIRD_PARTY"); expect(result.insuranceLineId).toBe(5); expect(result.vehicle).toEqual({ Id: 500, VIN: "IRNKAEK4150012345", + color: null, + vehicleKind: null, + used: null, + plaque: null, }); expect(lookup.thirdPartyPolicyById).toHaveBeenCalled(); expect(lookup.myPolicies.mock.calls[0][2]).toBe(5); @@ -103,6 +109,7 @@ describe("LookupsService.findLastProcessedCarPolicy", () => { PlaqueSerial: "67", }), getRemoteLookup: jest.fn().mockResolvedValue([]), + customerById: jest.fn().mockResolvedValue(null), }; const service = createService(lookup); @@ -116,6 +123,13 @@ describe("LookupsService.findLastProcessedCarPolicy", () => { expect(result.policyId).toBe(44); expect(result.product).toBe("car-body"); + expect(result.insuranceLine).toBe("CAR_BODY"); + expect(result.vehicle?.plaque).toEqual({ + leftTwoDigits: "12", + serialLetter: "ب", + threeDigits: "345", + rightTwoDigits: "67", + }); expect(lookup.bodyPolicyById).toHaveBeenCalled(); expect(lookup.thirdPartyPolicyById).not.toHaveBeenCalled(); expect(lookup.inquiryByVin).not.toHaveBeenCalled(); @@ -147,6 +161,7 @@ describe("LookupsService.findLastProcessedCarPolicy", () => { ChassisNo: "RDE07024", }), getRemoteLookup: jest.fn().mockResolvedValue([]), + customerById: jest.fn().mockResolvedValue(null), }; const service = createService(lookup); diff --git a/src/lookups/lookups.service.ts b/src/lookups/lookups.service.ts index 0ae83d3..35e0eb0 100644 --- a/src/lookups/lookups.service.ts +++ b/src/lookups/lookups.service.ts @@ -19,11 +19,13 @@ import { asObjectRecord, filterPoliciesByLine, insuranceLineIdForProduct, + insuranceLineLabel, parseFanavaranId, parseLastCarPolicyInput, pickVehicleId, selectLastAmongCarMatches, sortPoliciesNewestEndDateFirst, + toAppPlaque, vehicleMatchesCar, type FanavaranCarPolicyProduct, type HydratedFanavaranCarPolicy, @@ -203,9 +205,11 @@ export class LookupsService { }, ): Promise<{ product: FanavaranCarPolicyProduct; + insuranceLine: "CAR_BODY" | "THIRD_PARTY" | null; insuranceLineId: number; policyId: number; policy: any; + customer: Record | null; vehicle: Record | null; }> { const parsed = parseLastCarPolicyInput(query); @@ -355,10 +359,77 @@ export class LookupsService { return { product, + insuranceLine: insuranceLineLabel(insuranceLineId), insuranceLineId, policyId: selected.policyId, policy: await this.mapPolicyDetails(selected.policy), - vehicle: selected.vehicle, + customer: await this.fetchCustomer( + clientKey, + parseFanavaranId(selected.policy.CustomerId), + requestOptions, + ), + vehicle: await this.enrichVehicle(selected.vehicle), + }; + } + + private async fetchCustomer( + clientKey: ReturnType, + customerId: number | null, + requestOptions: { contractIdOverride?: string }, + ): Promise | null> { + if (customerId === null) return null; + try { + return asObjectRecord( + await this.fanavaranLookupService.customerById( + clientKey, + customerId, + requestOptions, + ), + ); + } catch (error) { + this.logger.warn( + `Customer GET failed for CustomerId=${customerId}: ${ + error instanceof Error ? error.message : error + }`, + ); + return null; + } + } + + private async lookupRowById( + lookupName: string, + id: number | null, + ): Promise | null> { + if (id === null) return null; + try { + const rows = await this.getClientRemoteLookup(lookupName); + if (!Array.isArray(rows)) return null; + const match = rows.find( + (row) => parseFanavaranId((row as { Id?: unknown })?.Id) === id, + ); + return asObjectRecord(match); + } catch (error) { + this.logger.warn( + `Failed to resolve ${lookupName} id=${id}: ${ + error instanceof Error ? error.message : error + }`, + ); + return null; + } + } + + private async enrichVehicle( + vehicle: Record | null, + ): Promise | null> { + if (!vehicle) return null; + const vehicleKindId = parseFanavaranId(vehicle.VehicleKindId); + const usedId = parseFanavaranId(vehicle.UsedId); + return { + ...vehicle, + color: null, + vehicleKind: await this.lookupRowById("vehicle-kinds", vehicleKindId), + used: await this.lookupRowById("vehicle-use-types", usedId), + plaque: toAppPlaque(vehicle), }; }