diff --git a/src/claim-request-management/claim-request-management.service.ts b/src/claim-request-management/claim-request-management.service.ts index 9f7eb57..f27ffd6 100644 --- a/src/claim-request-management/claim-request-management.service.ts +++ b/src/claim-request-management/claim-request-management.service.ts @@ -49,6 +49,12 @@ import { pickStoredCarBodyPolicyId, type FanavaranClaimProduct, } from "./fanavaran-claim-product"; +import { + hullExpertiseAssertFields, + pickHullVehicleIdentity, + toFanavaranHullExpertiseDmgSection, + toFanavaranHullExpertisePayload, +} from "./fanavaran-hull-expertise"; import { BlameRequestDbService } from "src/request-management/entities/db-service/blame-request.db.service"; import { CreateClaimFromBlameResponseDto } from "./dto/create-claim-v2.dto"; import { @@ -6722,6 +6728,41 @@ export class ClaimRequestManagementService { input.claimCase?._id ? String(input.claimCase._id) : undefined, ); + if (product === "car-body") { + const blame = input.claimCase?.blameRequestId + ? await this.blameRequestDbService.findById( + String(input.claimCase.blameRequestId), + ) + : null; + const hullSections = parts.map((part: any) => + toFanavaranHullExpertiseDmgSection({ + partId: part?.partId, + desc: this.fanavaranPartLabel(part), + wasteValue: this.fanavaranDaghiWasteValue(part?.daghi), + amount: + this.parseFanavaranMoney(part?.price) + + this.parseFanavaranMoney(part?.salary), + }), + ); + return { + replyKey: active?.replyKey, + warnings, + payload: toFanavaranHullExpertisePayload({ + claimExpertId, + dmgAssessmentDate: this.convertToPersianDate(submittedAt), + inspectionTime: this.getTime24Hour(submittedAt), + wage: repairWage, + componentReplacementCost, + wasteValue, + dropAmount: priceDropTotal || 0, + vehicleCurrentValue: + this.parseFanavaranMoney(priceDrop?.carPrice) || null, + vehicle: pickHullVehicleIdentity(blame), + dmgSections: hullSections, + }), + }; + } + const payload: Record = { ClaimExpertId: claimExpertId, ComponentReplacementCost: componentReplacementCost, @@ -6738,9 +6779,7 @@ export class ClaimRequestManagementService { this.parseFanavaranMoney(priceDrop?.carPrice) || null, DmgSections: dmgSections, }; - if (fanavaranHasDamageCaseStage(product)) { - payload.DmgCaseId = input.claimCase.dmgCaseId ?? null; - } + payload.DmgCaseId = input.claimCase.dmgCaseId ?? null; return { replyKey: active?.replyKey, @@ -6752,13 +6791,19 @@ export class ClaimRequestManagementService { private assertFanavaranExpertisePayloadReady( payload: Record, warnings: string[], - options?: { requireDmgCaseId?: boolean }, + options?: { requireDmgCaseId?: boolean; requireThirdPartyLookups?: boolean }, ): void { const sections = Array.isArray(payload.DmgSections) ? payload.DmgSections : []; if (options?.requireDmgCaseId !== false && !payload.DmgCaseId) warnings.push("DmgCaseId is required."); + if (options?.requireThirdPartyLookups !== false) { + if (!payload.InspectionPlaceId) + warnings.push("InspectionPlaceId is required."); + if (!payload.DropAmountStatus) + warnings.push("DropAmountStatus is required."); + } if (!payload.InspectionPlaceId) warnings.push("InspectionPlaceId is required."); if (!payload.DropAmountStatus) @@ -6839,7 +6884,7 @@ export class ClaimRequestManagementService { FANAVARAN_EXPERTISE_MANUAL_OVERRIDE_KEYS, ); this.assertFanavaranExpertisePayloadReady(payload, built.warnings, { - requireDmgCaseId: fanavaranHasDamageCaseStage(product), + ...hullExpertiseAssertFields(product), }); return await this.executeFanavaranExpertiseSubmit( claimCaseId, @@ -7035,7 +7080,7 @@ export class ClaimRequestManagementService { clientKey, }); this.assertFanavaranExpertisePayloadReady(payload, warnings, { - requireDmgCaseId: fanavaranHasDamageCaseStage(product), + ...hullExpertiseAssertFields(product), }); const fanavaranResponse = await this.executeFanavaranExpertiseSubmit( diff --git a/src/claim-request-management/fanavaran-claim-product.spec.ts b/src/claim-request-management/fanavaran-claim-product.spec.ts index 4fc9dd6..60aa0c9 100644 --- a/src/claim-request-management/fanavaran-claim-product.spec.ts +++ b/src/claim-request-management/fanavaran-claim-product.spec.ts @@ -5,6 +5,7 @@ import { fanavaranClaimsBaseUrl, fanavaranClaimStageUrl, fanavaranHasDamageCaseStage, + fanavaranHullImportClaimsUrl, fanavaranPartyInquirySources, isFanavaranSubmitSupportedBlameType, pickCarBodyPolicyNationalCode, @@ -39,8 +40,15 @@ describe("fanavaran claim product", () => { expect(fanavaranClaimStageUrl("car-body", "expertise", 5023617)).toBe( "https://apimanager.iraneit.com/BimeApiManager/api/BimeApi/v2.0/car/vehicle-hull-claims/5023617/expertise", ); - expect(fanavaranClaimStageUrl("third-party", "damage-case", 1)).toBe( - "https://apimanager.iraneit.com/BimeApiManager/api/BimeApi/v2.0/car/third-party-car-financial-claims/1/dmg-cases", + expect(fanavaranClaimStageUrl("car-body", "culprits", 5023617)).toBe( + "https://apimanager.iraneit.com/BimeApiManager/api/BimeApi/v2.0/car/vehicle-hull-claims/5023617/culprits", + ); + expect(fanavaranClaimStageUrl("third-party", "culprits", 1)).toBeNull(); + expect(fanavaranHullImportClaimsUrl()).toBe( + "https://apimanager.iraneit.com/BimeApiManager/api/BimeApi/v2.0/car/vehicle-hull-import-claims", + ); + expect(fanavaranClaimsBaseUrl("car-body")).not.toContain( + "vehicle-hull-import-claims", ); }); diff --git a/src/claim-request-management/fanavaran-claim-product.ts b/src/claim-request-management/fanavaran-claim-product.ts index 51c70aa..8f6d4b9 100644 --- a/src/claim-request-management/fanavaran-claim-product.ts +++ b/src/claim-request-management/fanavaran-claim-product.ts @@ -46,18 +46,28 @@ export function fanavaranClaimsBaseUrl( return `${FANAVARAN_CLAIMS_HOST}/${fanavaranClaimsResourcePath(product)}`; } -export type FanavaranClaimStage = "damage-case" | "attachments" | "expertise"; +export type FanavaranClaimStage = + | "damage-case" + | "attachments" + | "expertise" + | "culprits"; /** Hull (بدنه) has no GEN.12 dmg-cases resource. Proven 2026-09-15: Feature:url(.../vehicle-hull-claims/{id}/dmg-cases) not found. Files and expertise do exist. */ export const FANAVARAN_HULL_NO_DAMAGE_CASE_REASON = "Fanavaran vehicle-hull-claims has no dmg-cases resource. Hull is first-party (one damaged vehicle on the base claim). Continue with files and expertise."; +export const FANAVARAN_HULL_IMPORT_CLAIMS_PATH = "vehicle-hull-import-claims"; + export function fanavaranHasDamageCaseStage( product: FanavaranClaimProduct, ): boolean { return product === "third-party"; } +export function fanavaranHullImportClaimsUrl(): string { + return `${FANAVARAN_CLAIMS_HOST}/${FANAVARAN_HULL_IMPORT_CLAIMS_PATH}`; +} + export function fanavaranClaimStageUrl( product: FanavaranClaimProduct, stage: FanavaranClaimStage, @@ -66,12 +76,17 @@ export function fanavaranClaimStageUrl( if (stage === "damage-case" && !fanavaranHasDamageCaseStage(product)) { return null; } + if (stage === "culprits" && product !== "car-body") { + return null; + } const suffix = stage === "damage-case" ? "dmg-cases" : stage === "attachments" ? "files" - : "expertise"; + : stage === "culprits" + ? "culprits" + : "expertise"; return `${fanavaranClaimsBaseUrl(product)}/${claimId}/${suffix}`; } diff --git a/src/claim-request-management/fanavaran-hull-expertise.spec.ts b/src/claim-request-management/fanavaran-hull-expertise.spec.ts new file mode 100644 index 0000000..19852ea --- /dev/null +++ b/src/claim-request-management/fanavaran-hull-expertise.spec.ts @@ -0,0 +1,98 @@ +import { PartyRole } from "src/request-management/entities/schema/partyRole.enum"; +import { + pickHullVehicleIdentity, + toFanavaranHullExpertiseDmgSection, + toFanavaranHullExpertisePayload, +} from "./fanavaran-hull-expertise"; + +describe("fanavaran hull expertise", () => { + it("maps car-body inquiry identity onto GEN.06 vehicle fields", () => { + const vehicle = pickHullVehicleIdentity({ + parties: [ + { + role: PartyRole.FIRST, + insurance: { + carBodyInsurance: { + chassisNumber: "NAS431100K1050805", + motorNumber: "M136251767", + vin: "VIN123", + }, + }, + vehicle: { + inquiry: { + carBody: { + mapped: { + platePartOne: "29", + plateLetterTitle: "د", + platePartThree: "782", + plateSerialNumber: "60", + builtYear: 1398, + }, + }, + }, + }, + }, + ], + }); + + expect(vehicle).toEqual({ + motorNo: "M136251767", + chassisNo: "NAS431100K1050805", + vin: "VIN123", + plaqueNo: "29د782", + plaqueSerial: "60", + builtYear: 1398, + }); + }); + + it("builds a GEN.06 payload without ثالث expertise fields", () => { + const payload = toFanavaranHullExpertisePayload({ + claimExpertId: 29, + dmgAssessmentDate: "1405/06/24", + inspectionTime: "10:33", + wage: 1000, + componentReplacementCost: 2000, + wasteValue: 0, + dropAmount: 12, + vehicleCurrentValue: 1900000000, + vehicle: { + motorNo: "M1", + chassisNo: "C1", + vin: null, + plaqueNo: "695ط61", + plaqueSerial: "87", + builtYear: 1398, + }, + dmgSections: [ + toFanavaranHullExpertiseDmgSection({ + partId: 9, + desc: "bumper", + wasteValue: 0, + amount: 3000, + }), + ], + }); + + expect(payload.Wage).toBe(1000); + expect(payload.RepairWage).toBeUndefined(); + expect(payload.DmgCaseId).toBeUndefined(); + expect(payload.InspectionPlaceId).toBeUndefined(); + expect(payload.DropAmountStatus).toBeUndefined(); + expect(payload.DamagedVehicleCurrentPrice).toBeUndefined(); + expect(payload.ClaimExpertId).toBe(29); + expect(payload.PlaqueNo).toBe("695ط61"); + expect(payload.DmgSections).toEqual([ + { + Count: 1, + Desc: "bumper", + WasteValue: 0, + AccessoryKindId: 9, + DmgKindId: null, + VehicleHullAccessoryId: null, + DmgSectionCosts: [ + { Caption: "bumper", Amount: 3000, DmgCostKindId: null }, + ], + }, + ]); + }); +}); diff --git a/src/claim-request-management/fanavaran-hull-expertise.ts b/src/claim-request-management/fanavaran-hull-expertise.ts new file mode 100644 index 0000000..9eb7ff2 --- /dev/null +++ b/src/claim-request-management/fanavaran-hull-expertise.ts @@ -0,0 +1,124 @@ +import { parseFanavaranId } from "src/lookups/fanavaran-last-car-policy"; +import { + fanavaranPartyInquirySources, + type FanavaranClaimProduct, +} from "./fanavaran-claim-product"; + +export type FanavaranHullVehicleIdentity = { + motorNo: string | null; + chassisNo: string | null; + vin: string | null; + plaqueNo: string | null; + plaqueSerial: string | null; + builtYear: number | null; +}; + +export function pickHullVehicleIdentity( + blame?: { parties?: unknown[] } | null, +): FanavaranHullVehicleIdentity { + const party = (blame?.parties ?? [])[0] as + | Parameters[1] + | undefined; + const first = + (blame?.parties ?? []).find( + (row) => (row as { role?: string })?.role === "FIRST", + ) ?? party; + const { mapped } = fanavaranPartyInquirySources( + "car-body", + first as Parameters[1], + ); + const left = text(mapped.plk1 ?? mapped.Plk1); + const letter = text(mapped.plk2 ?? mapped.Plk2); + const right = text(mapped.plk3 ?? mapped.Plk3); + const serial = text(mapped.plksrl ?? mapped.PlkSrl); + const plaqueNo = [left, letter, right].filter(Boolean).join("") || null; + return { + motorNo: text(mapped.MtrNum), + chassisNo: text(mapped.ShsNum), + vin: text(mapped.VIN), + plaqueNo, + plaqueSerial: serial, + builtYear: parseFanavaranId(mapped.PrdDte), + }; +} + +function text(value: unknown): string | null { + if (value == null) return null; + const next = String(value).trim(); + return next ? next : null; +} + +export function toFanavaranHullExpertisePayload(input: { + claimExpertId: number; + dmgAssessmentDate: string; + inspectionTime: string; + wage: number; + componentReplacementCost: number; + wasteValue: number; + dropAmount: number; + vehicleCurrentValue: number | null; + vehicle: FanavaranHullVehicleIdentity; + dmgSections: Record[]; +}): Record { + return { + ClaimExpertId: input.claimExpertId, + DmgAssessmentDate: input.dmgAssessmentDate, + InspectionTime: input.inspectionTime, + MotorNo: input.vehicle.motorNo, + ChassisNo: input.vehicle.chassisNo, + BuiltYear: input.vehicle.builtYear, + VIN: input.vehicle.vin, + PlaqueNo: input.vehicle.plaqueNo, + PlaqueSerial: input.vehicle.plaqueSerial, + Wage: input.wage, + ComponentReplacementCost: input.componentReplacementCost, + WasteValue: input.wasteValue, + CarryAndRescueCost: null, + RepairDuration: null, + VehicleCurrentValue: input.vehicleCurrentValue, + WreckHighestValue: null, + InspectionDeduction: null, + DmgAndWasteDesc: null, + WentDistanceByExpert: null, + IsDestruction: null, + DropAmount: input.dropAmount, + ColorId: null, + PlaqueDesignId: null, + PlaqueCityId: null, + AccidentPercent: null, + TheftCases: [], + DmgSections: input.dmgSections, + }; +} + +export function toFanavaranHullExpertiseDmgSection(input: { + partId?: unknown; + desc: string; + wasteValue: number; + amount: number; +}): Record { + return { + Count: 1, + Desc: input.desc, + WasteValue: input.wasteValue, + AccessoryKindId: parseFanavaranId(input.partId), + DmgKindId: null, + VehicleHullAccessoryId: null, + DmgSectionCosts: [ + { + Caption: input.desc, + Amount: input.amount, + DmgCostKindId: null, + }, + ], + }; +} + +export function hullExpertiseAssertFields( + product: FanavaranClaimProduct, +): { requireDmgCaseId: boolean; requireThirdPartyLookups: boolean } { + if (product === "car-body") { + return { requireDmgCaseId: false, requireThirdPartyLookups: false }; + } + return { requireDmgCaseId: true, requireThirdPartyLookups: true }; +}