diff --git a/src/claim-request-management/claim-request-management.service.ts b/src/claim-request-management/claim-request-management.service.ts index 4a0dc27..45885ea 100644 --- a/src/claim-request-management/claim-request-management.service.ts +++ b/src/claim-request-management/claim-request-management.service.ts @@ -274,6 +274,12 @@ const FANAVARAN_ACCIDENT_LOCATION_ADDRESS = "استان تهران شهر تهر const FANAVARAN_DEFAULT_ACCIDENT_CAUSE_ID = 6; const FANAVARAN_DEFAULT_ACCIDENT_LEVEL = 5456; const FANAVARAN_PROVISIONAL_ESTIMATE_AMOUNT = 1000; +/** + * Fanavaran sometimes rejects missing/empty/whitespace licence numbers. + * We always send either the user-entered value (from DB) or this 10-digit dummy. + */ +const FANAVARAN_DUMMY_LICENCE_NO = + process.env.FANAVARAN_DUMMY_LICENCE_NO?.trim() || "9705463515"; /** Local carType → Fanavaran vehicle-kind Caption keywords for matching. */ const VEHICLE_KIND_KEYWORDS: Record = { @@ -3522,6 +3528,58 @@ export class ClaimRequestManagementService { }; } + /** + * Extract digits-only licence number from a possibly-null/empty value. + * Returns `null` when the value is "null", "undefined", "", '' or whitespace. + */ + private extractNonEmptyLicenceDigits( + value: unknown, + ): string | null { + if (value === null || value === undefined) return null; + const asString = + typeof value === "number" ? String(value) : String(value); + const trimmed = asString.trim(); + if (!trimmed) return null; + const lowered = trimmed.toLowerCase(); + if (lowered === "null" || lowered === "undefined") return null; + const digits = trimmed.replace(/[^\d]/g, ""); + return digits ? digits : null; + } + + /** + * Resolve licence number to send to Fanavaran. + * Preference order: + * - use the first non-empty value from DB (driverLicense/insurerLicense) + * - else use the configured 10-digit dummy + */ + private resolveFanavaranLicenceNoFromParty(input: { + driverIsInsurer?: boolean; + driverLicense?: unknown; + insurerLicense?: unknown; + }): string { + const { driverIsInsurer, driverLicense, insurerLicense } = input; + + // When driverIsInsurer === true, UI usually mirrors insurer/driver values, + // but DB can still miss one of the two fields — prefer both. + const candidates = driverIsInsurer + ? [driverLicense, insurerLicense] + : [driverLicense, insurerLicense]; + + for (const c of candidates) { + const digits = this.extractNonEmptyLicenceDigits(c); + if (digits) return digits; + } + + return FANAVARAN_DUMMY_LICENCE_NO; + } + + /** Last-resort sanitization for payload values coming from manual overrides. */ + private ensureFanavaranNonEmptyLicenceNo(value: unknown): string { + return ( + this.extractNonEmptyLicenceDigits(value) ?? FANAVARAN_DUMMY_LICENCE_NO + ); + } + private applyFanavaranDefaultFields(result: Record): void { result.ActualPremium = null; result.ArchiveNo = null; @@ -3536,7 +3594,8 @@ export class ClaimRequestManagementService { result.CulpritLicenceCountryId = null; result.CulpritLicenceForeignCityName = null; result.CulpritLicenceIssuDate = "1394/10/13"; - result.CulpritLicenceNo = "1124242"; + // Must never be empty/null. + result.CulpritLicenceNo = FANAVARAN_DUMMY_LICENCE_NO; result.DamagedCount = 1; result.DmgAssessorFirstCreationTime = null; result.EntryDate = null; @@ -4055,7 +4114,12 @@ export class ClaimRequestManagementService { LicenceCountryId: null, LicenceForeignCityName: null, LicenceIssuDate: person.driverBirthday ?? "1394/10/13", - LicenceNo: person.driverLicense ?? "1124242", + // Must never be empty/null; prefer stored DB value, else use dummy. + LicenceNo: this.resolveFanavaranLicenceNoFromParty({ + driverIsInsurer: person.driverIsInsurer, + driverLicense: person.driverLicense, + insurerLicense: person.insurerLicense, + }), LicenceTypeId: input.defaults.CulpritLicenceTypeId, MotorNo: this.pickPartyInquiryField(inquiryMapped, inquiryRaw, [ "MtrNum", @@ -6324,6 +6388,10 @@ export class ClaimRequestManagementService { clientKey, defaults: profile.defaults, }); + + // Guardrail: never send empty/null licence fields even if manual overrides arrive. + payload.LicenceNo = this.ensureFanavaranNonEmptyLicenceNo(payload.LicenceNo); + const url = `${this.FANAVARAN_SUBMIT_URL}/${claimCase.claimId}/dmg-cases`; this.logger.log( `[executeFanavaranDamageCaseSubmit] claimCaseId=${claimCaseId} claimId=${claimCase.claimId} payload.DriverId=${payload.DriverId ?? "NULL"} payload keys=${Object.keys(payload).join(", ")}`, @@ -7124,6 +7192,12 @@ export class ClaimRequestManagementService { auditSession, persistPayload: true, })); + + // Guardrail: never send empty/null licence fields even if manual overrides arrive. + fanavaranData.CulpritLicenceNo = this.ensureFanavaranNonEmptyLicenceNo( + fanavaranData.CulpritLicenceNo, + ); + this.logger.log( `${logPrefix} Mapped data prepared:`, JSON.stringify(fanavaranData, null, 2), diff --git a/src/core/config/fanavaran-client.config.ts b/src/core/config/fanavaran-client.config.ts index b8f5a2f..2120427 100644 --- a/src/core/config/fanavaran-client.config.ts +++ b/src/core/config/fanavaran-client.config.ts @@ -138,7 +138,9 @@ export const SEED_FANAVARAN_CLIENT_PROFILES: Record< ClaimExpertId: 154, // GEN.08 — ارزیاب خسارت ثالث مالی (رسول کرکی / شعبه غرب → 29) ExpertiseClaimExpertId: 29, - ClaimFileTypeId: 70, + // GEN.07 — proven 2026-08-03 (CL68535): 63 = سایر مدارک خسارت in parsian file-types. + // Do not use 70 — not in Parsian lookup → "نوع فايل با منبع لوکاپ مطابقت ندارد". + ClaimFileTypeId: 63, }, }, moallem: {