Files
yara724api/src/offline-inquiry/offline-inquiry.service.ts

155 lines
4.6 KiB
TypeScript

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,
};
}
}