fix: query policies only for current holder

This commit is contained in:
SepehrYahyaee
2026-09-19 11:26:40 +03:30
parent 51e1d495a9
commit 45e0ad883a
9 changed files with 153 additions and 375 deletions

View File

@@ -393,26 +393,6 @@ export function resolveInquiryVehicle(
};
}
export function vehiclePlateCandidates(input?: ResolvedInquiryVehicle): Array<{
kind: "CURRENT" | "PREVIOUS";
plate: InquiryVehicleInputDto["currentPlate"];
}> {
if (!input) return [];
return [
{ kind: "CURRENT" as const, plate: input.currentPlate },
...(input.registrationState ===
VehicleRegistrationState.RECENTLY_TRANSFERRED && input.previousPlate
? [{ kind: "PREVIOUS" as const, plate: input.previousPlate }]
: []),
];
}
function normalizeVehicleSerial(value: unknown): string {
return String(value ?? "")
.toUpperCase()
.replace(/[^A-Z0-9]/g, "");
}
/** A dated result is usable only while the returned policy has not expired. */
export function isMappedPolicyCurrent(
mapped: Record<string, any>,
@@ -429,19 +409,6 @@ export function isMappedPolicyCurrent(
return endDate != null && endDate >= todayGregorian;
}
function normalizePlateForComparison(
plate: InquiryVehicleInputDto["currentPlate"],
): string {
return [
plate?.ir,
plate?.leftDigits,
plate?.centerAlphabet,
plate?.centerDigits,
]
.map((part) => String(part ?? "").trim())
.join("|");
}
const LEGACY_INQUIRY_FIELDS = [
"nationalCodeOfDriver",
"driverBirthday",
@@ -469,63 +436,21 @@ function assertStructuredInquiryInput(input: Record<string, any>): void {
}
}
export function assertPreviousPlateInquiryMatchesVin(
expectedVin: string,
mapped: Record<string, any>,
): void {
const expected = normalizeVehicleSerial(expectedVin);
const candidates = [
mapped?.VinNumberField,
mapped?.vin,
mapped?.VIN,
mapped?.ChassisNumberField,
mapped?.chassisNumber,
mapped?.ChassisNo,
mapped?.vehicle?.VIN,
mapped?.vehicle?.ChassisNo,
]
.map(normalizeVehicleSerial)
.filter(Boolean);
if (!expected || !candidates.includes(expected)) {
throw new BadRequestException(
"نتیجه استعلام پلاک قبلی با شماره شاسی (VIN) واردشده مطابقت ندارد و پرونده نیازمند بررسی دستی است.",
);
}
}
export function isPolicyNotFoundError(error: unknown): boolean {
const candidate = error as Record<string, any> | null;
const status = candidate?.status ?? candidate?.response?.status;
if (Number(status) === 404) return true;
const code = String(
candidate?.code ?? candidate?.response?.data?.code ?? "",
).toUpperCase();
if (["NOT_FOUND", "POLICY_NOT_FOUND", "NO_POLICY"].includes(code)) {
return true;
}
const message = String(
candidate?.message ?? candidate?.response?.data?.message ?? error ?? "",
);
return /\bnot[ -]?found\b|\bno (?:relevant )?policy\b|یافت نشد|فاقد بیمه(?:نامه)?/i.test(
message,
);
}
export async function runPlateInquiryWithFallback<T>(options: {
vehicle?: ResolvedInquiryVehicle;
fallbackCurrentPlate: InquiryVehicleInputDto["currentPlate"];
query: (
plate: InquiryVehicleInputDto["currentPlate"],
plateKind: "CURRENT" | "PREVIOUS",
) => Promise<T>;
/**
* Run a policy inquiry only for the submitted current plate. Recent-transfer
* data is retained as case metadata, but must never trigger an inquiry for a
* previous plate or a previous policyholder.
*/
export async function runCurrentPlateInquiry<T>(options: {
currentPlate: InquiryVehicleInputDto["currentPlate"];
vin?: string;
query: (plate: InquiryVehicleInputDto["currentPlate"]) => Promise<T>;
isUsable: (value: T) => boolean;
mappedValue: (value: T) => Record<string, any>;
shouldFallbackOnError?: (error: unknown) => boolean;
}): Promise<{
value: T;
plateKind: "CURRENT" | "PREVIOUS";
plateKind: "CURRENT";
attempts: Array<{
plateKind: "CURRENT" | "PREVIOUS";
plateKind: "CURRENT";
plate: InquiryVehicleInputDto["currentPlate"];
vin?: string;
succeeded: boolean;
@@ -533,12 +458,8 @@ export async function runPlateInquiryWithFallback<T>(options: {
error?: string;
}>;
}> {
const candidates = options.vehicle
? vehiclePlateCandidates(options.vehicle)
: [{ kind: "CURRENT" as const, plate: options.fallbackCurrentPlate }];
let lastError: unknown;
const attempts: Array<{
plateKind: "CURRENT" | "PREVIOUS";
plateKind: "CURRENT";
plate: InquiryVehicleInputDto["currentPlate"];
vin?: string;
succeeded: boolean;
@@ -546,80 +467,50 @@ export async function runPlateInquiryWithFallback<T>(options: {
error?: string;
}> = [];
for (let index = 0; index < candidates.length; index += 1) {
const candidate = candidates[index];
const isLast = index === candidates.length - 1;
try {
const value = await options.query(candidate.plate, candidate.kind);
const usable = options.isUsable(value);
if (!usable) {
attempts.push({
plateKind: candidate.kind,
plate: candidate.plate,
...(options.vehicle?.vin ? { vin: options.vehicle.vin } : {}),
succeeded: true,
usable: false,
});
if (!isLast) continue;
const error = new BadRequestException(
"بیمه‌نامه معتبر و فعالی مطابق مشخصات خودرو و کد ملی واردشده یافت نشد.",
) as BadRequestException & { attempts?: typeof attempts };
error.attempts = attempts;
throw error;
}
if (candidate.kind === "PREVIOUS" && usable) {
assertPreviousPlateInquiryMatchesVin(
options.vehicle!.vin!,
options.mappedValue(value),
);
}
try {
const value = await options.query(options.currentPlate);
const usable = options.isUsable(value);
if (!usable) {
attempts.push({
plateKind: candidate.kind,
plate: candidate.plate,
...(options.vehicle?.vin ? { vin: options.vehicle.vin } : {}),
plateKind: "CURRENT",
plate: options.currentPlate,
...(options.vin ? { vin: options.vin } : {}),
succeeded: true,
usable: true,
usable: false,
});
return { value, plateKind: candidate.kind, attempts };
} catch (error) {
lastError = error;
const alreadyRecorded =
typeof error === "object" &&
error != null &&
Array.isArray((error as { attempts?: unknown }).attempts);
if (!alreadyRecorded) {
attempts.push({
plateKind: candidate.kind,
plate: candidate.plate,
...(options.vehicle?.vin ? { vin: options.vehicle.vin } : {}),
succeeded: false,
error: error instanceof Error ? error.message : String(error),
});
}
if (
!isLast &&
!(options.shouldFallbackOnError ?? isPolicyNotFoundError)(error)
) {
if (typeof error === "object" && error != null) {
(error as { attempts?: typeof attempts }).attempts = attempts;
}
throw error;
}
if (isLast) {
if (typeof error === "object" && error != null) {
(error as { attempts?: typeof attempts }).attempts = attempts;
}
throw error;
}
const error = new BadRequestException(
"بیمه‌نامه معتبر و فعالی مطابق مشخصات خودرو و کد ملی واردشده یافت نشد.",
) as BadRequestException & { attempts?: typeof attempts };
error.attempts = attempts;
throw error;
}
attempts.push({
plateKind: "CURRENT",
plate: options.currentPlate,
...(options.vin ? { vin: options.vin } : {}),
succeeded: true,
usable: true,
});
return { value, plateKind: "CURRENT", attempts };
} catch (error) {
const alreadyRecorded =
typeof error === "object" &&
error != null &&
Array.isArray((error as { attempts?: unknown }).attempts);
if (!alreadyRecorded) {
attempts.push({
plateKind: "CURRENT",
plate: options.currentPlate,
...(options.vin ? { vin: options.vin } : {}),
succeeded: false,
error: error instanceof Error ? error.message : String(error),
});
}
if (typeof error === "object" && error != null) {
(error as { attempts?: typeof attempts }).attempts = attempts;
}
throw error;
}
throw (
lastError ??
new BadRequestException(
"برای هیچ‌یک از پلاک‌های ثبت‌شده نتیجه معتبری یافت نشد.",
)
);
}
export function normalizeInquirySubmission<T extends Record<string, any>>(