forked from Yara724/api
fix: harden claim review and inquiry workflows
Preserve damage history and current vehicle price, restore depreciation mapping, normalize inquiry/report output, and support resumable expert review with paginated case retrieval.
This commit is contained in:
@@ -60,6 +60,20 @@ export interface InquirySubjects {
|
||||
driverNationalCode: string;
|
||||
}
|
||||
|
||||
const PARTICIPANT_ROLE_LABELS: Record<InquiryParticipantRole, string> = {
|
||||
[InquiryParticipantRole.DRIVER]: "راننده",
|
||||
[InquiryParticipantRole.VEHICLE_OWNER]: "مالک خودرو",
|
||||
[InquiryParticipantRole.THIRD_PARTY_POLICYHOLDER]: "بیمهگذار شخص ثالث",
|
||||
[InquiryParticipantRole.CAR_BODY_POLICYHOLDER]: "بیمهگذار بدنه",
|
||||
};
|
||||
|
||||
const PLATE_FIELD_LABELS = {
|
||||
leftDigits: "دو رقم سمت چپ پلاک",
|
||||
centerAlphabet: "حرف پلاک",
|
||||
centerDigits: "سه رقم میانی پلاک",
|
||||
ir: "کد ایران پلاک",
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* The single routing seam for external inquiries. Policy checks belong to
|
||||
* their policyholder, Sheba belongs to the vehicle owner, and licence data
|
||||
@@ -70,7 +84,7 @@ export function resolveInquirySubjects(
|
||||
): InquirySubjects {
|
||||
if (!submission.vehicleOwner) {
|
||||
throw new BadRequestException(
|
||||
"Vehicle owner identity is required for Sheba validation.",
|
||||
"اطلاعات هویتی مالک خودرو برای استعلام شبا الزامی است.",
|
||||
);
|
||||
}
|
||||
return {
|
||||
@@ -102,7 +116,11 @@ function assertCompleteInquiryPlate(
|
||||
"ir",
|
||||
] as const) {
|
||||
if (plate?.[field] == null || String(plate[field]).trim() === "") {
|
||||
throw new BadRequestException(`${path}.${field} is required.`);
|
||||
const plateLabel =
|
||||
path === "vehicle.currentPlate" ? "پلاک فعلی" : "پلاک قبلی";
|
||||
throw new BadRequestException(
|
||||
`${PLATE_FIELD_LABELS[field]} در ${plateLabel} الزامی است.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -171,21 +189,21 @@ function requiredIdentity(
|
||||
): ResolvedInquiryParticipant {
|
||||
if (Object.prototype.hasOwnProperty.call(input, "phoneNumber")) {
|
||||
throw new BadRequestException(
|
||||
`${role} does not accept phoneNumber; phone numbers are collected separately from inquiry identity.`,
|
||||
`شماره همراه ${PARTICIPANT_ROLE_LABELS[role]} باید جدا از اطلاعات هویتی استعلام ارسال شود.`,
|
||||
);
|
||||
}
|
||||
const nationalCode = String(input.nationalCode ?? "").trim();
|
||||
const birthday = String(input.birthday ?? "").trim();
|
||||
if (!nationalCode || !birthday) {
|
||||
throw new BadRequestException(
|
||||
`${role} requires nationalCode and birthday.`,
|
||||
`کد ملی و تاریخ تولد ${PARTICIPANT_ROLE_LABELS[role]} الزامی است.`,
|
||||
);
|
||||
}
|
||||
if (
|
||||
role === InquiryParticipantRole.DRIVER &&
|
||||
typeof input.hasDrivingLicense !== "boolean"
|
||||
) {
|
||||
throw new BadRequestException("DRIVER requires hasDrivingLicense.");
|
||||
throw new BadRequestException("وضعیت داشتن گواهینامه راننده الزامی است.");
|
||||
}
|
||||
if (
|
||||
role === InquiryParticipantRole.DRIVER &&
|
||||
@@ -194,7 +212,7 @@ function requiredIdentity(
|
||||
!String(input.licenseType ?? "").trim())
|
||||
) {
|
||||
throw new BadRequestException(
|
||||
"DRIVER requires licenseNumber and licenseType when hasDrivingLicense is true.",
|
||||
"شماره و نوع گواهینامه برای راننده دارای گواهینامه الزامی است.",
|
||||
);
|
||||
}
|
||||
return {
|
||||
@@ -220,7 +238,7 @@ export function resolveInquiryParticipants(
|
||||
);
|
||||
if (!hasRoleCompleteInput) {
|
||||
throw new BadRequestException(
|
||||
"driver, vehicleOwner, and thirdPartyPolicyholder are required in the structured inquiry format.",
|
||||
"اطلاعات راننده، مالک خودرو و بیمهگذار شخص ثالث برای استعلام الزامی است.",
|
||||
);
|
||||
}
|
||||
if (
|
||||
@@ -228,7 +246,7 @@ export function resolveInquiryParticipants(
|
||||
input.carBodyPolicyholder != null
|
||||
) {
|
||||
throw new BadRequestException(
|
||||
"CAR_BODY_POLICYHOLDER is not allowed for a THIRD_PARTY case.",
|
||||
"بیمهگذار بدنه برای پرونده شخص ثالث قابل ثبت نیست.",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -249,19 +267,23 @@ export function resolveInquiryParticipants(
|
||||
if (existing) return existing;
|
||||
if (resolving.has(role)) {
|
||||
throw new BadRequestException(
|
||||
"Participant sameAs references cannot be circular.",
|
||||
"ارتباط اشخاص یکسان در اطلاعات استعلام نامعتبر است.",
|
||||
);
|
||||
}
|
||||
const value = input[ROLE_FIELDS[role]] as
|
||||
| InquiryParticipantInputDto
|
||||
| undefined;
|
||||
if (!value) throw new BadRequestException(`${role} is required.`);
|
||||
if (!value) {
|
||||
throw new BadRequestException(
|
||||
`اطلاعات ${PARTICIPANT_ROLE_LABELS[role]} الزامی است.`,
|
||||
);
|
||||
}
|
||||
|
||||
resolving.add(role);
|
||||
let participantId: string;
|
||||
if (Object.prototype.hasOwnProperty.call(value, "unknown")) {
|
||||
throw new BadRequestException(
|
||||
`${role} does not support the unknown option.`,
|
||||
`ثبت ${PARTICIPANT_ROLE_LABELS[role]} بهصورت نامشخص امکانپذیر نیست.`,
|
||||
);
|
||||
} else if (value.sameAs) {
|
||||
const hasPersonSpecificFields = Object.entries(value).some(
|
||||
@@ -269,7 +291,7 @@ export function resolveInquiryParticipants(
|
||||
);
|
||||
if (hasPersonSpecificFields) {
|
||||
throw new BadRequestException(
|
||||
`${role} must contain either sameAs or identity fields, not both.`,
|
||||
`برای ${PARTICIPANT_ROLE_LABELS[role]} باید فقط ارتباط با شخص دیگر یا اطلاعات هویتی مستقل ارسال شود.`,
|
||||
);
|
||||
}
|
||||
participantId = resolveRole(value.sameAs);
|
||||
@@ -280,7 +302,7 @@ export function resolveInquiryParticipants(
|
||||
);
|
||||
if (duplicate) {
|
||||
throw new BadRequestException(
|
||||
`${role} duplicates an existing nationalCode; use sameAs instead.`,
|
||||
`کد ملی ${PARTICIPANT_ROLE_LABELS[role]} تکراری است؛ ارتباط با شخص ثبتشده را انتخاب کنید.`,
|
||||
);
|
||||
}
|
||||
participants.set(participant.participantId, participant);
|
||||
@@ -317,29 +339,29 @@ export function resolveInquiryVehicle(
|
||||
input: InquiryVehicleInputDto,
|
||||
): ResolvedInquiryVehicle {
|
||||
if (!input) {
|
||||
throw new BadRequestException("vehicle is required.");
|
||||
throw new BadRequestException("اطلاعات خودرو برای استعلام الزامی است.");
|
||||
}
|
||||
const registrationState =
|
||||
input.registrationState ?? VehicleRegistrationState.CURRENT;
|
||||
if (!Object.values(VehicleRegistrationState).includes(registrationState)) {
|
||||
throw new BadRequestException(
|
||||
"vehicle.registrationState must be CURRENT or RECENTLY_TRANSFERRED.",
|
||||
"وضعیت پلاک خودرو باید «فعلی» یا «تازه تعویضشده» باشد.",
|
||||
);
|
||||
}
|
||||
if (input.isNewCar != null && typeof input.isNewCar !== "boolean") {
|
||||
throw new BadRequestException("vehicle.isNewCar must be a boolean.");
|
||||
throw new BadRequestException("وضعیت صفر بودن خودرو نامعتبر است.");
|
||||
}
|
||||
const previousPolicyholderNationalCode = String(
|
||||
input.previousPolicyholderNationalCode ?? "",
|
||||
).trim();
|
||||
if (!input.currentPlate) {
|
||||
throw new BadRequestException("vehicle.currentPlate is required.");
|
||||
throw new BadRequestException("پلاک فعلی خودرو برای استعلام الزامی است.");
|
||||
}
|
||||
assertCompleteInquiryPlate(input.currentPlate, "vehicle.currentPlate");
|
||||
const vin = String(input.vin ?? "").trim();
|
||||
if (vin && vin.length !== 17) {
|
||||
throw new BadRequestException(
|
||||
"vehicle.vin must contain exactly 17 characters.",
|
||||
"شماره شاسی (VIN) باید دقیقاً ۱۷ کاراکتر باشد.",
|
||||
);
|
||||
}
|
||||
if (
|
||||
@@ -347,7 +369,7 @@ export function resolveInquiryVehicle(
|
||||
(!input.previousPlate || !vin || !previousPolicyholderNationalCode)
|
||||
) {
|
||||
throw new BadRequestException(
|
||||
"RECENTLY_TRANSFERRED requires previousPlate, vin, and previousPolicyholderNationalCode.",
|
||||
"برای خودروی تازه تعویضپلاکشده، پلاک قبلی، شماره شاسی (VIN) و کد ملی بیمهگذار قبلی الزامی است.",
|
||||
);
|
||||
}
|
||||
if (
|
||||
@@ -355,7 +377,7 @@ export function resolveInquiryVehicle(
|
||||
(input.previousPlate || input.previousPolicyholderNationalCode != null)
|
||||
) {
|
||||
throw new BadRequestException(
|
||||
"previousPlate and previousPolicyholderNationalCode are only allowed for RECENTLY_TRANSFERRED vehicles.",
|
||||
"پلاک و کد ملی بیمهگذار قبلی فقط برای خودروی تازه تعویضپلاکشده قابل ثبت است.",
|
||||
);
|
||||
}
|
||||
if (input.previousPlate) {
|
||||
@@ -442,7 +464,7 @@ function assertStructuredInquiryInput(input: Record<string, any>): void {
|
||||
);
|
||||
if (legacyFields.length > 0) {
|
||||
throw new BadRequestException(
|
||||
`Legacy inquiry fields are not accepted: ${legacyFields.join(", ")}. Use driver, vehicleOwner, thirdPartyPolicyholder, carBodyPolicyholder, and vehicle.`,
|
||||
"ساختار قدیمی اطلاعات استعلام پذیرفته نمیشود؛ اطلاعات اشخاص و خودرو را در بخشهای جدید ارسال کنید.",
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -466,7 +488,7 @@ export function assertPreviousPlateInquiryMatchesVin(
|
||||
.filter(Boolean);
|
||||
if (!expected || !candidates.includes(expected)) {
|
||||
throw new BadRequestException(
|
||||
"Previous-plate inquiry does not match the submitted VIN/chassis; manual review is required.",
|
||||
"نتیجه استعلام پلاک قبلی با شماره شاسی (VIN) واردشده مطابقت ندارد و پرونده نیازمند بررسی دستی است.",
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -540,7 +562,7 @@ export async function runPlateInquiryWithFallback<T>(options: {
|
||||
});
|
||||
if (!isLast) continue;
|
||||
const error = new BadRequestException(
|
||||
"No current usable policy was found for the submitted vehicle identifiers.",
|
||||
"بیمهنامه معتبر و فعالی مطابق مشخصات خودرو و کد ملی واردشده یافت نشد.",
|
||||
) as BadRequestException & { attempts?: typeof attempts };
|
||||
error.attempts = attempts;
|
||||
throw error;
|
||||
@@ -592,7 +614,12 @@ export async function runPlateInquiryWithFallback<T>(options: {
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError ?? new BadRequestException("Inquiry failed for all plates.");
|
||||
throw (
|
||||
lastError ??
|
||||
new BadRequestException(
|
||||
"برای هیچیک از پلاکهای ثبتشده نتیجه معتبری یافت نشد.",
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function normalizeInquirySubmission<T extends Record<string, any>>(
|
||||
@@ -610,7 +637,7 @@ export function normalizeInquirySubmission<T extends Record<string, any>>(
|
||||
);
|
||||
if (!driver || !thirdPartyPolicyholder) {
|
||||
throw new BadRequestException(
|
||||
"Driver and third-party policyholder identities are required.",
|
||||
"اطلاعات هویتی راننده و بیمهگذار شخص ثالث الزامی است.",
|
||||
);
|
||||
}
|
||||
const vehicleOwner = participantForRole(
|
||||
|
||||
Reference in New Issue
Block a user