forked from Yara724/api
659 lines
21 KiB
TypeScript
659 lines
21 KiB
TypeScript
import { BadRequestException } from "@nestjs/common";
|
|
import { BlameRequestType } from "src/Types&Enums/blame-request-management/blameRequestType.enum";
|
|
import { jalaliToGregorianDate } from "src/helpers/date-jalali";
|
|
import { gregorianDateInIran } from "src/helpers/iran-datetime";
|
|
import {
|
|
InquiryParticipantFieldsDto,
|
|
InquiryParticipantInputDto,
|
|
InquiryParticipantRole,
|
|
InquiryVehicleInputDto,
|
|
VehicleRegistrationState,
|
|
} from "src/common/dto/inquiry-participants.dto";
|
|
|
|
export interface ResolvedInquiryParticipant {
|
|
participantId: string;
|
|
nationalCode: string;
|
|
birthday: string;
|
|
fullName?: string;
|
|
hasDrivingLicense?: boolean;
|
|
licenseNumber?: string;
|
|
licenseType?: string;
|
|
}
|
|
|
|
export interface InquiryParticipantRoleAssignments {
|
|
driver: string;
|
|
vehicleOwner?: string;
|
|
thirdPartyPolicyholder: string;
|
|
carBodyPolicyholder?: string;
|
|
}
|
|
|
|
export interface ResolvedInquiryParticipants {
|
|
participants: ResolvedInquiryParticipant[];
|
|
roles: InquiryParticipantRoleAssignments;
|
|
legacy: boolean;
|
|
}
|
|
|
|
export interface NormalizedInquirySubmission<T extends Record<string, any>> {
|
|
dto: T & {
|
|
nationalCodeOfDriver: string;
|
|
driverBirthday: string;
|
|
driverLicense?: string;
|
|
licenseType?: string;
|
|
userNoCertificate?: boolean;
|
|
nationalCodeOfInsurer: string;
|
|
insurerBirthday: string;
|
|
driverIsInsurer: boolean;
|
|
insurerLicense?: string;
|
|
};
|
|
participants: ResolvedInquiryParticipants;
|
|
driver: ResolvedInquiryParticipant;
|
|
vehicleOwner?: ResolvedInquiryParticipant;
|
|
thirdPartyPolicyholder: ResolvedInquiryParticipant;
|
|
carBodyPolicyholder?: ResolvedInquiryParticipant;
|
|
vehicle?: ResolvedInquiryVehicle;
|
|
}
|
|
|
|
export interface InquirySubjects {
|
|
thirdPartyPolicyNationalCode: string;
|
|
carBodyPolicyNationalCode?: string;
|
|
shebaNationalCode: string;
|
|
driverNationalCode: string;
|
|
}
|
|
|
|
/**
|
|
* The single routing seam for external inquiries. Policy checks belong to
|
|
* their policyholder, Sheba belongs to the vehicle owner, and licence data
|
|
* belongs to the driver even when those roles resolve to different people.
|
|
*/
|
|
export function resolveInquirySubjects(
|
|
submission: NormalizedInquirySubmission<Record<string, any>>,
|
|
): InquirySubjects {
|
|
if (!submission.vehicleOwner) {
|
|
throw new BadRequestException(
|
|
"Vehicle owner identity is required for Sheba validation.",
|
|
);
|
|
}
|
|
return {
|
|
thirdPartyPolicyNationalCode:
|
|
submission.thirdPartyPolicyholder.nationalCode,
|
|
...(submission.carBodyPolicyholder
|
|
? {
|
|
carBodyPolicyNationalCode:
|
|
submission.carBodyPolicyholder.nationalCode,
|
|
}
|
|
: {}),
|
|
shebaNationalCode: submission.vehicleOwner.nationalCode,
|
|
driverNationalCode: submission.driver.nationalCode,
|
|
};
|
|
}
|
|
|
|
export type ResolvedInquiryVehicle = InquiryVehicleInputDto & {
|
|
registrationState: VehicleRegistrationState;
|
|
};
|
|
|
|
function assertCompleteInquiryPlate(
|
|
plate: InquiryVehicleInputDto["currentPlate"],
|
|
path: "vehicle.currentPlate" | "vehicle.previousPlate",
|
|
): void {
|
|
for (const field of [
|
|
"leftDigits",
|
|
"centerAlphabet",
|
|
"centerDigits",
|
|
"ir",
|
|
] as const) {
|
|
if (plate?.[field] == null || String(plate[field]).trim() === "") {
|
|
throw new BadRequestException(`${path}.${field} is required.`);
|
|
}
|
|
}
|
|
}
|
|
|
|
export function participantForRole(
|
|
resolved: ResolvedInquiryParticipants,
|
|
role: InquiryParticipantRole,
|
|
): ResolvedInquiryParticipant | undefined {
|
|
const roleField = ROLE_FIELDS[
|
|
role
|
|
] as keyof InquiryParticipantRoleAssignments;
|
|
const participantId = resolved.roles[roleField];
|
|
return resolved.participants.find(
|
|
(participant) => participant.participantId === participantId,
|
|
);
|
|
}
|
|
|
|
/** Resolve a participant from the role links persisted on a blame party. */
|
|
export function participantForStoredPartyRole(
|
|
party: {
|
|
participants?: Array<Record<string, any>>;
|
|
participantRoles?: Partial<InquiryParticipantRoleAssignments>;
|
|
},
|
|
role: InquiryParticipantRole,
|
|
): Record<string, any> | undefined {
|
|
const roleField = ROLE_FIELDS[
|
|
role
|
|
] as keyof InquiryParticipantRoleAssignments;
|
|
const participantId = party.participantRoles?.[roleField];
|
|
if (!participantId || !Array.isArray(party.participants)) return undefined;
|
|
return party.participants.find(
|
|
(participant) => participant?.participantId === participantId,
|
|
);
|
|
}
|
|
|
|
/** Remove fields retired from the participant contract from historical records. */
|
|
export function sanitizeStoredInquiryParticipants(
|
|
participants: unknown,
|
|
): Array<Record<string, any>> | undefined {
|
|
if (!Array.isArray(participants)) return undefined;
|
|
return participants.map((participant) => {
|
|
const plain =
|
|
participant &&
|
|
typeof participant === "object" &&
|
|
typeof (participant as { toObject?: unknown }).toObject === "function"
|
|
? (participant as { toObject: () => Record<string, any> }).toObject()
|
|
: { ...(participant as Record<string, any>) };
|
|
delete plain.unknown;
|
|
return plain;
|
|
});
|
|
}
|
|
|
|
const ROLE_FIELDS: Record<
|
|
InquiryParticipantRole,
|
|
keyof InquiryParticipantFieldsDto
|
|
> = {
|
|
[InquiryParticipantRole.DRIVER]: "driver",
|
|
[InquiryParticipantRole.VEHICLE_OWNER]: "vehicleOwner",
|
|
[InquiryParticipantRole.THIRD_PARTY_POLICYHOLDER]: "thirdPartyPolicyholder",
|
|
[InquiryParticipantRole.CAR_BODY_POLICYHOLDER]: "carBodyPolicyholder",
|
|
};
|
|
|
|
function requiredIdentity(
|
|
role: InquiryParticipantRole,
|
|
input: InquiryParticipantInputDto,
|
|
): ResolvedInquiryParticipant {
|
|
if (Object.prototype.hasOwnProperty.call(input, "phoneNumber")) {
|
|
throw new BadRequestException(
|
|
`${role} does not accept phoneNumber; phone numbers are collected separately from inquiry identity.`,
|
|
);
|
|
}
|
|
const nationalCode = String(input.nationalCode ?? "").trim();
|
|
const birthday = String(input.birthday ?? "").trim();
|
|
if (!nationalCode || !birthday) {
|
|
throw new BadRequestException(
|
|
`${role} requires nationalCode and birthday.`,
|
|
);
|
|
}
|
|
if (
|
|
role === InquiryParticipantRole.DRIVER &&
|
|
typeof input.hasDrivingLicense !== "boolean"
|
|
) {
|
|
throw new BadRequestException("DRIVER requires hasDrivingLicense.");
|
|
}
|
|
if (
|
|
role === InquiryParticipantRole.DRIVER &&
|
|
input.hasDrivingLicense === true &&
|
|
(!String(input.licenseNumber ?? "").trim() ||
|
|
!String(input.licenseType ?? "").trim())
|
|
) {
|
|
throw new BadRequestException(
|
|
"DRIVER requires licenseNumber and licenseType when hasDrivingLicense is true.",
|
|
);
|
|
}
|
|
return {
|
|
participantId: role,
|
|
nationalCode,
|
|
birthday,
|
|
...(input.fullName ? { fullName: input.fullName } : {}),
|
|
...(input.hasDrivingLicense != null
|
|
? { hasDrivingLicense: input.hasDrivingLicense }
|
|
: {}),
|
|
...(input.licenseNumber ? { licenseNumber: input.licenseNumber } : {}),
|
|
...(input.licenseType ? { licenseType: input.licenseType } : {}),
|
|
};
|
|
}
|
|
|
|
export function resolveInquiryParticipants(
|
|
caseType: BlameRequestType,
|
|
input: Partial<InquiryParticipantFieldsDto> & Record<string, any>,
|
|
): ResolvedInquiryParticipants {
|
|
assertStructuredInquiryInput(input);
|
|
const hasRoleCompleteInput = Object.values(ROLE_FIELDS).some(
|
|
(field) => input[field] != null,
|
|
);
|
|
if (!hasRoleCompleteInput) {
|
|
throw new BadRequestException(
|
|
"driver, vehicleOwner, and thirdPartyPolicyholder are required in the structured inquiry format.",
|
|
);
|
|
}
|
|
if (
|
|
caseType === BlameRequestType.THIRD_PARTY &&
|
|
input.carBodyPolicyholder != null
|
|
) {
|
|
throw new BadRequestException(
|
|
"CAR_BODY_POLICYHOLDER is not allowed for a THIRD_PARTY case.",
|
|
);
|
|
}
|
|
|
|
const requiredRoles = [
|
|
InquiryParticipantRole.DRIVER,
|
|
InquiryParticipantRole.VEHICLE_OWNER,
|
|
InquiryParticipantRole.THIRD_PARTY_POLICYHOLDER,
|
|
...(caseType === BlameRequestType.CAR_BODY
|
|
? [InquiryParticipantRole.CAR_BODY_POLICYHOLDER]
|
|
: []),
|
|
];
|
|
const participants = new Map<string, ResolvedInquiryParticipant>();
|
|
const resolvedRoleIds = new Map<InquiryParticipantRole, string>();
|
|
const resolving = new Set<InquiryParticipantRole>();
|
|
|
|
const resolveRole = (role: InquiryParticipantRole): string => {
|
|
const existing = resolvedRoleIds.get(role);
|
|
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.`);
|
|
|
|
resolving.add(role);
|
|
let participantId: string;
|
|
if (Object.prototype.hasOwnProperty.call(value, "unknown")) {
|
|
throw new BadRequestException(
|
|
`${role} does not support the unknown option.`,
|
|
);
|
|
} else if (value.sameAs) {
|
|
const hasPersonSpecificFields = Object.entries(value).some(
|
|
([field, fieldValue]) => field !== "sameAs" && fieldValue != null,
|
|
);
|
|
if (hasPersonSpecificFields) {
|
|
throw new BadRequestException(
|
|
`${role} must contain either sameAs or identity fields, not both.`,
|
|
);
|
|
}
|
|
participantId = resolveRole(value.sameAs);
|
|
} else {
|
|
const participant = requiredIdentity(role, value);
|
|
const duplicate = [...participants.values()].find(
|
|
(item) => item.nationalCode === participant.nationalCode,
|
|
);
|
|
if (duplicate) {
|
|
throw new BadRequestException(
|
|
`${role} duplicates an existing nationalCode; use sameAs instead.`,
|
|
);
|
|
}
|
|
participants.set(participant.participantId, participant);
|
|
participantId = participant.participantId;
|
|
}
|
|
resolving.delete(role);
|
|
resolvedRoleIds.set(role, participantId);
|
|
return participantId;
|
|
};
|
|
|
|
for (const role of requiredRoles) resolveRole(role);
|
|
|
|
return {
|
|
participants: [...participants.values()],
|
|
roles: {
|
|
driver: resolvedRoleIds.get(InquiryParticipantRole.DRIVER)!,
|
|
vehicleOwner: resolvedRoleIds.get(InquiryParticipantRole.VEHICLE_OWNER)!,
|
|
thirdPartyPolicyholder: resolvedRoleIds.get(
|
|
InquiryParticipantRole.THIRD_PARTY_POLICYHOLDER,
|
|
)!,
|
|
...(caseType === BlameRequestType.CAR_BODY
|
|
? {
|
|
carBodyPolicyholder: resolvedRoleIds.get(
|
|
InquiryParticipantRole.CAR_BODY_POLICYHOLDER,
|
|
)!,
|
|
}
|
|
: {}),
|
|
},
|
|
legacy: false,
|
|
};
|
|
}
|
|
|
|
export function resolveInquiryVehicle(
|
|
input: InquiryVehicleInputDto,
|
|
): ResolvedInquiryVehicle {
|
|
if (!input) {
|
|
throw new BadRequestException("vehicle is required.");
|
|
}
|
|
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.");
|
|
}
|
|
const previousPolicyholderNationalCode = String(
|
|
input.previousPolicyholderNationalCode ?? "",
|
|
).trim();
|
|
if (!input.currentPlate) {
|
|
throw new BadRequestException("vehicle.currentPlate is required.");
|
|
}
|
|
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.",
|
|
);
|
|
}
|
|
if (
|
|
registrationState === VehicleRegistrationState.RECENTLY_TRANSFERRED &&
|
|
(!input.previousPlate || !vin || !previousPolicyholderNationalCode)
|
|
) {
|
|
throw new BadRequestException(
|
|
"RECENTLY_TRANSFERRED requires previousPlate, vin, and previousPolicyholderNationalCode.",
|
|
);
|
|
}
|
|
if (
|
|
registrationState !== VehicleRegistrationState.RECENTLY_TRANSFERRED &&
|
|
(input.previousPlate || input.previousPolicyholderNationalCode != null)
|
|
) {
|
|
throw new BadRequestException(
|
|
"previousPlate and previousPolicyholderNationalCode are only allowed for RECENTLY_TRANSFERRED vehicles.",
|
|
);
|
|
}
|
|
if (input.previousPlate) {
|
|
assertCompleteInquiryPlate(input.previousPlate, "vehicle.previousPlate");
|
|
}
|
|
return {
|
|
...input,
|
|
registrationState,
|
|
...(vin ? { vin } : {}),
|
|
...(previousPolicyholderNationalCode
|
|
? { previousPolicyholderNationalCode }
|
|
: {}),
|
|
};
|
|
}
|
|
|
|
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>,
|
|
todayGregorian: string = gregorianDateInIran(new Date()),
|
|
): boolean {
|
|
const rawEndDate =
|
|
mapped?.EndDate ??
|
|
mapped?.HEndDte ??
|
|
mapped?.persianEndDate ??
|
|
mapped?.PolicyEndDate ??
|
|
mapped?.endDate;
|
|
if (rawEndDate == null || String(rawEndDate).trim() === "") return true;
|
|
const endDate = jalaliToGregorianDate(rawEndDate);
|
|
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",
|
|
"driverLicense",
|
|
"licenseType",
|
|
"nationalCodeOfInsurer",
|
|
"insurerBirthday",
|
|
"insurerLicense",
|
|
"driverIsInsurer",
|
|
"userNoCertificate",
|
|
"plate",
|
|
"plateId",
|
|
"vin",
|
|
"isNewCar",
|
|
] as const;
|
|
|
|
function assertStructuredInquiryInput(input: Record<string, any>): void {
|
|
const legacyFields = LEGACY_INQUIRY_FIELDS.filter((field) =>
|
|
Object.prototype.hasOwnProperty.call(input, field),
|
|
);
|
|
if (legacyFields.length > 0) {
|
|
throw new BadRequestException(
|
|
`Legacy inquiry fields are not accepted: ${legacyFields.join(", ")}. Use driver, vehicleOwner, thirdPartyPolicyholder, carBodyPolicyholder, and vehicle.`,
|
|
);
|
|
}
|
|
}
|
|
|
|
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(
|
|
"Previous-plate inquiry does not match the submitted VIN/chassis; manual review is required.",
|
|
);
|
|
}
|
|
}
|
|
|
|
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>;
|
|
isUsable: (value: T) => boolean;
|
|
mappedValue: (value: T) => Record<string, any>;
|
|
shouldFallbackOnError?: (error: unknown) => boolean;
|
|
}): Promise<{
|
|
value: T;
|
|
plateKind: "CURRENT" | "PREVIOUS";
|
|
attempts: Array<{
|
|
plateKind: "CURRENT" | "PREVIOUS";
|
|
plate: InquiryVehicleInputDto["currentPlate"];
|
|
vin?: string;
|
|
succeeded: boolean;
|
|
usable?: boolean;
|
|
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";
|
|
plate: InquiryVehicleInputDto["currentPlate"];
|
|
vin?: string;
|
|
succeeded: boolean;
|
|
usable?: boolean;
|
|
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(
|
|
"No current usable policy was found for the submitted vehicle identifiers.",
|
|
) as BadRequestException & { attempts?: typeof attempts };
|
|
error.attempts = attempts;
|
|
throw error;
|
|
}
|
|
if (candidate.kind === "PREVIOUS" && usable) {
|
|
assertPreviousPlateInquiryMatchesVin(
|
|
options.vehicle!.vin!,
|
|
options.mappedValue(value),
|
|
);
|
|
}
|
|
attempts.push({
|
|
plateKind: candidate.kind,
|
|
plate: candidate.plate,
|
|
...(options.vehicle?.vin ? { vin: options.vehicle.vin } : {}),
|
|
succeeded: true,
|
|
usable: true,
|
|
});
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
throw lastError ?? new BadRequestException("Inquiry failed for all plates.");
|
|
}
|
|
|
|
export function normalizeInquirySubmission<T extends Record<string, any>>(
|
|
caseType: BlameRequestType,
|
|
input: T,
|
|
): NormalizedInquirySubmission<T> {
|
|
const participants = resolveInquiryParticipants(caseType, input as any);
|
|
const driver = participantForRole(
|
|
participants,
|
|
InquiryParticipantRole.DRIVER,
|
|
);
|
|
const thirdPartyPolicyholder = participantForRole(
|
|
participants,
|
|
InquiryParticipantRole.THIRD_PARTY_POLICYHOLDER,
|
|
);
|
|
if (!driver || !thirdPartyPolicyholder) {
|
|
throw new BadRequestException(
|
|
"Driver and third-party policyholder identities are required.",
|
|
);
|
|
}
|
|
const vehicleOwner = participantForRole(
|
|
participants,
|
|
InquiryParticipantRole.VEHICLE_OWNER,
|
|
);
|
|
const carBodyPolicyholder = participantForRole(
|
|
participants,
|
|
InquiryParticipantRole.CAR_BODY_POLICYHOLDER,
|
|
);
|
|
const vehicle = resolveInquiryVehicle(
|
|
input.vehicle as InquiryVehicleInputDto,
|
|
);
|
|
const sameDriverAndPolicyholder =
|
|
participants.roles.driver === participants.roles.thirdPartyPolicyholder;
|
|
|
|
return {
|
|
dto: {
|
|
...input,
|
|
plate: vehicle.currentPlate,
|
|
vin: vehicle.vin,
|
|
nationalCodeOfDriver: driver.nationalCode,
|
|
driverBirthday: driver.birthday,
|
|
driverLicense: driver.licenseNumber,
|
|
licenseType: driver.licenseType,
|
|
userNoCertificate:
|
|
driver.hasDrivingLicense == null
|
|
? undefined
|
|
: !driver.hasDrivingLicense,
|
|
nationalCodeOfInsurer: thirdPartyPolicyholder.nationalCode,
|
|
insurerBirthday: thirdPartyPolicyholder.birthday,
|
|
driverIsInsurer: sameDriverAndPolicyholder,
|
|
insurerLicense: sameDriverAndPolicyholder
|
|
? driver.licenseNumber
|
|
: undefined,
|
|
isNewCar: vehicle.isNewCar,
|
|
} as NormalizedInquirySubmission<T>["dto"],
|
|
participants,
|
|
driver,
|
|
vehicleOwner,
|
|
thirdPartyPolicyholder,
|
|
carBodyPolicyholder,
|
|
vehicle,
|
|
};
|
|
}
|