Files
yara724-api/src/lookups/fanavaran-last-car-policy.ts

773 lines
22 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { jalaliToGregorianDate } from "src/helpers/date-jalali";
import { gregorianDateInIran } from "src/helpers/iran-datetime";
import { normalizePlateText } from "src/utils/plate-normalizer/plate-normalizer.service";
export const FANAVARAN_THIRD_PARTY_LINE_ID = 5;
export const FANAVARAN_CAR_BODY_LINE_ID = 4;
export type FanavaranCarPolicyProduct = "third-party" | "car-body";
export type FanavaranPlaqueParts = {
left: string;
letter: string;
right: string;
serial: string;
};
export type FanavaranCarMatchInput = {
vin?: string;
plaque?: FanavaranPlaqueParts;
};
export type FanavaranPolicyListRow = {
PolicyId?: unknown;
InsuranceLineId?: unknown;
BeginDate?: unknown;
EndDate?: unknown;
};
export type HydratedFanavaranCarPolicy = {
policyId: number;
beginDate?: unknown;
endDate?: unknown;
vehicleId?: number | null;
policy: Record<string, unknown>;
vehicle: Record<string, unknown> | null;
};
/** Same Fanavaran middle-letter codes used on claim submit. */
export const FANAVARAN_PLATE_LETTER_CODE: Record<string, number> = {
الف: 1,
ب: 2,
پ: 3,
ج: 4,
د: 5,
س: 6,
ص: 7,
ط: 8,
ع: 9,
ق: 10,
ل: 11,
م: 12,
ن: 13,
و: 14,
ه: 15,
ی: 16,
ک: 17,
ژ: 18,
ت: 19,
ث: 20,
ز: 21,
ش: 22,
ف: 23,
گ: 24,
};
const PERSIAN_AND_ARABIC_DIGITS: Record<string, string> = {
"۰": "0",
"۱": "1",
"۲": "2",
"۳": "3",
"۴": "4",
"۵": "5",
"۶": "6",
"۷": "7",
"۸": "8",
"۹": "9",
"٠": "0",
"١": "1",
"٢": "2",
"٣": "3",
"٤": "4",
"٥": "5",
"٦": "6",
"٧": "7",
"٨": "8",
"٩": "9",
};
export function insuranceLineIdForProduct(
product: FanavaranCarPolicyProduct,
): number {
return product === "car-body"
? FANAVARAN_CAR_BODY_LINE_ID
: FANAVARAN_THIRD_PARTY_LINE_ID;
}
export function insuranceLineLabel(
insuranceLineId: number,
): "CAR_BODY" | "THIRD_PARTY" | null {
if (insuranceLineId === FANAVARAN_CAR_BODY_LINE_ID) return "CAR_BODY";
if (insuranceLineId === FANAVARAN_THIRD_PARTY_LINE_ID) return "THIRD_PARTY";
return null;
}
export function plaqueLetterFromMiddleCode(code: unknown): string | null {
const id = parseFanavaranId(code);
if (id === null) return null;
const found = Object.entries(FANAVARAN_PLATE_LETTER_CODE).find(
([, value]) => value === id,
);
return found?.[0] ?? null;
}
export type AppPlaque = {
leftTwoDigits: string;
serialLetter: string;
threeDigits: string;
rightTwoDigits: string;
};
export function toAppPlaque(
vehicle: Record<string, unknown> | null,
): AppPlaque | null {
if (!vehicle) return null;
const leftTwoDigits = normalizePlaquePart(
vehicle.PlaqueLeftNo ?? vehicle.plaqueLeftNo,
);
const threeDigits = normalizePlaquePart(
vehicle.PlaqueRightNo ?? vehicle.plaqueRightNo,
);
const rightTwoDigits = normalizePlaquePart(
vehicle.PlaqueSerial ?? vehicle.plaqueSerial,
);
const serialLetter = plaqueLetterFromMiddleCode(
vehicle.PlaqueMiddleCodeId ?? vehicle.plaqueMiddleCodeId,
);
if (!leftTwoDigits || !serialLetter || !threeDigits || !rightTwoDigits) {
return null;
}
return { leftTwoDigits, serialLetter, threeDigits, rightTwoDigits };
}
export function toEnglishDigits(value: unknown): string {
return String(value ?? "")
.split("")
.map((char) => PERSIAN_AND_ARABIC_DIGITS[char] ?? char)
.join("");
}
export function normalizeVin(value: unknown): string {
return toEnglishDigits(value).trim().toUpperCase();
}
export function normalizePlaquePart(value: unknown): string {
return toEnglishDigits(value).replace(/\s+/g, "").trim();
}
export function parseFanavaranId(value: unknown): number | null {
if (value === null || value === undefined) return null;
if (typeof value === "string" && value.trim() === "") return null;
const id = Number(toEnglishDigits(value).trim());
return Number.isFinite(id) && id > 0 ? id : null;
}
/** Live Fanavaran / cache id wins; tenant `fanavaranClientConfigs.defaults` if the API returned nothing. */
export function fanavaranIdOrConfigDefault(
resolved: unknown,
configDefault: number,
): number {
return parseFanavaranId(resolved) ?? configDefault;
}
export function asObjectRecord(value: unknown): Record<string, unknown> | null {
if (!value || typeof value !== "object") {
return null;
}
if (Array.isArray(value)) {
return asObjectRecord(value[0]);
}
return value as Record<string, unknown>;
}
export function asPolicyList(data: unknown): FanavaranPolicyListRow[] {
if (Array.isArray(data)) {
return data.filter(
(row): row is FanavaranPolicyListRow =>
!!row && typeof row === "object" && !Array.isArray(row),
);
}
const record = asObjectRecord(data);
if (!record) return [];
for (const key of ["value", "Value", "items", "Items", "data", "Data"]) {
const nested = record[key];
if (Array.isArray(nested)) {
return asPolicyList(nested);
}
}
return [];
}
export function filterPoliciesByLine(
policies: unknown,
insuranceLineId: number,
): FanavaranPolicyListRow[] {
return asPolicyList(policies).filter((row) => {
const line = parseFanavaranId(row.InsuranceLineId);
return line === null || line === insuranceLineId;
});
}
export function sortPoliciesNewestEndDateFirst(
policies: FanavaranPolicyListRow[],
): FanavaranPolicyListRow[] {
return [...policies].sort((left, right) => {
const leftEnd = toGregorianDate(left.EndDate) ?? "";
const rightEnd = toGregorianDate(right.EndDate) ?? "";
return rightEnd.localeCompare(leftEnd);
});
}
export function toGregorianDate(value: unknown): string | null {
return jalaliToGregorianDate(toEnglishDigits(value).trim() || null);
}
export function pickVehicleVin(vehicle: Record<string, unknown> | null): string {
if (!vehicle) return "";
for (const key of ["VIN", "Vin", "VinNo", "vin", "ChassisNo", "chassisNo"]) {
const vin = normalizeVin(vehicle[key]);
if (vin) return vin;
}
return "";
}
export function pickVehicleId(source: unknown): number | null {
const record = asObjectRecord(source);
if (!record) return parseFanavaranId(source);
for (const key of ["VehicleId", "vehicleId", "Id", "id"]) {
const id = parseFanavaranId(record[key]);
if (id !== null) return id;
}
return null;
}
/** Prefer the inquiry row whose Id matches the policy VehicleId. */
export function pickFanavaranVehicleFromInquiry(
inquired: unknown,
vehicleId?: number | null,
): Record<string, unknown> | null {
if (Array.isArray(inquired)) {
if (vehicleId != null) {
const match = inquired.find((row) => pickVehicleId(row) === vehicleId);
if (match) return asObjectRecord(match);
}
const withPlaqueKind = inquired.find(
(row) =>
parseFanavaranId(asObjectRecord(row)?.PlaqueKindId) != null ||
parseFanavaranId(asObjectRecord(row)?.plaqueKindId) != null,
);
if (withPlaqueKind) return asObjectRecord(withPlaqueKind);
return asObjectRecord(inquired[0]);
}
return asObjectRecord(inquired);
}
export type FanavaranDamagedVehicleIdentity = {
vin?: string | null;
chassis?: string | null;
plaque?: FanavaranPlaqueParts | null;
policyCINumber?: string | null;
vehicleKindId?: number | null;
};
export function asVehicleInquiryRows(
inquired: unknown,
): Record<string, unknown>[] {
if (Array.isArray(inquired)) {
return inquired
.map((row) => asObjectRecord(row))
.filter((row): row is Record<string, unknown> => row != null);
}
const one = asObjectRecord(inquired);
return one ? [one] : [];
}
export function normalizePolicyCINumber(value: unknown): string {
return toEnglishDigits(value).replace(/\s+/g, "").trim();
}
function vehicleHasPlaqueParts(vehicle: Record<string, unknown>): boolean {
return Boolean(
asPlaqueText(vehicle.PlaqueLeftNo ?? vehicle.plaqueLeftNo) &&
(parseFanavaranId(
vehicle.PlaqueMiddleCodeId ?? vehicle.plaqueMiddleCodeId,
) != null ||
String(vehicle.PlaqueLetter ?? vehicle.plaqueLetter ?? "").trim()) &&
asPlaqueText(vehicle.PlaqueRightNo ?? vehicle.plaqueRightNo) &&
asPlaqueText(vehicle.PlaqueSerial ?? vehicle.plaqueSerial),
);
}
function vinEquals(left: unknown, right: unknown): boolean {
const a = normalizeVin(left);
const b = normalizeVin(right);
return Boolean(a && b && a === b);
}
/**
* Same physical car: VIN and plaque must not contradict the damaged-party
* inquiry. A row that only "has PlaqueKindId" is not enough.
*/
export function fanavaranVehicleMatchesDamagedIdentity(
vehicle: Record<string, unknown> | null,
identity: FanavaranDamagedVehicleIdentity,
): boolean {
if (!vehicle) return false;
const identityVin = normalizeVin(identity.vin);
const identityChassis = normalizeVin(identity.chassis);
const vehicleVin = pickVehicleVin(vehicle);
const vehicleChassis = normalizeVin(
vehicle.ChassisNo ?? vehicle.chassisNo ?? vehicle.ShsNum,
);
const vinMatched = Boolean(
identityVin &&
(vinEquals(vehicleVin, identityVin) ||
vinEquals(vehicleChassis, identityVin)),
);
const chassisMatched = Boolean(
identityChassis &&
(vinEquals(vehicleVin, identityChassis) ||
vinEquals(vehicleChassis, identityChassis)),
);
if (identityVin && vehicleVin && !vinMatched && !chassisMatched) {
return false;
}
const plaqueMatched = Boolean(
identity.plaque && plaqueMatchesVehicle(identity.plaque, vehicle),
);
if (identity.plaque && vehicleHasPlaqueParts(vehicle) && !plaqueMatched) {
return false;
}
return vinMatched || chassisMatched || plaqueMatched;
}
export function fanavaranPayloadMatchesDamagedIdentity(
payload: Record<string, unknown> | null,
identity: FanavaranDamagedVehicleIdentity,
): boolean {
if (!payload) return false;
const payloadVin = normalizeVin(payload.VIN ?? payload.vin);
const identityVin = normalizeVin(identity.vin);
if (payloadVin && identityVin && payloadVin !== identityVin) return false;
const payloadChassis = normalizeVin(
payload.ChassisNo ?? payload.chassisNo,
);
const identityChassis = normalizeVin(identity.chassis);
if (payloadChassis && identityChassis && payloadChassis !== identityChassis) {
return false;
}
const payloadCI = normalizePolicyCINumber(payload.PolicyCINumber);
const identityCI = normalizePolicyCINumber(identity.policyCINumber);
if (payloadCI && identityCI && payloadCI !== identityCI) return false;
if (identity.plaque && vehicleHasPlaqueParts(payload)) {
if (!plaqueMatchesVehicle(identity.plaque, payload)) return false;
}
const vinMatched = Boolean(payloadVin && identityVin && payloadVin === identityVin);
const chassisMatched = Boolean(
payloadChassis && identityChassis && payloadChassis === identityChassis,
);
const ciMatched = Boolean(payloadCI && identityCI && payloadCI === identityCI);
const plaqueMatched = Boolean(
identity.plaque && plaqueMatchesVehicle(identity.plaque, payload),
);
return vinMatched || chassisMatched || ciMatched || plaqueMatched;
}
export function canReuseCachedFanavaranVehicleIds(source: unknown): boolean {
return source === "vehicle-inquiry" || source === "vin-inquiry" || source === "vehicle-get";
}
/** Proven Parsian national (چهار تکه) plaque codebook ids. */
export const FANAVARAN_NATIONAL_PLAQUE_KIND_ID = 8;
export const FANAVARAN_NATIONAL_PLAQUE_SAMPLE_ID = 10;
export const FANAVARAN_DAMAGE_MANUAL_OVERRIDE_KEYS = [
"Desc",
"EstimateAmount",
"LicenceNo",
"LicenceIssuDate",
"DriverIsOwner",
] as const;
export const FANAVARAN_EXPERTISE_MANUAL_OVERRIDE_KEYS = [
"RepairWage",
"ComponentReplacementCost",
"WasteValue",
"DmgAssessmentDate",
"InspectionTime",
"DamagedVehicleCurrentPrice",
"DropAmountAdditionsDeductions",
] as const;
export const FANAVARAN_BASE_MANUAL_OVERRIDE_KEYS = [
"EstimateAmount",
"CulpritLicenceNo",
"CulpritLicenceIssuDate",
"AccidentLocationAddress",
"AccidentDate",
"AnnouncementDate",
"DocReceivedDate",
"AccidentTime",
"PoliceReportDesc",
"PoliceReportSeri",
"PoliceReportSerial",
] as const;
/**
* Swagger POST body is optional user-entry tweaks only.
* Inquiry / lookup-filter ids always come from the live rebuilt payload.
*/
export function applyFanavaranManualPayloadOverrides(
built: Record<string, unknown>,
body: Record<string, unknown> | null | undefined,
keys: readonly string[],
): Record<string, unknown> {
if (!body) return built;
const next = { ...built };
for (const key of keys) {
if (!Object.prototype.hasOwnProperty.call(body, key)) continue;
if (body[key] === undefined) continue;
next[key] = body[key];
}
return next;
}
/**
* Plaque kind/sample only from a matched Fanavaran vehicle record.
* CII/ESG inquiry, unsourced cache, and tenant defaults of 15 must not win.
* National 4-part plates fall back to 8/10 (lookup-filter safe on Parsian).
*/
export function resolveDamageCasePlaqueLookupIds(matchedVehicle: Record<
string,
unknown
> | null): {
kindId: number;
sampleId: number;
source: "vehicle-inquiry" | "national-plate-default";
} {
const plaque = pickFanavaranPlaqueLookupFields(matchedVehicle);
if (plaque.kindId != null) {
return {
kindId: plaque.kindId,
sampleId: plaque.sampleId ?? FANAVARAN_NATIONAL_PLAQUE_SAMPLE_ID,
source: "vehicle-inquiry",
};
}
return {
kindId: FANAVARAN_NATIONAL_PLAQUE_KIND_ID,
sampleId: FANAVARAN_NATIONAL_PLAQUE_SAMPLE_ID,
source: "national-plate-default",
};
}
/**
* Pick the VIN-inquiry / vehicle-GET row that is the damaged car.
* Newest VersionNo wins among matches; PlaqueKindId is a tie-break only.
*/
export function selectFanavaranVehicleForDamageCase(
inquired: unknown,
identity: FanavaranDamagedVehicleIdentity,
): Record<string, unknown> | null {
const matches = asVehicleInquiryRows(inquired).filter((row) =>
fanavaranVehicleMatchesDamagedIdentity(row, identity),
);
if (matches.length === 0) return null;
return matches.reduce((current, candidate) => {
const currentVersion = parseFanavaranId(current.VersionNo) ?? 0;
const candidateVersion = parseFanavaranId(candidate.VersionNo) ?? 0;
if (candidateVersion !== currentVersion) {
return candidateVersion > currentVersion ? candidate : current;
}
const currentHasKind =
pickFanavaranPlaqueLookupFields(current).kindId != null;
const candidateHasKind =
pickFanavaranPlaqueLookupFields(candidate).kindId != null;
if (candidateHasKind && !currentHasKind) return candidate;
return current;
});
}
export function pickVinFromPartyVehicle(party: {
vehicle?: { vin?: unknown; inquiry?: unknown };
} | null | undefined): string | null {
return collectPartyVinCandidates(party)[0] ?? null;
}
/**
* VIN first (mapped/raw VIN fields), then party.vehicle.vin, then chassis.
* Damaged cars often store chassis on `vehicle.vin` which must not win over the real VIN.
*/
export function collectPartyVinCandidates(party: {
vehicle?: { vin?: unknown; inquiry?: unknown };
} | null | undefined): string[] {
const inquiry = asObjectRecord(party?.vehicle?.inquiry);
const mapped = asObjectRecord(inquiry?.mapped);
const rawData = asObjectRecord(
asObjectRecord(inquiry?.raw)?.data ?? inquiry?.raw ?? inquiry,
);
const values = [
mapped?.VIN,
mapped?.Vin,
mapped?.vin,
mapped?.VinNumberField,
rawData?.VIN,
rawData?.Vin,
rawData?.vin,
rawData?.VinNumberField,
party?.vehicle?.vin,
mapped?.ChassisNo,
mapped?.chassisNo,
mapped?.ShsNum,
mapped?.shsNam,
mapped?.ChassisNumberField,
rawData?.ChassisNo,
rawData?.chassisNo,
rawData?.ShsNum,
rawData?.shsNam,
rawData?.ChassisNumberField,
];
const unique: string[] = [];
const seen = new Set<string>();
for (const value of values) {
const vin = normalizeVin(value);
if (!vin || seen.has(vin)) continue;
seen.add(vin);
unique.push(vin);
}
return unique;
}
export type FanavaranPlaqueLookupFields = {
kindId: number | null;
sampleId: number | null;
cityId: number | null;
leftNo: string | null;
middleCodeId: number | null;
rightNo: string | null;
serial: string | null;
plaqueNo: string | null;
};
function asPlaqueText(value: unknown): string | null {
const text = normalizePlaquePart(value);
return text || null;
}
/** Plaque lookup-filter fields from VIN inquiry / vehicle GET. */
export function pickFanavaranPlaqueLookupFields(
vehicle: Record<string, unknown> | null,
): FanavaranPlaqueLookupFields {
return {
kindId: parseFanavaranId(vehicle?.PlaqueKindId ?? vehicle?.plaqueKindId),
sampleId: parseFanavaranId(
vehicle?.PlaqueSampleId ?? vehicle?.plaqueSampleId,
),
cityId: parseFanavaranId(vehicle?.PlaqueCityId ?? vehicle?.plaqueCityId),
leftNo: asPlaqueText(vehicle?.PlaqueLeftNo ?? vehicle?.plaqueLeftNo),
middleCodeId: parseFanavaranId(
vehicle?.PlaqueMiddleCodeId ?? vehicle?.plaqueMiddleCodeId,
),
rightNo: asPlaqueText(vehicle?.PlaqueRightNo ?? vehicle?.plaqueRightNo),
serial: asPlaqueText(vehicle?.PlaqueSerial ?? vehicle?.plaqueSerial),
plaqueNo: asPlaqueText(vehicle?.PlaqueNo ?? vehicle?.plaqueNo),
};
}
function plaqueNumberEquals(left: unknown, right: unknown): boolean {
const a = normalizePlaquePart(left);
const b = normalizePlaquePart(right);
if (!a || !b) return false;
const aNum = Number(a);
const bNum = Number(b);
if (Number.isFinite(aNum) && Number.isFinite(bNum)) {
return aNum === bNum;
}
return a === b;
}
function plaqueLetterEquals(
inputLetter: string,
vehicle: Record<string, unknown>,
): boolean {
const normalizedInput = normalizePlateText(toEnglishDigits(inputLetter).trim());
if (!normalizedInput) return false;
const vehicleLetterRaw = [
vehicle.PlaqueMiddleCodeCaption,
vehicle.PlaqueLetter,
vehicle.MiddleCodeCaption,
vehicle.PlaqueMiddleCode,
]
.map((value) => normalizePlateText(String(value ?? "").trim()))
.find((value) => value && Number.isNaN(Number(value)));
if (vehicleLetterRaw && normalizePlateText(vehicleLetterRaw) === normalizedInput) {
return true;
}
const inputCode =
FANAVARAN_PLATE_LETTER_CODE[normalizedInput] ??
(Number.isFinite(Number(normalizedInput))
? Number(normalizedInput)
: null);
const vehicleCode = parseFanavaranId(
vehicle.PlaqueMiddleCodeId ?? vehicle.plaqueMiddleCodeId,
);
if (inputCode !== null && vehicleCode !== null) {
return inputCode === vehicleCode;
}
return false;
}
export function plaqueMatchesVehicle(
plaque: FanavaranPlaqueParts,
vehicle: Record<string, unknown> | null,
): boolean {
if (!vehicle) return false;
return (
plaqueNumberEquals(plaque.left, vehicle.PlaqueLeftNo ?? vehicle.plaqueLeftNo) &&
plaqueLetterEquals(plaque.letter, vehicle) &&
plaqueNumberEquals(
plaque.right,
vehicle.PlaqueRightNo ?? vehicle.plaqueRightNo,
) &&
plaqueNumberEquals(
plaque.serial,
vehicle.PlaqueSerial ?? vehicle.plaqueSerial,
)
);
}
export function vehicleMatchesCar(
vehicle: Record<string, unknown> | null,
input: FanavaranCarMatchInput,
vinVehicleId?: number | null,
): boolean {
const vin = normalizeVin(input.vin);
if (vin) {
const vehicleId = pickVehicleId(vehicle);
if (
vinVehicleId != null &&
vehicleId != null &&
vinVehicleId === vehicleId
) {
return true;
}
return pickVehicleVin(vehicle) === vin;
}
if (input.plaque) {
return plaqueMatchesVehicle(input.plaque, vehicle);
}
return false;
}
export function isPolicyActiveOn(
policy: { beginDate?: unknown; endDate?: unknown },
todayGregorian: string,
): boolean {
const begin = toGregorianDate(policy.beginDate);
const end = toGregorianDate(policy.endDate);
if (!end) return false;
if (end < todayGregorian) return false;
if (begin && begin > todayGregorian) return false;
return true;
}
/**
* Same car, several renewals: keep that car only.
* Active (EndDate overlaps today) → newest BeginDate.
* Else → latest EndDate.
*/
export function selectLastAmongCarMatches(
matches: HydratedFanavaranCarPolicy[],
todayGregorian: string = gregorianDateInIran(new Date()),
): HydratedFanavaranCarPolicy | null {
if (matches.length === 0) return null;
const active = matches.filter((match) => isPolicyActiveOn(match, todayGregorian));
const pool = active.length > 0 ? active : matches;
const useBeginDate = active.length > 0;
return pool.reduce((current, candidate) => {
const currentKey = useBeginDate
? toGregorianDate(current.beginDate) ?? ""
: toGregorianDate(current.endDate) ?? "";
const candidateKey = useBeginDate
? toGregorianDate(candidate.beginDate) ?? ""
: toGregorianDate(candidate.endDate) ?? "";
return candidateKey > currentKey ? candidate : current;
});
}
export function completePlaqueParts(input: {
plaqueLeft?: string;
plaqueLetter?: string;
plaqueRight?: string;
plaqueSerial?: string;
}): FanavaranPlaqueParts | null {
const left = normalizePlaquePart(input.plaqueLeft);
const letter = normalizePlateText(toEnglishDigits(input.plaqueLetter).trim());
const right = normalizePlaquePart(input.plaqueRight);
const serial = normalizePlaquePart(input.plaqueSerial);
if (!left || !letter || !right || !serial) {
return null;
}
return { left, letter, right, serial };
}
export function parseLastCarPolicyInput(query: {
nationalCode?: string;
vin?: string;
plaqueLeft?: string;
plaqueLetter?: string;
plaqueRight?: string;
plaqueSerial?: string;
}): {
nationalCode: string;
vin?: string;
plaque?: FanavaranPlaqueParts;
} | { error: string } {
const nationalCode = toEnglishDigits(query.nationalCode).replace(/\D/g, "");
if (!nationalCode) {
return { error: "nationalCode is required" };
}
const vin = normalizeVin(query.vin);
const plaque = completePlaqueParts(query);
const anyPlaquePart = [
query.plaqueLeft,
query.plaqueLetter,
query.plaqueRight,
query.plaqueSerial,
].some((part) => String(part ?? "").trim() !== "");
if (!vin && !plaque) {
if (anyPlaquePart) {
return {
error:
"plaque requires all four parts: plaqueLeft, plaqueLetter, plaqueRight, plaqueSerial",
};
}
return { error: "vin and/or a complete plaque is required" };
}
return {
nationalCode,
...(vin ? { vin } : {}),
...(plaque ? { plaque } : {}),
};
}