forked from Yara724/api
Require structured inquiry participant inputs
This commit is contained in:
@@ -6,9 +6,9 @@
|
||||
|
||||
## interface پیادهسازیشده
|
||||
|
||||
فیلدهای نقشها و آبجکت اختیاری `vehicle` که در ادامه آمدهاند، مستقیماً در body تمام درخواستهای استعلام فعلی پذیرفته میشوند. این تغییر شامل فرم اولیه کاربر و mirror کارشناس/ثبتکننده در V2، جریان کارشناس V3، جریانهای پروندهساز V4/V5، مرکز تماس V6 و مسیرهای تکدرخواستی حضوری است. routeهای پلاک و VIN از قوانین مشترک اشخاص استفاده میکنند.
|
||||
فیلدهای نقشها و آبجکت الزامی `vehicle` که در ادامه آمدهاند، مستقیماً در body تمام درخواستهای استعلام فعلی پذیرفته میشوند. این تغییر شامل فرم اولیه کاربر و mirror کارشناس/ثبتکننده در V2، جریان کارشناس V3، جریانهای پروندهساز V4/V5، مرکز تماس V6 و مسیرهای تکدرخواستی حضوری است. routeهای پلاک و VIN از قوانین مشترک اشخاص استفاده میکنند. فیلدهای تخت راننده/بیمهگذار و شماره تلفن، ورودی استعلام نیستند.
|
||||
|
||||
در دوره مهاجرت، فیلدهای قدیمی و تخت همچنان پذیرفته میشوند. پاسخها و جزئیات پرونده برای نقشهای عملیاتی، در صورت وجود داده، فیلدهای نرمالشده `participants`، `participantRoles`، `vehicle.registrationState` و `vehicle.previousPlateId` را نمایش میدهند.
|
||||
پاسخها و جزئیات پرونده برای نقشهای عملیاتی، در صورت وجود داده، فیلدهای نرمالشده `participants`، `participantRoles`، `vehicle.registrationState` و `vehicle.previousPlateId` را نمایش میدهند.
|
||||
|
||||
## مسئله
|
||||
|
||||
@@ -106,14 +106,9 @@
|
||||
|
||||
جریانهای V2 کاربر/کارشناس، V3، V4، V5 و V6 باید adapter همین قوانین مشترک باشند و منطق نسبت اشخاص را جداگانه پیادهسازی نکنند.
|
||||
|
||||
## سازگاری و انتشار تدریجی
|
||||
## مرز قرارداد
|
||||
|
||||
1. در دوره گذار، ساختار جدید در کنار فیلدهای قدیمی پذیرفته شود.
|
||||
2. `nationalCodeOfDriver` به راننده و `nationalCodeOfInsurer` به بیمهگذار شخص ثالث نگاشت شود. اگر `driverIsInsurer=true` است، هر دو نقش به یک شخص متصل شوند.
|
||||
3. `plate` یا `plateId` قدیمی به پلاک فعلی نگاشت شود؛ پلاک قبلی فقط وقتی ثبت شود که صریحاً از کاربر دریافت شده باشد.
|
||||
4. برای دادههای قدیمی، مالک خودرو یا بیمهگذار بدنه حدس زده نشود؛ مگر اینکه نتیجه استعلام ذخیرهشده آن را قطعی کند، نقش «نامشخص» باقی بماند.
|
||||
5. پاسخ جزئیات و گزارش، اشخاص را بر اساس نقش نمایش دهد و در زمان مهاجرت فیلدهای قدیمی را نیز حفظ کند.
|
||||
6. پس از مهاجرت همه فرانتاندها، قرارداد جدید برای پروندههای تازه الزامی و فیلدهای قدیمی deprecated شوند.
|
||||
ورودی استعلام فقط شامل آبجکتهای ساختاریافته اشخاص و خودرو است. بکاند فیلدهای تختی مانند `nationalCodeOfDriver`، `nationalCodeOfInsurer`، `driverIsInsurer`، `plate`/`vin` سطح بالا و `phoneNumber` را با خطای اعتبارسنجی رد میکند. احراز هویت تلفنی و جریانهای تماس با طرفین از جمعآوری هویت برای استعلام جدا هستند.
|
||||
|
||||
## تصمیم پیشنهادی
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@ Persian version: [inquiry-participants-proposal.fa.md](./inquiry-participants-pr
|
||||
|
||||
## Implemented interface
|
||||
|
||||
The role fields and optional `vehicle` object shown below are accepted directly in every existing inquiry request body. This covers V2 user and expert/registrar mirror initial forms, V3 expert flow, V4/V5 FileMaker flows, V6 call-center flow, and the one-shot in-person completion paths. Plate and VIN routes share the same participant rules.
|
||||
The role fields and required `vehicle` object shown below are accepted directly in every existing inquiry request body. This covers V2 user and expert/registrar mirror initial forms, V3 expert flow, V4/V5 FileMaker flows, V6 call-center flow, and the one-shot in-person completion paths. Plate and VIN routes share the same participant rules. Flat driver/insurer fields and phone numbers are not inquiry inputs.
|
||||
|
||||
Existing flat fields remain accepted during migration. Responses and file-detail views for operational actors expose normalized `participants`, `participantRoles`, `vehicle.registrationState`, and `vehicle.previousPlateId` where available.
|
||||
Responses and file-detail views for operational actors expose normalized `participants`, `participantRoles`, `vehicle.registrationState`, and `vehicle.previousPlateId` where available.
|
||||
|
||||
## Problem
|
||||
|
||||
@@ -106,14 +106,9 @@ Create one shared participant resolver used by every inquiry route. Its interfac
|
||||
|
||||
V2 user/expert routes, V3, V4, V5, and V6 should be adapters over this shared rule set rather than implementing their own relationship logic.
|
||||
|
||||
## Compatibility and rollout
|
||||
## Contract boundary
|
||||
|
||||
1. Accept the new participant object alongside the legacy fields temporarily.
|
||||
2. Map legacy `nationalCodeOfDriver` to Driver and `nationalCodeOfInsurer` to Third-party Policyholder. When `driverIsInsurer=true`, bind both roles to the same participant.
|
||||
3. Map the legacy `plate`/`plateId` to Current Plate; leave Previous Plate absent unless it was explicitly collected.
|
||||
4. Do not guess Vehicle Owner or Car-body Policyholder for old records. Mark unresolved roles as unknown unless stored inquiry evidence identifies them.
|
||||
5. Update report/detail responses to expose people by role while retaining legacy fields during migration.
|
||||
6. After all frontends use the new step, require the new contract for newly created cases and deprecate the legacy fields.
|
||||
The structured participant and vehicle objects are the only accepted inquiry input. The backend rejects flat fields such as `nationalCodeOfDriver`, `nationalCodeOfInsurer`, `driverIsInsurer`, top-level `plate`/`vin`, and `phoneNumber` with a validation error. Phone-based authentication and party contact flows remain separate from inquiry identity collection.
|
||||
|
||||
## Decision
|
||||
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
||||
import {
|
||||
ApiHideProperty,
|
||||
ApiProperty,
|
||||
ApiPropertyOptional,
|
||||
} from "@nestjs/swagger";
|
||||
import { Type } from "class-transformer";
|
||||
import {
|
||||
IsBoolean,
|
||||
@@ -69,11 +73,6 @@ export class InquiryParticipantInputDto {
|
||||
@IsString()
|
||||
fullName?: string;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
phoneNumber?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: "Required for a driver who has a licence.",
|
||||
})
|
||||
@@ -117,27 +116,31 @@ export class InquiryVehicleInputDto {
|
||||
@IsString()
|
||||
@MaxLength(17)
|
||||
vin?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: "Whether the vehicle is newly purchased/registered.",
|
||||
})
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
isNewCar?: boolean;
|
||||
}
|
||||
|
||||
/** New role-complete contract mixed into every inquiry DTO. Legacy fields remain during rollout. */
|
||||
/** Structured role-complete contract mixed into every inquiry DTO. */
|
||||
export class InquiryParticipantFieldsDto {
|
||||
@ApiPropertyOptional({ type: InquiryParticipantInputDto })
|
||||
@IsOptional()
|
||||
@ApiProperty({ type: InquiryParticipantInputDto })
|
||||
@ValidateNested()
|
||||
@Type(() => InquiryParticipantInputDto)
|
||||
driver?: InquiryParticipantInputDto;
|
||||
driver: InquiryParticipantInputDto;
|
||||
|
||||
@ApiPropertyOptional({ type: InquiryParticipantInputDto })
|
||||
@IsOptional()
|
||||
@ApiProperty({ type: InquiryParticipantInputDto })
|
||||
@ValidateNested()
|
||||
@Type(() => InquiryParticipantInputDto)
|
||||
vehicleOwner?: InquiryParticipantInputDto;
|
||||
vehicleOwner: InquiryParticipantInputDto;
|
||||
|
||||
@ApiPropertyOptional({ type: InquiryParticipantInputDto })
|
||||
@IsOptional()
|
||||
@ApiProperty({ type: InquiryParticipantInputDto })
|
||||
@ValidateNested()
|
||||
@Type(() => InquiryParticipantInputDto)
|
||||
thirdPartyPolicyholder?: InquiryParticipantInputDto;
|
||||
thirdPartyPolicyholder: InquiryParticipantInputDto;
|
||||
|
||||
@ApiPropertyOptional({ type: InquiryParticipantInputDto })
|
||||
@IsOptional()
|
||||
@@ -145,9 +148,48 @@ export class InquiryParticipantFieldsDto {
|
||||
@Type(() => InquiryParticipantInputDto)
|
||||
carBodyPolicyholder?: InquiryParticipantInputDto;
|
||||
|
||||
@ApiPropertyOptional({ type: InquiryVehicleInputDto })
|
||||
@IsOptional()
|
||||
@ApiProperty({ type: InquiryVehicleInputDto })
|
||||
@ValidateNested()
|
||||
@Type(() => InquiryVehicleInputDto)
|
||||
vehicle?: InquiryVehicleInputDto;
|
||||
vehicle: InquiryVehicleInputDto;
|
||||
|
||||
/** Internal normalized projections; rejected as request input by the resolver. */
|
||||
@ApiHideProperty()
|
||||
nationalCodeOfDriver?: string;
|
||||
|
||||
@ApiHideProperty()
|
||||
driverBirthday?: any;
|
||||
|
||||
@ApiHideProperty()
|
||||
driverLicense?: string;
|
||||
|
||||
@ApiHideProperty()
|
||||
licenseType?: string;
|
||||
|
||||
@ApiHideProperty()
|
||||
nationalCodeOfInsurer?: string;
|
||||
|
||||
@ApiHideProperty()
|
||||
insurerBirthday?: any;
|
||||
|
||||
@ApiHideProperty()
|
||||
insurerLicense?: string;
|
||||
|
||||
@ApiHideProperty()
|
||||
driverIsInsurer?: boolean;
|
||||
|
||||
@ApiHideProperty()
|
||||
userNoCertificate?: boolean;
|
||||
|
||||
@ApiHideProperty()
|
||||
plate?: any;
|
||||
|
||||
@ApiHideProperty()
|
||||
plateId?: string;
|
||||
|
||||
@ApiHideProperty()
|
||||
vin?: string;
|
||||
|
||||
@ApiHideProperty()
|
||||
isNewCar?: boolean;
|
||||
}
|
||||
|
||||
@@ -1,61 +1,9 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { PlatesDto } from "src/plates/dto/plate.dto";
|
||||
import { Plates } from "src/Types&Enums/plate.interface";
|
||||
import { InquiryParticipantFieldsDto } from "src/common/dto/inquiry-participants.dto";
|
||||
|
||||
export class AddPlateDto extends InquiryParticipantFieldsDto {
|
||||
plateId: string;
|
||||
@ApiPropertyOptional({
|
||||
type: String,
|
||||
description: "Legacy third-party policyholder national code",
|
||||
})
|
||||
nationalCodeOfInsurer: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
type: String,
|
||||
description: "Legacy driver national code",
|
||||
})
|
||||
nationalCodeOfDriver: string;
|
||||
|
||||
@ApiPropertyOptional({ type: String })
|
||||
insurerLicense: string;
|
||||
|
||||
@ApiPropertyOptional({ type: String })
|
||||
driverLicense: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
type: PlatesDto,
|
||||
description:
|
||||
"Legacy/current plate; may be supplied as vehicle.currentPlate",
|
||||
})
|
||||
plate: Plates;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
type: Boolean,
|
||||
description: "Legacy role relationship",
|
||||
})
|
||||
driverIsInsurer: boolean;
|
||||
|
||||
@ApiProperty({ type: Boolean, required: true, default: false })
|
||||
isNewCar: boolean;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
type: Boolean,
|
||||
description: "Legacy; use driver.hasDrivingLicense",
|
||||
})
|
||||
userNoCertificate: boolean;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
type: Number,
|
||||
description: "Legacy third-party policyholder birthday",
|
||||
})
|
||||
insurerBirthday: number;
|
||||
@ApiProperty({
|
||||
type: String,
|
||||
required: false,
|
||||
})
|
||||
driverBirthday: string | null;
|
||||
}
|
||||
export class AddPlateDto extends InquiryParticipantFieldsDto {}
|
||||
|
||||
export class AddPlateProfileDto {
|
||||
plateId: string;
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
||||
import {
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString,
|
||||
MaxLength,
|
||||
ValidateIf,
|
||||
} from "class-validator";
|
||||
import { IsNotEmpty, IsOptional, IsString } from "class-validator";
|
||||
import { Types } from "mongoose";
|
||||
import { AddPlateDto } from "src/profile/dto/user/AddPlateDto";
|
||||
import { StepsEnum } from "src/Types&Enums/blame-request-management/steps.enum";
|
||||
@@ -229,70 +223,8 @@ export class BlameConfessionDtoV2 {
|
||||
imGuilty?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* V2 initial-form step submitted with a VIN/chassis number instead of a plate.
|
||||
* All identity and license fields from {@link AddPlateDto} are preserved; only
|
||||
* `plate` is replaced by `vin` (the 17-character chassis / VIN string).
|
||||
*/
|
||||
export class InitialFormVinDto extends InquiryParticipantFieldsDto {
|
||||
@ApiPropertyOptional({
|
||||
type: String,
|
||||
required: false,
|
||||
description: "17-character VIN / chassis number (شماره شاسی)",
|
||||
example: "NAAM01E15HK123456",
|
||||
maxLength: 17,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@MaxLength(17)
|
||||
@ValidateIf((dto) => !dto.vehicle?.vin)
|
||||
vin: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
type: String,
|
||||
description: "Legacy third-party policyholder national code",
|
||||
})
|
||||
@ValidateIf((dto) => !dto.thirdPartyPolicyholder)
|
||||
nationalCodeOfInsurer: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
type: String,
|
||||
description: "Legacy driver national code",
|
||||
})
|
||||
@ValidateIf((dto) => !dto.driver)
|
||||
nationalCodeOfDriver: string;
|
||||
|
||||
@ApiPropertyOptional({ type: String })
|
||||
insurerLicense: string;
|
||||
|
||||
@ApiPropertyOptional({ type: String })
|
||||
driverLicense: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
type: Boolean,
|
||||
description: "Legacy role relationship",
|
||||
})
|
||||
@ValidateIf((dto) => !dto.driver)
|
||||
driverIsInsurer: boolean;
|
||||
|
||||
@ApiPropertyOptional({ type: Boolean, default: false })
|
||||
isNewCar: boolean;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
type: Boolean,
|
||||
description: "Legacy; use driver.hasDrivingLicense",
|
||||
})
|
||||
userNoCertificate: boolean;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
type: Number,
|
||||
description: "Legacy policyholder birthday",
|
||||
})
|
||||
insurerBirthday: number;
|
||||
|
||||
@ApiPropertyOptional({ type: String, required: false })
|
||||
driverBirthday: string | null;
|
||||
}
|
||||
/** V2 initial-form step submitted with the shared structured vehicle contract. */
|
||||
export class InitialFormVinDto extends InquiryParticipantFieldsDto {}
|
||||
|
||||
// export class DocsOfThisFile {
|
||||
// @ApiProperty()
|
||||
|
||||
@@ -1,182 +1,14 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
||||
import {
|
||||
IsBoolean,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString,
|
||||
MaxLength,
|
||||
ValidateIf,
|
||||
ValidateNested,
|
||||
} from "class-validator";
|
||||
import { Type } from "class-transformer";
|
||||
import { InquiryParticipantFieldsDto } from "src/common/dto/inquiry-participants.dto";
|
||||
|
||||
class PlateV6Dto {
|
||||
@ApiProperty({ example: "44", description: "Left two digits" })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
leftDigits: string;
|
||||
|
||||
@ApiProperty({ example: "ب", description: "Center alphabet letter" })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
centerAlphabet: string;
|
||||
|
||||
@ApiProperty({ example: "111", description: "Center three digits" })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
centerDigits: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: "22",
|
||||
description: "Right two digits (Iran region code)",
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
ir: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inquiry body for the V6 call-center flow.
|
||||
* Same as V3 but without `sheba` — the user adds their own IBAN later via the link.
|
||||
*/
|
||||
export class RunCallCenterInquiryV6Dto extends InquiryParticipantFieldsDto {
|
||||
@ApiPropertyOptional({
|
||||
type: PlateV6Dto,
|
||||
description: "Plate segments — Tejarat block / third-party inquiry.",
|
||||
})
|
||||
@ValidateNested()
|
||||
@Type(() => PlateV6Dto)
|
||||
@ValidateIf((dto) => !dto.vehicle?.currentPlate)
|
||||
plate: PlateV6Dto;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: "1234567890",
|
||||
description: "Legacy third-party policyholder national code",
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@ValidateIf((dto) => !dto.thirdPartyPolicyholder)
|
||||
nationalCodeOfInsurer: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: "1234567890",
|
||||
description: "Legacy driver national code",
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@ValidateIf((dto) => !dto.driver)
|
||||
nationalCodeOfDriver: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: true,
|
||||
description: "Legacy role relationship",
|
||||
})
|
||||
@IsBoolean()
|
||||
@ValidateIf((dto) => !dto.driver)
|
||||
driverIsInsurer: boolean;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: 13780624,
|
||||
description: "Legacy policyholder birth date (Jalali)",
|
||||
})
|
||||
insurerBirthday: number | string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: 13780624,
|
||||
description:
|
||||
"Driver birth date (Jalali). Required when driverIsInsurer is false.",
|
||||
})
|
||||
@IsOptional()
|
||||
driverBirthday?: number | string | null;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: "123456789",
|
||||
description: "Driver license (required when driverIsInsurer is false).",
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
driverLicense?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: "123456789",
|
||||
description: "Insurer license (required when driverIsInsurer is true).",
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
insurerLicense?: string;
|
||||
}
|
||||
export class RunCallCenterInquiryV6Dto extends InquiryParticipantFieldsDto {}
|
||||
|
||||
/**
|
||||
* VIN / chassis variant of the V6 call-center inquiry.
|
||||
* Identical to `RunCallCenterInquiryV6Dto` but replaces `plate` with `vin`.
|
||||
* Sheba (IBAN) is intentionally absent — the user provides it themselves via the link.
|
||||
*/
|
||||
export class RunCallCenterInquiryVinV6Dto extends InquiryParticipantFieldsDto {
|
||||
@ApiPropertyOptional({
|
||||
example: "NAAM01E15HK123456",
|
||||
description: "17-character VIN / chassis number (شماره شاسی)",
|
||||
maxLength: 17,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@MaxLength(17)
|
||||
@ValidateIf((dto) => !dto.vehicle?.vin)
|
||||
vin: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: "1234567890",
|
||||
description: "Legacy third-party policyholder national code",
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@ValidateIf((dto) => !dto.thirdPartyPolicyholder)
|
||||
nationalCodeOfInsurer: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: "1234567890",
|
||||
description: "Legacy driver national code",
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@ValidateIf((dto) => !dto.driver)
|
||||
nationalCodeOfDriver: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: true,
|
||||
description: "Legacy role relationship",
|
||||
})
|
||||
@IsBoolean()
|
||||
@ValidateIf((dto) => !dto.driver)
|
||||
driverIsInsurer: boolean;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: 13780624,
|
||||
description: "Legacy policyholder birth date (Jalali)",
|
||||
})
|
||||
insurerBirthday: number | string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: 13780624,
|
||||
description:
|
||||
"Driver birth date (Jalali). Required when driverIsInsurer is false.",
|
||||
})
|
||||
@IsOptional()
|
||||
driverBirthday?: number | string | null;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: "123456789",
|
||||
description: "Driver license (required when driverIsInsurer is false).",
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
driverLicense?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: "123456789",
|
||||
description: "Insurer license (required when driverIsInsurer is true).",
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
insurerLicense?: string;
|
||||
}
|
||||
export class RunCallCenterInquiryVinV6Dto extends InquiryParticipantFieldsDto {}
|
||||
|
||||
@@ -1,95 +1,12 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
||||
import {
|
||||
IsBoolean,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString,
|
||||
MaxLength,
|
||||
ValidateIf,
|
||||
ValidateNested,
|
||||
} from "class-validator";
|
||||
import { Type } from "class-transformer";
|
||||
import { ApiPropertyOptional } from "@nestjs/swagger";
|
||||
import { IsOptional, IsString } from "class-validator";
|
||||
import { InquiryParticipantFieldsDto } from "src/common/dto/inquiry-participants.dto";
|
||||
|
||||
class PlateV3Dto {
|
||||
@ApiProperty({ example: "44", description: "Left two digits" })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
leftDigits: string;
|
||||
|
||||
@ApiProperty({ example: "ب", description: "Center alphabet letter" })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
centerAlphabet: string;
|
||||
|
||||
@ApiProperty({ example: "111", description: "Center three digits" })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
centerDigits: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: "22",
|
||||
description: "Right two digits (Iran region code)",
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
ir: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Body for `POST run-inquiries/:requestId`.
|
||||
* First call = guilty party (+ auto claim). Second call = damaged party (THIRD_PARTY only).
|
||||
*/
|
||||
export class RunInquiriesV3Dto extends InquiryParticipantFieldsDto {
|
||||
@ApiPropertyOptional({
|
||||
type: PlateV3Dto,
|
||||
description: "Plate segments — Tejarat block / third-party inquiry.",
|
||||
})
|
||||
@ValidateNested()
|
||||
@Type(() => PlateV3Dto)
|
||||
@ValidateIf((dto) => !dto.vehicle?.currentPlate)
|
||||
plate: PlateV3Dto;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: "1234567890",
|
||||
description: "Legacy third-party policyholder national code",
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@ValidateIf((dto) => !dto.thirdPartyPolicyholder)
|
||||
nationalCodeOfInsurer: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: "1234567890",
|
||||
description: "Legacy driver national code",
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@ValidateIf((dto) => !dto.driver)
|
||||
nationalCodeOfDriver: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: true,
|
||||
description: "Legacy role relationship",
|
||||
})
|
||||
@IsBoolean()
|
||||
@ValidateIf((dto) => !dto.driver)
|
||||
driverIsInsurer: boolean;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: 13780624,
|
||||
description: "Legacy policyholder birth date (Jalali)",
|
||||
})
|
||||
insurerBirthday: number | string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: 13780624,
|
||||
description:
|
||||
"Driver birth date (Jalali). Required when driverIsInsurer is false.",
|
||||
})
|
||||
@IsOptional()
|
||||
driverBirthday?: number | string | null;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: "IR123456789012345678901234",
|
||||
description:
|
||||
@@ -98,32 +15,6 @@ export class RunInquiriesV3Dto extends InquiryParticipantFieldsDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
sheba?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: "123456789",
|
||||
description: "Driver license (required when driverIsInsurer is false).",
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
driverLicense?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: "123456789",
|
||||
description: "Insurer license (required when driverIsInsurer is true).",
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
insurerLicense?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: "1",
|
||||
description:
|
||||
"Driving licence type code from GET /lookups/driving-licence-types. " +
|
||||
"Optional — used in V4 FileMaker flow.",
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
licenseType?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,57 +23,6 @@ export class RunInquiriesV3Dto extends InquiryParticipantFieldsDto {
|
||||
* First call = guilty party (+ auto claim). Second call = damaged party (THIRD_PARTY only).
|
||||
*/
|
||||
export class RunInquiriesVinV3Dto extends InquiryParticipantFieldsDto {
|
||||
@ApiPropertyOptional({
|
||||
example: "NAAM01E15HK123456",
|
||||
description: "17-character VIN / chassis number (شماره شاسی)",
|
||||
maxLength: 17,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@MaxLength(17)
|
||||
@ValidateIf((dto) => !dto.vehicle?.vin)
|
||||
vin: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: "1234567890",
|
||||
description: "Legacy third-party policyholder national code",
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@ValidateIf((dto) => !dto.thirdPartyPolicyholder)
|
||||
nationalCodeOfInsurer: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: "1234567890",
|
||||
description: "Legacy driver national code",
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@ValidateIf((dto) => !dto.driver)
|
||||
nationalCodeOfDriver: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: true,
|
||||
description: "Legacy role relationship",
|
||||
})
|
||||
@IsBoolean()
|
||||
@ValidateIf((dto) => !dto.driver)
|
||||
driverIsInsurer: boolean;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: 13780624,
|
||||
description: "Legacy policyholder birth date (Jalali)",
|
||||
})
|
||||
insurerBirthday: number | string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: 13780624,
|
||||
description:
|
||||
"Driver birth date (Jalali). Required when driverIsInsurer is false.",
|
||||
})
|
||||
@IsOptional()
|
||||
driverBirthday?: number | string | null;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: "IR123456789012345678901234",
|
||||
description:
|
||||
@@ -191,30 +31,4 @@ export class RunInquiriesVinV3Dto extends InquiryParticipantFieldsDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
sheba?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: "123456789",
|
||||
description: "Driver license (required when driverIsInsurer is false).",
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
driverLicense?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: "123456789",
|
||||
description: "Insurer license (required when driverIsInsurer is true).",
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
insurerLicense?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: "1",
|
||||
description:
|
||||
"Driving licence type code from GET /lookups/driving-licence-types. " +
|
||||
"Optional — used in V4 FileMaker flow.",
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
licenseType?: string;
|
||||
}
|
||||
|
||||
@@ -66,22 +66,34 @@ describe("inquiry participant resolver", () => {
|
||||
).toBe(VehicleRegistrationState.CURRENT);
|
||||
});
|
||||
|
||||
it("keeps legacy driver/policyholder payloads working during rollout", () => {
|
||||
const resolved = resolveInquiryParticipants(BlameRequestType.THIRD_PARTY, {
|
||||
it("rejects the removed flat inquiry contract", () => {
|
||||
expect(() =>
|
||||
resolveInquiryParticipants(BlameRequestType.THIRD_PARTY, {
|
||||
nationalCodeOfDriver: "0012345678",
|
||||
driverBirthday: "1370/01/01",
|
||||
driverLicense: "123456789",
|
||||
nationalCodeOfInsurer: "0098765432",
|
||||
insurerBirthday: "1360/02/02",
|
||||
driverIsInsurer: false,
|
||||
} as any);
|
||||
} as any),
|
||||
).toThrow(BadRequestException);
|
||||
});
|
||||
|
||||
expect(resolved.legacy).toBe(true);
|
||||
expect(resolved.roles.driver).toBe("DRIVER");
|
||||
expect(resolved.roles.thirdPartyPolicyholder).toBe(
|
||||
"THIRD_PARTY_POLICYHOLDER",
|
||||
);
|
||||
expect(resolved.roles.vehicleOwner).toBeUndefined();
|
||||
it("rejects phone numbers in participant inquiry identity", () => {
|
||||
expect(() =>
|
||||
resolveInquiryParticipants(BlameRequestType.THIRD_PARTY, {
|
||||
driver: {
|
||||
nationalCode: "0012345678",
|
||||
birthday: "1370/01/01",
|
||||
hasDrivingLicense: false,
|
||||
phoneNumber: "09120000000",
|
||||
},
|
||||
vehicleOwner: { sameAs: InquiryParticipantRole.DRIVER },
|
||||
thirdPartyPolicyholder: {
|
||||
sameAs: InquiryParticipantRole.DRIVER,
|
||||
},
|
||||
} as any),
|
||||
).toThrow(BadRequestException);
|
||||
});
|
||||
|
||||
it("keeps third-party and car-body policyholders distinct", () => {
|
||||
@@ -116,12 +128,14 @@ describe("inquiry participant resolver", () => {
|
||||
|
||||
it("projects role-complete input onto the existing inquiry fields", () => {
|
||||
const normalized = normalizeInquirySubmission(BlameRequestType.CAR_BODY, {
|
||||
plate: {
|
||||
vehicle: {
|
||||
currentPlate: {
|
||||
leftDigits: "44",
|
||||
centerAlphabet: "ب",
|
||||
centerDigits: "111",
|
||||
ir: "22",
|
||||
},
|
||||
},
|
||||
driver: {
|
||||
nationalCode: "0012345678",
|
||||
birthday: "1370/01/01",
|
||||
|
||||
@@ -15,7 +15,6 @@ export interface ResolvedInquiryParticipant {
|
||||
nationalCode: string;
|
||||
birthday: string;
|
||||
fullName?: string;
|
||||
phoneNumber?: string;
|
||||
hasDrivingLicense?: boolean;
|
||||
licenseNumber?: string;
|
||||
licenseType?: string;
|
||||
@@ -90,6 +89,11 @@ 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) {
|
||||
@@ -118,7 +122,6 @@ function requiredIdentity(
|
||||
nationalCode,
|
||||
birthday,
|
||||
...(input.fullName ? { fullName: input.fullName } : {}),
|
||||
...(input.phoneNumber ? { phoneNumber: input.phoneNumber } : {}),
|
||||
...(input.hasDrivingLicense != null
|
||||
? { hasDrivingLicense: input.hasDrivingLicense }
|
||||
: {}),
|
||||
@@ -129,52 +132,17 @@ function requiredIdentity(
|
||||
|
||||
export function resolveInquiryParticipants(
|
||||
caseType: BlameRequestType,
|
||||
input: InquiryParticipantFieldsDto & Record<string, any>,
|
||||
input: Partial<InquiryParticipantFieldsDto> & Record<string, any>,
|
||||
options: InquiryParticipantResolutionOptions = {},
|
||||
): ResolvedInquiryParticipants {
|
||||
assertStructuredInquiryInput(input);
|
||||
const hasRoleCompleteInput = Object.values(ROLE_FIELDS).some(
|
||||
(field) => input[field] != null,
|
||||
);
|
||||
if (!hasRoleCompleteInput) {
|
||||
const driverId = InquiryParticipantRole.DRIVER;
|
||||
const policyholderId = input.driverIsInsurer
|
||||
? driverId
|
||||
: InquiryParticipantRole.THIRD_PARTY_POLICYHOLDER;
|
||||
const participants: ResolvedInquiryParticipant[] = [
|
||||
{
|
||||
participantId: driverId,
|
||||
nationalCode: String(input.nationalCodeOfDriver ?? ""),
|
||||
birthday: String(
|
||||
input.driverBirthday ??
|
||||
(input.driverIsInsurer ? input.insurerBirthday : "") ??
|
||||
"",
|
||||
),
|
||||
...(input.driverLicense
|
||||
? { licenseNumber: String(input.driverLicense) }
|
||||
: {}),
|
||||
...(input.licenseType
|
||||
? { licenseType: String(input.licenseType) }
|
||||
: {}),
|
||||
...(input.userNoCertificate != null
|
||||
? { hasDrivingLicense: !input.userNoCertificate }
|
||||
: {}),
|
||||
},
|
||||
];
|
||||
if (policyholderId !== driverId) {
|
||||
participants.push({
|
||||
participantId: policyholderId,
|
||||
nationalCode: String(input.nationalCodeOfInsurer ?? ""),
|
||||
birthday: String(input.insurerBirthday ?? ""),
|
||||
});
|
||||
}
|
||||
return {
|
||||
participants,
|
||||
roles: {
|
||||
driver: driverId,
|
||||
thirdPartyPolicyholder: policyholderId,
|
||||
},
|
||||
legacy: true,
|
||||
};
|
||||
throw new BadRequestException(
|
||||
"driver, vehicleOwner, and thirdPartyPolicyholder are required in the structured inquiry format.",
|
||||
);
|
||||
}
|
||||
if (
|
||||
caseType === BlameRequestType.THIRD_PARTY &&
|
||||
@@ -367,6 +335,33 @@ function normalizePlateForComparison(
|
||||
.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>,
|
||||
@@ -517,7 +512,11 @@ export function normalizeInquirySubmission<T extends Record<string, any>>(
|
||||
input: T,
|
||||
options: InquiryParticipantResolutionOptions = {},
|
||||
): NormalizedInquirySubmission<T> {
|
||||
const participants = resolveInquiryParticipants(caseType, input, options);
|
||||
const participants = resolveInquiryParticipants(
|
||||
caseType,
|
||||
input as any,
|
||||
options,
|
||||
);
|
||||
const driver = participantForRole(
|
||||
participants,
|
||||
InquiryParticipantRole.DRIVER,
|
||||
@@ -539,43 +538,32 @@ export function normalizeInquirySubmission<T extends Record<string, any>>(
|
||||
participants,
|
||||
InquiryParticipantRole.CAR_BODY_POLICYHOLDER,
|
||||
);
|
||||
const vehicle = input.vehicle
|
||||
? resolveInquiryVehicle(input.vehicle as InquiryVehicleInputDto)
|
||||
: undefined;
|
||||
if (
|
||||
vehicle &&
|
||||
input.plate &&
|
||||
normalizePlateForComparison(vehicle.currentPlate) !==
|
||||
normalizePlateForComparison(input.plate)
|
||||
) {
|
||||
throw new BadRequestException(
|
||||
"plate and vehicle.currentPlate must identify the same current plate.",
|
||||
const vehicle = resolveInquiryVehicle(
|
||||
input.vehicle as InquiryVehicleInputDto,
|
||||
);
|
||||
}
|
||||
const sameDriverAndPolicyholder =
|
||||
participants.roles.driver === participants.roles.thirdPartyPolicyholder;
|
||||
|
||||
return {
|
||||
dto: {
|
||||
...input,
|
||||
...(vehicle?.currentPlate && !input.plate
|
||||
? { plate: vehicle.currentPlate }
|
||||
: {}),
|
||||
...(vehicle?.vin && !input.vin ? { vin: vehicle.vin } : {}),
|
||||
plate: vehicle.currentPlate,
|
||||
vin: vehicle.vin,
|
||||
nationalCodeOfDriver: driver.nationalCode,
|
||||
driverBirthday: driver.birthday,
|
||||
driverLicense: driver.licenseNumber,
|
||||
licenseType: driver.licenseType,
|
||||
userNoCertificate:
|
||||
driver.hasDrivingLicense == null
|
||||
? input.userNoCertificate
|
||||
? undefined
|
||||
: !driver.hasDrivingLicense,
|
||||
nationalCodeOfInsurer: thirdPartyPolicyholder.nationalCode,
|
||||
insurerBirthday: thirdPartyPolicyholder.birthday,
|
||||
driverIsInsurer: sameDriverAndPolicyholder,
|
||||
insurerLicense: sameDriverAndPolicyholder
|
||||
? driver.licenseNumber
|
||||
: input.insurerLicense,
|
||||
: undefined,
|
||||
isNewCar: vehicle.isNewCar,
|
||||
} as NormalizedInquirySubmission<T>["dto"],
|
||||
participants,
|
||||
driver,
|
||||
|
||||
Reference in New Issue
Block a user