merge upstream

This commit is contained in:
2026-09-07 11:05:56 +03:30
13 changed files with 544 additions and 107 deletions

View File

@@ -5,22 +5,18 @@ import {
ValidatorConstraint, ValidatorConstraint,
ValidatorConstraintInterface, ValidatorConstraintInterface,
} from "class-validator"; } from "class-validator";
import { normalizeMoneyAmountString } from "src/utils/unicode-digits"; import { parseMoneyAmountToman } from "src/utils/unicode-digits";
@ValidatorConstraint({ name: "isMoneyAmountString", async: false }) @ValidatorConstraint({ name: "isMoneyAmountString", async: false })
export class IsMoneyAmountStringConstraint export class IsMoneyAmountStringConstraint implements ValidatorConstraintInterface {
implements ValidatorConstraintInterface
{
validate(value: unknown): boolean { validate(value: unknown): boolean {
if (value == null || value === "") return true; if (value == null || value === "") return true;
if (typeof value !== "string") return false; if (typeof value !== "string") return false;
const n = normalizeMoneyAmountString(value); return parseMoneyAmountToman(value) !== null;
if (!n) return false;
return /^\d+(\.\d+)?$/.test(n);
} }
defaultMessage(): string { defaultMessage(): string {
return "Must be a non-negative amount (digits only, optional decimal)."; return "Must be a non-negative whole-Toman amount.";
} }
} }

View File

@@ -26,7 +26,7 @@ describe("SubmitExpertReplyV2Dto", () => {
expect(errors).not.toHaveLength(0); expect(errors).not.toHaveLength(0);
}); });
it("accepts a replacement part without a price", async () => { it("rejects a replacement part without a price", async () => {
const dto = plainToInstance(SubmitExpertReplyV2Dto, { const dto = plainToInstance(SubmitExpertReplyV2Dto, {
description: "Damage assessment", description: "Damage assessment",
parts: [ parts: [
@@ -41,7 +41,7 @@ describe("SubmitExpertReplyV2Dto", () => {
], ],
}); });
expect(await validate(dto)).toHaveLength(0); expect(await validate(dto)).not.toHaveLength(0);
}); });
it("accepts a repair part without daghi", async () => { it("accepts a repair part without daghi", async () => {
@@ -61,4 +61,33 @@ describe("SubmitExpertReplyV2Dto", () => {
expect(await validate(dto)).toHaveLength(0); expect(await validate(dto)).toHaveLength(0);
}); });
it("accepts the expert's repair and replacement pricing rules", async () => {
const dto = plainToInstance(SubmitExpertReplyV2Dto, {
description: "Damage assessment",
parts: [
{
partId: 11,
typeOfDamage: TypeOfDamage.Repair,
salary: "۵,۰۰۰",
totalPayment: "5000",
factorNeeded: false,
},
{
partId: 23,
typeOfDamage: TypeOfDamage.Change,
price: "۵۰,۰۰۰",
salary: "۱۰۰,۰۰۰",
totalPayment: "150000",
factorNeeded: false,
daghi: {
option: DaghiOption.RECYCLED_PARTS_VALUE,
price: "۱۰۰,۵۰۰,۰۰۰",
},
},
],
});
expect(await validate(dto)).toHaveLength(0);
});
}); });

View File

@@ -12,7 +12,7 @@ import {
IsInt, IsInt,
} from 'class-validator'; } from 'class-validator';
import { Type } from 'class-transformer'; import { Type } from 'class-transformer';
import { IsRepairLineAmountToman } from 'src/common/validators/repair-line-amount-toman.validator'; import { IsMoneyAmountString } from 'src/common/validators/money-amount-string.validator';
import { ClaimRequiredDocumentType } from 'src/Types&Enums/claim-request-management/required-document-type.enum'; import { ClaimRequiredDocumentType } from 'src/Types&Enums/claim-request-management/required-document-type.enum';
import { DamagedPartItem } from 'src/claim-request-management/dto/capture-requirements-v2.dto'; import { DamagedPartItem } from 'src/claim-request-management/dto/capture-requirements-v2.dto';
import { DaghiOption } from 'src/Types&Enums/claim-request-management/daghi-option.enum'; import { DaghiOption } from 'src/Types&Enums/claim-request-management/daghi-option.enum';
@@ -35,7 +35,7 @@ export class DaghiDetailsV2Dto {
) )
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
@IsRepairLineAmountToman() @IsMoneyAmountString()
price?: string; price?: string;
@ApiPropertyOptional({ @ApiPropertyOptional({
@@ -56,24 +56,24 @@ export class PartPricingV2Dto {
@ApiProperty({ @ApiProperty({
enum: TypeOfDamage, enum: TypeOfDamage,
description: "'repair' requires price; 'change' may omit it.", description: "'change' requires price; 'repair' may omit it.",
}) })
@IsEnum(TypeOfDamage) @IsEnum(TypeOfDamage)
typeOfDamage: TypeOfDamage; typeOfDamage: TypeOfDamage;
@ApiPropertyOptional({ @ApiPropertyOptional({
example: "5000000", example: "5000000",
description: "Required for repair lines; omitted for change lines. Use 0 if the full amount is in salary.", description: "Required for change lines; omitted for repair lines. Use 0 if unused.",
}) })
@ValidateIf( @ValidateIf(
(part: PartPricingV2Dto) => (part: PartPricingV2Dto) =>
part.typeOfDamage === TypeOfDamage.Repair || part.typeOfDamage === TypeOfDamage.Change ||
(part.price != null && (part.price != null &&
(typeof part.price !== 'string' || part.price.trim() !== '')), (typeof part.price !== 'string' || part.price.trim() !== '')),
) )
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
@IsRepairLineAmountToman({ allowZero: true }) @IsMoneyAmountString()
price: string; price: string;
@ApiProperty({ @ApiProperty({
@@ -82,7 +82,7 @@ export class PartPricingV2Dto {
}) })
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
@IsRepairLineAmountToman({ allowZero: true }) @IsMoneyAmountString()
salary: string; salary: string;
@ApiProperty({ @ApiProperty({
@@ -91,7 +91,7 @@ export class PartPricingV2Dto {
}) })
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
@IsRepairLineAmountToman({ allowZero: true }) @IsMoneyAmountString()
totalPayment: string; totalPayment: string;
@ApiPropertyOptional({ @ApiPropertyOptional({

View File

@@ -47,7 +47,7 @@ describe("getExpertReplyPricingValidationError", () => {
).toBeNull(); ).toBeNull();
}); });
it("rejects a repair line without a price", () => { it("accepts a repair line without a price", () => {
expect( expect(
getExpertReplyPricingValidationError([ getExpertReplyPricingValidationError([
{ {
@@ -55,10 +55,9 @@ describe("getExpertReplyPricingValidationError", () => {
typeOfDamage: TypeOfDamage.Repair, typeOfDamage: TypeOfDamage.Repair,
salary: "0", salary: "0",
totalPayment: "0", totalPayment: "0",
daghi: { option: DaghiOption.NO_VALUE },
}, },
]), ]),
).toMatch(/price is also required/); ).toBeNull();
}); });
it("rejects recycled-value daghi without its price", () => { it("rejects recycled-value daghi without its price", () => {
@@ -67,6 +66,7 @@ describe("getExpertReplyPricingValidationError", () => {
{ {
partId: 201, partId: 201,
typeOfDamage: TypeOfDamage.Change, typeOfDamage: TypeOfDamage.Change,
price: "0",
salary: "0", salary: "0",
totalPayment: "0", totalPayment: "0",
daghi: { option: DaghiOption.RECYCLED_PARTS_VALUE }, daghi: { option: DaghiOption.RECYCLED_PARTS_VALUE },
@@ -75,7 +75,7 @@ describe("getExpertReplyPricingValidationError", () => {
).toMatch(/requires a valid daghi price/); ).toMatch(/requires a valid daghi price/);
}); });
it("accepts a replacement line without a part price", () => { it("rejects a replacement line without a part price", () => {
expect( expect(
getExpertReplyPricingValidationError([ getExpertReplyPricingValidationError([
{ {
@@ -86,7 +86,7 @@ describe("getExpertReplyPricingValidationError", () => {
daghi: { option: DaghiOption.NO_VALUE }, daghi: { option: DaghiOption.NO_VALUE },
}, },
]), ]),
).toBeNull(); ).toMatch(/price is also required/);
}); });
it("rejects an invalid price when a replacement line supplies one", () => { it("rejects an invalid price when a replacement line supplies one", () => {
@@ -103,4 +103,22 @@ describe("getExpertReplyPricingValidationError", () => {
]), ]),
).toMatch(/requires valid salary and totalPayment/); ).toMatch(/requires valid salary and totalPayment/);
}); });
it("accepts a recycled-parts daghi price above the repair-line cap", () => {
expect(
getExpertReplyPricingValidationError([
{
partId: 23,
typeOfDamage: TypeOfDamage.Change,
price: "۵۰,۰۰۰",
salary: "۱۰۰,۰۰۰",
totalPayment: "150000",
daghi: {
option: DaghiOption.RECYCLED_PARTS_VALUE,
price: "۱۰۰,۵۰۰,۰۰۰",
},
},
]),
).toBeNull();
});
}); });

View File

@@ -1,4 +1,3 @@
import { REPAIR_LINE_AMOUNT_TOMAN } from "src/constants/repair-amount-limits";
import { DaghiOption } from "src/Types&Enums/claim-request-management/daghi-option.enum"; import { DaghiOption } from "src/Types&Enums/claim-request-management/daghi-option.enum";
import { TypeOfDamage } from "src/Types&Enums/claim-request-management/type-of-damage.enum"; import { TypeOfDamage } from "src/Types&Enums/claim-request-management/type-of-damage.enum";
import { parseMoneyAmountToman } from "src/utils/unicode-digits"; import { parseMoneyAmountToman } from "src/utils/unicode-digits";
@@ -59,33 +58,22 @@ export function getExpertReplyPricingValidationError(
part.price != null && part.price != null &&
(typeof part.price !== "string" || part.price.trim() !== ""); (typeof part.price !== "string" || part.price.trim() !== "");
const splitAmountIsValid = (amount: number | null) => const amountIsValid = (amount: number | null) => amount !== null;
amount !== null && const priceIsRequired = part.typeOfDamage === TypeOfDamage.Change;
(amount === 0 || const priceIsValid = !hasPrice || amountIsValid(price);
(amount >= REPAIR_LINE_AMOUNT_TOMAN.MIN &&
amount <= REPAIR_LINE_AMOUNT_TOMAN.MAX));
const totalIsValid =
totalPayment !== null &&
totalPayment >= 0 &&
totalPayment <= REPAIR_LINE_AMOUNT_TOMAN.MAX;
const priceIsRequired = part.typeOfDamage === TypeOfDamage.Repair;
const priceIsValid =
!hasPrice || (price !== null && splitAmountIsValid(price));
if ( if (
!splitAmountIsValid(salary) || !amountIsValid(salary) ||
!totalIsValid || !amountIsValid(totalPayment) ||
(priceIsRequired && !splitAmountIsValid(price)) || (priceIsRequired && !amountIsValid(price)) ||
!priceIsValid !priceIsValid
) { ) {
return `${label} requires valid salary and totalPayment; price is also required for '${TypeOfDamage.Repair}' damage. Price and salary may be 0; totalPayment may be 0.`; return `${label} requires valid salary and totalPayment; price is also required for '${TypeOfDamage.Change}' damage. Price, salary, and totalPayment may be 0.`;
} }
if ( if (
daghi?.option === DaghiOption.RECYCLED_PARTS_VALUE && daghi?.option === DaghiOption.RECYCLED_PARTS_VALUE &&
(daghiPrice === null || daghiPrice === null
daghiPrice < REPAIR_LINE_AMOUNT_TOMAN.MIN ||
daghiPrice > REPAIR_LINE_AMOUNT_TOMAN.MAX)
) { ) {
return `${label} requires a valid daghi price when option is '${DaghiOption.RECYCLED_PARTS_VALUE}'.`; return `${label} requires a valid daghi price when option is '${DaghiOption.RECYCLED_PARTS_VALUE}'.`;
} }

View File

@@ -79,14 +79,44 @@ export class Insurance {
@Prop({ type: [String] }) @Prop({ type: [String] })
coverages?: string[]; coverages?: string[];
/** CAR_BODY only: mocked car-body insurance inquiry result */ /** CAR_BODY inquiry result; raw provider data is retained in vehicle.inquiry.carBody.raw. */
@Prop({ type: MongooseSchema.Types.Mixed }) @Prop({ type: MongooseSchema.Types.Mixed })
carBodyInsurance?: { carBodyInsurance?: {
policyNumber?: string; policyNumber?: string;
policyId?: number;
contractId?: number;
startDate?: string; startDate?: string;
endDate?: string; endDate?: string;
insurerCompany?: string; insurerCompany?: string;
coverages?: string[]; coverages?: string[];
companyId?: number | string;
companyName?: string;
insurerName?: string;
insurerNationalCode?: string;
ownerNationalCode?: string;
ownerName?: string;
customerName?: string;
customerLastName?: string;
customerFatherName?: string;
customerMobile?: string;
customerAddress?: string;
customerPostalCode?: string;
chassisNumber?: string;
vin?: string;
motorNumber?: string;
vehicleGroup?: string;
vehicleSystem?: string;
vehicleKind?: string;
builtYear?: number;
cylinderCount?: number;
passengerCount?: number;
usage?: string;
issueDate?: string;
vehicleValue?: number;
totalPremium?: number;
noLossYearsCount?: number;
lossDocuments?: unknown[];
hasEndorsement?: boolean;
}; };
} }
export const InsuranceSchema = SchemaFactory.createForClass(Insurance); export const InsuranceSchema = SchemaFactory.createForClass(Insurance);
@@ -183,4 +213,4 @@ export class Party {
@Prop({ type: PartyConfirmationSchema }) @Prop({ type: PartyConfirmationSchema })
confirmation?: PartyConfirmation; confirmation?: PartyConfirmation;
} }
export const PartySchema = SchemaFactory.createForClass(Party); export const PartySchema = SchemaFactory.createForClass(Party);

View File

@@ -1,5 +1,5 @@
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose"; import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
import { Types } from "mongoose"; import { Schema as MongooseSchema, Types } from "mongoose";
import { import {
ResendFirstPartyDto, ResendFirstPartyDto,
ResendSecondPartyDto, ResendSecondPartyDto,
@@ -79,6 +79,13 @@ export class CarBodyInsuranceDetail {
@Prop({ type: [String] }) @Prop({ type: [String] })
coverages?: string[]; coverages?: string[];
/** Complete provider payload, retained for audit and future field mapping. */
@Prop({ type: MongooseSchema.Types.Mixed })
raw?: unknown;
@Prop()
source?: string;
} }
export class SecondPartyFile { export class SecondPartyFile {

View File

@@ -1513,7 +1513,7 @@ export class RequestManagementService {
if (req.type === BlameRequestType.CAR_BODY && role === PartyRole.FIRST) { if (req.type === BlameRequestType.CAR_BODY && role === PartyRole.FIRST) {
let carBodyInfo: any; let carBodyInfo: any;
try { try {
carBodyInfo = await this.sandHubService.getTejaratCarBodyInquiry( carBodyInfo = await this.sandHubService.getCarBodyInquiry(
{ {
nationalCodeOfInsurer: body.nationalCodeOfInsurer, nationalCodeOfInsurer: body.nationalCodeOfInsurer,
plate: body.plate, plate: body.plate,
@@ -1523,13 +1523,13 @@ export class RequestManagementService {
: inquiryOptions, : inquiryOptions,
); );
this.recordPartyCaseInquiryStatus(req, "carBody", role, true, { this.recordPartyCaseInquiryStatus(req, "carBody", role, true, {
source: "TEJARAT_CAR_BODY_INQUIRY", source: carBodyInfo.source,
raw: carBodyInfo.raw, raw: carBodyInfo.raw,
mapped: carBodyInfo.mapped, mapped: carBodyInfo.mapped,
}); });
} catch (err: any) { } catch (err: any) {
this.logger.error( this.logger.error(
`[TEJARAT] car body inquiry failed for request=${req._id}: ${err?.message || err}`, `[CAR_BODY] inquiry failed for request=${req._id}: ${err?.message || err}`,
); );
this.recordPartyCaseInquiryStatus( this.recordPartyCaseInquiryStatus(
req, req,
@@ -1552,7 +1552,7 @@ export class RequestManagementService {
party.vehicle.inquiry = { party.vehicle.inquiry = {
...party.vehicle.inquiry, ...party.vehicle.inquiry,
carBody: { carBody: {
source: "TEJARAT_CAR_BODY_INQUIRY", source: carBodyInfo.source,
raw: carBodyInfo.raw, raw: carBodyInfo.raw,
mapped: carBodyInfo.mapped, mapped: carBodyInfo.mapped,
}, },
@@ -1562,19 +1562,35 @@ export class RequestManagementService {
const m = carBodyInfo.mapped; const m = carBodyInfo.mapped;
(party.insurance as any).carBodyInsurance = { (party.insurance as any).carBodyInsurance = {
policyNumber: m.policyNumber ?? null, policyNumber: m.policyNumber ?? null,
policyId: m.policyId ?? null,
contractId: m.contractId ?? null,
companyId: m.companyId ?? null, companyId: m.companyId ?? null,
companyName: m.CompanyName ?? null, companyName: m.CompanyName ?? null,
insurerName: m.insurerName ?? null, insurerName: m.insurerName ?? null,
insurerNationalCode: m.insurerNationalCode ?? null, insurerNationalCode: m.insurerNationalCode ?? null,
ownerNationalCode: m.ownerNationalCode ?? null, ownerNationalCode: m.ownerNationalCode ?? null,
ownerName: m.ownerName ?? null,
customerName: m.customerName ?? null,
customerLastName: m.customerLastName ?? null,
customerFatherName: m.customerFatherName ?? null,
customerMobile: m.customerMobile ?? null,
customerAddress: m.customerAddress ?? null,
customerPostalCode: m.customerPostalCode ?? null,
chassisNumber: m.ChassisNumberField ?? null, chassisNumber: m.ChassisNumberField ?? null,
vin: m.VinNumberField ?? null, vin: m.VinNumberField ?? null,
motorNumber: m.EngineNumberField ?? null, motorNumber: m.EngineNumberField ?? null,
vehicleGroup: m.vehicleGroupTitle ?? null, vehicleGroup: m.vehicleGroupTitle ?? null,
vehicleSystem: m.vehicleSystemTitle ?? null, vehicleSystem: m.vehicleSystemTitle ?? null,
vehicleKind: m.vehicleKind ?? null,
builtYear: m.builtYear ?? null,
cylinderCount: m.cylinderCount ?? null,
passengerCount: m.passengerCount ?? null,
usage: m.usage ?? null,
startDate: m.StartDate ?? null, startDate: m.StartDate ?? null,
endDate: m.EndDate ?? null, endDate: m.EndDate ?? null,
issueDate: m.IssueDate ?? null, issueDate: m.IssueDate ?? null,
vehicleValue: m.vehicleValue ?? null,
totalPremium: m.totalPremium ?? null,
noLossYearsCount: m.noLossYearsCount ?? null, noLossYearsCount: m.noLossYearsCount ?? null,
lossDocuments: m.lossDocuments ?? [], lossDocuments: m.lossDocuments ?? [],
hasEndorsement: m.hasEndorsement ?? null, hasEndorsement: m.hasEndorsement ?? null,
@@ -3006,13 +3022,15 @@ export class RequestManagementService {
sandHubReport.EndDate, sandHubReport.EndDate,
}; };
// For CAR_BODY type, also fetch and persist mocked car body insurance info // For CAR_BODY type, persist the provider response and its mapped fields.
if (request.type === "CAR_BODY" && partyType === "firstParty") { if (request.type === "CAR_BODY" && partyType === "firstParty") {
const carBodyInfo = await this.mockCarBodyInsuranceInquiry( const carBodyInquiry = await this.sandHubService.getCarBodyInquiry(
request._id, {
body.plate, nationalCodeOfInsurer: body.nationalCodeOfInsurer,
body.nationalCodeOfInsurer, plate: body.plate,
} as any,
); );
const carBodyInfo = carBodyInquiry.mapped as any;
this.logger.log( this.logger.log(
`[CAR_BODY] Saving car body insurance data to blame file ${request._id}:`, `[CAR_BODY] Saving car body insurance data to blame file ${request._id}:`,
@@ -3035,6 +3053,12 @@ export class RequestManagementService {
setFields[ setFields[
"firstPartyDetails.firstPartyCarBodyInsuranceDetail.coverages" "firstPartyDetails.firstPartyCarBodyInsuranceDetail.coverages"
] = carBodyInfo.coverages; ] = carBodyInfo.coverages;
setFields[
"firstPartyDetails.firstPartyCarBodyInsuranceDetail.raw"
] = carBodyInquiry.raw;
setFields[
"firstPartyDetails.firstPartyCarBodyInsuranceDetail.source"
] = carBodyInquiry.source;
} }
// Build final update payload with proper MongoDB operators // Build final update payload with proper MongoDB operators
@@ -4423,40 +4447,6 @@ export class RequestManagementService {
return { message: "Update saved successfully." }; return { message: "Update saved successfully." };
} }
// Mocked CAR_BODY insurance inquiry – replace with real external API later
private async mockCarBodyInsuranceInquiry(
requestId: string,
plate: any,
nationalCodeOfInsurer: string,
): Promise<{
policyNumber: string;
startDate: string;
endDate: string;
insurerCompany: string;
coverages: string[];
}> {
this.logger.log(
`[CAR_BODY] Mocking car body insurance inquiry for request ${requestId} (plate=${JSON.stringify(
plate,
)}, nationalCodeOfInsurer=${nationalCodeOfInsurer})`,
);
const today = new Date();
const oneYearLater = new Date(
today.getFullYear() + 1,
today.getMonth(),
today.getDate(),
);
return {
policyNumber: "CB-MOCK-123456",
startDate: today.toISOString().slice(0, 10),
endDate: oneYearLater.toISOString().slice(0, 10),
insurerCompany: "Mock Car Body Insurance Co.",
coverages: ["آتش‌سوزی", "سرقت", "بدنه کامل"],
};
}
private async processResendUploads( private async processResendUploads(
files: { [key: string]: Express.Multer.File[] }, files: { [key: string]: Express.Multer.File[] },
requestId: string, requestId: string,
@@ -6325,17 +6315,21 @@ export class RequestManagementService {
}); });
} }
const carBodyInfo = await this.mockCarBodyInsuranceInquiry( const carBodyInquiry = await this.sandHubService.getCarBodyInquiry({
requestId, nationalCodeOfInsurer: firstPartyPlate.nationalCodeOfInsurer,
firstPartyPlate.plate, plate: firstPartyPlate.plate,
firstPartyPlate.nationalCodeOfInsurer, } as any);
); const carBodyInfo = carBodyInquiry.mapped as any;
this.recordPartyCaseInquiryStatus( this.recordPartyCaseInquiryStatus(
req, req,
"carBody", "carBody",
PartyRole.FIRST, PartyRole.FIRST,
true, true,
carBodyInfo, {
source: carBodyInquiry.source,
raw: carBodyInquiry.raw,
mapped: carBodyInfo,
},
); );
const firstParty: any = { const firstParty: any = {
@@ -6364,7 +6358,14 @@ export class RequestManagementService {
model: sandHubReport?.MapTypNam, model: sandHubReport?.MapTypNam,
type: `${sandHubReport?.UsageField || ""} / ${sandHubReport?.MapUsageName || "-"}`, type: `${sandHubReport?.UsageField || ""} / ${sandHubReport?.MapUsageName || "-"}`,
isNew: firstPartyPlate.isNewCar, isNew: firstPartyPlate.isNewCar,
inquiry: sandHubReport, inquiry: {
...sandHubReport,
carBody: {
source: carBodyInquiry.source,
raw: carBodyInquiry.raw,
mapped: carBodyInfo,
},
},
}, },
insurance: { insurance: {
policyNumber: policyNumber:
@@ -6381,6 +6382,8 @@ export class RequestManagementService {
endDate: carBodyInfo.endDate, endDate: carBodyInfo.endDate,
insurerCompany: carBodyInfo.insurerCompany, insurerCompany: carBodyInfo.insurerCompany,
coverages: carBodyInfo.coverages, coverages: carBodyInfo.coverages,
raw: carBodyInquiry.raw,
source: carBodyInquiry.source,
}, },
}, },
statement: { statement: {
@@ -7585,11 +7588,11 @@ export class RequestManagementService {
}; };
// CAR_BODY specific insurance info // CAR_BODY specific insurance info
const carBodyInfo = await this.mockCarBodyInsuranceInquiry( const carBodyInquiry = await this.sandHubService.getCarBodyInquiry({
request._id.toString(), nationalCodeOfInsurer: firstPartyPlate.nationalCodeOfInsurer,
firstPartyPlate.plate, plate: firstPartyPlate.plate,
firstPartyPlate.nationalCodeOfInsurer, } as any);
); const carBodyInfo = carBodyInquiry.mapped as any;
firstPartyDetails.firstPartyCarBodyInsuranceDetail = { firstPartyDetails.firstPartyCarBodyInsuranceDetail = {
policyNumber: carBodyInfo.policyNumber, policyNumber: carBodyInfo.policyNumber,
@@ -7597,6 +7600,8 @@ export class RequestManagementService {
endDate: carBodyInfo.endDate, endDate: carBodyInfo.endDate,
insurerCompany: carBodyInfo.insurerCompany, insurerCompany: carBodyInfo.insurerCompany,
coverages: carBodyInfo.coverages, coverages: carBodyInfo.coverages,
raw: carBodyInquiry.raw,
source: carBodyInquiry.source,
}; };
// Update the firstPartyDetails in the payload with all plate data // Update the firstPartyDetails in the payload with all plate data
@@ -8808,7 +8813,7 @@ export class RequestManagementService {
partyRole === PartyRole.FIRST partyRole === PartyRole.FIRST
) { ) {
try { try {
const carBodyInfo = await this.sandHubService.getTejaratCarBodyInquiry( const carBodyInfo = await this.sandHubService.getCarBodyInquiry(
{ {
nationalCodeOfInsurer: partyData.nationalCodeOfInsurer, nationalCodeOfInsurer: partyData.nationalCodeOfInsurer,
plate: partyData.plate as any, plate: partyData.plate as any,
@@ -8816,14 +8821,14 @@ export class RequestManagementService {
clientId ? { clientId } : undefined, clientId ? { clientId } : undefined,
); );
this.recordPartyCaseInquiryStatus(req, "carBody", partyRole, true, { this.recordPartyCaseInquiryStatus(req, "carBody", partyRole, true, {
source: "TEJARAT_CAR_BODY_INQUIRY", source: carBodyInfo.source,
raw: carBodyInfo.raw, raw: carBodyInfo.raw,
mapped: carBodyInfo.mapped, mapped: carBodyInfo.mapped,
}); });
party.vehicle.inquiry = { party.vehicle.inquiry = {
...party.vehicle.inquiry, ...party.vehicle.inquiry,
carBody: { carBody: {
source: "TEJARAT_CAR_BODY_INQUIRY", source: carBodyInfo.source,
raw: carBodyInfo.raw, raw: carBodyInfo.raw,
mapped: carBodyInfo.mapped, mapped: carBodyInfo.mapped,
}, },
@@ -8831,8 +8836,38 @@ export class RequestManagementService {
const m = carBodyInfo.mapped; const m = carBodyInfo.mapped;
(party.insurance as any).carBodyInsurance = { (party.insurance as any).carBodyInsurance = {
policyNumber: m.policyNumber ?? null, policyNumber: m.policyNumber ?? null,
policyId: m.policyId ?? null,
contractId: m.contractId ?? null,
companyId: m.companyId ?? null, companyId: m.companyId ?? null,
companyName: m.CompanyName ?? null, companyName: m.CompanyName ?? null,
insurerName: m.insurerName ?? null,
insurerNationalCode: m.insurerNationalCode ?? null,
ownerNationalCode: m.ownerNationalCode ?? null,
ownerName: m.ownerName ?? null,
customerName: m.customerName ?? null,
customerLastName: m.customerLastName ?? null,
customerFatherName: m.customerFatherName ?? null,
customerMobile: m.customerMobile ?? null,
customerAddress: m.customerAddress ?? null,
customerPostalCode: m.customerPostalCode ?? null,
chassisNumber: m.ChassisNumberField ?? null,
vin: m.VinNumberField ?? null,
motorNumber: m.EngineNumberField ?? null,
vehicleGroup: m.vehicleGroupTitle ?? null,
vehicleSystem: m.vehicleSystemTitle ?? null,
vehicleKind: m.vehicleKind ?? null,
builtYear: m.builtYear ?? null,
cylinderCount: m.cylinderCount ?? null,
passengerCount: m.passengerCount ?? null,
usage: m.usage ?? null,
startDate: m.StartDate ?? null,
endDate: m.EndDate ?? null,
issueDate: m.IssueDate ?? null,
vehicleValue: m.vehicleValue ?? null,
totalPremium: m.totalPremium ?? null,
noLossYearsCount: m.noLossYearsCount ?? null,
lossDocuments: m.lossDocuments ?? [],
hasEndorsement: m.hasEndorsement ?? null,
}; };
const cbCompanyCode = m.companyId ?? m.CompanyCode; const cbCompanyCode = m.companyId ?? m.CompanyCode;
const cbCompanyName = m.CompanyName ?? m.companyPersianName; const cbCompanyName = m.CompanyName ?? m.companyPersianName;
@@ -9633,7 +9668,7 @@ export class RequestManagementService {
partyRole === PartyRole.FIRST partyRole === PartyRole.FIRST
) { ) {
try { try {
const carBodyInfo = await this.sandHubService.getTejaratCarBodyInquiry( const carBodyInfo = await this.sandHubService.getCarBodyInquiry(
{ {
nationalCodeOfInsurer: partyData.nationalCodeOfInsurer, nationalCodeOfInsurer: partyData.nationalCodeOfInsurer,
plate: partyData.vin as any, // VIN used as identifier for CAR_BODY plate: partyData.vin as any, // VIN used as identifier for CAR_BODY
@@ -9641,14 +9676,14 @@ export class RequestManagementService {
clientId ? { clientId } : undefined, clientId ? { clientId } : undefined,
); );
this.recordPartyCaseInquiryStatus(req, "carBody", partyRole, true, { this.recordPartyCaseInquiryStatus(req, "carBody", partyRole, true, {
source: "TEJARAT_CAR_BODY_VIN_INQUIRY", source: carBodyInfo.source,
raw: carBodyInfo.raw, raw: carBodyInfo.raw,
mapped: carBodyInfo.mapped, mapped: carBodyInfo.mapped,
}); });
party.vehicle.inquiry = { party.vehicle.inquiry = {
...party.vehicle.inquiry, ...party.vehicle.inquiry,
carBody: { carBody: {
source: "TEJARAT_CAR_BODY_VIN_INQUIRY", source: carBodyInfo.source,
raw: carBodyInfo.raw, raw: carBodyInfo.raw,
mapped: carBodyInfo.mapped, mapped: carBodyInfo.mapped,
}, },
@@ -9656,8 +9691,38 @@ export class RequestManagementService {
const m = carBodyInfo.mapped; const m = carBodyInfo.mapped;
(party.insurance as any).carBodyInsurance = { (party.insurance as any).carBodyInsurance = {
policyNumber: m.policyNumber ?? null, policyNumber: m.policyNumber ?? null,
policyId: m.policyId ?? null,
contractId: m.contractId ?? null,
companyId: m.companyId ?? null, companyId: m.companyId ?? null,
companyName: m.CompanyName ?? null, companyName: m.CompanyName ?? null,
insurerName: m.insurerName ?? null,
insurerNationalCode: m.insurerNationalCode ?? null,
ownerNationalCode: m.ownerNationalCode ?? null,
ownerName: m.ownerName ?? null,
customerName: m.customerName ?? null,
customerLastName: m.customerLastName ?? null,
customerFatherName: m.customerFatherName ?? null,
customerMobile: m.customerMobile ?? null,
customerAddress: m.customerAddress ?? null,
customerPostalCode: m.customerPostalCode ?? null,
chassisNumber: m.ChassisNumberField ?? null,
vin: m.VinNumberField ?? null,
motorNumber: m.EngineNumberField ?? null,
vehicleGroup: m.vehicleGroupTitle ?? null,
vehicleSystem: m.vehicleSystemTitle ?? null,
vehicleKind: m.vehicleKind ?? null,
builtYear: m.builtYear ?? null,
cylinderCount: m.cylinderCount ?? null,
passengerCount: m.passengerCount ?? null,
usage: m.usage ?? null,
startDate: m.StartDate ?? null,
endDate: m.EndDate ?? null,
issueDate: m.IssueDate ?? null,
vehicleValue: m.vehicleValue ?? null,
totalPremium: m.totalPremium ?? null,
noLossYearsCount: m.noLossYearsCount ?? null,
lossDocuments: m.lossDocuments ?? [],
hasEndorsement: m.hasEndorsement ?? null,
}; };
const cbCompanyCode = m.companyId ?? m.CompanyCode; const cbCompanyCode = m.companyId ?? m.CompanyCode;
const cbCompanyName = m.CompanyName ?? m.companyPersianName; const cbCompanyName = m.CompanyName ?? m.companyPersianName;

View File

@@ -0,0 +1,65 @@
import { mapEsgCarBodyPolicyToInquiry } from "./esg-car-body-inquiry.mapper";
describe("mapEsgCarBodyPolicyToInquiry", () => {
it("maps the processed CAR_BODY lookup while retaining useful policy, customer, and vehicle fields", () => {
const mapped = mapEsgCarBodyPolicyToInquiry({
product: "car-body",
insuranceLine: "CAR_BODY",
insuranceLineId: 4,
policyId: 15292336,
policy: {
CINumber: "70019846985",
ContractId: 12525,
BeginDate: "1405/05/18",
EndDate: "1406/05/18",
PolicyIssuDate: "1405/05/18",
VehicleValue: 18510000000,
TotalPremium: 73299600,
EndoNo: 0,
},
customer: {
Name: "سهيل",
LastName: "حاجي زاده",
NationalCode: "4311402422",
Mobile: "09226187419",
},
vehicle: {
BuiltYear: 1403,
ChassisNo: "NAAR03HFFRDE07024",
MotorNo: "172B0222818",
VIN: "NAAR03HFFRDE07024",
vehicleKind: {
Caption: "پژو 207I",
VehicleSystemCaption: "پژو",
CylinderCount: 4,
PassengerCount: 5,
},
used: { Caption: "شخصي" },
plaque: {
leftTwoDigits: "29",
serialLetter: "د",
threeDigits: "782",
rightTwoDigits: "60",
},
},
});
expect(mapped).toMatchObject({
policyNumber: "70019846985",
policyId: 15292336,
contractId: 12525,
insurerName: null,
insurerNationalCode: null,
ownerName: "سهيل حاجي زاده",
ownerNationalCode: "4311402422",
VinNumberField: "NAAR03HFFRDE07024",
MapTypNam: "پژو 207I",
vehicleValue: 18510000000,
totalPremium: 73299600,
platePartOne: "29",
plateLetterTitle: "د",
platePartThree: "782",
plateSerialNumber: "60",
});
});
});

View File

@@ -0,0 +1,79 @@
/**
* Converts the processed CAR_BODY policy returned by the ESG/Fanavaran lookup
* into the same mapped contract used by the existing car-body inquiry flow.
* The original response is deliberately kept separately as `raw` by the
* caller, so newly exposed provider fields are never discarded.
*/
export function mapEsgCarBodyPolicyToInquiry(
response: Record<string, any>,
): Record<string, unknown> {
const policy = response.policy ?? {};
const customer = response.customer ?? {};
const vehicle = response.vehicle ?? {};
const vehicleKind = vehicle.vehicleKind ?? {};
const usage = vehicle.used ?? {};
const plaque = vehicle.plaque ?? {};
const ownerName =
[customer.Name, customer.LastName]
.filter((value) => typeof value === "string" && value.trim())
.join(" ") || null;
return {
policyNumber: policy.CINumber ?? policy.PolicyNo ?? null,
policyId: response.policyId ?? policy.PolicyId ?? null,
contractId: policy.ContractId ?? null,
insuranceLine: response.insuranceLine ?? "CAR_BODY",
insuranceLineId: response.insuranceLineId ?? 4,
// Provider responses do not currently include an insurer company identity.
companyId: policy.CompanyId ?? null,
CompanyCode: policy.CompanyId ?? null,
CompanyName: policy.CompanyName ?? null,
// The lookup's customer is the policyholder/owner, not the insurer.
insurerName: null,
InsuranceFullName: null,
insurerNationalCode: null,
ownerNationalCode: customer.NationalCode ?? null,
ownerName,
customerName: customer.Name ?? null,
customerLastName: customer.LastName ?? null,
customerFatherName: customer.FatherName ?? null,
customerMobile: customer.Mobile ?? null,
customerAddress: customer.Address ?? null,
customerPostalCode: customer.PostalCode ?? null,
motorNumber: vehicle.MotorNo ?? null,
EngineNumberField: vehicle.MotorNo ?? null,
chassisNumber: vehicle.ChassisNo ?? null,
ChassisNumberField: vehicle.ChassisNo ?? null,
vin: vehicle.VIN ?? vehicle.ChassisNo ?? null,
VinNumberField: vehicle.VIN ?? vehicle.ChassisNo ?? null,
vehicleGroupTitle: vehicleKind.VehicleCategoryCaption ?? null,
vehicleSystemTitle: vehicleKind.VehicleSystemCaption ?? null,
vehicleKind: vehicleKind.Caption ?? null,
MapTypNam: vehicleKind.Caption ?? vehicleKind.VehicleSystemCaption ?? null,
builtYear: vehicle.BuiltYear ?? null,
cylinderCount: vehicleKind.CylinderCount ?? null,
passengerCount: vehicleKind.PassengerCount ?? null,
usage: usage.Caption ?? null,
IssueDate: policy.PolicyIssuDate ?? policy.IssuDate ?? null,
StartDate: policy.BeginDate ?? null,
EndDate: policy.EndDate ?? null,
vehicleValue: policy.VehicleValue ?? null,
totalPremium: policy.TotalPremium ?? null,
noLossYearsCount:
policy.YearsCountWithoutLoss ??
policy.AdditionalCovYearsCountWithoutLoss ??
null,
lossDocuments: [],
hasEndorsement: Number(policy.EndoNo ?? 0) > 0,
platePartOne: plaque.leftTwoDigits ?? vehicle.PlaqueLeftNo ?? null,
plateLetterTitle: plaque.serialLetter ?? null,
platePartThree: plaque.threeDigits ?? vehicle.PlaqueRightNo ?? null,
plateSerialNumber: plaque.rightTwoDigits ?? vehicle.PlaqueSerial ?? null,
};
}

View File

@@ -7,6 +7,7 @@ import { ClientModule } from "src/client/client.module";
import { SystemSettingsModule } from "src/system-settings/system-settings.module"; import { SystemSettingsModule } from "src/system-settings/system-settings.module";
import { PlateNormalizerModule } from "src/utils/plate-normalizer/plate-normalizer.module"; import { PlateNormalizerModule } from "src/utils/plate-normalizer/plate-normalizer.module";
import { OfflineInquiryModule } from "src/offline-inquiry/offline-inquiry.module"; import { OfflineInquiryModule } from "src/offline-inquiry/offline-inquiry.module";
import { LookupsModule } from "src/lookups/lookups.module";
import { SandHubDbService } from "src/sand-hub/entity/db-service/sand-hub.db.service"; import { SandHubDbService } from "src/sand-hub/entity/db-service/sand-hub.db.service";
import { SandHubModel, SandHubSchema } from "./entity/schema/sand-hub.schema"; import { SandHubModel, SandHubSchema } from "./entity/schema/sand-hub.schema";
import { SandHubService } from "./sand-hub.service"; import { SandHubService } from "./sand-hub.service";
@@ -22,6 +23,7 @@ import { SandHubService } from "./sand-hub.service";
PlateNormalizerModule, PlateNormalizerModule,
ClientModule, ClientModule,
OfflineInquiryModule, OfflineInquiryModule,
LookupsModule,
MongooseModule.forFeature([ MongooseModule.forFeature([
{ name: SandHubModel.name, schema: SandHubSchema }, { name: SandHubModel.name, schema: SandHubSchema },
]), ]),

View File

@@ -12,6 +12,9 @@ describe("SandHubService inquiry mocks", () => {
const plateNormalizer = { const plateNormalizer = {
normalizePlateText: (text: string) => text, normalizePlateText: (text: string) => text,
}; };
const lookupsService = {
findLastProcessedCarPolicy: jest.fn(),
};
let service: SandHubService; let service: SandHubService;
@@ -37,6 +40,7 @@ describe("SandHubService inquiry mocks", () => {
{ {
findPlateInquiry: jest.fn().mockResolvedValue(null), findPlateInquiry: jest.fn().mockResolvedValue(null),
} as any, } as any,
lookupsService as any,
); );
externalInquirySettings.isInquiryLive.mockResolvedValue(false); externalInquirySettings.isInquiryLive.mockResolvedValue(false);
externalInquirySettings.getMockCompanyContext.mockResolvedValue({ externalInquirySettings.getMockCompanyContext.mockResolvedValue({
@@ -64,6 +68,77 @@ describe("SandHubService inquiry mocks", () => {
expect(result.mapped.platePartThree).toBe(498); expect(result.mapped.platePartThree).toBe(498);
}); });
it("uses the processed ESG CAR_BODY lookup and preserves it as raw data", async () => {
process.env.CLIENT_ID = "8";
externalInquirySettings.isInquiryLive.mockResolvedValue(true);
lookupsService.findLastProcessedCarPolicy.mockResolvedValue({
product: "car-body",
insuranceLine: "CAR_BODY",
insuranceLineId: 4,
policyId: 15292336,
policy: { CINumber: "70019846985", BeginDate: "1405/05/18" },
customer: { Name: "سهيل", LastName: "حاجي زاده" },
vehicle: {
VIN: "NAAR03HFFRDE07024",
plaque: {
leftTwoDigits: "16",
serialLetter: "ب",
threeDigits: "498",
rightTwoDigits: "60",
},
},
});
const result = await service.getCarBodyInquiry(userDetail);
expect(lookupsService.findLastProcessedCarPolicy).toHaveBeenCalledWith(
"car-body",
{
nationalCode: "1234567890",
plaqueLeft: "16",
plaqueLetter: "12",
plaqueRight: "498",
plaqueSerial: "60",
},
);
expect(result.source).toBe("ESG_CAR_BODY_INQUIRY");
expect(result.raw.policyId).toBe(15292336);
expect(result.mapped.policyNumber).toBe("70019846985");
expect(httpService.post).not.toHaveBeenCalled();
});
it("keeps ESG car-body inquiry in mock mode when the per-client toggle is off", async () => {
process.env.CLIENT_ID = "8";
const result = await service.getCarBodyInquiry(userDetail);
expect(lookupsService.findLastProcessedCarPolicy).not.toHaveBeenCalled();
expect(result.source).toBe("ESG_CAR_BODY_INQUIRY");
expect(result.raw?.isSuccess).toBe(true);
expect(httpService.post).not.toHaveBeenCalled();
});
it("uses the VIN lookup and VIN audit source for ESG car-body VIN inquiries", async () => {
process.env.CLIENT_ID = "8";
externalInquirySettings.isInquiryLive.mockResolvedValue(true);
lookupsService.findLastProcessedCarPolicy.mockResolvedValue({
policy: { CINumber: "70019846985" },
customer: {},
vehicle: {},
});
const result = await service.getCarBodyInquiry({
nationalCodeOfInsurer: "1234567890",
plate: "NAAR03HFFRDE07024",
});
expect(lookupsService.findLastProcessedCarPolicy).toHaveBeenCalledWith(
"car-body",
{ nationalCode: "1234567890", vin: "NAAR03HFFRDE07024" },
);
expect(result.source).toBe("ESG_CAR_BODY_VIN_INQUIRY");
});
it("uses car-body mock shape in Tejarat helper when inquiry is off", async () => { it("uses car-body mock shape in Tejarat helper when inquiry is off", async () => {
const raw = await (service as any).makeTejaratRequest( const raw = await (service as any).makeTejaratRequest(
"http://example/block-inquiry-tejarat/badane", "http://example/block-inquiry-tejarat/badane",

View File

@@ -16,10 +16,17 @@ import { PlateNormalizerService } from "src/utils/plate-normalizer/plate-normali
import type { ExternalInquiryType } from "src/common/types/external-inquiry.types"; import type { ExternalInquiryType } from "src/common/types/external-inquiry.types";
import type { MockInquiryCompanyContext } from "src/common/types/external-inquiry.types"; import type { MockInquiryCompanyContext } from "src/common/types/external-inquiry.types";
import { OfflineInquiryService } from "src/offline-inquiry/offline-inquiry.service"; import { OfflineInquiryService } from "src/offline-inquiry/offline-inquiry.service";
import { LookupsService } from "src/lookups/lookups.service";
import { resolveFanavaranClientKey } from "src/core/config/fanavaran-client.config"; import { resolveFanavaranClientKey } from "src/core/config/fanavaran-client.config";
import { SandHubDetailDto, SandHubInquiryOptions } from "./dto/sand-hub.dto"; import { SandHubDetailDto, SandHubInquiryOptions } from "./dto/sand-hub.dto";
import { jalaliToGregorianDate } from "src/helpers/date-jalali"; import { jalaliToGregorianDate } from "src/helpers/date-jalali";
import { firstValueFrom } from "rxjs"; import { firstValueFrom } from "rxjs";
import { mapEsgCarBodyPolicyToInquiry } from "./esg-car-body-inquiry.mapper";
import type { Plates } from "src/Types&Enums/plate.interface";
type CarBodyInquiryDetail = Omit<SandHubDetailDto, "plate"> & {
plate: Plates | string;
};
@Injectable() @Injectable()
export class SandHubService { export class SandHubService {
@@ -44,6 +51,7 @@ export class SandHubService {
private readonly externalInquirySettings: ExternalInquirySettingsService, private readonly externalInquirySettings: ExternalInquirySettingsService,
private readonly plateNormalizer: PlateNormalizerService, private readonly plateNormalizer: PlateNormalizerService,
private readonly offlineInquiryService: OfflineInquiryService, private readonly offlineInquiryService: OfflineInquiryService,
private readonly lookupsService: LookupsService,
) {} ) {}
private clientRefFrom(options?: SandHubInquiryOptions): string | undefined { private clientRefFrom(options?: SandHubInquiryOptions): string | undefined {
@@ -833,6 +841,81 @@ export class SandHubService {
return await this.sandHubDbService.findOneBySandHubId(sandHubId); return await this.sandHubDbService.findOneBySandHubId(sandHubId);
} }
/**
* CAR_BODY is the only ESG product that uses the processed Fanavaran lookup.
* THIRD_PARTY remains on its established provider-specific inquiry path.
*/
async getCarBodyInquiry(
userDetail: CarBodyInquiryDetail,
options?: SandHubInquiryOptions,
): Promise<{
source:
| "ESG_CAR_BODY_INQUIRY"
| "ESG_CAR_BODY_VIN_INQUIRY"
| "TEJARAT_CAR_BODY_INQUIRY"
| "TEJARAT_CAR_BODY_VIN_INQUIRY";
raw: any;
mapped: Record<string, unknown>;
}> {
if (!this.shouldUseEsgInquiryProvider()) {
const result = await this.getTejaratCarBodyInquiry(
userDetail as SandHubDetailDto,
options,
);
return {
source:
typeof userDetail.plate === "string"
? "TEJARAT_CAR_BODY_VIN_INQUIRY"
: "TEJARAT_CAR_BODY_INQUIRY",
...result,
};
}
const live = await this.isInquiryLive("carBodyPlate", options);
if (!live) {
// Reuse the established local mock contract without sending an HTTP request.
const result = await this.getTejaratCarBodyInquiry(
userDetail as SandHubDetailDto,
options,
);
return {
source:
typeof userDetail.plate === "string"
? "ESG_CAR_BODY_VIN_INQUIRY"
: "ESG_CAR_BODY_INQUIRY",
...result,
};
}
const plateOrVin = userDetail.plate;
const query =
typeof plateOrVin === "string"
? {
nationalCode: String(userDetail.nationalCodeOfInsurer),
vin: plateOrVin,
}
: {
nationalCode: String(userDetail.nationalCodeOfInsurer),
plaqueLeft: String(plateOrVin.leftDigits),
plaqueLetter: String(plateOrVin.centerAlphabet),
plaqueRight: String(plateOrVin.centerDigits),
plaqueSerial: String(plateOrVin.ir),
};
const raw = await this.lookupsService.findLastProcessedCarPolicy(
"car-body",
query,
);
return {
source:
typeof plateOrVin === "string"
? "ESG_CAR_BODY_VIN_INQUIRY"
: "ESG_CAR_BODY_INQUIRY",
raw,
mapped: mapEsgCarBodyPolicyToInquiry(raw),
};
}
async getTejaratCarBodyInquiry( async getTejaratCarBodyInquiry(
userDetail: SandHubDetailDto, userDetail: SandHubDetailDto,
options?: SandHubInquiryOptions, options?: SandHubInquiryOptions,