Fixed Participants

This commit is contained in:
SepehrYahyaee
2026-09-14 14:27:23 +03:30
parent 51166a8d0d
commit 9930e9ff5b
13 changed files with 518 additions and 199 deletions

View File

@@ -18,11 +18,6 @@ export interface ResolvedInquiryParticipant {
hasDrivingLicense?: boolean;
licenseNumber?: string;
licenseType?: string;
unknown?: boolean;
}
export interface InquiryParticipantResolutionOptions {
allowUnknownThirdPartyPolicyholder?: boolean;
}
export interface InquiryParticipantRoleAssignments {
@@ -96,6 +91,22 @@ 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,
@@ -127,6 +138,23 @@ export function participantForStoredPartyRole(
);
}
/** 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
@@ -185,7 +213,6 @@ function requiredIdentity(
export function resolveInquiryParticipants(
caseType: BlameRequestType,
input: Partial<InquiryParticipantFieldsDto> & Record<string, any>,
options: InquiryParticipantResolutionOptions = {},
): ResolvedInquiryParticipants {
assertStructuredInquiryInput(input);
const hasRoleCompleteInput = Object.values(ROLE_FIELDS).some(
@@ -232,32 +259,7 @@ export function resolveInquiryParticipants(
resolving.add(role);
let participantId: string;
if (
role === InquiryParticipantRole.THIRD_PARTY_POLICYHOLDER &&
value.unknown === true
) {
if (!options.allowUnknownThirdPartyPolicyholder) {
throw new BadRequestException(
"An unknown third-party policyholder is allowed only for the damaged party.",
);
}
const hasIdentityFields = Object.entries(value).some(
([field, fieldValue]) => field !== "unknown" && fieldValue != null,
);
if (hasIdentityFields) {
throw new BadRequestException(
`${role} must contain either unknown=true or person fields, not both.`,
);
}
const participant: ResolvedInquiryParticipant = {
participantId: role,
nationalCode: "",
birthday: "",
unknown: true,
};
participants.set(participant.participantId, participant);
participantId = participant.participantId;
} else if (value.unknown != null) {
if (Object.prototype.hasOwnProperty.call(value, "unknown")) {
throw new BadRequestException(
`${role} does not support the unknown option.`,
);
@@ -314,28 +316,59 @@ export function resolveInquiryParticipants(
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 || !String(input.vin ?? "").trim())
(!input.previousPlate || !vin || !previousPolicyholderNationalCode)
) {
throw new BadRequestException(
"RECENTLY_TRANSFERRED requires previousPlate and vin.",
"RECENTLY_TRANSFERRED requires previousPlate, vin, and previousPolicyholderNationalCode.",
);
}
if (
registrationState !== VehicleRegistrationState.RECENTLY_TRANSFERRED &&
input.previousPlate
(input.previousPlate || input.previousPolicyholderNationalCode != null)
) {
throw new BadRequestException(
"previousPlate is only allowed for RECENTLY_TRANSFERRED vehicles.",
"previousPlate and previousPolicyholderNationalCode are only allowed for RECENTLY_TRANSFERRED vehicles.",
);
}
return { ...input, registrationState };
if (input.previousPlate) {
assertCompleteInquiryPlate(input.previousPlate, "vehicle.previousPlate");
}
return {
...input,
registrationState,
...(vin ? { vin } : {}),
...(previousPolicyholderNationalCode
? { previousPolicyholderNationalCode }
: {}),
};
}
export function vehiclePlateCandidates(input?: ResolvedInquiryVehicle): Array<{
@@ -459,7 +492,10 @@ export function isPolicyNotFoundError(error: unknown): boolean {
export async function runPlateInquiryWithFallback<T>(options: {
vehicle?: ResolvedInquiryVehicle;
fallbackCurrentPlate: InquiryVehicleInputDto["currentPlate"];
query: (plate: InquiryVehicleInputDto["currentPlate"]) => Promise<T>;
query: (
plate: InquiryVehicleInputDto["currentPlate"],
plateKind: "CURRENT" | "PREVIOUS",
) => Promise<T>;
isUsable: (value: T) => boolean;
mappedValue: (value: T) => Record<string, any>;
shouldFallbackOnError?: (error: unknown) => boolean;
@@ -492,7 +528,7 @@ export async function runPlateInquiryWithFallback<T>(options: {
const candidate = candidates[index];
const isLast = index === candidates.length - 1;
try {
const value = await options.query(candidate.plate);
const value = await options.query(candidate.plate, candidate.kind);
const usable = options.isUsable(value);
if (!usable) {
attempts.push({
@@ -562,13 +598,8 @@ export async function runPlateInquiryWithFallback<T>(options: {
export function normalizeInquirySubmission<T extends Record<string, any>>(
caseType: BlameRequestType,
input: T,
options: InquiryParticipantResolutionOptions = {},
): NormalizedInquirySubmission<T> {
const participants = resolveInquiryParticipants(
caseType,
input as any,
options,
);
const participants = resolveInquiryParticipants(caseType, input as any);
const driver = participantForRole(
participants,
InquiryParticipantRole.DRIVER,