Merge pull request 'main' (#321) from s.hajizadeh/yara724api:main into main

Reviewed-on: Yara724/api#321
This commit is contained in:
2026-09-15 17:07:37 +03:30
5 changed files with 300 additions and 10 deletions

View File

@@ -49,6 +49,12 @@ import {
pickStoredCarBodyPolicyId, pickStoredCarBodyPolicyId,
type FanavaranClaimProduct, type FanavaranClaimProduct,
} from "./fanavaran-claim-product"; } 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 { BlameRequestDbService } from "src/request-management/entities/db-service/blame-request.db.service";
import { CreateClaimFromBlameResponseDto } from "./dto/create-claim-v2.dto"; import { CreateClaimFromBlameResponseDto } from "./dto/create-claim-v2.dto";
import { import {
@@ -6722,6 +6728,41 @@ export class ClaimRequestManagementService {
input.claimCase?._id ? String(input.claimCase._id) : undefined, 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<string, unknown> = { const payload: Record<string, unknown> = {
ClaimExpertId: claimExpertId, ClaimExpertId: claimExpertId,
ComponentReplacementCost: componentReplacementCost, ComponentReplacementCost: componentReplacementCost,
@@ -6738,9 +6779,7 @@ export class ClaimRequestManagementService {
this.parseFanavaranMoney(priceDrop?.carPrice) || null, this.parseFanavaranMoney(priceDrop?.carPrice) || null,
DmgSections: dmgSections, DmgSections: dmgSections,
}; };
if (fanavaranHasDamageCaseStage(product)) {
payload.DmgCaseId = input.claimCase.dmgCaseId ?? null; payload.DmgCaseId = input.claimCase.dmgCaseId ?? null;
}
return { return {
replyKey: active?.replyKey, replyKey: active?.replyKey,
@@ -6752,13 +6791,19 @@ export class ClaimRequestManagementService {
private assertFanavaranExpertisePayloadReady( private assertFanavaranExpertisePayloadReady(
payload: Record<string, unknown>, payload: Record<string, unknown>,
warnings: string[], warnings: string[],
options?: { requireDmgCaseId?: boolean }, options?: { requireDmgCaseId?: boolean; requireThirdPartyLookups?: boolean },
): void { ): void {
const sections = Array.isArray(payload.DmgSections) const sections = Array.isArray(payload.DmgSections)
? payload.DmgSections ? payload.DmgSections
: []; : [];
if (options?.requireDmgCaseId !== false && !payload.DmgCaseId) if (options?.requireDmgCaseId !== false && !payload.DmgCaseId)
warnings.push("DmgCaseId is required."); 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) if (!payload.InspectionPlaceId)
warnings.push("InspectionPlaceId is required."); warnings.push("InspectionPlaceId is required.");
if (!payload.DropAmountStatus) if (!payload.DropAmountStatus)
@@ -6839,7 +6884,7 @@ export class ClaimRequestManagementService {
FANAVARAN_EXPERTISE_MANUAL_OVERRIDE_KEYS, FANAVARAN_EXPERTISE_MANUAL_OVERRIDE_KEYS,
); );
this.assertFanavaranExpertisePayloadReady(payload, built.warnings, { this.assertFanavaranExpertisePayloadReady(payload, built.warnings, {
requireDmgCaseId: fanavaranHasDamageCaseStage(product), ...hullExpertiseAssertFields(product),
}); });
return await this.executeFanavaranExpertiseSubmit( return await this.executeFanavaranExpertiseSubmit(
claimCaseId, claimCaseId,
@@ -7035,7 +7080,7 @@ export class ClaimRequestManagementService {
clientKey, clientKey,
}); });
this.assertFanavaranExpertisePayloadReady(payload, warnings, { this.assertFanavaranExpertisePayloadReady(payload, warnings, {
requireDmgCaseId: fanavaranHasDamageCaseStage(product), ...hullExpertiseAssertFields(product),
}); });
const fanavaranResponse = await this.executeFanavaranExpertiseSubmit( const fanavaranResponse = await this.executeFanavaranExpertiseSubmit(

View File

@@ -5,6 +5,7 @@ import {
fanavaranClaimsBaseUrl, fanavaranClaimsBaseUrl,
fanavaranClaimStageUrl, fanavaranClaimStageUrl,
fanavaranHasDamageCaseStage, fanavaranHasDamageCaseStage,
fanavaranHullImportClaimsUrl,
fanavaranPartyInquirySources, fanavaranPartyInquirySources,
isFanavaranSubmitSupportedBlameType, isFanavaranSubmitSupportedBlameType,
pickCarBodyPolicyNationalCode, pickCarBodyPolicyNationalCode,
@@ -39,8 +40,15 @@ describe("fanavaran claim product", () => {
expect(fanavaranClaimStageUrl("car-body", "expertise", 5023617)).toBe( expect(fanavaranClaimStageUrl("car-body", "expertise", 5023617)).toBe(
"https://apimanager.iraneit.com/BimeApiManager/api/BimeApi/v2.0/car/vehicle-hull-claims/5023617/expertise", "https://apimanager.iraneit.com/BimeApiManager/api/BimeApi/v2.0/car/vehicle-hull-claims/5023617/expertise",
); );
expect(fanavaranClaimStageUrl("third-party", "damage-case", 1)).toBe( expect(fanavaranClaimStageUrl("car-body", "culprits", 5023617)).toBe(
"https://apimanager.iraneit.com/BimeApiManager/api/BimeApi/v2.0/car/third-party-car-financial-claims/1/dmg-cases", "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",
); );
}); });

View File

@@ -46,18 +46,28 @@ export function fanavaranClaimsBaseUrl(
return `${FANAVARAN_CLAIMS_HOST}/${fanavaranClaimsResourcePath(product)}`; 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. */ /** 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 = 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."; "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( export function fanavaranHasDamageCaseStage(
product: FanavaranClaimProduct, product: FanavaranClaimProduct,
): boolean { ): boolean {
return product === "third-party"; return product === "third-party";
} }
export function fanavaranHullImportClaimsUrl(): string {
return `${FANAVARAN_CLAIMS_HOST}/${FANAVARAN_HULL_IMPORT_CLAIMS_PATH}`;
}
export function fanavaranClaimStageUrl( export function fanavaranClaimStageUrl(
product: FanavaranClaimProduct, product: FanavaranClaimProduct,
stage: FanavaranClaimStage, stage: FanavaranClaimStage,
@@ -66,11 +76,16 @@ export function fanavaranClaimStageUrl(
if (stage === "damage-case" && !fanavaranHasDamageCaseStage(product)) { if (stage === "damage-case" && !fanavaranHasDamageCaseStage(product)) {
return null; return null;
} }
if (stage === "culprits" && product !== "car-body") {
return null;
}
const suffix = const suffix =
stage === "damage-case" stage === "damage-case"
? "dmg-cases" ? "dmg-cases"
: stage === "attachments" : stage === "attachments"
? "files" ? "files"
: stage === "culprits"
? "culprits"
: "expertise"; : "expertise";
return `${fanavaranClaimsBaseUrl(product)}/${claimId}/${suffix}`; return `${fanavaranClaimsBaseUrl(product)}/${claimId}/${suffix}`;
} }

View File

@@ -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 },
],
},
]);
});
});

View File

@@ -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<typeof fanavaranPartyInquirySources>[1]
| undefined;
const first =
(blame?.parties ?? []).find(
(row) => (row as { role?: string })?.role === "FIRST",
) ?? party;
const { mapped } = fanavaranPartyInquirySources(
"car-body",
first as Parameters<typeof fanavaranPartyInquirySources>[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<string, unknown>[];
}): Record<string, unknown> {
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<string, unknown> {
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 };
}