forked from Yara724/api
third party update , optional body is done
This commit is contained in:
@@ -37,8 +37,11 @@ import { ClaimRequestManagementDbService } from "./entites/db-service/claim-requ
|
||||
import { ClaimCaseDbService } from "./entites/db-service/claim-case.db.service";
|
||||
import { fanavaranClaimReferences } from "./fanavaran-claim-references";
|
||||
import {
|
||||
FANAVARAN_HULL_NO_DAMAGE_CASE_REASON,
|
||||
fanavaranClaimProductFromBlameType,
|
||||
fanavaranClaimsBaseUrl,
|
||||
fanavaranClaimStageUrl,
|
||||
fanavaranHasDamageCaseStage,
|
||||
fanavaranInsuranceLineIdForProduct,
|
||||
fanavaranPartyInquirySources,
|
||||
isFanavaranSubmitSupportedBlameType,
|
||||
@@ -228,18 +231,20 @@ import {
|
||||
import {
|
||||
asObjectRecord,
|
||||
asVehicleInquiryRows,
|
||||
canReuseCachedFanavaranVehicleIds,
|
||||
collectPartyVinCandidates,
|
||||
completePlaqueParts,
|
||||
fanavaranIdOrConfigDefault,
|
||||
fanavaranPayloadMatchesDamagedIdentity,
|
||||
fanavaranVehicleMatchesDamagedIdentity,
|
||||
normalizeVin,
|
||||
applyFanavaranManualPayloadOverrides,
|
||||
FANAVARAN_BASE_MANUAL_OVERRIDE_KEYS,
|
||||
FANAVARAN_DAMAGE_MANUAL_OVERRIDE_KEYS,
|
||||
FANAVARAN_EXPERTISE_MANUAL_OVERRIDE_KEYS,
|
||||
parseFanavaranId,
|
||||
pickFanavaranPlaqueLookupFields,
|
||||
pickFanavaranVehicleFromInquiry,
|
||||
pickVehicleId,
|
||||
pickVinFromPartyVehicle,
|
||||
resolveDamageCasePlaqueLookupIds,
|
||||
selectFanavaranVehicleForDamageCase,
|
||||
type FanavaranDamagedVehicleIdentity,
|
||||
} from "src/lookups/fanavaran-last-car-policy";
|
||||
@@ -4336,17 +4341,6 @@ export class ClaimRequestManagementService {
|
||||
}
|
||||
const cachedDamage =
|
||||
(input.claimCase as any)?.fanavaranSync?.damageCase ?? {};
|
||||
const lastDamagePayload = asObjectRecord(cachedDamage.lastPayload);
|
||||
const cacheMatchesIdentity = fanavaranPayloadMatchesDamagedIdentity(
|
||||
lastDamagePayload,
|
||||
damagedIdentity,
|
||||
);
|
||||
const canReuseCachedPlaque =
|
||||
cacheMatchesIdentity &&
|
||||
canReuseCachedFanavaranVehicleIds(cachedDamage.plaqueKindSource);
|
||||
const canReuseCachedVehicleKind =
|
||||
cacheMatchesIdentity &&
|
||||
canReuseCachedFanavaranVehicleIds(cachedDamage.vehicleKindSource);
|
||||
|
||||
this.logger.log(
|
||||
`[buildFanavaranDamageCasePayload] identity VIN=${damagedIdentity.vin ?? "none"} chassis=${damagedIdentity.chassis ?? "none"} plate=${damagedIdentity.plaque ? `${damagedIdentity.plaque.right}${damagedIdentity.plaque.letter}${damagedIdentity.plaque.left}/${damagedIdentity.plaque.serial}` : "none"} CI=${damagedIdentity.policyCINumber ?? "none"} VIN candidates=${damagedVinCandidates.join(",") || "none"}`,
|
||||
@@ -4358,61 +4352,23 @@ export class ClaimRequestManagementService {
|
||||
product,
|
||||
);
|
||||
const apiPlaque = pickFanavaranPlaqueLookupFields(apiVehicle);
|
||||
const inquiryVehicle =
|
||||
asObjectRecord(inquiryMapped) ?? asObjectRecord(inquiryRaw);
|
||||
const inquiryPlaque = fanavaranVehicleMatchesDamagedIdentity(
|
||||
inquiryVehicle,
|
||||
damagedIdentity,
|
||||
)
|
||||
? pickFanavaranPlaqueLookupFields(inquiryVehicle)
|
||||
: pickFanavaranPlaqueLookupFields(null);
|
||||
const plaqueKindFromApi = this.firstPositiveFanavaranId(
|
||||
apiPlaque.kindId,
|
||||
inquiryPlaque.kindId,
|
||||
canReuseCachedPlaque ? cachedDamage.plaqueKindId : null,
|
||||
canReuseCachedPlaque ? lastDamagePayload?.PlaqueKindId : null,
|
||||
);
|
||||
const plaqueSampleFromApi = this.firstPositiveFanavaranId(
|
||||
apiPlaque.sampleId,
|
||||
inquiryPlaque.sampleId,
|
||||
canReuseCachedPlaque ? cachedDamage.plaqueSampleId : null,
|
||||
canReuseCachedPlaque ? lastDamagePayload?.PlaqueSampleId : null,
|
||||
);
|
||||
const plaqueKindId = fanavaranIdOrConfigDefault(
|
||||
plaqueKindFromApi,
|
||||
input.defaults.PlaqueKindId,
|
||||
);
|
||||
const plaqueSampleId = fanavaranIdOrConfigDefault(
|
||||
plaqueSampleFromApi,
|
||||
input.defaults.PlaqueSampleId,
|
||||
);
|
||||
const plaqueKindSource =
|
||||
plaqueKindFromApi != null ? "vehicle-inquiry" : "config-default";
|
||||
const plaqueSampleSource =
|
||||
plaqueSampleFromApi != null ? "vehicle-inquiry" : "config-default";
|
||||
if (plaqueKindFromApi == null) {
|
||||
const plaqueLookup = resolveDamageCasePlaqueLookupIds(apiVehicle);
|
||||
const plaqueKindId = plaqueLookup.kindId;
|
||||
const plaqueSampleId = plaqueLookup.sampleId;
|
||||
const plaqueKindSource = plaqueLookup.source;
|
||||
const plaqueSampleSource = plaqueLookup.source;
|
||||
if (plaqueLookup.source !== "vehicle-inquiry") {
|
||||
this.logger.log(
|
||||
`[buildFanavaranDamageCasePayload] PlaqueKindId API miss; using config default ${plaqueKindId}`,
|
||||
);
|
||||
}
|
||||
if (plaqueSampleFromApi == null) {
|
||||
this.logger.log(
|
||||
`[buildFanavaranDamageCasePayload] PlaqueSampleId API miss; using config default ${plaqueSampleId}`,
|
||||
`[buildFanavaranDamageCasePayload] PlaqueKindId not on a matched Fanavaran vehicle; using national-plate default ${plaqueKindId}/${plaqueSampleId}`,
|
||||
);
|
||||
}
|
||||
const builtYear =
|
||||
parseFanavaranId(apiVehicle?.BuiltYear) ?? builtYearFromInquiry;
|
||||
|
||||
const carType = input.claimCase?.vehicle?.carType as string | undefined;
|
||||
let vehicleKindId = this.firstPositiveFanavaranId(
|
||||
apiVehicle?.VehicleKindId,
|
||||
canReuseCachedVehicleKind ? cachedDamage.vehicleKindId : null,
|
||||
canReuseCachedVehicleKind ? lastDamagePayload?.VehicleKindId : null,
|
||||
);
|
||||
let vehicleKindId = this.firstPositiveFanavaranId(apiVehicle?.VehicleKindId);
|
||||
let vehicleKindSource: "vehicle-inquiry" | "car-type-lookup" | null =
|
||||
parseFanavaranId(apiVehicle?.VehicleKindId) != null
|
||||
? "vehicle-inquiry"
|
||||
: canReuseCachedVehicleKind
|
||||
? "vehicle-inquiry"
|
||||
: null;
|
||||
if (vehicleKindId == null) {
|
||||
@@ -4423,28 +4379,24 @@ export class ClaimRequestManagementService {
|
||||
if (vehicleKindId != null) vehicleKindSource = "car-type-lookup";
|
||||
}
|
||||
|
||||
// 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(
|
||||
`[buildFanavaranDamageCasePayload] Using CACHED DriverId=${driverFanavaranId}`,
|
||||
);
|
||||
} else {
|
||||
driverFanavaranId = await this.resolveDriverFanavaranIdFromBlameCase(
|
||||
let driverFanavaranId = await this.resolveDriverFanavaranIdFromBlameCase(
|
||||
input.clientKey,
|
||||
input.blameCase?.parties ?? [],
|
||||
person,
|
||||
input.claimCase,
|
||||
);
|
||||
let otherPersonId = this.firstPositiveFanavaranId(cachedDamage.otherPersonId);
|
||||
|
||||
if (!driverFanavaranId && input.registerMissingPerson) {
|
||||
if (driverFanavaranId) {
|
||||
this.logger.log(
|
||||
`[buildFanavaranDamageCasePayload] DriverId=${driverFanavaranId} from live parties inquiry`,
|
||||
);
|
||||
} else if (otherPersonId) {
|
||||
driverFanavaranId = otherPersonId;
|
||||
this.logger.log(
|
||||
`[buildFanavaranDamageCasePayload] Parties inquiry missed; reusing GEN.44 otherPersonId=${otherPersonId}`,
|
||||
);
|
||||
} else if (input.registerMissingPerson) {
|
||||
const registered = await this.registerFanavaranOtherPerson({
|
||||
clientKey: input.clientKey,
|
||||
person,
|
||||
@@ -4458,7 +4410,6 @@ export class ClaimRequestManagementService {
|
||||
}
|
||||
}
|
||||
|
||||
// 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(
|
||||
(p: any) => p === damagedParty ||
|
||||
@@ -4471,36 +4422,19 @@ export class ClaimRequestManagementService {
|
||||
{ $set: { [`parties.${partyIndex}.person.fanavaranDriverId`]: driverFanavaranId } },
|
||||
);
|
||||
this.logger.log(
|
||||
`[buildFanavaranDamageCasePayload] CACHED DriverId=${driverFanavaranId} on blameCase=${input.blameCase._id} party[${partyIndex}]`,
|
||||
`[buildFanavaranDamageCasePayload] stored DriverId=${driverFanavaranId} on blameCase=${input.blameCase._id} party[${partyIndex}]`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let insuranceCorpId =
|
||||
cachedDamage.insuranceCorpId != null
|
||||
? Number(cachedDamage.insuranceCorpId)
|
||||
: null;
|
||||
if (insuranceCorpId == null) {
|
||||
insuranceCorpId = await this.fanavaranLookupService.resolveInsuranceCorpId(
|
||||
const insuranceCorpId =
|
||||
await this.fanavaranLookupService.resolveInsuranceCorpId(
|
||||
input.clientKey,
|
||||
);
|
||||
}
|
||||
|
||||
const cachedUsedId =
|
||||
cacheMatchesIdentity && cachedDamage.accidentVehicleUsedId != null
|
||||
? Number(cachedDamage.accidentVehicleUsedId)
|
||||
: null;
|
||||
const cachedUsedSource = cachedDamage.accidentVehicleUsedSource;
|
||||
const canReuseApiUsedId =
|
||||
cachedUsedId != null &&
|
||||
cachedUsedId > 0 &&
|
||||
cachedUsedSource === "vehicle-inquiry";
|
||||
let accidentVehicleUsedId: number | null = canReuseApiUsedId
|
||||
? cachedUsedId
|
||||
: null;
|
||||
let accidentVehicleUsedId: number | null = null;
|
||||
let usedSource: "vin-inquiry" | "policy-vehicle" | "vehicle-inquiry" | null =
|
||||
canReuseApiUsedId ? "vehicle-inquiry" : null;
|
||||
null;
|
||||
if (accidentVehicleUsedId == null && apiVehicle) {
|
||||
const fromDamagedVehicle =
|
||||
await this.resolveAccidentVehicleUsedFromVehicleRecord(
|
||||
@@ -4597,10 +4531,7 @@ export class ClaimRequestManagementService {
|
||||
PolicyCINumber: policyCINumber,
|
||||
};
|
||||
|
||||
const persistedPayload = this.keepPreviousPositiveIds(lastDamagePayload, payload, [
|
||||
"DriverId",
|
||||
"InsuranceCorpId",
|
||||
]);
|
||||
const persistedPayload = payload;
|
||||
|
||||
// Persist Fanavaran-sourced IDs + last payload for reuse on later preview/submit
|
||||
if (input.claimCase?._id) {
|
||||
@@ -5502,12 +5433,9 @@ export class ClaimRequestManagementService {
|
||||
/**
|
||||
* Build Fanavaran GEN.03 payload from local claim/blame data.
|
||||
*
|
||||
* Fanavaran-sourced fields (PolicyId) use resolve-once caching:
|
||||
* - If `fanavaranSync.baseClaim.policyId` exists → reuse (no policy HTTP).
|
||||
* - Else → live inquiry-my-policies once, then persist policyId + lastPayload.
|
||||
*
|
||||
* HTTP preview/submit always pass forceRefreshPolicy + forceRefreshUsedId,
|
||||
* so PolicyId and AccidentVehicleUsedId are re-inquired every call.
|
||||
* Auth still uses the shared midnight-TTL token cache (not a new login per preview).
|
||||
* Pass `forceRefreshPolicy: true` to bust the PolicyId cache.
|
||||
*/
|
||||
async previewFanavaranSubmitV2(
|
||||
claimCaseId: string,
|
||||
@@ -5577,7 +5505,7 @@ export class ClaimRequestManagementService {
|
||||
},
|
||||
failureReason: null as string | null,
|
||||
note:
|
||||
"PolicyId is resolve-once: first preview/submit may call Fanavaran policy inquiry; later calls reuse fanavaranSync.baseClaim.policyId. Auth token is shared until Tehran midnight.",
|
||||
"HTTP preview/submit always re-inquire PolicyId and AccidentVehicleUsedId. Auth token is shared until Tehran midnight.",
|
||||
};
|
||||
|
||||
const claimCase = await this.claimCaseDbService.findById(claimCaseId);
|
||||
@@ -5911,6 +5839,21 @@ export class ClaimRequestManagementService {
|
||||
);
|
||||
}
|
||||
|
||||
const product = fanavaranClaimProductFromBlameType(blameCase.type);
|
||||
if (!fanavaranHasDamageCaseStage(product)) {
|
||||
return {
|
||||
clientKey,
|
||||
claimCaseId,
|
||||
claimId: claimCase.claimId ?? null,
|
||||
dmgCaseId: claimCase.dmgCaseId ?? null,
|
||||
submitUrl: null,
|
||||
skipped: true,
|
||||
skipReason: FANAVARAN_HULL_NO_DAMAGE_CASE_REASON,
|
||||
warning: FANAVARAN_HULL_NO_DAMAGE_CASE_REASON,
|
||||
payload: null,
|
||||
};
|
||||
}
|
||||
|
||||
const payload = await this.buildFanavaranDamageCasePayload({
|
||||
claimCase,
|
||||
blameCase,
|
||||
@@ -5935,7 +5878,7 @@ export class ClaimRequestManagementService {
|
||||
claimId: claimCase.claimId ?? null,
|
||||
dmgCaseId: claimCase.dmgCaseId ?? null,
|
||||
submitUrl: claimCase.claimId
|
||||
? `${fanavaranClaimsBaseUrl(fanavaranClaimProductFromBlameType(blameCase.type))}/${claimCase.claimId}/dmg-cases`
|
||||
? fanavaranClaimStageUrl(product, "damage-case", claimCase.claimId)
|
||||
: null,
|
||||
warning: warnings.length ? warnings.join(" ") : undefined,
|
||||
payload,
|
||||
@@ -6775,15 +6718,14 @@ export class ClaimRequestManagementService {
|
||||
0,
|
||||
);
|
||||
const submittedAt = reply?.submittedAt ?? new Date();
|
||||
const product = await this.resolveFanavaranClaimProduct(
|
||||
input.claimCase?._id ? String(input.claimCase._id) : undefined,
|
||||
);
|
||||
|
||||
return {
|
||||
replyKey: active?.replyKey,
|
||||
warnings,
|
||||
payload: {
|
||||
const payload: Record<string, unknown> = {
|
||||
ClaimExpertId: claimExpertId,
|
||||
ComponentReplacementCost: componentReplacementCost,
|
||||
DmgAssessmentDate: this.convertToPersianDate(submittedAt),
|
||||
DmgCaseId: input.claimCase.dmgCaseId ?? null,
|
||||
InspectionPlaceId: inspectionPlaceId,
|
||||
InspectionTime: this.getTime24Hour(submittedAt),
|
||||
RepairWage: repairWage,
|
||||
@@ -6795,18 +6737,28 @@ export class ClaimRequestManagementService {
|
||||
DamagedVehicleCurrentPrice:
|
||||
this.parseFanavaranMoney(priceDrop?.carPrice) || null,
|
||||
DmgSections: dmgSections,
|
||||
},
|
||||
};
|
||||
if (fanavaranHasDamageCaseStage(product)) {
|
||||
payload.DmgCaseId = input.claimCase.dmgCaseId ?? null;
|
||||
}
|
||||
|
||||
return {
|
||||
replyKey: active?.replyKey,
|
||||
warnings,
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
private assertFanavaranExpertisePayloadReady(
|
||||
payload: Record<string, unknown>,
|
||||
warnings: string[],
|
||||
options?: { requireDmgCaseId?: boolean },
|
||||
): void {
|
||||
const sections = Array.isArray(payload.DmgSections)
|
||||
? payload.DmgSections
|
||||
: [];
|
||||
if (!payload.DmgCaseId) warnings.push("DmgCaseId is required.");
|
||||
if (options?.requireDmgCaseId !== false && !payload.DmgCaseId)
|
||||
warnings.push("DmgCaseId is required.");
|
||||
if (!payload.InspectionPlaceId)
|
||||
warnings.push("InspectionPlaceId is required.");
|
||||
if (!payload.DropAmountStatus)
|
||||
@@ -6876,17 +6828,19 @@ export class ClaimRequestManagementService {
|
||||
);
|
||||
const claimCase = await this.claimCaseDbService.findById(claimCaseId);
|
||||
if (!claimCase) throw new NotFoundException("Claim case not found");
|
||||
let payload: Record<string, unknown>;
|
||||
if (bodyOverride) {
|
||||
payload = bodyOverride;
|
||||
} else {
|
||||
const product = await this.resolveFanavaranClaimProduct(claimCaseId);
|
||||
const built = await this.buildFanavaranExpertisePayload({
|
||||
claimCase,
|
||||
clientKey,
|
||||
});
|
||||
payload = built.payload;
|
||||
this.assertFanavaranExpertisePayloadReady(payload, built.warnings);
|
||||
}
|
||||
const payload = applyFanavaranManualPayloadOverrides(
|
||||
built.payload,
|
||||
bodyOverride,
|
||||
FANAVARAN_EXPERTISE_MANUAL_OVERRIDE_KEYS,
|
||||
);
|
||||
this.assertFanavaranExpertisePayloadReady(payload, built.warnings, {
|
||||
requireDmgCaseId: fanavaranHasDamageCaseStage(product),
|
||||
});
|
||||
return await this.executeFanavaranExpertiseSubmit(
|
||||
claimCaseId,
|
||||
clientKey,
|
||||
@@ -6910,7 +6864,11 @@ export class ClaimRequestManagementService {
|
||||
auditSource: FanavaranAuditSource,
|
||||
): Promise<any> {
|
||||
let claimCase = await this.claimCaseDbService.findById(claimCaseId);
|
||||
if (!claimCase?.claimId || claimCase.dmgCaseId == null) {
|
||||
const product = await this.resolveFanavaranClaimProduct(claimCaseId);
|
||||
if (
|
||||
!claimCase?.claimId ||
|
||||
(fanavaranHasDamageCaseStage(product) && claimCase.dmgCaseId == null)
|
||||
) {
|
||||
await this.ensureFanavaranDamageCase(
|
||||
claimCaseId,
|
||||
clientKey,
|
||||
@@ -7063,7 +7021,8 @@ export class ClaimRequestManagementService {
|
||||
await this.autoSubmitToFanavaranV2OnClaimCreated(claimCaseId);
|
||||
claimCase = await this.claimCaseDbService.findById(claimCaseId);
|
||||
}
|
||||
if (!claimCase?.dmgCaseId) {
|
||||
const product = await this.resolveFanavaranClaimProduct(claimCaseId);
|
||||
if (fanavaranHasDamageCaseStage(product) && !claimCase?.dmgCaseId) {
|
||||
await this.autoSubmitFanavaranDamageCaseOnOuterPartsSelected(
|
||||
claimCaseId,
|
||||
claimCase?.damage?.selectedParts ?? [],
|
||||
@@ -7075,7 +7034,9 @@ export class ClaimRequestManagementService {
|
||||
claimCase,
|
||||
clientKey,
|
||||
});
|
||||
this.assertFanavaranExpertisePayloadReady(payload, warnings);
|
||||
this.assertFanavaranExpertisePayloadReady(payload, warnings, {
|
||||
requireDmgCaseId: fanavaranHasDamageCaseStage(product),
|
||||
});
|
||||
|
||||
const fanavaranResponse = await this.executeFanavaranExpertiseSubmit(
|
||||
claimCaseId,
|
||||
@@ -7245,7 +7206,11 @@ export class ClaimRequestManagementService {
|
||||
clientKey: FanavaranClientKey,
|
||||
auditSource: FanavaranAuditSource,
|
||||
selectedParts?: unknown[],
|
||||
): Promise<{ claimId: number; dmgCaseId: number; created: boolean }> {
|
||||
): Promise<{
|
||||
claimId: number;
|
||||
dmgCaseId: number | null;
|
||||
created: boolean;
|
||||
}> {
|
||||
await this.ensureFanavaranBaseClaim(claimCaseId, clientKey, auditSource);
|
||||
|
||||
const existing = await this.claimCaseDbService.findById(claimCaseId);
|
||||
@@ -7257,6 +7222,20 @@ export class ClaimRequestManagementService {
|
||||
};
|
||||
}
|
||||
|
||||
const product = await this.resolveFanavaranClaimProduct(claimCaseId);
|
||||
if (!fanavaranHasDamageCaseStage(product)) {
|
||||
if (existing?.claimId == null) {
|
||||
throw new BadRequestException(
|
||||
"Fanavaran claimId is required before the next hull stage.",
|
||||
);
|
||||
}
|
||||
return {
|
||||
claimId: Number(existing.claimId),
|
||||
dmgCaseId: null,
|
||||
created: false,
|
||||
};
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`[Fanavaran ${clientKey}] Soft-ensuring damage case before next stage claimCaseId=${claimCaseId}`,
|
||||
);
|
||||
@@ -7396,7 +7375,7 @@ export class ClaimRequestManagementService {
|
||||
);
|
||||
}
|
||||
}
|
||||
if (claimCase.dmgCaseId != null && !bodyOverride) {
|
||||
if (claimCase.dmgCaseId != null) {
|
||||
this.logger.log(
|
||||
`[executeFanavaranDamageCaseSubmit] Already have dmgCaseId=${claimCase.dmgCaseId}; skipping Fanavaran POST`,
|
||||
);
|
||||
@@ -7422,7 +7401,34 @@ export class ClaimRequestManagementService {
|
||||
);
|
||||
}
|
||||
|
||||
const payload = bodyOverride ?? await this.buildFanavaranDamageCasePayload({
|
||||
const product = fanavaranClaimProductFromBlameType(blameCase.type);
|
||||
const url = fanavaranClaimStageUrl(
|
||||
product,
|
||||
"damage-case",
|
||||
claimCase.claimId,
|
||||
);
|
||||
if (url == null) {
|
||||
this.logger.log(
|
||||
`[executeFanavaranDamageCaseSubmit] claimCaseId=${claimCaseId} claimId=${claimCase.claimId} skipping hull dmg-cases`,
|
||||
);
|
||||
await this.claimCaseDbService.findByIdAndUpdate(claimCaseId, {
|
||||
$set: {
|
||||
"fanavaranSync.damageCase": {
|
||||
status: "skipped",
|
||||
lastTriedAt: new Date(),
|
||||
lastError: FANAVARAN_HULL_NO_DAMAGE_CASE_REASON,
|
||||
claimId: claimCase.claimId,
|
||||
},
|
||||
},
|
||||
});
|
||||
return {
|
||||
skipped: true,
|
||||
skipReason: FANAVARAN_HULL_NO_DAMAGE_CASE_REASON,
|
||||
claimId: claimCase.claimId,
|
||||
};
|
||||
}
|
||||
|
||||
const builtPayload = await this.buildFanavaranDamageCasePayload({
|
||||
claimCase,
|
||||
blameCase,
|
||||
selectedParts: selectedParts ?? claimCase.damage?.selectedParts ?? [],
|
||||
@@ -7430,11 +7436,15 @@ export class ClaimRequestManagementService {
|
||||
defaults: profile.defaults,
|
||||
registerMissingPerson: true,
|
||||
});
|
||||
const payload = applyFanavaranManualPayloadOverrides(
|
||||
builtPayload,
|
||||
bodyOverride,
|
||||
FANAVARAN_DAMAGE_MANUAL_OVERRIDE_KEYS,
|
||||
);
|
||||
|
||||
// Guardrail: never send empty/null licence fields even if manual overrides arrive.
|
||||
payload.LicenceNo = this.ensureFanavaranNonEmptyLicenceNo(payload.LicenceNo);
|
||||
|
||||
const url = `${fanavaranClaimsBaseUrl(fanavaranClaimProductFromBlameType(blameCase.type))}/${claimCase.claimId}/dmg-cases`;
|
||||
this.logger.log(
|
||||
`[executeFanavaranDamageCaseSubmit] claimCaseId=${claimCaseId} claimId=${claimCase.claimId} payload.DriverId=${payload.DriverId ?? "NULL"} payload keys=${Object.keys(payload).join(", ")}`,
|
||||
);
|
||||
@@ -7583,6 +7593,16 @@ export class ClaimRequestManagementService {
|
||||
clientKey,
|
||||
selectedParts,
|
||||
);
|
||||
if (fanavaranResponse?.skipped) {
|
||||
return {
|
||||
attempted: false,
|
||||
submitted: false,
|
||||
skipped: true,
|
||||
skipReason:
|
||||
fanavaranResponse.skipReason ?? FANAVARAN_HULL_NO_DAMAGE_CASE_REASON,
|
||||
claimId: claimCase.claimId,
|
||||
};
|
||||
}
|
||||
const dmgCaseId = fanavaranResponse?.Id;
|
||||
|
||||
await this.claimCaseDbService.findByIdAndUpdate(claimCaseId, {
|
||||
@@ -8208,7 +8228,7 @@ export class ClaimRequestManagementService {
|
||||
);
|
||||
|
||||
const existing = await this.claimCaseDbService.findById(claimCaseId);
|
||||
if (existing?.claimId != null && !bodyOverride) {
|
||||
if (existing?.claimId != null) {
|
||||
this.logger.log(
|
||||
`${logPrefix} Base claim already exists claimId=${existing.claimId}; skipping Fanavaran POST`,
|
||||
);
|
||||
@@ -8227,14 +8247,19 @@ export class ClaimRequestManagementService {
|
||||
claimCaseId,
|
||||
};
|
||||
|
||||
const fanavaranData =
|
||||
bodyOverride ??
|
||||
(await this.previewFanavaranSubmitV2(claimCaseId, clientKey, {
|
||||
const built =
|
||||
await this.previewFanavaranSubmitV2(claimCaseId, clientKey, {
|
||||
requirePolicyId: true,
|
||||
auditSession,
|
||||
persistPayload: true,
|
||||
forceRefreshPolicy: true,
|
||||
forceRefreshUsedId: true,
|
||||
}));
|
||||
});
|
||||
const fanavaranData = applyFanavaranManualPayloadOverrides(
|
||||
built,
|
||||
bodyOverride,
|
||||
FANAVARAN_BASE_MANUAL_OVERRIDE_KEYS,
|
||||
);
|
||||
|
||||
// Guardrail: never send empty/null licence fields even if manual overrides arrive.
|
||||
fanavaranData.CulpritLicenceNo = this.ensureFanavaranNonEmptyLicenceNo(
|
||||
|
||||
@@ -3,6 +3,8 @@ import { PartyRole } from "src/request-management/entities/schema/partyRole.enum
|
||||
import {
|
||||
fanavaranClaimProductFromBlameType,
|
||||
fanavaranClaimsBaseUrl,
|
||||
fanavaranClaimStageUrl,
|
||||
fanavaranHasDamageCaseStage,
|
||||
fanavaranPartyInquirySources,
|
||||
isFanavaranSubmitSupportedBlameType,
|
||||
pickCarBodyPolicyNationalCode,
|
||||
@@ -28,6 +30,20 @@ describe("fanavaran claim product", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("does not POST hull damage to the missing dmg-cases resource", () => {
|
||||
expect(fanavaranHasDamageCaseStage("car-body")).toBe(false);
|
||||
expect(fanavaranClaimStageUrl("car-body", "damage-case", 5023617)).toBeNull();
|
||||
expect(fanavaranClaimStageUrl("car-body", "attachments", 5023617)).toBe(
|
||||
"https://apimanager.iraneit.com/BimeApiManager/api/BimeApi/v2.0/car/vehicle-hull-claims/5023617/files",
|
||||
);
|
||||
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",
|
||||
);
|
||||
});
|
||||
|
||||
it("allows Fanavaran submit for both car products", () => {
|
||||
expect(isFanavaranSubmitSupportedBlameType("THIRD_PARTY")).toBe(true);
|
||||
expect(isFanavaranSubmitSupportedBlameType("CAR_BODY")).toBe(true);
|
||||
|
||||
@@ -46,6 +46,35 @@ export function fanavaranClaimsBaseUrl(
|
||||
return `${FANAVARAN_CLAIMS_HOST}/${fanavaranClaimsResourcePath(product)}`;
|
||||
}
|
||||
|
||||
export type FanavaranClaimStage = "damage-case" | "attachments" | "expertise";
|
||||
|
||||
/** 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 function fanavaranHasDamageCaseStage(
|
||||
product: FanavaranClaimProduct,
|
||||
): boolean {
|
||||
return product === "third-party";
|
||||
}
|
||||
|
||||
export function fanavaranClaimStageUrl(
|
||||
product: FanavaranClaimProduct,
|
||||
stage: FanavaranClaimStage,
|
||||
claimId: number | string,
|
||||
): string | null {
|
||||
if (stage === "damage-case" && !fanavaranHasDamageCaseStage(product)) {
|
||||
return null;
|
||||
}
|
||||
const suffix =
|
||||
stage === "damage-case"
|
||||
? "dmg-cases"
|
||||
: stage === "attachments"
|
||||
? "files"
|
||||
: "expertise";
|
||||
return `${fanavaranClaimsBaseUrl(product)}/${claimId}/${suffix}`;
|
||||
}
|
||||
|
||||
export function fanavaranInsuranceLineIdForProduct(
|
||||
product: FanavaranClaimProduct,
|
||||
): number {
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from "@nestjs/common";
|
||||
import {
|
||||
ApiBearerAuth,
|
||||
ApiBody,
|
||||
ApiOperation,
|
||||
ApiParam,
|
||||
ApiQuery,
|
||||
@@ -57,7 +58,7 @@ export class FanavaranController {
|
||||
@ApiOperation({
|
||||
summary: "Preview Fanavaran base claim create payload",
|
||||
description:
|
||||
"Builds the GEN.03 payload from local claim/blame data. Fanavaran-sourced PolicyId is resolve-once: first call may inquire and caches on the claim; later calls reuse the cache. Auth token is shared until Asia/Tehran midnight. Pass forceRefreshPolicy=true to re-inquire.",
|
||||
"GET has no request body. Always rebuilds GEN.03 from live policy + VIN/vehicle inquiries. Does not reuse fanavaranSync lastPayload / PolicyId / UsedId cache. Auth token is still shared until Asia/Tehran midnight.",
|
||||
})
|
||||
@ApiParam({
|
||||
name: "client",
|
||||
@@ -76,34 +77,30 @@ export class FanavaranController {
|
||||
@ApiQuery({
|
||||
name: "forceRefreshPolicy",
|
||||
required: false,
|
||||
description:
|
||||
"When true, ignores cached PolicyId and performs a live Fanavaran policy inquiry again. Do not pass this from normal UI loads.",
|
||||
deprecated: true,
|
||||
description: "Ignored. Preview always re-inquires PolicyId.",
|
||||
})
|
||||
@ApiQuery({
|
||||
name: "resolvePolicy",
|
||||
required: false,
|
||||
deprecated: true,
|
||||
description:
|
||||
"Deprecated. Ignored for cache-busting. PolicyId is resolve-once from fanavaranSync.baseClaim.policyId; use forceRefreshPolicy=true only to re-inquire.",
|
||||
description: "Ignored. Preview always re-inquires PolicyId.",
|
||||
})
|
||||
async preview(
|
||||
@Param("client") client: string,
|
||||
@Param("claimCaseId") claimCaseId: string,
|
||||
@Query("debug") debug?: string,
|
||||
@Query("forceRefreshPolicy") forceRefreshPolicy?: string,
|
||||
@Query("forceRefreshPolicy") _forceRefreshPolicy?: string,
|
||||
@Query("resolvePolicy") _resolvePolicy?: string,
|
||||
) {
|
||||
const clientKey = this.parseClientParam(client);
|
||||
// IMPORTANT: resolvePolicy must NOT force a live inquiry. Older UI clients
|
||||
// send resolvePolicy=true on every preview load; that used to defeat the
|
||||
// PolicyId cache and re-Login Fanavaran on every click.
|
||||
return await this.claimRequestManagementService.previewFanavaranSubmitV2(
|
||||
claimCaseId,
|
||||
clientKey,
|
||||
{
|
||||
debug: debug === "1" || debug === "true",
|
||||
forceRefreshPolicy:
|
||||
forceRefreshPolicy === "1" || forceRefreshPolicy === "true",
|
||||
forceRefreshPolicy: true,
|
||||
forceRefreshUsedId: true,
|
||||
requirePolicyId: false,
|
||||
},
|
||||
);
|
||||
@@ -113,7 +110,7 @@ export class FanavaranController {
|
||||
@ApiOperation({
|
||||
summary: "Submit Fanavaran base claim create request",
|
||||
description:
|
||||
"Authenticates with the selected tenant credentials and submits the GEN.03 base claim create request. Stores returned Id as claimId and ClaimNo when present.",
|
||||
"Swagger JSON body is optional. Always rebuilds GEN.03 from live inquiries, then POSTs that rebuilt payload. Pasting a previous preview JSON does not skip inquiry. Only user-entry keys are applied from the body (EstimateAmount, licence, dates, address, police report). PolicyId and AccidentVehicleUsedId in pasted JSON are ignored.",
|
||||
})
|
||||
@ApiParam({
|
||||
name: "client",
|
||||
@@ -124,6 +121,12 @@ export class FanavaranController {
|
||||
name: "claimCaseId",
|
||||
description: "Claim case MongoDB ObjectId",
|
||||
})
|
||||
@ApiBody({
|
||||
required: false,
|
||||
description:
|
||||
"Optional. Paste previous preview JSON if you want to tweak user-entry fields. Lookup ids in that JSON are ignored.",
|
||||
schema: { type: "object", additionalProperties: true, example: {} },
|
||||
})
|
||||
async submit(
|
||||
@Param("client") client: string,
|
||||
@Param("claimCaseId") claimCaseId: string,
|
||||
@@ -141,7 +144,7 @@ export class FanavaranController {
|
||||
@ApiOperation({
|
||||
summary: "Preview Fanavaran damage-case payload",
|
||||
description:
|
||||
"Builds the GEN.12 damaged vehicle/person case payload without calling Fanavaran. Requires selected damaged parts; submit requires a Fanavaran claimId.",
|
||||
"GET has no request body. Always rebuilds GEN.12 from live VIN/vehicle/driver inquiries. Does not reuse lastPayload / plaqueKindId / DriverId cache.",
|
||||
})
|
||||
@ApiParam({
|
||||
name: "client",
|
||||
@@ -167,7 +170,7 @@ export class FanavaranController {
|
||||
@ApiOperation({
|
||||
summary: "Submit Fanavaran damage-case request",
|
||||
description:
|
||||
"Submits the GEN.12 dmg-cases request. If base claim (claimId) is missing, soft-ensures GEN.03 base claim first, then submits damage. Skips when dmgCaseId already exists.",
|
||||
"Swagger JSON body is optional. Always rebuilds GEN.12 from live VIN/vehicle/driver inquiries, then POSTs that rebuilt payload. Pasting a previous preview JSON does not skip inquiry. Only Desc, EstimateAmount, LicenceNo, LicenceIssuDate, DriverIsOwner are taken from the body. PlaqueKindId, PlaqueSampleId, VehicleKindId, DriverId, InsuranceCorpId in pasted JSON are ignored.",
|
||||
})
|
||||
@ApiParam({
|
||||
name: "client",
|
||||
@@ -178,6 +181,12 @@ export class FanavaranController {
|
||||
name: "claimCaseId",
|
||||
description: "Claim case MongoDB ObjectId",
|
||||
})
|
||||
@ApiBody({
|
||||
required: false,
|
||||
description:
|
||||
"Optional. Paste previous preview JSON if you want to tweak Desc / licence / estimate. Lookup-filter ids in that JSON are ignored.",
|
||||
schema: { type: "object", additionalProperties: true, example: {} },
|
||||
})
|
||||
async submitDamageCase(
|
||||
@Param("client") client: string,
|
||||
@Param("claimCaseId") claimCaseId: string,
|
||||
@@ -247,7 +256,7 @@ export class FanavaranController {
|
||||
@ApiOperation({
|
||||
summary: "Preview Fanavaran expertise payload",
|
||||
description:
|
||||
"Builds the GEN.08 expertise payload from the active damage expert reply, price-drop data, and Fanavaran lookup mappings without calling Fanavaran.",
|
||||
"GET has no request body. Rebuilds GEN.08 from the active expert reply and lookups on every call. Does not reuse lastPayload cache.",
|
||||
})
|
||||
@ApiParam({
|
||||
name: "client",
|
||||
@@ -273,7 +282,7 @@ export class FanavaranController {
|
||||
@ApiOperation({
|
||||
summary: "Submit Fanavaran expertise request",
|
||||
description:
|
||||
"Submits the GEN.08 expertise payload for a claim with existing Fanavaran claimId and dmgCaseId. Stores returned Id as local expertiseId.",
|
||||
"Swagger JSON body is optional. Always rebuilds GEN.08 from the local expert reply and lookups, then POSTs that rebuilt payload. Pasting a previous preview JSON does not skip rebuild. Only money/date fields are taken from the body. DmgCaseId, ClaimExpertId, and DmgSections in pasted JSON are ignored.",
|
||||
})
|
||||
@ApiParam({
|
||||
name: "client",
|
||||
@@ -284,6 +293,12 @@ export class FanavaranController {
|
||||
name: "claimCaseId",
|
||||
description: "Claim case MongoDB ObjectId",
|
||||
})
|
||||
@ApiBody({
|
||||
required: false,
|
||||
description:
|
||||
"Optional. Paste previous preview JSON if you want to tweak money/date fields. DmgCaseId / ClaimExpertId / DmgSections in that JSON are ignored.",
|
||||
schema: { type: "object", additionalProperties: true, example: {} },
|
||||
})
|
||||
async submitExpertise(
|
||||
@Param("client") client: string,
|
||||
@Param("claimCaseId") claimCaseId: string,
|
||||
|
||||
@@ -5,6 +5,8 @@ import {
|
||||
fanavaranPayloadMatchesDamagedIdentity,
|
||||
fanavaranVehicleMatchesDamagedIdentity,
|
||||
filterPoliciesByLine,
|
||||
applyFanavaranManualPayloadOverrides,
|
||||
resolveDamageCasePlaqueLookupIds,
|
||||
insuranceLineLabel,
|
||||
parseLastCarPolicyInput,
|
||||
pickFanavaranPlaqueLookupFields,
|
||||
@@ -465,4 +467,38 @@ describe("fanavaran last car policy", () => {
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("uses national plaque 8/10 when the damaged VIN is unknown to Fanavaran", () => {
|
||||
expect(resolveDamageCasePlaqueLookupIds(null)).toEqual({
|
||||
kindId: 8,
|
||||
sampleId: 10,
|
||||
source: "national-plate-default",
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps live PlaqueKindId 8 when Swagger body still has stale 15", () => {
|
||||
expect(
|
||||
applyFanavaranManualPayloadOverrides(
|
||||
{
|
||||
PlaqueKindId: 8,
|
||||
PlaqueSampleId: 10,
|
||||
VehicleKindId: 6704,
|
||||
AccidentVehicleUsedId: 1,
|
||||
Desc: "سپر جلو",
|
||||
},
|
||||
{
|
||||
PlaqueKindId: 15,
|
||||
PlaqueSampleId: 24,
|
||||
Desc: "سپر جلو (edited)",
|
||||
},
|
||||
["Desc", "EstimateAmount", "LicenceNo"],
|
||||
),
|
||||
).toEqual({
|
||||
PlaqueKindId: 8,
|
||||
PlaqueSampleId: 10,
|
||||
VehicleKindId: 6704,
|
||||
AccidentVehicleUsedId: 1,
|
||||
Desc: "سپر جلو (edited)",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -387,6 +387,89 @@ export function canReuseCachedFanavaranVehicleIds(source: unknown): boolean {
|
||||
return source === "vehicle-inquiry" || source === "vin-inquiry" || source === "vehicle-get";
|
||||
}
|
||||
|
||||
/** Proven Parsian national (چهار تکه) plaque codebook ids. */
|
||||
export const FANAVARAN_NATIONAL_PLAQUE_KIND_ID = 8;
|
||||
export const FANAVARAN_NATIONAL_PLAQUE_SAMPLE_ID = 10;
|
||||
|
||||
export const FANAVARAN_DAMAGE_MANUAL_OVERRIDE_KEYS = [
|
||||
"Desc",
|
||||
"EstimateAmount",
|
||||
"LicenceNo",
|
||||
"LicenceIssuDate",
|
||||
"DriverIsOwner",
|
||||
] as const;
|
||||
|
||||
export const FANAVARAN_EXPERTISE_MANUAL_OVERRIDE_KEYS = [
|
||||
"RepairWage",
|
||||
"ComponentReplacementCost",
|
||||
"WasteValue",
|
||||
"DmgAssessmentDate",
|
||||
"InspectionTime",
|
||||
"DamagedVehicleCurrentPrice",
|
||||
"DropAmountAdditionsDeductions",
|
||||
] as const;
|
||||
|
||||
export const FANAVARAN_BASE_MANUAL_OVERRIDE_KEYS = [
|
||||
"EstimateAmount",
|
||||
"CulpritLicenceNo",
|
||||
"CulpritLicenceIssuDate",
|
||||
"AccidentLocationAddress",
|
||||
"AccidentDate",
|
||||
"AnnouncementDate",
|
||||
"DocReceivedDate",
|
||||
"AccidentTime",
|
||||
"PoliceReportDesc",
|
||||
"PoliceReportSeri",
|
||||
"PoliceReportSerial",
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Swagger POST body is optional user-entry tweaks only.
|
||||
* Inquiry / lookup-filter ids always come from the live rebuilt payload.
|
||||
*/
|
||||
export function applyFanavaranManualPayloadOverrides(
|
||||
built: Record<string, unknown>,
|
||||
body: Record<string, unknown> | null | undefined,
|
||||
keys: readonly string[],
|
||||
): Record<string, unknown> {
|
||||
if (!body) return built;
|
||||
const next = { ...built };
|
||||
for (const key of keys) {
|
||||
if (!Object.prototype.hasOwnProperty.call(body, key)) continue;
|
||||
if (body[key] === undefined) continue;
|
||||
next[key] = body[key];
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plaque kind/sample only from a matched Fanavaran vehicle record.
|
||||
* CII/ESG inquiry, unsourced cache, and tenant defaults of 15 must not win.
|
||||
* National 4-part plates fall back to 8/10 (lookup-filter safe on Parsian).
|
||||
*/
|
||||
export function resolveDamageCasePlaqueLookupIds(matchedVehicle: Record<
|
||||
string,
|
||||
unknown
|
||||
> | null): {
|
||||
kindId: number;
|
||||
sampleId: number;
|
||||
source: "vehicle-inquiry" | "national-plate-default";
|
||||
} {
|
||||
const plaque = pickFanavaranPlaqueLookupFields(matchedVehicle);
|
||||
if (plaque.kindId != null) {
|
||||
return {
|
||||
kindId: plaque.kindId,
|
||||
sampleId: plaque.sampleId ?? FANAVARAN_NATIONAL_PLAQUE_SAMPLE_ID,
|
||||
source: "vehicle-inquiry",
|
||||
};
|
||||
}
|
||||
return {
|
||||
kindId: FANAVARAN_NATIONAL_PLAQUE_KIND_ID,
|
||||
sampleId: FANAVARAN_NATIONAL_PLAQUE_SAMPLE_ID,
|
||||
source: "national-plate-default",
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Pick the VIN-inquiry / vehicle-GET row that is the damaged car.
|
||||
* Newest VersionNo wins among matches; PlaqueKindId is a tie-break only.
|
||||
|
||||
Reference in New Issue
Block a user