diff --git a/docs/external-integrations-reference.fa.html b/docs/external-integrations-reference.fa.html
index e3e32af..1626b01 100644
--- a/docs/external-integrations-reference.fa.html
+++ b/docs/external-integrations-reference.fa.html
@@ -159,6 +159,7 @@
برای هر پرسوجوی مبتنی بر پلاک:
+ - درخواست همیشه با پلاک فعلی ارسالشده و بیمهگذار نهایی همان نوع بیمه انجام میشود. متادیتای انتقال اخیر هیچ استعلامی برای پلاک یا بیمهگذار قبلی ایجاد نمیکند.
- ۱. بررسی دادههای آفلاین (MongoDB) — اگر داده مطابق یافت شد، آن را برگردانده و تمام HTTP را رد کن.
- ۲. اگر
CLIENT_ID=8 (تنانت پارسیان/ESG) → مسیریابی به ESG /inquiry/policyByPlate یا /inquiry/policyByChassis.
- ۳. در غیر این صورت → مسیریابی به پرسوجوی تجارت
/block-inquiry-tejarat (THIRD_PARTY) یا /block-inquiry-tejarat/badane (CAR_BODY).
diff --git a/docs/external-integrations-reference.html b/docs/external-integrations-reference.html
index 73daada..ce00df3 100644
--- a/docs/external-integrations-reference.html
+++ b/docs/external-integrations-reference.html
@@ -154,6 +154,7 @@
For every plate-based block inquiry:
+ - The request always uses the submitted current plate and the resolved policyholder for that policy type. Recent-transfer metadata never triggers a previous-plate or previous-policyholder lookup.
- 1. Check offline-inquiry seeds (MongoDB) — if a matching seed exists, return it and skip all HTTP.
- 2. If
CLIENT_ID=8 (Parsian/ESG tenant) → route to ESG /inquiry/policyByPlate or /inquiry/policyByChassis.
- 3. Otherwise → route to Tejarat inquiry
/block-inquiry-tejarat (THIRD_PARTY) or /block-inquiry-tejarat/badane (CAR_BODY).
diff --git a/docs/inquiry-participants-frontend-guide.fa.md b/docs/inquiry-participants-frontend-guide.fa.md
index d1fa90f..68ee806 100644
--- a/docs/inquiry-participants-frontend-guide.fa.md
+++ b/docs/inquiry-participants-frontend-guide.fa.md
@@ -157,7 +157,7 @@
}
```
-سیستم ابتدا پلاک فعلی را با کد ملی بیمهگذار فعلیِ مرتبط با نوع بیمه استعلام میکند. اگر نتیجه ناموجود، منقضی یا فاقد بیمهنامه مرتبط باشد، پلاک قبلی را با `previousPolicyholderNationalCode` امتحان میکند. نتیجه پلاک قبلی فقط در صورت تطبیق VIN پذیرفته میشود؛ پلاک قبلی هرگز جایگزین پلاک فعلی نمیشود.
+اطلاعات انتقال اخیر فقط بهعنوان متادیتای پرونده ذخیره میشوند. در route پلاک، سیستم فقط `currentPlate` را با کد ملی بیمهگذار نهاییِ مرتبط با نوع بیمه استعلام میکند و هیچ fallbackای به `previousPlate` یا `previousPolicyholderNationalCode` ندارد. در route شماره شاسی نیز فقط `vehicle.vin` با همان بیمهگذار نهایی استعلام میشود.
حتی در route مربوط به VIN، آبجکت `vehicle` از قرارداد مشترک استفاده میکند و `currentPlate` در قرارداد فعلی الزامی است. مقدار VIN در `vehicle.vin` قرار میگیرد، نه در فیلد سطح بالای `vin`.
diff --git a/docs/inquiry-participants-proposal.fa.md b/docs/inquiry-participants-proposal.fa.md
index c31ce78..e8cd0b7 100644
--- a/docs/inquiry-participants-proposal.fa.md
+++ b/docs/inquiry-participants-proposal.fa.md
@@ -79,9 +79,9 @@
}
```
-مقدار پیشفرض `registrationState` برابر `CURRENT` است و برای این مسیر استثنایی مقدار `RECENTLY_TRANSFERRED` استفاده میشود. در انتقال اخیر، `previousPlate`، `previousPolicyholderNationalCode` و `vin` الزامیاند؛ فیلدهای مربوط به پلاک قبلی در حالت عادی `CURRENT` نباید ارسال شوند. پلاک فعلی همچنان شناسه اصلی خودرو است. هماهنگکننده استعلام ابتدا پلاک فعلی را با کد ملی بیمهگذار فعلی بررسی میکند و اگر نتیجه ناموجود، قدیمی یا فاقد بیمهنامه مرتبط بود، پلاک قبلی را با `previousPolicyholderNationalCode` استعلام میکند.
+مقدار پیشفرض `registrationState` برابر `CURRENT` است و برای این مسیر استثنایی مقدار `RECENTLY_TRANSFERRED` استفاده میشود. در انتقال اخیر، `previousPlate`، `previousPolicyholderNationalCode` و `vin` الزامیاند؛ فیلدهای مربوط به پلاک قبلی در حالت عادی `CURRENT` نباید ارسال شوند. این اطلاعات انتقال فقط بهعنوان متادیتای پرونده نگهداری میشوند و پلاک فعلی همچنان شناسه اصلی خودرو است.
-پیش از پذیرش نتیجه پلاک قبلی، بکاند باید یکسان بودن VIN/شماره شاسی را بررسی کند. در صورت مغایرت، انتخاب خودکار متوقف و اصلاح اطلاعات یا بررسی دستی الزامی شود. هر دو پلاک و تمام تلاشهای استعلام برای ممیزی نگهداری شوند، اما پلاک قبلی هیچگاه نباید روی پلاک فعلی نوشته شود.
+در route پلاک، هماهنگکننده دقیقاً یک استعلام بیمه انجام میدهد: `currentPlate` همراه با بیمهگذار نهایی همان نوع بیمه. در route شماره شاسی نیز `vehicle.vin` با همان بیمهگذار نهایی ارسال میشود. `previousPlate` هیچگاه استعلام نمیشود، `previousPolicyholderNationalCode` به ارائهدهنده استعلام ارسال نمیشود و VIN برای انتخاب نتیجه پلاک قبلی به کار نمیرود.
## ترتیب پیشنهادی فرم
@@ -102,7 +102,7 @@
- شخص نهایی هر نقش را برگرداند؛
- هویت درست را به استعلام مرتبط بدهد: گواهینامه ← راننده، مالکیت و تطبیق شبا ← مالک خودرو، بیمه شخص ثالث با پلاک/VIN ← بیمهگذار شخص ثالث، بیمه بدنه با پلاک/VIN ← بیمهگذار بدنه؛
- شبا را در استعلام شخص مطالبهکننده خسارت (`SECOND` زیاندیده در `THIRD_PARTY` و طرف اول در `CAR_BODY`) الزامی کند و با کد ملی مالک خودرو اعتبارسنجی کند؛ در استعلام `FIRST` مقصر پرونده ثالث شبا دریافت نمیشود؛
-- بر اساس یک قاعده مشخص، پلاک فعلی یا قبلی را انتخاب و نتیجه پلاک قبلی را با VIN/شماره شاسی تطبیق دهد؛
+- فقط پلاک فعلی یا VIN ارسالشده را با بیمهگذار نهایی همان نوع بیمه استعلام کند؛ متادیتای انتقال قبلی نباید مسیریابی استعلام را تغییر دهد؛
- استعلام هویت را برای هر شخص یکتا فقط یک بار اجرا کند؛
- اشخاص نرمالشده و نقشهای آنها را در `Party` مربوط ذخیره کند.
- `participants` و `participantRoles` ذخیرهشده را بدون حذف اطلاعات در جزئیات پرونده پنلهای کارشناسی و پرونده خسارت متصل نمایش دهد تا اطلاعات راننده و سایر نقشها برای بررسی در دسترس بماند.
diff --git a/docs/inquiry-participants-proposal.md b/docs/inquiry-participants-proposal.md
index fb0dee3..183070e 100644
--- a/docs/inquiry-participants-proposal.md
+++ b/docs/inquiry-participants-proposal.md
@@ -79,9 +79,9 @@ Participant roles and vehicle identifiers are separate concerns. When a vehicle
}
```
-`registrationState` is `CURRENT` by default or `RECENTLY_TRANSFERRED` for this exceptional path. `previousPlate`, `previousPolicyholderNationalCode`, and `vin` are required when `registrationState=RECENTLY_TRANSFERRED`; the previous-plate fields are forbidden for the normal `CURRENT` path. The current plate remains the vehicle's primary identifier. The inquiry orchestrator queries the current plate with the current policyholder's national code first, then automatically tries the previous plate with `previousPolicyholderNationalCode` when the current result is missing, stale, or does not find the relevant policy.
+`registrationState` is `CURRENT` by default or `RECENTLY_TRANSFERRED` for this exceptional path. `previousPlate`, `previousPolicyholderNationalCode`, and `vin` are required when `registrationState=RECENTLY_TRANSFERRED`; the previous-plate fields are forbidden for the normal `CURRENT` path. These transfer fields are retained only as case metadata, and the current plate remains the vehicle's primary identifier.
-Before accepting a previous-plate result, the backend must correlate it to the same VIN/chassis. A mismatch must stop automatic selection and require correction or manual review. Both identifiers and every attempted inquiry should be retained for audit, but a previous plate must never overwrite the current plate.
+For a plate route, the inquiry orchestrator performs exactly one policy lookup: `currentPlate` with the resolved policyholder for that policy type. For a VIN route, it uses `vehicle.vin` with the same resolved policyholder. It never queries `previousPlate`, never sends `previousPolicyholderNationalCode` to an inquiry provider, and does not use VIN to select a previous-plate result.
## Suggested UI sequence
@@ -102,7 +102,7 @@ Create one shared participant resolver used by every inquiry route. Its interfac
- return the resolved person for each role;
- route the correct identity to each inquiry: driver licence → Driver, ownership and Sheba validation → Vehicle Owner, third-party policy by plate/VIN → Third-party Policyholder, car-body policy by plate/VIN → Car-body Policyholder;
- require Sheba in the claimant inquiry (`THIRD_PARTY` damaged/SECOND party and `CAR_BODY` first party) and validate it against the resolved Vehicle Owner; the `THIRD_PARTY` guilty/FIRST inquiry does not collect Sheba;
-- choose the current or previous plate deterministically and verify previous-plate results against VIN/chassis;
+- query only the submitted current plate or VIN with the resolved policyholder for that policy type; previous-transfer metadata must not affect inquiry routing;
- run personal identity inquiry once per distinct person;
- persist normalized participants and role assignments on the relevant `Party`.
- expose the persisted `participants` and `participantRoles` unchanged in expert-facing blame and linked-claim details so driver and other role data remain available for review.
diff --git a/src/request-management/inquiry-participant-resolver.spec.ts b/src/request-management/inquiry-participant-resolver.spec.ts
index 20553b9..7cf6036 100644
--- a/src/request-management/inquiry-participant-resolver.spec.ts
+++ b/src/request-management/inquiry-participant-resolver.spec.ts
@@ -5,7 +5,6 @@ import {
VehicleRegistrationState,
} from "src/common/dto/inquiry-participants.dto";
import {
- assertPreviousPlateInquiryMatchesVin,
isMappedPolicyCurrent,
normalizeInquirySubmission,
participantForRole,
@@ -13,9 +12,8 @@ import {
resolveInquiryParticipants,
resolveInquirySubjects,
resolveInquiryVehicle,
- runPlateInquiryWithFallback,
+ runCurrentPlateInquiry,
sanitizeStoredInquiryParticipants,
- vehiclePlateCandidates,
} from "./inquiry-participant-resolver";
describe("inquiry participant resolver", () => {
@@ -309,42 +307,6 @@ describe("inquiry participant resolver", () => {
).toBe("0022222222");
});
- it("orders the current plate before the previous-plate fallback", () => {
- const currentPlate = {
- leftDigits: "44",
- centerAlphabet: "ب",
- centerDigits: "111",
- ir: "22",
- };
- const previousPlate = {
- leftDigits: "55",
- centerAlphabet: "ج",
- centerDigits: "222",
- ir: "33",
- };
-
- expect(
- vehiclePlateCandidates({
- registrationState: VehicleRegistrationState.RECENTLY_TRANSFERRED,
- currentPlate,
- previousPlate,
- vin: "NAAM01E15HK123456",
- previousPolicyholderNationalCode: "0098765432",
- }),
- ).toEqual([
- { kind: "CURRENT", plate: currentPlate },
- { kind: "PREVIOUS", plate: previousPlate },
- ]);
- });
-
- it("rejects a previous-plate result for another chassis", () => {
- expect(() =>
- assertPreviousPlateInquiryMatchesVin("NAAM01E15HK123456", {
- VinNumberField: "DIFFERENTVIN00001",
- }),
- ).toThrow(BadRequestException);
- });
-
it("requires the driver's licence status in the new contract", () => {
expect(() =>
resolveInquiryParticipants(BlameRequestType.THIRD_PARTY, {
@@ -357,58 +319,36 @@ describe("inquiry participant resolver", () => {
).toThrow(BadRequestException);
});
- it("falls back to the previous plate and accepts only a matching VIN", async () => {
+ it("runs only one inquiry for the current plate", async () => {
const currentPlate = {
leftDigits: "44",
centerAlphabet: "ب",
centerDigits: "111",
ir: "22",
};
- const previousPlate = {
- leftDigits: "55",
- centerAlphabet: "ج",
- centerDigits: "222",
- ir: "33",
- };
- const query = jest
- .fn()
- .mockRejectedValueOnce(new Error("not found"))
- .mockResolvedValueOnce({
- mapped: { VinNumberField: "NAAM01E15HK123456", CompanyName: "پارسیان" },
- });
-
- const result = await runPlateInquiryWithFallback<{
- mapped: { VinNumberField?: string; CompanyName?: string };
- }>({
- vehicle: {
- registrationState: VehicleRegistrationState.RECENTLY_TRANSFERRED,
- currentPlate,
- previousPlate,
- vin: "NAAM01E15HK123456",
- previousPolicyholderNationalCode: "0098765432",
- },
- fallbackCurrentPlate: currentPlate,
- query,
- isUsable: (value) => !!value.mapped.CompanyName,
- mappedValue: (value) => value.mapped,
+ const query = jest.fn().mockResolvedValue({
+ mapped: { CompanyName: "پارسیان" },
});
- expect(query).toHaveBeenCalledTimes(2);
- expect(query).toHaveBeenNthCalledWith(1, currentPlate, "CURRENT");
- expect(query).toHaveBeenNthCalledWith(2, previousPlate, "PREVIOUS");
- expect(result.plateKind).toBe("PREVIOUS");
+ const result = await runCurrentPlateInquiry<{
+ mapped: { CompanyName?: string };
+ }>({
+ currentPlate,
+ vin: "NAAM01E15HK123456",
+ query,
+ isUsable: (value) => !!value.mapped.CompanyName,
+ });
+
+ expect(query).toHaveBeenCalledTimes(1);
+ expect(query).toHaveBeenCalledWith(currentPlate);
+ expect(result.plateKind).toBe("CURRENT");
expect(result.attempts).toMatchObject([
- { plateKind: "CURRENT", succeeded: false, error: "not found" },
- { plateKind: "PREVIOUS", succeeded: true, usable: true },
+ { plateKind: "CURRENT", succeeded: true, usable: true },
]);
expect(result.attempts[0]).toMatchObject({
plate: currentPlate,
vin: "NAAM01E15HK123456",
});
- expect(result.attempts[1]).toMatchObject({
- plate: previousPlate,
- vin: "NAAM01E15HK123456",
- });
});
it("rejects a car-body policyholder on a third-party case", () => {
@@ -510,95 +450,52 @@ describe("inquiry participant resolver", () => {
);
});
- it("falls back from a stale current policy to a current previous-plate policy", async () => {
+ it("rejects a stale current policy without trying another plate", async () => {
const currentPlate = {
leftDigits: "44",
centerAlphabet: "ب",
centerDigits: "111",
ir: "22",
};
- const previousPlate = {
- leftDigits: "55",
- centerAlphabet: "ج",
- centerDigits: "222",
- ir: "33",
- };
- const query = jest
- .fn()
- .mockResolvedValueOnce({
- mapped: { CompanyName: "پارسیان", EndDate: "1404/01/01" },
- })
- .mockResolvedValueOnce({
- mapped: {
- CompanyName: "پارسیان",
- EndDate: "1406/01/01",
- VinNumberField: "NAAM01E15HK123456",
- },
- });
+ const query = jest.fn().mockResolvedValue({
+ mapped: { CompanyName: "پارسیان", EndDate: "1404/01/01" },
+ });
- const result = await runPlateInquiryWithFallback<{
- mapped: Record;
- }>({
- vehicle: resolveInquiryVehicle({
- registrationState: VehicleRegistrationState.RECENTLY_TRANSFERRED,
+ await expect(
+ runCurrentPlateInquiry<{ mapped: Record }>({
currentPlate,
- previousPlate,
- vin: "NAAM01E15HK123456",
- previousPolicyholderNationalCode: "0098765432",
+ query,
+ isUsable: (value) =>
+ !!value.mapped.CompanyName &&
+ isMappedPolicyCurrent(value.mapped, "2026-09-13"),
}),
- fallbackCurrentPlate: currentPlate,
- query,
- isUsable: (value) =>
- !!value.mapped.CompanyName &&
- isMappedPolicyCurrent(value.mapped, "2026-09-13"),
- mappedValue: (value) => value.mapped,
- });
-
- expect(result.plateKind).toBe("PREVIOUS");
- expect(result.attempts[0]).toMatchObject({
- plateKind: "CURRENT",
- succeeded: true,
- usable: false,
+ ).rejects.toMatchObject({
+ attempts: [{ plateKind: "CURRENT", succeeded: true, usable: false }],
});
+ expect(query).toHaveBeenCalledTimes(1);
});
- it("rejects the result and retains audit attempts when every plate is unusable", async () => {
+ it("retains the current-plate audit attempt when the result is unusable", async () => {
const currentPlate = {
leftDigits: "44",
centerAlphabet: "ب",
centerDigits: "111",
ir: "22",
};
- const previousPlate = {
- leftDigits: "55",
- centerAlphabet: "ج",
- centerDigits: "222",
- ir: "33",
- };
await expect(
- runPlateInquiryWithFallback({
- vehicle: resolveInquiryVehicle({
- registrationState: VehicleRegistrationState.RECENTLY_TRANSFERRED,
- currentPlate,
- previousPlate,
- vin: "NAAM01E15HK123456",
- previousPolicyholderNationalCode: "0098765432",
- }),
- fallbackCurrentPlate: currentPlate,
+ runCurrentPlateInquiry({
+ currentPlate,
+ vin: "NAAM01E15HK123456",
query: async () => ({ mapped: { CompanyName: "پارسیان" } }),
isUsable: () => false,
- mappedValue: (value) => value.mapped,
}),
).rejects.toMatchObject({
- attempts: [
- { plateKind: "CURRENT", succeeded: true, usable: false },
- { plateKind: "PREVIOUS", succeeded: true, usable: false },
- ],
+ attempts: [{ plateKind: "CURRENT", succeeded: true, usable: false }],
});
});
- it("does not use the previous plate after a transport or provider outage", async () => {
+ it("retains one failed current-plate attempt after a provider outage", async () => {
const currentPlate = {
leftDigits: "44",
centerAlphabet: "ب",
@@ -612,23 +509,11 @@ describe("inquiry participant resolver", () => {
);
await expect(
- runPlateInquiryWithFallback({
- vehicle: resolveInquiryVehicle({
- registrationState: VehicleRegistrationState.RECENTLY_TRANSFERRED,
- currentPlate,
- previousPlate: {
- leftDigits: "55",
- centerAlphabet: "ج",
- centerDigits: "222",
- ir: "33",
- },
- vin: "NAAM01E15HK123456",
- previousPolicyholderNationalCode: "0098765432",
- }),
- fallbackCurrentPlate: currentPlate,
+ runCurrentPlateInquiry({
+ currentPlate,
+ vin: "NAAM01E15HK123456",
query,
isUsable: () => false,
- mappedValue: () => ({}),
}),
).rejects.toThrow("upstream timeout");
expect(query).toHaveBeenCalledTimes(1);
diff --git a/src/request-management/inquiry-participant-resolver.ts b/src/request-management/inquiry-participant-resolver.ts
index 550a352..b6adec4 100644
--- a/src/request-management/inquiry-participant-resolver.ts
+++ b/src/request-management/inquiry-participant-resolver.ts
@@ -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,
@@ -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): void {
}
}
-export function assertPreviousPlateInquiryMatchesVin(
- expectedVin: string,
- mapped: Record,
-): 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 | 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(options: {
- vehicle?: ResolvedInquiryVehicle;
- fallbackCurrentPlate: InquiryVehicleInputDto["currentPlate"];
- query: (
- plate: InquiryVehicleInputDto["currentPlate"],
- plateKind: "CURRENT" | "PREVIOUS",
- ) => Promise;
+/**
+ * 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(options: {
+ currentPlate: InquiryVehicleInputDto["currentPlate"];
+ vin?: string;
+ query: (plate: InquiryVehicleInputDto["currentPlate"]) => Promise;
isUsable: (value: T) => boolean;
- mappedValue: (value: T) => Record;
- 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(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(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>(
diff --git a/src/request-management/request-management.previous-policyholder.spec.ts b/src/request-management/request-management.policyholder-inquiry.spec.ts
similarity index 55%
rename from src/request-management/request-management.previous-policyholder.spec.ts
rename to src/request-management/request-management.policyholder-inquiry.spec.ts
index 53874d5..f37149e 100644
--- a/src/request-management/request-management.previous-policyholder.spec.ts
+++ b/src/request-management/request-management.policyholder-inquiry.spec.ts
@@ -1,7 +1,7 @@
import { BlameRequestType } from "src/Types&Enums/blame-request-management/blameRequestType.enum";
import { RequestManagementService } from "./request-management.service";
-describe("RequestManagementService previous policyholder routing", () => {
+describe("RequestManagementService policyholder inquiry routing", () => {
const currentPlate = {
leftDigits: "44",
centerAlphabet: "ب",
@@ -46,7 +46,7 @@ describe("RequestManagementService previous policyholder routing", () => {
};
}
- it("uses the previous code only for the previous-plate third-party lookup", async () => {
+ it("does not fall back from the current plate for third-party insurance", async () => {
const service = Object.create(RequestManagementService.prototype) as any;
service.sandHubService = {
getTejaratBlockInquiry: jest
@@ -54,10 +54,7 @@ describe("RequestManagementService previous policyholder routing", () => {
.mockResolvedValueOnce({ raw: {}, mapped: {} })
.mockResolvedValueOnce({
raw: {},
- mapped: {
- CompanyName: "پارسیان",
- VinNumberField: vehicle.vin,
- },
+ mapped: { CompanyName: "پارسیان", VinNumberField: vehicle.vin },
}),
};
const submission = service.normalizeInquiryInput(
@@ -65,32 +62,23 @@ describe("RequestManagementService previous policyholder routing", () => {
participantInput(BlameRequestType.THIRD_PARTY),
);
- const result = await service.getThirdPartyPlateInquiry(submission);
+ await expect(service.getThirdPartyPlateInquiry(submission)).rejects.toThrow(
+ "بیمهنامه معتبر و فعالی مطابق مشخصات خودرو و کد ملی واردشده یافت نشد.",
+ );
- expect(result.plateKind).toBe("PREVIOUS");
- expect(
- service.sandHubService.getTejaratBlockInquiry,
- ).toHaveBeenNthCalledWith(
+ expect(service.sandHubService.getTejaratBlockInquiry).toHaveBeenCalledTimes(
1,
+ );
+ expect(service.sandHubService.getTejaratBlockInquiry).toHaveBeenCalledWith(
expect.objectContaining({
plate: currentPlate,
nationalCodeOfInsurer: "0022222222",
}),
undefined,
);
- expect(
- service.sandHubService.getTejaratBlockInquiry,
- ).toHaveBeenNthCalledWith(
- 2,
- expect.objectContaining({
- plate: previousPlate,
- nationalCodeOfInsurer: "0098765432",
- }),
- undefined,
- );
});
- it("uses the previous code only for the previous-plate car-body lookup", async () => {
+ it("does not fall back from the current plate for car-body insurance", async () => {
const service = Object.create(RequestManagementService.prototype) as any;
service.sandHubService = {
getCarBodyInquiry: jest
@@ -98,10 +86,7 @@ describe("RequestManagementService previous policyholder routing", () => {
.mockResolvedValueOnce({ raw: {}, mapped: {} })
.mockResolvedValueOnce({
raw: {},
- mapped: {
- policyNumber: "BODY-1",
- VinNumberField: vehicle.vin,
- },
+ mapped: { policyNumber: "BODY-1", VinNumberField: vehicle.vin },
}),
};
const submission = service.normalizeInquiryInput(
@@ -109,22 +94,45 @@ describe("RequestManagementService previous policyholder routing", () => {
participantInput(BlameRequestType.CAR_BODY),
);
- const result = await service.getCarBodyPlateInquiry(submission);
+ await expect(service.getCarBodyPlateInquiry(submission)).rejects.toThrow(
+ "بیمهنامه معتبر و فعالی مطابق مشخصات خودرو و کد ملی واردشده یافت نشد.",
+ );
- expect(result.plateKind).toBe("PREVIOUS");
- expect(service.sandHubService.getCarBodyInquiry).toHaveBeenNthCalledWith(
- 1,
+ expect(service.sandHubService.getCarBodyInquiry).toHaveBeenCalledTimes(1);
+ expect(service.sandHubService.getCarBodyInquiry).toHaveBeenCalledWith(
expect.objectContaining({
plate: currentPlate,
nationalCodeOfInsurer: "0033333333",
}),
);
- expect(service.sandHubService.getCarBodyInquiry).toHaveBeenNthCalledWith(
- 2,
- expect.objectContaining({
- plate: previousPlate,
- nationalCodeOfInsurer: "0098765432",
+ });
+
+ it("queries VIN with the third-party policyholder", async () => {
+ const service = Object.create(RequestManagementService.prototype) as any;
+ service.sandHubService = {
+ getPolicyByChassisInquiry: jest.fn().mockResolvedValue({
+ raw: {},
+ mapped: { CompanyName: "پارسیان" },
}),
+ };
+ const submission = service.normalizeInquiryInput(
+ BlameRequestType.THIRD_PARTY,
+ participantInput(BlameRequestType.THIRD_PARTY),
+ );
+
+ await service.getThirdPartyVinInquiry(submission);
+
+ expect(
+ service.sandHubService.getPolicyByChassisInquiry,
+ ).toHaveBeenCalledTimes(1);
+ expect(
+ service.sandHubService.getPolicyByChassisInquiry,
+ ).toHaveBeenCalledWith(
+ {
+ nationalCode: "0022222222",
+ chassis: vehicle.vin,
+ },
+ undefined,
);
});
});
diff --git a/src/request-management/request-management.service.ts b/src/request-management/request-management.service.ts
index c3db7bf..0dfb1ab 100644
--- a/src/request-management/request-management.service.ts
+++ b/src/request-management/request-management.service.ts
@@ -140,7 +140,7 @@ import {
NormalizedInquirySubmission,
normalizeInquirySubmission,
resolveInquirySubjects,
- runPlateInquiryWithFallback,
+ runCurrentPlateInquiry,
sanitizeStoredInquiryParticipants,
} from "./inquiry-participant-resolver";
import {
@@ -386,17 +386,14 @@ export class RequestManagementService {
options?: Record,
): Promise {
const subjects = resolveInquirySubjects(submission);
- const result = await runPlateInquiryWithFallback({
- vehicle: submission.vehicle,
- fallbackCurrentPlate: submission.dto.plate,
- query: (plate, plateKind) =>
+ const result = await runCurrentPlateInquiry({
+ currentPlate: submission.vehicle?.currentPlate ?? submission.dto.plate,
+ vin: submission.vehicle?.vin,
+ query: (plate) =>
this.sandHubService.getTejaratBlockInquiry(
{
plate: plate as any,
- nationalCodeOfInsurer:
- plateKind === "PREVIOUS"
- ? submission.vehicle!.previousPolicyholderNationalCode!
- : subjects.thirdPartyPolicyNationalCode,
+ nationalCodeOfInsurer: subjects.thirdPartyPolicyNationalCode,
},
options,
),
@@ -404,7 +401,6 @@ export class RequestManagementService {
!value?.mapped?.Error &&
!!value?.mapped?.CompanyName &&
isMappedPolicyCurrent(value.mapped),
- mappedValue: (value) => value?.mapped ?? {},
});
return {
...result.value,
@@ -423,15 +419,12 @@ export class RequestManagementService {
"اطلاعات بیمهگذار برای استعلام بیمه بدنه الزامی است.",
);
}
- const result = await runPlateInquiryWithFallback({
- vehicle: submission.vehicle,
- fallbackCurrentPlate: submission.dto.plate,
- query: (plate, plateKind) =>
+ const result = await runCurrentPlateInquiry({
+ currentPlate: submission.vehicle?.currentPlate ?? submission.dto.plate,
+ vin: submission.vehicle?.vin,
+ query: (plate) =>
this.sandHubService.getCarBodyInquiry({
- nationalCodeOfInsurer:
- plateKind === "PREVIOUS"
- ? submission.vehicle!.previousPolicyholderNationalCode!
- : policyholderNationalCode,
+ nationalCodeOfInsurer: policyholderNationalCode,
plate: plate as any,
}),
isUsable: (value) =>
@@ -442,7 +435,6 @@ export class RequestManagementService {
value.mapped.CompanyName ||
value.mapped.companyId
),
- mappedValue: (value) => value?.mapped ?? {},
});
return {
...result.value,