diff --git a/src/claim-request-management/claim-request-management.service.ts b/src/claim-request-management/claim-request-management.service.ts index 63b75c5..4529090 100644 --- a/src/claim-request-management/claim-request-management.service.ts +++ b/src/claim-request-management/claim-request-management.service.ts @@ -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> { 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 { + 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. diff --git a/src/claim-request-management/entites/schema/claim-cases.schema.ts b/src/claim-request-management/entites/schema/claim-cases.schema.ts index 5516ef7..c9b9fc7 100644 --- a/src/claim-request-management/entites/schema/claim-cases.schema.ts +++ b/src/claim-request-management/entites/schema/claim-cases.schema.ts @@ -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; diff --git a/src/claim-request-management/fanavaran-other-people.spec.ts b/src/claim-request-management/fanavaran-other-people.spec.ts new file mode 100644 index 0000000..7eae3b0 --- /dev/null +++ b/src/claim-request-management/fanavaran-other-people.spec.ts @@ -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: "مهدوی نیا", + }); + }); +}); diff --git a/src/claim-request-management/fanavaran-other-people.ts b/src/claim-request-management/fanavaran-other-people.ts new file mode 100644 index 0000000..f6953c4 --- /dev/null +++ b/src/claim-request-management/fanavaran-other-people.ts @@ -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; + 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, + ), + }; +} diff --git a/src/fanavaran/fanavaran-lookup.config.ts b/src/fanavaran/fanavaran-lookup.config.ts index e393571..6309a4c 100644 --- a/src/fanavaran/fanavaran-lookup.config.ts +++ b/src/fanavaran/fanavaran-lookup.config.ts @@ -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 = { diff --git a/src/fanavaran/fanavaran-lookup.service.ts b/src/fanavaran/fanavaran-lookup.service.ts index fad58c3..f0dc436 100644 --- a/src/fanavaran/fanavaran-lookup.service.ts +++ b/src/fanavaran/fanavaran-lookup.service.ts @@ -315,6 +315,63 @@ export class FanavaranLookupService { return this.fetchFromFanavaran(clientKey, url); } + async getOtherPerson( + clientKey: FanavaranClientKey, + personId: number, + ): Promise { + const url = `${FANAVARAN_LOOKUP_BASE_URL}/common/other-people/${personId}`; + return this.fetchFromFanavaran(clientKey, url); + } + + async createOtherPerson( + clientKey: FanavaranClientKey, + payload: Record, + ): Promise { + const url = `${FANAVARAN_LOOKUP_BASE_URL}/common/other-people`; + return this.postToFanavaran(clientKey, url, payload); + } + + async postToFanavaran( + clientKey: FanavaranClientKey, + url: string, + payload: Record, + ): Promise { + 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 { diff --git a/src/fanavaran/schema/fanavaran-audit-log.schema.ts b/src/fanavaran/schema/fanavaran-audit-log.schema.ts index c718881..755d496 100644 --- a/src/fanavaran/schema/fanavaran-audit-log.schema.ts +++ b/src/fanavaran/schema/fanavaran-audit-log.schema.ts @@ -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", diff --git a/src/lookups/lookups.controller.ts b/src/lookups/lookups.controller.ts index eab1306..ea1d7d9 100644 --- a/src/lookups/lookups.controller.ts +++ b/src/lookups/lookups.controller.ts @@ -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", diff --git a/src/lookups/lookups.service.ts b/src/lookups/lookups.service.ts index 522fda3..550f857 100644 --- a/src/lookups/lookups.service.ts +++ b/src/lookups/lookups.service.ts @@ -182,6 +182,44 @@ export class LookupsService { return await this.getClientRemoteLookup("dmg-business-line"); } + async getGender(): Promise { + return await this.getClientRemoteLookup("gender"); + } + + async getMaritalStatus(): Promise { + return await this.getClientRemoteLookup("marital-status"); + } + + async getAns(): Promise { + return await this.getClientRemoteLookup("ans"); + } + + async getCountries(): Promise { + return await this.getClientRemoteLookup("countries"); + } + + async getPersonKind(): Promise { + return await this.getClientRemoteLookup("person-kind"); + } + + async getCiiValidationStatus(): Promise { + return await this.getClientRemoteLookup("cii-validation-status"); + } + + async otherPersonById(personId: number): Promise { + 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;