forked from Yara724/api
Merge pull request 'update the fanavaran which accepts unknown person too' (#313) from s.hajizadeh/yara724api:main into main
Reviewed-on: Yara724/api#313
This commit is contained in:
@@ -185,7 +185,10 @@ import {
|
||||
import { FanavaranAuditService } from "src/fanavaran/fanavaran-audit.service";
|
||||
import { FanavaranAuthService } from "src/fanavaran/fanavaran-auth.service";
|
||||
import { FanavaranLookupService } from "src/fanavaran/fanavaran-lookup.service";
|
||||
import { FANAVARAN_REMOTE_LOOKUPS } from "src/fanavaran/fanavaran-lookup.config";
|
||||
import {
|
||||
FANAVARAN_LOOKUP_BASE_URL,
|
||||
FANAVARAN_REMOTE_LOOKUPS,
|
||||
} from "src/fanavaran/fanavaran-lookup.config";
|
||||
import type { FanavaranAuditSession } from "src/fanavaran/fanavaran-audit.types";
|
||||
import {
|
||||
FanavaranAuditSource,
|
||||
@@ -207,6 +210,10 @@ import {
|
||||
pickPersonNationalCode,
|
||||
selectFanavaranDriverId,
|
||||
} from "./fanavaran-driver-inquiry";
|
||||
import {
|
||||
buildFanavaranOtherPeoplePayload,
|
||||
pickFanavaranRecordId,
|
||||
} from "./fanavaran-other-people";
|
||||
import {
|
||||
asObjectRecord,
|
||||
collectPartyVinCandidates,
|
||||
@@ -4127,6 +4134,8 @@ export class ClaimRequestManagementService {
|
||||
DriverIsOwner: number;
|
||||
FaultPercent: number;
|
||||
};
|
||||
/** GEN.44 write: register the damaged party in Fanavaran when inquiry misses. */
|
||||
registerMissingPerson?: boolean;
|
||||
}): Promise<Record<string, unknown>> {
|
||||
const damagedParty = this.pickDamagedPartyForFanavaran(
|
||||
input.blameCase,
|
||||
@@ -4288,9 +4297,11 @@ export class ClaimRequestManagementService {
|
||||
// Prefer claim-level cache, then party.person.fanavaranDriverId, then live inquiry, then last payload
|
||||
let driverFanavaranId = this.firstPositiveFanavaranId(
|
||||
cachedDamage.driverId,
|
||||
cachedDamage.otherPersonId,
|
||||
person.fanavaranDriverId,
|
||||
lastDamagePayload?.DriverId,
|
||||
);
|
||||
let otherPersonId = this.firstPositiveFanavaranId(cachedDamage.otherPersonId);
|
||||
|
||||
if (driverFanavaranId) {
|
||||
this.logger.log(
|
||||
@@ -4304,6 +4315,20 @@ export class ClaimRequestManagementService {
|
||||
input.claimCase,
|
||||
);
|
||||
|
||||
if (!driverFanavaranId && input.registerMissingPerson) {
|
||||
const registered = await this.registerFanavaranOtherPerson({
|
||||
clientKey: input.clientKey,
|
||||
person,
|
||||
claimCaseId: input.claimCase?._id
|
||||
? String(input.claimCase._id)
|
||||
: undefined,
|
||||
});
|
||||
if (registered != null) {
|
||||
driverFanavaranId = registered;
|
||||
otherPersonId = registered;
|
||||
}
|
||||
}
|
||||
|
||||
// Persist the resolved ID back to the blame case party for future use
|
||||
if (driverFanavaranId && input.blameCase?._id && damagedParty) {
|
||||
const partyIndex = (input.blameCase.parties ?? []).findIndex(
|
||||
@@ -4473,6 +4498,9 @@ export class ClaimRequestManagementService {
|
||||
...(driverFanavaranId != null
|
||||
? { "fanavaranSync.damageCase.driverId": driverFanavaranId }
|
||||
: {}),
|
||||
...(otherPersonId != null
|
||||
? { "fanavaranSync.damageCase.otherPersonId": otherPersonId }
|
||||
: {}),
|
||||
...(vehicleKindId != null
|
||||
? { "fanavaranSync.damageCase.vehicleKindId": vehicleKindId }
|
||||
: {}),
|
||||
@@ -5075,6 +5103,158 @@ export class ClaimRequestManagementService {
|
||||
return null;
|
||||
}
|
||||
|
||||
private async loadFanavaranOtherPeopleLookups(
|
||||
clientKey: FanavaranClientKey,
|
||||
): Promise<{
|
||||
cities: unknown[];
|
||||
gender: unknown[];
|
||||
maritalStatus: unknown[];
|
||||
ans: unknown[];
|
||||
countries: unknown[];
|
||||
personKind: unknown[];
|
||||
}> {
|
||||
const [cities, gender, maritalStatus, ans, countries, personKind] =
|
||||
await Promise.all([
|
||||
this.getFanavaranLookupRows(clientKey, "cities"),
|
||||
this.getFanavaranLookupRows(clientKey, "gender"),
|
||||
this.getFanavaranLookupRows(clientKey, "marital-status"),
|
||||
this.getFanavaranLookupRows(clientKey, "ans"),
|
||||
this.getFanavaranLookupRows(clientKey, "countries"),
|
||||
this.getFanavaranLookupRows(clientKey, "person-kind"),
|
||||
]);
|
||||
return { cities, gender, maritalStatus, ans, countries, personKind };
|
||||
}
|
||||
|
||||
/**
|
||||
* GEN.44: create the damaged party in Fanavaran when parties inquiry has no row.
|
||||
* Failures stay best-effort — GEN.12 submit continues without DriverId.
|
||||
*/
|
||||
private async registerFanavaranOtherPerson(input: {
|
||||
clientKey: FanavaranClientKey;
|
||||
person: any;
|
||||
claimCaseId?: string;
|
||||
}): Promise<number | null> {
|
||||
const nationalCode = pickPersonNationalCode(input.person);
|
||||
const birthday = pickPersonBirthday(input.person);
|
||||
if (!nationalCode || !parseJalaliDateParts(birthday)) {
|
||||
this.logger.warn(
|
||||
`[registerFanavaranOtherPerson] SKIP: missing nationalCode or birthday`,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
let user: {
|
||||
fullName?: string;
|
||||
mobile?: string;
|
||||
city?: string;
|
||||
address?: string;
|
||||
gender?: string;
|
||||
} | null = null;
|
||||
const userId = input.person?.userId;
|
||||
if (userId && Types.ObjectId.isValid(String(userId))) {
|
||||
try {
|
||||
user = await this.userDbService.findOne({
|
||||
_id: new Types.ObjectId(String(userId)),
|
||||
});
|
||||
} catch (error) {
|
||||
this.logger.warn(
|
||||
`[registerFanavaranOtherPerson] user lookup failed: ${
|
||||
error instanceof Error ? error.message : error
|
||||
}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const lookups = await this.loadFanavaranOtherPeopleLookups(input.clientKey);
|
||||
const payload = buildFanavaranOtherPeoplePayload(
|
||||
{
|
||||
nationalCode,
|
||||
birthday,
|
||||
fullName: input.person?.fullName ?? user?.fullName,
|
||||
mobile: input.person?.phoneNumber ?? user?.mobile,
|
||||
address: user?.address,
|
||||
cityName: user?.city,
|
||||
gender: user?.gender,
|
||||
},
|
||||
lookups,
|
||||
);
|
||||
if (!payload) {
|
||||
this.logger.warn(
|
||||
`[registerFanavaranOtherPerson] SKIP: could not build GEN.44 payload for nationalCode=${nationalCode}`,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
const url = `${FANAVARAN_LOOKUP_BASE_URL}/common/other-people`;
|
||||
const startedAt = Date.now();
|
||||
const auditSession: FanavaranAuditSession = {
|
||||
trackingCode: this.fanavaranAuditService.generateTrackingCode(),
|
||||
clientKey: input.clientKey,
|
||||
source: FanavaranAuditSource.AUTO_SUBMIT,
|
||||
claimCaseId: input.claimCaseId,
|
||||
};
|
||||
|
||||
await this.fanavaranAuditService.recordStep({
|
||||
session: auditSession,
|
||||
step: FanavaranAuditStep.CREATE_OTHER_PERSON,
|
||||
status: FanavaranAuditStatus.STARTED,
|
||||
requestUrl: url,
|
||||
requestMethod: "POST",
|
||||
requestBody: payload,
|
||||
requestMeta: { nationalCode },
|
||||
});
|
||||
|
||||
try {
|
||||
const created = await this.fanavaranLookupService.createOtherPerson(
|
||||
input.clientKey,
|
||||
payload,
|
||||
);
|
||||
const personId = pickFanavaranRecordId(created);
|
||||
await this.fanavaranAuditService.recordStep({
|
||||
session: auditSession,
|
||||
step: FanavaranAuditStep.CREATE_OTHER_PERSON,
|
||||
status: FanavaranAuditStatus.SUCCESS,
|
||||
requestUrl: url,
|
||||
requestMethod: "POST",
|
||||
requestBody: payload,
|
||||
responseBody: created,
|
||||
responseMeta: { otherPersonId: personId, nationalCode },
|
||||
durationMs: Date.now() - startedAt,
|
||||
});
|
||||
if (personId != null) {
|
||||
this.logger.log(
|
||||
`[registerFanavaranOtherPerson] CREATED Id=${personId} for nationalCode=${nationalCode}`,
|
||||
);
|
||||
return personId;
|
||||
}
|
||||
this.logger.warn(
|
||||
`[registerFanavaranOtherPerson] create returned no Id for nationalCode=${nationalCode}; retrying inquiry`,
|
||||
);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
await this.fanavaranAuditService.recordStep({
|
||||
session: auditSession,
|
||||
step: FanavaranAuditStep.CREATE_OTHER_PERSON,
|
||||
status: FanavaranAuditStatus.FAILURE,
|
||||
requestUrl: url,
|
||||
requestMethod: "POST",
|
||||
requestBody: payload,
|
||||
errorMessage: message,
|
||||
durationMs: Date.now() - startedAt,
|
||||
});
|
||||
this.logger.warn(
|
||||
`[registerFanavaranOtherPerson] CREATE failed for nationalCode=${nationalCode}: ${message}; retrying inquiry`,
|
||||
);
|
||||
}
|
||||
|
||||
return this.resolveDriverFanavaranId(
|
||||
input.clientKey,
|
||||
nationalCode,
|
||||
birthday,
|
||||
input.person?.driverIsInsurer,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* زیاندیده for GEN.12: claim owner when linked to a blame party,
|
||||
* else the party that is not the guilty one.
|
||||
@@ -5521,6 +5701,16 @@ export class ClaimRequestManagementService {
|
||||
defaults: profile.defaults,
|
||||
});
|
||||
|
||||
const warnings: string[] = [];
|
||||
if (!claimCase.claimId) {
|
||||
warnings.push("Fanavaran base claimId is required before submit.");
|
||||
}
|
||||
if (payload.DriverId == null) {
|
||||
warnings.push(
|
||||
"Damaged party is not in Fanavaran yet; submit will register them via other-people (GEN.44).",
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
clientKey,
|
||||
claimCaseId,
|
||||
@@ -5529,9 +5719,7 @@ export class ClaimRequestManagementService {
|
||||
submitUrl: claimCase.claimId
|
||||
? `${this.FANAVARAN_SUBMIT_URL}/${claimCase.claimId}/dmg-cases`
|
||||
: null,
|
||||
warning: claimCase.claimId
|
||||
? undefined
|
||||
: "Fanavaran base claimId is required before submit.",
|
||||
warning: warnings.length ? warnings.join(" ") : undefined,
|
||||
payload,
|
||||
};
|
||||
}
|
||||
@@ -7021,6 +7209,7 @@ export class ClaimRequestManagementService {
|
||||
selectedParts: selectedParts ?? claimCase.damage?.selectedParts ?? [],
|
||||
clientKey,
|
||||
defaults: profile.defaults,
|
||||
registerMissingPerson: true,
|
||||
});
|
||||
|
||||
// Guardrail: never send empty/null licence fields even if manual overrides arrive.
|
||||
|
||||
@@ -89,6 +89,10 @@ export class FanavaranSyncStage {
|
||||
@Prop({ type: Number })
|
||||
driverId?: number;
|
||||
|
||||
/** Cached GEN.44 other-people Id when the person was created because inquiry missed. */
|
||||
@Prop({ type: Number })
|
||||
otherPersonId?: number;
|
||||
|
||||
/** Cached Fanavaran VehicleKindId. */
|
||||
@Prop({ type: Number })
|
||||
vehicleKindId?: number;
|
||||
|
||||
80
src/claim-request-management/fanavaran-other-people.spec.ts
Normal file
80
src/claim-request-management/fanavaran-other-people.spec.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import {
|
||||
buildFanavaranOtherPeoplePayload,
|
||||
pickFanavaranRecordId,
|
||||
pickLookupIdByCaption,
|
||||
splitPersianFullName,
|
||||
} from "./fanavaran-other-people";
|
||||
|
||||
describe("fanavaran other people (GEN.44)", () => {
|
||||
it("builds a civil-registry create payload from local case + user fields", () => {
|
||||
const payload = buildFanavaranOtherPeoplePayload(
|
||||
{
|
||||
nationalCode: "3392645966",
|
||||
birthday: "1364/09/01",
|
||||
fullName: "الهام مهدوی نیا",
|
||||
mobile: "9366666666",
|
||||
address: "گاندی ک نهم",
|
||||
cityName: "تهران",
|
||||
gender: "female",
|
||||
},
|
||||
{
|
||||
cities: [{ Id: 9131, Caption: "تهران", IsActive: 1 }],
|
||||
gender: [
|
||||
{ Id: 26, Caption: "مرد", IsActive: 1 },
|
||||
{ Id: 27, Caption: "زن", IsActive: 1 },
|
||||
],
|
||||
ans: [
|
||||
{ Id: 1, Caption: "بله", IsActive: 1 },
|
||||
{ Id: 0, Caption: "خیر", IsActive: 1 },
|
||||
],
|
||||
personKind: [{ Id: 46, Caption: "حقیقی", IsActive: 1 }],
|
||||
},
|
||||
);
|
||||
|
||||
expect(payload).toMatchObject({
|
||||
NationalCode: "3392645966",
|
||||
Name: "الهام",
|
||||
LastName: "مهدوی نیا",
|
||||
BirthYear: 1364,
|
||||
BirthMonth: 9,
|
||||
BirthDay: 1,
|
||||
Mobile: "09366666666",
|
||||
Address: "گاندی ک نهم",
|
||||
CityId: 9131,
|
||||
GenderId: 27,
|
||||
IsIranian: 1,
|
||||
PersonKindId: 46,
|
||||
ADBirthYear: null,
|
||||
NationalityId: null,
|
||||
});
|
||||
});
|
||||
|
||||
it("returns null when national code or birthday is missing", () => {
|
||||
expect(
|
||||
buildFanavaranOtherPeoplePayload({
|
||||
nationalCode: "3392645966",
|
||||
}),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("picks the created person Id from a wrapped Fanavaran response", () => {
|
||||
expect(pickFanavaranRecordId({ data: { Id: 4553876 } })).toBe(4553876);
|
||||
expect(pickFanavaranRecordId([{ Id: "4553876" }])).toBe(4553876);
|
||||
});
|
||||
|
||||
it("matches lookup captions ignoring yeh/keheh and extra spaces", () => {
|
||||
expect(
|
||||
pickLookupIdByCaption(
|
||||
[{ Id: 701, Caption: "ايران", IsActive: 1 }],
|
||||
["ایران"],
|
||||
),
|
||||
).toBe(701);
|
||||
});
|
||||
|
||||
it("splits a Persian full name into first and last name", () => {
|
||||
expect(splitPersianFullName("الهام مهدوی نیا")).toEqual({
|
||||
name: "الهام",
|
||||
lastName: "مهدوی نیا",
|
||||
});
|
||||
});
|
||||
});
|
||||
252
src/claim-request-management/fanavaran-other-people.ts
Normal file
252
src/claim-request-management/fanavaran-other-people.ts
Normal file
@@ -0,0 +1,252 @@
|
||||
import { toEnglishDigits } from "src/lookups/fanavaran-last-car-policy";
|
||||
import {
|
||||
parseJalaliDateParts,
|
||||
pickPersonBirthday,
|
||||
pickPersonNationalCode,
|
||||
} from "./fanavaran-driver-inquiry";
|
||||
|
||||
export type FanavaranLookupRow = {
|
||||
Id?: unknown;
|
||||
Caption?: unknown;
|
||||
Name?: unknown;
|
||||
IsActive?: unknown;
|
||||
};
|
||||
|
||||
export type FanavaranOtherPeopleLookups = {
|
||||
cities?: unknown;
|
||||
gender?: unknown;
|
||||
maritalStatus?: unknown;
|
||||
ans?: unknown;
|
||||
countries?: unknown;
|
||||
personKind?: unknown;
|
||||
};
|
||||
|
||||
export type FanavaranOtherPeopleSource = {
|
||||
nationalCode?: unknown;
|
||||
birthday?: unknown;
|
||||
fullName?: unknown;
|
||||
fatherName?: unknown;
|
||||
mobile?: unknown;
|
||||
tel?: unknown;
|
||||
email?: unknown;
|
||||
address?: unknown;
|
||||
jobAddress?: unknown;
|
||||
postalCode?: unknown;
|
||||
cityName?: unknown;
|
||||
gender?: unknown;
|
||||
isIranian?: boolean;
|
||||
naturalizedCode?: unknown;
|
||||
identityNo?: unknown;
|
||||
identityNoIssuPlace?: unknown;
|
||||
passportNo?: unknown;
|
||||
companyCode?: unknown;
|
||||
economicCode?: unknown;
|
||||
registerNo?: unknown;
|
||||
};
|
||||
|
||||
export type FanavaranOtherPeoplePayload = {
|
||||
NationalCode: string | null;
|
||||
Name: string | null;
|
||||
LastName: string | null;
|
||||
FatherName: string | null;
|
||||
BirthYear: number | null;
|
||||
BirthMonth: number | null;
|
||||
BirthDay: number | null;
|
||||
ADBirthYear: null;
|
||||
ADBirthMonth: null;
|
||||
ADBirthDay: null;
|
||||
IdentityNoIssuPlace: string | null;
|
||||
PassportNo: string | null;
|
||||
Address: string | null;
|
||||
PostalCode: string | null;
|
||||
Tel: string | null;
|
||||
Mobile: string | null;
|
||||
Email: string | null;
|
||||
JobAddress: string | null;
|
||||
EconomicCode: string | null;
|
||||
NaturalizedCode: string | null;
|
||||
CompanyCode: string | null;
|
||||
IdentityNo: string | null;
|
||||
RegisterNo: string | null;
|
||||
CityId: number | null;
|
||||
GenderId: number | null;
|
||||
MaritalStatus: number | null;
|
||||
IsIranian: number | string | null;
|
||||
NationalityId: number | null;
|
||||
PersonKindId: number | null;
|
||||
};
|
||||
|
||||
const GENDER_MALE_TEXTS = ["مرد", "آقا", "male", "m"];
|
||||
const GENDER_FEMALE_TEXTS = ["زن", "خانم", "female", "f"];
|
||||
const ANS_YES_TEXTS = ["بله", "بلی", "yes", "1"];
|
||||
const NATURAL_PERSON_TEXTS = ["حقیقی", "شخص حقیقی", "طبيعي", "natural"];
|
||||
const IRAN_COUNTRY_TEXTS = ["ایران", "ايران", "iran"];
|
||||
|
||||
function asLookupRows(value: unknown): FanavaranLookupRow[] {
|
||||
if (!Array.isArray(value)) return [];
|
||||
return value.filter(
|
||||
(row): row is FanavaranLookupRow =>
|
||||
!!row && typeof row === "object" && !Array.isArray(row),
|
||||
);
|
||||
}
|
||||
|
||||
function asPositiveId(value: unknown): number | null {
|
||||
if (value === null || value === undefined) return null;
|
||||
const id = Number(value);
|
||||
return Number.isFinite(id) && id > 0 ? id : null;
|
||||
}
|
||||
|
||||
function normalizeLookupText(value: unknown): string {
|
||||
return toEnglishDigits(value)
|
||||
.toLowerCase()
|
||||
.replace(/[ي]/g, "ی")
|
||||
.replace(/[ك]/g, "ک")
|
||||
.replace(/[\u200c\s_\-\/]+/g, "")
|
||||
.replace(/[^\p{L}\p{N}]/gu, "");
|
||||
}
|
||||
|
||||
function lookupRowId(row: FanavaranLookupRow): number | null {
|
||||
return asPositiveId(row.Id);
|
||||
}
|
||||
|
||||
function lookupRowTexts(row: FanavaranLookupRow): string[] {
|
||||
return [row.Caption, row.Name]
|
||||
.filter((value): value is string => typeof value === "string")
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
export function pickLookupIdByCaption(
|
||||
rows: unknown,
|
||||
texts: unknown[],
|
||||
): number | null {
|
||||
const wanted = texts
|
||||
.map((text) => normalizeLookupText(text))
|
||||
.filter(Boolean);
|
||||
if (!wanted.length) return null;
|
||||
|
||||
for (const row of asLookupRows(rows)) {
|
||||
if (row.IsActive === 0) continue;
|
||||
const rowTexts = lookupRowTexts(row).map(normalizeLookupText);
|
||||
const matched = rowTexts.some((rowText) =>
|
||||
wanted.some(
|
||||
(want) =>
|
||||
rowText === want || rowText.includes(want) || want.includes(rowText),
|
||||
),
|
||||
);
|
||||
if (matched) return lookupRowId(row);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function splitPersianFullName(
|
||||
fullName: unknown,
|
||||
): { name: string | null; lastName: string | null } {
|
||||
const trimmed = String(fullName ?? "").trim().replace(/\s+/g, " ");
|
||||
if (!trimmed) return { name: null, lastName: null };
|
||||
const parts = trimmed.split(" ");
|
||||
if (parts.length === 1) return { name: parts[0], lastName: null };
|
||||
return { name: parts[0], lastName: parts.slice(1).join(" ") };
|
||||
}
|
||||
|
||||
export function normalizeIranMobile(value: unknown): string | null {
|
||||
const digits = toEnglishDigits(value).replace(/\D/g, "");
|
||||
if (!digits) return null;
|
||||
if (digits.length === 10 && digits.startsWith("9")) return `0${digits}`;
|
||||
if (digits.length === 11 && digits.startsWith("09")) return digits;
|
||||
if (digits.length === 12 && digits.startsWith("989")) {
|
||||
return `0${digits.slice(2)}`;
|
||||
}
|
||||
return digits.length >= 8 ? digits : null;
|
||||
}
|
||||
|
||||
export function pickFanavaranRecordId(value: unknown): number | null {
|
||||
if (value == null) return null;
|
||||
if (typeof value === "number" || typeof value === "string") {
|
||||
return asPositiveId(value);
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
for (const item of value) {
|
||||
const id = pickFanavaranRecordId(item);
|
||||
if (id != null) return id;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (typeof value !== "object") return null;
|
||||
const record = value as Record<string, unknown>;
|
||||
const direct = asPositiveId(record.Id ?? record.id);
|
||||
if (direct != null) return direct;
|
||||
for (const key of ["value", "Value", "data", "Data", "item", "Item"]) {
|
||||
const nested = pickFanavaranRecordId(record[key]);
|
||||
if (nested != null) return nested;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function emptyToNull(value: unknown): string | null {
|
||||
if (value == null) return null;
|
||||
const text = String(value).trim();
|
||||
return text ? text : null;
|
||||
}
|
||||
|
||||
export function buildFanavaranOtherPeoplePayload(
|
||||
source: FanavaranOtherPeopleSource,
|
||||
lookups: FanavaranOtherPeopleLookups = {},
|
||||
): FanavaranOtherPeoplePayload | null {
|
||||
const nationalCode = pickPersonNationalCode({
|
||||
nationalCode: source.nationalCode,
|
||||
nationalCodeOfDriver: source.nationalCode,
|
||||
});
|
||||
const birthday = parseJalaliDateParts(
|
||||
source.birthday ?? pickPersonBirthday({ birthday: source.birthday }),
|
||||
);
|
||||
if (!nationalCode || !birthday) return null;
|
||||
|
||||
const isIranian =
|
||||
source.isIranian !== false && !emptyToNull(source.naturalizedCode);
|
||||
const names = splitPersianFullName(source.fullName);
|
||||
const genderTexts =
|
||||
source.gender === "male" || source.gender === "m"
|
||||
? GENDER_MALE_TEXTS
|
||||
: source.gender === "female" || source.gender === "f"
|
||||
? GENDER_FEMALE_TEXTS
|
||||
: [source.gender];
|
||||
|
||||
return {
|
||||
NationalCode: nationalCode,
|
||||
Name: names.name,
|
||||
LastName: names.lastName,
|
||||
FatherName: emptyToNull(source.fatherName),
|
||||
BirthYear: birthday.year,
|
||||
BirthMonth: birthday.month,
|
||||
BirthDay: birthday.day,
|
||||
ADBirthYear: null,
|
||||
ADBirthMonth: null,
|
||||
ADBirthDay: null,
|
||||
IdentityNoIssuPlace: emptyToNull(source.identityNoIssuPlace),
|
||||
PassportNo: emptyToNull(source.passportNo),
|
||||
Address: emptyToNull(source.address),
|
||||
PostalCode: emptyToNull(source.postalCode),
|
||||
Tel: normalizeIranMobile(source.tel) ?? emptyToNull(source.tel),
|
||||
Mobile: normalizeIranMobile(source.mobile),
|
||||
Email: emptyToNull(source.email),
|
||||
JobAddress: emptyToNull(source.jobAddress),
|
||||
EconomicCode: emptyToNull(source.economicCode),
|
||||
NaturalizedCode: emptyToNull(source.naturalizedCode),
|
||||
CompanyCode: emptyToNull(source.companyCode),
|
||||
IdentityNo: emptyToNull(source.identityNo),
|
||||
RegisterNo: emptyToNull(source.registerNo),
|
||||
CityId: pickLookupIdByCaption(lookups.cities, [source.cityName]),
|
||||
GenderId: pickLookupIdByCaption(lookups.gender, genderTexts),
|
||||
MaritalStatus: pickLookupIdByCaption(lookups.maritalStatus, []),
|
||||
IsIranian: isIranian
|
||||
? (pickLookupIdByCaption(lookups.ans, ANS_YES_TEXTS) ?? 1)
|
||||
: (pickLookupIdByCaption(lookups.ans, ["خیر", "no", "0"]) ?? 0),
|
||||
NationalityId: isIranian
|
||||
? null
|
||||
: pickLookupIdByCaption(lookups.countries, IRAN_COUNTRY_TEXTS),
|
||||
PersonKindId: pickLookupIdByCaption(
|
||||
lookups.personKind,
|
||||
NATURAL_PERSON_TEXTS,
|
||||
),
|
||||
};
|
||||
}
|
||||
@@ -117,6 +117,36 @@ export const FANAVARAN_REMOTE_LOOKUPS: FanavaranRemoteLookupDefinition[] = [
|
||||
url: `${FANAVARAN_LOOKUP_BASE_URL}/car/code-list/dmg-business-line`,
|
||||
cacheFile: "dmg-business-line.json",
|
||||
},
|
||||
{
|
||||
name: "gender",
|
||||
url: `${FANAVARAN_LOOKUP_BASE_URL}/common/code-list/gender`,
|
||||
cacheFile: "gender.json",
|
||||
},
|
||||
{
|
||||
name: "marital-status",
|
||||
url: `${FANAVARAN_LOOKUP_BASE_URL}/common/code-list/marital-status`,
|
||||
cacheFile: "marital-status.json",
|
||||
},
|
||||
{
|
||||
name: "ans",
|
||||
url: `${FANAVARAN_LOOKUP_BASE_URL}/common/code-list/ans`,
|
||||
cacheFile: "ans.json",
|
||||
},
|
||||
{
|
||||
name: "countries",
|
||||
url: `${FANAVARAN_LOOKUP_BASE_URL}/common/base-info/countries`,
|
||||
cacheFile: "countries.json",
|
||||
},
|
||||
{
|
||||
name: "person-kind",
|
||||
url: `${FANAVARAN_LOOKUP_BASE_URL}/common/code-list/person-kind`,
|
||||
cacheFile: "person-kind.json",
|
||||
},
|
||||
{
|
||||
name: "cii-validation-status",
|
||||
url: `${FANAVARAN_LOOKUP_BASE_URL}/common/code-list/cii-validation-status`,
|
||||
cacheFile: "cii-validation-status.json",
|
||||
},
|
||||
];
|
||||
|
||||
export const TEJARAT_STATIC_ACCIDENT_FILES = {
|
||||
|
||||
@@ -315,6 +315,63 @@ export class FanavaranLookupService {
|
||||
return this.fetchFromFanavaran(clientKey, url);
|
||||
}
|
||||
|
||||
async getOtherPerson(
|
||||
clientKey: FanavaranClientKey,
|
||||
personId: number,
|
||||
): Promise<unknown> {
|
||||
const url = `${FANAVARAN_LOOKUP_BASE_URL}/common/other-people/${personId}`;
|
||||
return this.fetchFromFanavaran(clientKey, url);
|
||||
}
|
||||
|
||||
async createOtherPerson(
|
||||
clientKey: FanavaranClientKey,
|
||||
payload: Record<string, unknown>,
|
||||
): Promise<unknown> {
|
||||
const url = `${FANAVARAN_LOOKUP_BASE_URL}/common/other-people`;
|
||||
return this.postToFanavaran(clientKey, url, payload);
|
||||
}
|
||||
|
||||
async postToFanavaran(
|
||||
clientKey: FanavaranClientKey,
|
||||
url: string,
|
||||
payload: Record<string, unknown>,
|
||||
): Promise<unknown> {
|
||||
try {
|
||||
const headers = await this.fanavaranAuthService.getRequestHeaders(
|
||||
clientKey,
|
||||
);
|
||||
|
||||
this.logger.log(`[${clientKey}] POST Fanavaran: ${url}`);
|
||||
|
||||
const response = await firstValueFrom(
|
||||
this.httpService.post(url, payload, {
|
||||
headers: {
|
||||
...headers,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
timeout: 20000,
|
||||
}),
|
||||
);
|
||||
|
||||
this.fanavaranAuthService.clearBackoff(clientKey);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
this.fanavaranAuthService.registerFailure(clientKey, error);
|
||||
const message = isAxiosError(error)
|
||||
? error.response?.data?.Message ||
|
||||
error.response?.data?.message ||
|
||||
error.message
|
||||
: error instanceof Error
|
||||
? error.message
|
||||
: "Fanavaran POST request failed";
|
||||
|
||||
this.logger.error(
|
||||
`Fanavaran POST failed for ${clientKey} (${url}): ${message}`,
|
||||
);
|
||||
throw new BadGatewayException(String(message));
|
||||
}
|
||||
}
|
||||
|
||||
async resolveInsuranceCorpId(
|
||||
clientKey: FanavaranClientKey,
|
||||
): Promise<number | null> {
|
||||
|
||||
@@ -8,6 +8,7 @@ export enum FanavaranAuditStep {
|
||||
POLICY_INQUIRY = "POLICY_INQUIRY",
|
||||
BUILD_PAYLOAD = "BUILD_PAYLOAD",
|
||||
SUBMIT_CLAIM = "SUBMIT_CLAIM",
|
||||
CREATE_OTHER_PERSON = "CREATE_OTHER_PERSON",
|
||||
SUBMIT_DAMAGE_CASE = "SUBMIT_DAMAGE_CASE",
|
||||
SUBMIT_ATTACHMENT = "SUBMIT_ATTACHMENT",
|
||||
SUBMIT_EXPERTISE = "SUBMIT_EXPERTISE",
|
||||
|
||||
@@ -327,6 +327,90 @@ export class LookupsController {
|
||||
return await this.lookupsService.getDmgBusinessLine();
|
||||
}
|
||||
|
||||
@Get("gender")
|
||||
@ApiOperation({
|
||||
summary: "Fanavaran GEN.44 gender lookup",
|
||||
description:
|
||||
"Returns values for other-people payload field GenderId from common/code-list/gender.",
|
||||
})
|
||||
@ApiOkResponse({
|
||||
description: "Returns Fanavaran gender lookup data",
|
||||
schema: { type: "array", items: { type: "object" } },
|
||||
})
|
||||
async getGender() {
|
||||
return await this.lookupsService.getGender();
|
||||
}
|
||||
|
||||
@Get("marital-status")
|
||||
@ApiOperation({
|
||||
summary: "Fanavaran GEN.44 marital status lookup",
|
||||
description:
|
||||
"Returns values for other-people payload field MaritalStatus from common/code-list/marital-status.",
|
||||
})
|
||||
@ApiOkResponse({
|
||||
description: "Returns Fanavaran marital status lookup data",
|
||||
schema: { type: "array", items: { type: "object" } },
|
||||
})
|
||||
async getMaritalStatus() {
|
||||
return await this.lookupsService.getMaritalStatus();
|
||||
}
|
||||
|
||||
@Get("ans")
|
||||
@ApiOperation({
|
||||
summary: "Fanavaran GEN.44 yes/no lookup",
|
||||
description:
|
||||
"Returns values for other-people fields IsIranian / IsValid / IsVerified from common/code-list/ans.",
|
||||
})
|
||||
@ApiOkResponse({
|
||||
description: "Returns Fanavaran yes/no (ans) lookup data",
|
||||
schema: { type: "array", items: { type: "object" } },
|
||||
})
|
||||
async getAns() {
|
||||
return await this.lookupsService.getAns();
|
||||
}
|
||||
|
||||
@Get("countries")
|
||||
@ApiOperation({
|
||||
summary: "Fanavaran GEN.44 countries lookup",
|
||||
description:
|
||||
"Returns values for other-people payload field NationalityId from common/base-info/countries.",
|
||||
})
|
||||
@ApiOkResponse({
|
||||
description: "Returns Fanavaran countries lookup data",
|
||||
schema: { type: "array", items: { type: "object" } },
|
||||
})
|
||||
async getCountries() {
|
||||
return await this.lookupsService.getCountries();
|
||||
}
|
||||
|
||||
@Get("person-kind")
|
||||
@ApiOperation({
|
||||
summary: "Fanavaran GEN.44 person kind lookup",
|
||||
description:
|
||||
"Returns values for other-people payload field PersonKindId from common/code-list/person-kind.",
|
||||
})
|
||||
@ApiOkResponse({
|
||||
description: "Returns Fanavaran person kind lookup data",
|
||||
schema: { type: "array", items: { type: "object" } },
|
||||
})
|
||||
async getPersonKind() {
|
||||
return await this.lookupsService.getPersonKind();
|
||||
}
|
||||
|
||||
@Get("cii-validation-status")
|
||||
@ApiOperation({
|
||||
summary: "Fanavaran GEN.44 CII validation status lookup",
|
||||
description:
|
||||
"Returns values for other-people response fields CIIValidationStatus / CIIMobileStatus from common/code-list/cii-validation-status.",
|
||||
})
|
||||
@ApiOkResponse({
|
||||
description: "Returns Fanavaran CII validation status lookup data",
|
||||
schema: { type: "array", items: { type: "object" } },
|
||||
})
|
||||
async getCiiValidationStatus() {
|
||||
return await this.lookupsService.getCiiValidationStatus();
|
||||
}
|
||||
|
||||
@Get("inquiry-by-vin")
|
||||
@ApiOperation({
|
||||
summary: "Fanavaran vehicle inquiry by VIN",
|
||||
@@ -382,6 +466,25 @@ export class LookupsController {
|
||||
});
|
||||
}
|
||||
|
||||
@Get("other-people/:personId")
|
||||
@ApiOperation({
|
||||
summary: "Fanavaran GEN.44 other person by id",
|
||||
description:
|
||||
"GET common/other-people/{Id}. Use after parties inquiry misses and GEN.44 create, or to inspect a known person Id.",
|
||||
})
|
||||
@ApiParam({
|
||||
name: "personId",
|
||||
description: "Fanavaran other-people Id",
|
||||
example: 4553876,
|
||||
})
|
||||
@ApiOkResponse({
|
||||
description: "Returns the Fanavaran other-person record",
|
||||
schema: { type: "object" },
|
||||
})
|
||||
async otherPersonById(@Param("personId", ParseIntPipe) personId: number) {
|
||||
return await this.lookupsService.otherPersonById(personId);
|
||||
}
|
||||
|
||||
@Get("fanavaran")
|
||||
@ApiOperation({
|
||||
summary: "List configured Fanavaran remote lookups",
|
||||
|
||||
@@ -182,6 +182,44 @@ export class LookupsService {
|
||||
return await this.getClientRemoteLookup("dmg-business-line");
|
||||
}
|
||||
|
||||
async getGender(): Promise<any> {
|
||||
return await this.getClientRemoteLookup("gender");
|
||||
}
|
||||
|
||||
async getMaritalStatus(): Promise<any> {
|
||||
return await this.getClientRemoteLookup("marital-status");
|
||||
}
|
||||
|
||||
async getAns(): Promise<any> {
|
||||
return await this.getClientRemoteLookup("ans");
|
||||
}
|
||||
|
||||
async getCountries(): Promise<any> {
|
||||
return await this.getClientRemoteLookup("countries");
|
||||
}
|
||||
|
||||
async getPersonKind(): Promise<any> {
|
||||
return await this.getClientRemoteLookup("person-kind");
|
||||
}
|
||||
|
||||
async getCiiValidationStatus(): Promise<any> {
|
||||
return await this.getClientRemoteLookup("cii-validation-status");
|
||||
}
|
||||
|
||||
async otherPersonById(personId: number): Promise<unknown> {
|
||||
const clientKey = this.activeClientKey();
|
||||
const person = await this.fanavaranLookupService.getOtherPerson(
|
||||
clientKey,
|
||||
personId,
|
||||
);
|
||||
if (person == null) {
|
||||
throw new NotFoundException(
|
||||
`Fanavaran other-person ${personId} was not found.`,
|
||||
);
|
||||
}
|
||||
return person;
|
||||
}
|
||||
|
||||
async inquiryByUniqueIdentifier(query: {
|
||||
nationalCode?: string;
|
||||
birthday?: string;
|
||||
|
||||
Reference in New Issue
Block a user