forked from Yara724/api
fix: query policies only for current holder
This commit is contained in:
@@ -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>>(
|
||||
|
||||
Reference in New Issue
Block a user