forked from Yara724/api
Fix transferred vehicle inquiry routing
This commit is contained in:
68
src/request-management/inquiry-refresh.service.spec.ts
Normal file
68
src/request-management/inquiry-refresh.service.spec.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import { InquiryRefreshService } from "./inquiry-refresh.service";
|
||||||
|
|
||||||
|
describe("InquiryRefreshService current-identifier routing", () => {
|
||||||
|
const createService = () => {
|
||||||
|
const service = Object.create(InquiryRefreshService.prototype) as any;
|
||||||
|
service.plateNormalizer = {
|
||||||
|
normalizePlateText: (value: string) => value,
|
||||||
|
};
|
||||||
|
service.cleanString = (value: unknown) =>
|
||||||
|
value === undefined || value === null ? "" : String(value).trim();
|
||||||
|
return service;
|
||||||
|
};
|
||||||
|
|
||||||
|
it("uses the participant-linked current policyholder, not transfer metadata", () => {
|
||||||
|
const service = createService();
|
||||||
|
const party = {
|
||||||
|
participants: [
|
||||||
|
{
|
||||||
|
participantId: "THIRD_PARTY_POLICYHOLDER",
|
||||||
|
nationalCode: "0011111111",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
participantRoles: {
|
||||||
|
thirdPartyPolicyholder: "THIRD_PARTY_POLICYHOLDER",
|
||||||
|
},
|
||||||
|
person: {
|
||||||
|
nationalCodeOfInsurer: "0099999999",
|
||||||
|
nationalCodeOfDriver: "0088888888",
|
||||||
|
},
|
||||||
|
vehicle: {
|
||||||
|
registrationState: "RECENTLY_TRANSFERRED",
|
||||||
|
plateId: "22-44-ب-111",
|
||||||
|
previousPlateId: "33-55-ج-222",
|
||||||
|
previousPolicyholderNationalCode: "0077777777",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(service.resolveCurrentPolicyholderNationalCode(party)).toBe(
|
||||||
|
"0011111111",
|
||||||
|
);
|
||||||
|
expect(service.resolvePartyPlate(party)).toEqual({
|
||||||
|
ir: 22,
|
||||||
|
leftDigits: 44,
|
||||||
|
centerAlphabet: "ب",
|
||||||
|
centerDigits: 111,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("fails closed instead of rebuilding a plate from an old inquiry payload", () => {
|
||||||
|
const service = createService();
|
||||||
|
const party = {
|
||||||
|
vehicle: {
|
||||||
|
registrationState: "RECENTLY_TRANSFERRED",
|
||||||
|
previousPlateId: "33-55-ج-222",
|
||||||
|
inquiry: {
|
||||||
|
mapped: {
|
||||||
|
Plk1: 55,
|
||||||
|
Plk2: "ج",
|
||||||
|
Plk3: 222,
|
||||||
|
PlkSrl: 33,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(service.resolvePartyPlate(party)).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -18,6 +18,10 @@ import {
|
|||||||
ReinquiryPartyResultDto,
|
ReinquiryPartyResultDto,
|
||||||
} from "./dto/reinquiry-inquiries.dto";
|
} from "./dto/reinquiry-inquiries.dto";
|
||||||
import { getInquiryErrorMessage } from "src/common/utils/inquiry-error";
|
import { getInquiryErrorMessage } from "src/common/utils/inquiry-error";
|
||||||
|
import { InquiryParticipantRole } from "src/common/dto/inquiry-participants.dto";
|
||||||
|
import {
|
||||||
|
participantForStoredPartyRole,
|
||||||
|
} from "./inquiry-participant-resolver";
|
||||||
|
|
||||||
type PlateParts = {
|
type PlateParts = {
|
||||||
leftDigits: number;
|
leftDigits: number;
|
||||||
@@ -193,10 +197,10 @@ export class InquiryRefreshService {
|
|||||||
let partyChanged = false;
|
let partyChanged = false;
|
||||||
let inquiriesChanged = false;
|
let inquiriesChanged = false;
|
||||||
|
|
||||||
|
// Re-inquiry must use the canonical current identifiers only. Previous
|
||||||
|
// transfer metadata and old provider payloads are never valid lookup input.
|
||||||
const plate = this.resolvePartyPlate(party);
|
const plate = this.resolvePartyPlate(party);
|
||||||
const nationalCode =
|
const nationalCode = this.resolveCurrentPolicyholderNationalCode(party);
|
||||||
this.cleanString(party?.person?.nationalCodeOfInsurer) ||
|
|
||||||
this.cleanString(party?.person?.nationalCodeOfDriver);
|
|
||||||
const birthDate =
|
const birthDate =
|
||||||
party?.person?.insurerBirthday ??
|
party?.person?.insurerBirthday ??
|
||||||
party?.person?.driverBirthday ??
|
party?.person?.driverBirthday ??
|
||||||
@@ -619,12 +623,14 @@ export class InquiryRefreshService {
|
|||||||
);
|
);
|
||||||
if (fromPlateId) return fromPlateId;
|
if (fromPlateId) return fromPlateId;
|
||||||
|
|
||||||
const candidates = [
|
// A missing plateId is intentionally not recovered from an inquiry
|
||||||
party?.vehicle?.inquiry?.mapped,
|
// response: that response may belong to a previous plate on transferred
|
||||||
party?.vehicle?.inquiry?.raw,
|
// vehicles. The refresh operation must fail closed instead of mixing
|
||||||
party?.vehicle?.inquiry,
|
// identifiers.
|
||||||
party?.vehicle,
|
const currentPlate = party?.vehicle?.currentPlate;
|
||||||
].filter(Boolean);
|
if (!currentPlate || typeof currentPlate !== "object") return null;
|
||||||
|
|
||||||
|
const candidates = [currentPlate];
|
||||||
|
|
||||||
for (const candidate of candidates) {
|
for (const candidate of candidates) {
|
||||||
const leftDigits = this.firstPresent(
|
const leftDigits = this.firstPresent(
|
||||||
@@ -675,6 +681,19 @@ export class InquiryRefreshService {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private resolveCurrentPolicyholderNationalCode(
|
||||||
|
party: Record<string, any>,
|
||||||
|
): string {
|
||||||
|
const participant = participantForStoredPartyRole(
|
||||||
|
party,
|
||||||
|
InquiryParticipantRole.THIRD_PARTY_POLICYHOLDER,
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
this.cleanString(participant?.nationalCode) ||
|
||||||
|
this.cleanString(party?.person?.nationalCodeOfInsurer)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private parsePlateFromCompactString(plateId?: string): PlateParts | null {
|
private parsePlateFromCompactString(plateId?: string): PlateParts | null {
|
||||||
if (!plateId) return null;
|
if (!plateId) return null;
|
||||||
const parts = String(plateId).split("-");
|
const parts = String(plateId).split("-");
|
||||||
|
|||||||
@@ -215,7 +215,7 @@ describe("RequestManagementService policyholder inquiry routing", () => {
|
|||||||
expect(service.getThirdPartyVinInquiry).toHaveBeenCalledTimes(1);
|
expect(service.getThirdPartyVinInquiry).toHaveBeenCalledTimes(1);
|
||||||
expect(service.sandHubService.getCarBodyInquiry).toHaveBeenCalledWith({
|
expect(service.sandHubService.getCarBodyInquiry).toHaveBeenCalledWith({
|
||||||
nationalCodeOfInsurer: "0033333333",
|
nationalCodeOfInsurer: "0033333333",
|
||||||
plate: vehicle.vin,
|
plate: currentPlate,
|
||||||
});
|
});
|
||||||
expect(req.inquiries.thirdParty.has).toBe(true);
|
expect(req.inquiries.thirdParty.has).toBe(true);
|
||||||
expect(req.inquiries.carBody.has).toBe(true);
|
expect(req.inquiries.carBody.has).toBe(true);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { ExternalExceptionFilter } from "@nestjs/core/exceptions/external-except
|
|||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { ClientService } from "src/client/client.service";
|
import { ClientService } from "src/client/client.service";
|
||||||
import { AddPlateDto } from "src/profile/dto/user/AddPlateDto";
|
import { AddPlateDto } from "src/profile/dto/user/AddPlateDto";
|
||||||
|
import { VehicleRegistrationState } from "src/common/dto/inquiry-participants.dto";
|
||||||
import {
|
import {
|
||||||
CarBodyFormDto,
|
CarBodyFormDto,
|
||||||
CarBodySecondForm,
|
CarBodySecondForm,
|
||||||
@@ -466,6 +467,29 @@ export class RequestManagementService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A VIN submission still carries the canonical current plate. For a recent
|
||||||
|
* transfer, the car-body policy must be checked against that current plate
|
||||||
|
* and the current policyholder; the previous plate is historical metadata
|
||||||
|
* only. For ordinary VIN submissions we retain the dedicated VIN lookup.
|
||||||
|
*/
|
||||||
|
private async getCarBodyInquiryForVinSubmission(
|
||||||
|
submission: NormalizedInquirySubmission<Record<string, any>>,
|
||||||
|
): Promise<any> {
|
||||||
|
if (
|
||||||
|
submission.vehicle?.registrationState ===
|
||||||
|
VehicleRegistrationState.RECENTLY_TRANSFERRED
|
||||||
|
) {
|
||||||
|
return this.getCarBodyPlateInquiry(submission);
|
||||||
|
}
|
||||||
|
|
||||||
|
const subjects = resolveInquirySubjects(submission);
|
||||||
|
return this.sandHubService.getCarBodyInquiry({
|
||||||
|
nationalCodeOfInsurer: subjects.carBodyPolicyNationalCode!,
|
||||||
|
plate: submission.vehicle?.vin as any,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private async getThirdPartyVinInquiry(
|
private async getThirdPartyVinInquiry(
|
||||||
submission: NormalizedInquirySubmission<Record<string, any>>,
|
submission: NormalizedInquirySubmission<Record<string, any>>,
|
||||||
options?: Record<string, any>,
|
options?: Record<string, any>,
|
||||||
@@ -2519,10 +2543,8 @@ export class RequestManagementService {
|
|||||||
// flow before the case is allowed to advance.
|
// flow before the case is allowed to advance.
|
||||||
if (req.type === BlameRequestType.CAR_BODY && role === PartyRole.FIRST) {
|
if (req.type === BlameRequestType.CAR_BODY && role === PartyRole.FIRST) {
|
||||||
try {
|
try {
|
||||||
const carBodyInfo = await this.sandHubService.getCarBodyInquiry({
|
const carBodyInfo =
|
||||||
nationalCodeOfInsurer: subjects.carBodyPolicyNationalCode!,
|
await this.getCarBodyInquiryForVinSubmission(inquirySubmission);
|
||||||
plate: body.vin,
|
|
||||||
});
|
|
||||||
this.recordPartyCaseInquiryStatus(req, "carBody", role, true, {
|
this.recordPartyCaseInquiryStatus(req, "carBody", role, true, {
|
||||||
source: carBodyInfo.source,
|
source: carBodyInfo.source,
|
||||||
raw: carBodyInfo.raw,
|
raw: carBodyInfo.raw,
|
||||||
@@ -11117,10 +11139,8 @@ export class RequestManagementService {
|
|||||||
partyRole === PartyRole.FIRST
|
partyRole === PartyRole.FIRST
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const carBodyInfo = await this.sandHubService.getCarBodyInquiry({
|
const carBodyInfo =
|
||||||
nationalCodeOfInsurer: subjects.carBodyPolicyNationalCode!,
|
await this.getCarBodyInquiryForVinSubmission(inquirySubmission);
|
||||||
plate: partyData.vin as any, // VIN used as identifier for CAR_BODY
|
|
||||||
});
|
|
||||||
this.recordPartyCaseInquiryStatus(req, "carBody", partyRole, true, {
|
this.recordPartyCaseInquiryStatus(req, "carBody", partyRole, true, {
|
||||||
source: carBodyInfo.source,
|
source: carBodyInfo.source,
|
||||||
raw: carBodyInfo.raw,
|
raw: carBodyInfo.raw,
|
||||||
|
|||||||
Reference in New Issue
Block a user