forked from Yara724/api
added offline inquiry in system setting also fix some bugs
This commit is contained in:
20
src/offline-inquiry/offline-inquiry.module.ts
Normal file
20
src/offline-inquiry/offline-inquiry.module.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { MongooseModule } from "@nestjs/mongoose";
|
||||
import { SystemSettingsModule } from "src/system-settings/system-settings.module";
|
||||
import { OfflineInquiryService } from "./offline-inquiry.service";
|
||||
import {
|
||||
OfflineInquiry,
|
||||
OfflineInquirySchema,
|
||||
} from "./schema/offline-inquiry.schema";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
MongooseModule.forFeature([
|
||||
{ name: OfflineInquiry.name, schema: OfflineInquirySchema },
|
||||
]),
|
||||
SystemSettingsModule,
|
||||
],
|
||||
providers: [OfflineInquiryService],
|
||||
exports: [OfflineInquiryService],
|
||||
})
|
||||
export class OfflineInquiryModule {}
|
||||
287
src/offline-inquiry/offline-inquiry.seeds.ts
Normal file
287
src/offline-inquiry/offline-inquiry.seeds.ts
Normal file
@@ -0,0 +1,287 @@
|
||||
import type { FanavaranClientKey } from "src/core/config/fanavaran-client.config";
|
||||
|
||||
/**
|
||||
* Seed payloads for offline plate/policy inquiry.
|
||||
* Inserted into `offlineInquiries` on boot when the unique key is missing.
|
||||
*/
|
||||
export const OFFLINE_INQUIRY_SEEDS: Array<{
|
||||
clientKey: FanavaranClientKey;
|
||||
inquiryType: string;
|
||||
nationalCode: string;
|
||||
plate: {
|
||||
leftDigits: string;
|
||||
centerAlphabet: string;
|
||||
centerDigits: string;
|
||||
ir: string;
|
||||
};
|
||||
plateId: string;
|
||||
label: string;
|
||||
fanavaranDriverId?: number;
|
||||
person?: Record<string, unknown>;
|
||||
insurance?: Record<string, unknown>;
|
||||
inquiry: {
|
||||
source: string;
|
||||
raw: Record<string, unknown>;
|
||||
mapped: Record<string, unknown>;
|
||||
};
|
||||
}> = [
|
||||
{
|
||||
clientKey: "parsian",
|
||||
inquiryType: "thirdPartyPlate",
|
||||
nationalCode: "0651828562",
|
||||
plate: {
|
||||
leftDigits: "56",
|
||||
centerAlphabet: "د",
|
||||
centerDigits: "394",
|
||||
ir: "66",
|
||||
},
|
||||
plateId: "66-56-د-394",
|
||||
label: "روح اله حيدري — Parsian FIRST party (offline seed)",
|
||||
person: {
|
||||
driverBirthday: "13571201",
|
||||
insurerBirthday: 13571201,
|
||||
driverIsInsurer: true,
|
||||
nationalCodeOfDriver: "0651828562",
|
||||
nationalCodeOfInsurer: "0651828562",
|
||||
phoneNumber: "09120723528",
|
||||
},
|
||||
insurance: {
|
||||
coverages: [],
|
||||
policyNumber: null,
|
||||
company: "بيمه پارسيان",
|
||||
financialCeiling: "0",
|
||||
startDate: "1404/10/21",
|
||||
endDate: "1405/10/23",
|
||||
},
|
||||
inquiry: {
|
||||
source: "OFFLINE_SEED_INQUIRY",
|
||||
raw: {
|
||||
success: true,
|
||||
provider: "PARSIAN",
|
||||
trackingCode: "OFFLINE-PARSIAN-0651828562",
|
||||
message: "offline seeded policy by plate",
|
||||
duration: 0,
|
||||
data: {
|
||||
nationalCode: "0651828562",
|
||||
plk1: "56",
|
||||
plk2: "د",
|
||||
plk3: "394",
|
||||
plksrl: "66",
|
||||
CarGrpCod: "2",
|
||||
CmpCod: "8",
|
||||
CmpNam: "بيمه پارسيان",
|
||||
DisFnYrPrcnt: "35",
|
||||
DisLfYrPrcnt: "35",
|
||||
DisPrsnYrPrcnt: "70",
|
||||
FndCst: "0.00",
|
||||
HBgnDte: "1404/10/23",
|
||||
HEndDte: "1405/10/23",
|
||||
HIsuDte: "1404/10/21",
|
||||
InsNam: "روح اله حيدري",
|
||||
LastCmpCod: "8",
|
||||
LastCmpDocNo: "1110/111130/403/002650",
|
||||
MtrNum: "13389030167",
|
||||
NtnlId: "0651828562",
|
||||
PlcyUnqCod: "16018466143",
|
||||
Plk: "394د56 66",
|
||||
PrdDte: "1389",
|
||||
PrntCmpDocNo: "1110/111130/404/003412",
|
||||
ShsNum: "NAAP41FD5BJ301065",
|
||||
TypPlcy: "بيمه ثالث و حوادث",
|
||||
UsgCod: "8",
|
||||
VIN: "IRFC891V7D2301065",
|
||||
VehSysCod: "2",
|
||||
},
|
||||
error: null,
|
||||
},
|
||||
mapped: {
|
||||
nationalCode: "0651828562",
|
||||
plk1: "56",
|
||||
plk2: "د",
|
||||
plk3: "394",
|
||||
plksrl: "66",
|
||||
CarGrpCod: "2",
|
||||
CmpCod: "8",
|
||||
CmpNam: "بيمه پارسيان",
|
||||
DisFnYrPrcnt: "35",
|
||||
DisLfYrPrcnt: "35",
|
||||
DisPrsnYrPrcnt: "70",
|
||||
FndCst: "0.00",
|
||||
HBgnDte: "1404/10/23",
|
||||
HEndDte: "1405/10/23",
|
||||
HIsuDte: "1404/10/21",
|
||||
InsNam: "روح اله حيدري",
|
||||
LastCmpCod: "8",
|
||||
LastCmpDocNo: "1110/111130/403/002650",
|
||||
MtrNum: "13389030167",
|
||||
NtnlId: "0651828562",
|
||||
PlcyUnqCod: "16018466143",
|
||||
Plk: "394د56 66",
|
||||
PrdDte: "1389",
|
||||
PrntCmpDocNo: "1110/111130/404/003412",
|
||||
ShsNum: "NAAP41FD5BJ301065",
|
||||
TypPlcy: "بيمه ثالث و حوادث",
|
||||
UsgCod: "8",
|
||||
VIN: "IRFC891V7D2301065",
|
||||
VehSysCod: "2",
|
||||
companyId: "8",
|
||||
companyPersianName: "بيمه پارسيان",
|
||||
carGrpCod: "2",
|
||||
usgCod: "8",
|
||||
vehSysCod: "2",
|
||||
mtrnum: "13389030167",
|
||||
shsNam: "NAAP41FD5BJ301065",
|
||||
vin: "IRFC891V7D2301065",
|
||||
ntnlId: "0651828562",
|
||||
Name: "روح اله حيدري",
|
||||
ThirdPolicyCode: "16018466143",
|
||||
LastCompanyDocumentNumber: "1110/111130/403/002650",
|
||||
IssueDate: "1404/10/21",
|
||||
StartDate: "1404/10/23",
|
||||
EndDate: "1405/10/23",
|
||||
CompanyCode: "8",
|
||||
CompanyName: "بيمه پارسيان",
|
||||
UsageField: "شخصی",
|
||||
FinancialCvrCptl: "0",
|
||||
PrntPlcyCmpDocNo: "1110/111130/404/003412",
|
||||
VinNumberField: "IRFC891V7D2301065",
|
||||
ChassisNumberField: "NAAP41FD5BJ301065",
|
||||
EngineNumberField: "13389030167",
|
||||
UsageCode: "8",
|
||||
VehicleSystemCode: "2",
|
||||
CarGroupCode: "2",
|
||||
InsuranceFullName: "روح اله حيدري",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
clientKey: "parsian",
|
||||
inquiryType: "thirdPartyPlate",
|
||||
nationalCode: "0021557985",
|
||||
plate: {
|
||||
leftDigits: "77",
|
||||
centerAlphabet: "ه",
|
||||
centerDigits: "339",
|
||||
ir: "20",
|
||||
},
|
||||
plateId: "20-77-ه-339",
|
||||
label: "سيدنويد صالحي — Parsian SECOND party (offline seed)",
|
||||
fanavaranDriverId: 2426953,
|
||||
person: {
|
||||
driverBirthday: "13770819",
|
||||
insurerBirthday: 13770819,
|
||||
driverIsInsurer: true,
|
||||
nationalCodeOfDriver: "0021557985",
|
||||
nationalCodeOfInsurer: "0021557985",
|
||||
phoneNumber: "09912536917",
|
||||
},
|
||||
insurance: {
|
||||
coverages: [],
|
||||
policyNumber: null,
|
||||
company: "بيمه پارسيان",
|
||||
financialCeiling: "0",
|
||||
startDate: "1405/02/29",
|
||||
endDate: "1406/02/29",
|
||||
},
|
||||
inquiry: {
|
||||
source: "OFFLINE_SEED_INQUIRY",
|
||||
raw: {
|
||||
success: true,
|
||||
provider: "PARSIAN",
|
||||
trackingCode: "OFFLINE-PARSIAN-0021557985",
|
||||
message: "offline seeded policy by plate",
|
||||
duration: 0,
|
||||
data: {
|
||||
nationalCode: "0021557985",
|
||||
plk1: "77",
|
||||
plk2: "ه",
|
||||
plk3: "339",
|
||||
plksrl: "20",
|
||||
CarGrpCod: "2",
|
||||
CmpCod: "8",
|
||||
CmpNam: "بيمه پارسيان",
|
||||
DisFnYrPrcnt: "15",
|
||||
DisLfYrPrcnt: "15",
|
||||
DisPrsnYrPrcnt: "15",
|
||||
FndCst: "716122.00",
|
||||
HBgnDte: "1405/02/29",
|
||||
HEndDte: "1406/02/29",
|
||||
HIsuDte: "1405/02/29",
|
||||
InsNam: "سيدنويد صالحي",
|
||||
LastCmpCod: "8",
|
||||
LastCmpDocNo: "1110/111130/404/000575",
|
||||
MtrNum: "M159895611",
|
||||
NtnlId: "0021557985",
|
||||
PlcyUnqCod: "16035590431",
|
||||
Plk: "339ه77 20",
|
||||
PrdDte: "1402",
|
||||
PrntCmpDocNo: "1110/111130/405/001098",
|
||||
ShsNum: "NAPX212AAP1173773",
|
||||
TypPlcy: "بيمه ثالث و حوادث",
|
||||
UsgCod: "8",
|
||||
VIN: "NAPX212AAP1173773",
|
||||
VehSysCod: "315",
|
||||
},
|
||||
error: null,
|
||||
},
|
||||
mapped: {
|
||||
nationalCode: "0021557985",
|
||||
plk1: "77",
|
||||
plk2: "ه",
|
||||
plk3: "339",
|
||||
plksrl: "20",
|
||||
CarGrpCod: "2",
|
||||
CmpCod: "8",
|
||||
CmpNam: "بيمه پارسيان",
|
||||
DisFnYrPrcnt: "15",
|
||||
DisLfYrPrcnt: "15",
|
||||
DisPrsnYrPrcnt: "15",
|
||||
FndCst: "716122.00",
|
||||
HBgnDte: "1405/02/29",
|
||||
HEndDte: "1406/02/29",
|
||||
HIsuDte: "1405/02/29",
|
||||
InsNam: "سيدنويد صالحي",
|
||||
LastCmpCod: "8",
|
||||
LastCmpDocNo: "1110/111130/404/000575",
|
||||
MtrNum: "M159895611",
|
||||
NtnlId: "0021557985",
|
||||
PlcyUnqCod: "16035590431",
|
||||
Plk: "339ه77 20",
|
||||
PrdDte: "1402",
|
||||
PrntCmpDocNo: "1110/111130/405/001098",
|
||||
ShsNum: "NAPX212AAP1173773",
|
||||
TypPlcy: "بيمه ثالث و حوادث",
|
||||
UsgCod: "8",
|
||||
VIN: "NAPX212AAP1173773",
|
||||
VehSysCod: "315",
|
||||
companyId: "8",
|
||||
companyPersianName: "بيمه پارسيان",
|
||||
carGrpCod: "2",
|
||||
usgCod: "8",
|
||||
vehSysCod: "315",
|
||||
mtrnum: "M159895611",
|
||||
shsNam: "NAPX212AAP1173773",
|
||||
vin: "NAPX212AAP1173773",
|
||||
ntnlId: "0021557985",
|
||||
Name: "سيدنويد صالحي",
|
||||
ThirdPolicyCode: "16035590431",
|
||||
LastCompanyDocumentNumber: "1110/111130/404/000575",
|
||||
IssueDate: "1405/02/29",
|
||||
StartDate: "1405/02/29",
|
||||
EndDate: "1406/02/29",
|
||||
CompanyCode: "8",
|
||||
CompanyName: "بيمه پارسيان",
|
||||
UsageField: "شخصی",
|
||||
FinancialCvrCptl: "0",
|
||||
PrntPlcyCmpDocNo: "1110/111130/405/001098",
|
||||
VinNumberField: "NAPX212AAP1173773",
|
||||
ChassisNumberField: "NAPX212AAP1173773",
|
||||
EngineNumberField: "M159895611",
|
||||
UsageCode: "8",
|
||||
VehicleSystemCode: "315",
|
||||
CarGroupCode: "2",
|
||||
InsuranceFullName: "سيدنويد صالحي",
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
154
src/offline-inquiry/offline-inquiry.service.ts
Normal file
154
src/offline-inquiry/offline-inquiry.service.ts
Normal file
@@ -0,0 +1,154 @@
|
||||
import { Injectable, Logger, OnModuleInit } from "@nestjs/common";
|
||||
import { InjectModel } from "@nestjs/mongoose";
|
||||
import { Model } from "mongoose";
|
||||
import {
|
||||
resolveFanavaranClientKey,
|
||||
type FanavaranClientKey,
|
||||
} from "src/core/config/fanavaran-client.config";
|
||||
import { SystemSettingsService } from "src/system-settings/system-settings.service";
|
||||
import { OFFLINE_INQUIRY_SEEDS } from "./offline-inquiry.seeds";
|
||||
import {
|
||||
OfflineInquiry,
|
||||
OfflineInquiryDocument,
|
||||
} from "./schema/offline-inquiry.schema";
|
||||
|
||||
export interface OfflinePlateLookupInput {
|
||||
nationalCode: string;
|
||||
leftDigits: string;
|
||||
centerAlphabet: string;
|
||||
centerDigits: string;
|
||||
ir: string;
|
||||
clientKey?: FanavaranClientKey;
|
||||
}
|
||||
|
||||
export interface OfflinePlateInquiryHit {
|
||||
raw: Record<string, unknown>;
|
||||
mapped: Record<string, unknown>;
|
||||
source: string;
|
||||
fanavaranDriverId?: number;
|
||||
insurance?: Record<string, unknown>;
|
||||
person?: Record<string, unknown>;
|
||||
plateId?: string;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class OfflineInquiryService implements OnModuleInit {
|
||||
private readonly logger = new Logger(OfflineInquiryService.name);
|
||||
|
||||
constructor(
|
||||
@InjectModel(OfflineInquiry.name)
|
||||
private readonly offlineInquiryModel: Model<OfflineInquiryDocument>,
|
||||
private readonly systemSettingsService: SystemSettingsService,
|
||||
) {}
|
||||
|
||||
async onModuleInit(): Promise<void> {
|
||||
await this.seedMissing();
|
||||
}
|
||||
|
||||
/** Normalize Persian plate letter (strip ZWNJ / Arabic variants). */
|
||||
static normalizePlateAlphabet(value: string): string {
|
||||
return String(value ?? "")
|
||||
.replace(/[\u200c\u200d\uFEFF]/g, "")
|
||||
.replace(/ي/g, "ی")
|
||||
.replace(/ك/g, "ک")
|
||||
.trim();
|
||||
}
|
||||
|
||||
static normalizeDigits(value: string | number): string {
|
||||
return String(value ?? "").trim();
|
||||
}
|
||||
|
||||
async seedMissing(): Promise<void> {
|
||||
for (const seed of OFFLINE_INQUIRY_SEEDS) {
|
||||
const plate = {
|
||||
leftDigits: OfflineInquiryService.normalizeDigits(seed.plate.leftDigits),
|
||||
centerAlphabet: OfflineInquiryService.normalizePlateAlphabet(
|
||||
seed.plate.centerAlphabet,
|
||||
),
|
||||
centerDigits: OfflineInquiryService.normalizeDigits(
|
||||
seed.plate.centerDigits,
|
||||
),
|
||||
ir: OfflineInquiryService.normalizeDigits(seed.plate.ir),
|
||||
};
|
||||
const existing = await this.offlineInquiryModel
|
||||
.findOne({
|
||||
clientKey: seed.clientKey,
|
||||
inquiryType: seed.inquiryType,
|
||||
nationalCode: seed.nationalCode,
|
||||
"plate.leftDigits": plate.leftDigits,
|
||||
"plate.centerAlphabet": plate.centerAlphabet,
|
||||
"plate.centerDigits": plate.centerDigits,
|
||||
"plate.ir": plate.ir,
|
||||
})
|
||||
.select({ _id: 1 })
|
||||
.lean()
|
||||
.exec();
|
||||
if (existing) {
|
||||
continue;
|
||||
}
|
||||
await this.offlineInquiryModel.create({
|
||||
...seed,
|
||||
plate,
|
||||
enabled: true,
|
||||
});
|
||||
this.logger.log(
|
||||
`Seeded offlineInquiry ${seed.clientKey} ${seed.nationalCode} plate=${seed.plateId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async findPlateInquiry(
|
||||
input: OfflinePlateLookupInput,
|
||||
): Promise<OfflinePlateInquiryHit | null> {
|
||||
if (!(await this.systemSettingsService.isOfflineInquiryEnabled())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const clientKey = input.clientKey ?? resolveFanavaranClientKey();
|
||||
const nationalCode = OfflineInquiryService.normalizeDigits(
|
||||
input.nationalCode,
|
||||
);
|
||||
const leftDigits = OfflineInquiryService.normalizeDigits(input.leftDigits);
|
||||
const centerAlphabet = OfflineInquiryService.normalizePlateAlphabet(
|
||||
input.centerAlphabet,
|
||||
);
|
||||
const centerDigits = OfflineInquiryService.normalizeDigits(
|
||||
input.centerDigits,
|
||||
);
|
||||
const ir = OfflineInquiryService.normalizeDigits(input.ir);
|
||||
|
||||
const doc = await this.offlineInquiryModel
|
||||
.findOne({
|
||||
clientKey,
|
||||
inquiryType: "thirdPartyPlate",
|
||||
nationalCode,
|
||||
enabled: true,
|
||||
"plate.leftDigits": leftDigits,
|
||||
"plate.centerAlphabet": centerAlphabet,
|
||||
"plate.centerDigits": centerDigits,
|
||||
"plate.ir": ir,
|
||||
})
|
||||
.lean()
|
||||
.exec();
|
||||
|
||||
if (!doc?.inquiry?.raw || !doc?.inquiry?.mapped) {
|
||||
return null;
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`[OFFLINE] Hit plate inquiry client=${clientKey} nationalCode=${nationalCode} plate=${doc.plateId ?? `${ir}-${leftDigits}-${centerAlphabet}-${centerDigits}`}`,
|
||||
);
|
||||
|
||||
return {
|
||||
raw: doc.inquiry.raw as Record<string, unknown>,
|
||||
mapped: doc.inquiry.mapped as Record<string, unknown>,
|
||||
source: doc.inquiry.source || "OFFLINE_SEED_INQUIRY",
|
||||
fanavaranDriverId: doc.fanavaranDriverId,
|
||||
insurance: doc.insurance,
|
||||
person: doc.person,
|
||||
plateId: doc.plateId,
|
||||
label: doc.label,
|
||||
};
|
||||
}
|
||||
}
|
||||
85
src/offline-inquiry/schema/offline-inquiry.schema.ts
Normal file
85
src/offline-inquiry/schema/offline-inquiry.schema.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
|
||||
import { HydratedDocument } from "mongoose";
|
||||
import type { FanavaranClientKey } from "src/core/config/fanavaran-client.config";
|
||||
|
||||
@Schema({ _id: false })
|
||||
export class OfflineInquiryPlateParts {
|
||||
@Prop({ type: String, required: true })
|
||||
leftDigits: string;
|
||||
|
||||
@Prop({ type: String, required: true })
|
||||
centerAlphabet: string;
|
||||
|
||||
@Prop({ type: String, required: true })
|
||||
centerDigits: string;
|
||||
|
||||
@Prop({ type: String, required: true })
|
||||
ir: string;
|
||||
}
|
||||
export const OfflineInquiryPlatePartsSchema = SchemaFactory.createForClass(
|
||||
OfflineInquiryPlateParts,
|
||||
);
|
||||
|
||||
/**
|
||||
* Captured plate/policy inquiry (+ optional Fanavaran driver id) for offline use
|
||||
* when live ESG/Tejarat inquiry is unavailable (e.g. Parsian off their LAN).
|
||||
*/
|
||||
@Schema({ collection: "offlineInquiries", timestamps: true })
|
||||
export class OfflineInquiry {
|
||||
@Prop({ type: String, required: true, index: true })
|
||||
clientKey: FanavaranClientKey;
|
||||
|
||||
/** thirdPartyPlate | personalIdentity (extensible). */
|
||||
@Prop({ type: String, required: true, index: true, default: "thirdPartyPlate" })
|
||||
inquiryType: string;
|
||||
|
||||
@Prop({ type: String, required: true, index: true })
|
||||
nationalCode: string;
|
||||
|
||||
@Prop({ type: OfflineInquiryPlatePartsSchema, required: false })
|
||||
plate?: OfflineInquiryPlateParts;
|
||||
|
||||
/** Human plate id e.g. 66-56-د-394 */
|
||||
@Prop({ type: String, required: false })
|
||||
plateId?: string;
|
||||
|
||||
@Prop({ type: String, required: false })
|
||||
label?: string;
|
||||
|
||||
@Prop({ type: Object, required: true })
|
||||
inquiry: {
|
||||
source: string;
|
||||
raw: Record<string, unknown>;
|
||||
mapped: Record<string, unknown>;
|
||||
};
|
||||
|
||||
@Prop({ type: Object, required: false })
|
||||
insurance?: Record<string, unknown>;
|
||||
|
||||
/** Cached Fanavaran DriverId from parties inquiry-by-unique-identifier. */
|
||||
@Prop({ type: Number, required: false })
|
||||
fanavaranDriverId?: number;
|
||||
|
||||
@Prop({ type: Object, required: false })
|
||||
person?: Record<string, unknown>;
|
||||
|
||||
@Prop({ type: Boolean, default: true })
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
export type OfflineInquiryDocument = HydratedDocument<OfflineInquiry>;
|
||||
export const OfflineInquirySchema =
|
||||
SchemaFactory.createForClass(OfflineInquiry);
|
||||
|
||||
OfflineInquirySchema.index(
|
||||
{
|
||||
clientKey: 1,
|
||||
inquiryType: 1,
|
||||
nationalCode: 1,
|
||||
"plate.leftDigits": 1,
|
||||
"plate.centerAlphabet": 1,
|
||||
"plate.centerDigits": 1,
|
||||
"plate.ir": 1,
|
||||
},
|
||||
{ unique: true, sparse: true },
|
||||
);
|
||||
Reference in New Issue
Block a user