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; mapped: Record; source: string; fanavaranDriverId?: number; insurance?: Record; person?: Record; 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, private readonly systemSettingsService: SystemSettingsService, ) {} async onModuleInit(): Promise { 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 { 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 { 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, mapped: doc.inquiry.mapped as Record, source: doc.inquiry.source || "OFFLINE_SEED_INQUIRY", fanavaranDriverId: doc.fanavaranDriverId, insurance: doc.insurance, person: doc.person, plateId: doc.plateId, label: doc.label, }; } }