forked from Yara724/api
Merge pull request 'third party update , optional body is done' (#320) from s.hajizadeh/yara724api:main into main
Reviewed-on: Yara724/api#320
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 { ClaimCaseDbService } from "./entites/db-service/claim-case.db.service";
|
||||||
import { fanavaranClaimReferences } from "./fanavaran-claim-references";
|
import { fanavaranClaimReferences } from "./fanavaran-claim-references";
|
||||||
import {
|
import {
|
||||||
|
FANAVARAN_HULL_NO_DAMAGE_CASE_REASON,
|
||||||
fanavaranClaimProductFromBlameType,
|
fanavaranClaimProductFromBlameType,
|
||||||
fanavaranClaimsBaseUrl,
|
fanavaranClaimsBaseUrl,
|
||||||
|
fanavaranClaimStageUrl,
|
||||||
|
fanavaranHasDamageCaseStage,
|
||||||
fanavaranInsuranceLineIdForProduct,
|
fanavaranInsuranceLineIdForProduct,
|
||||||
fanavaranPartyInquirySources,
|
fanavaranPartyInquirySources,
|
||||||
isFanavaranSubmitSupportedBlameType,
|
isFanavaranSubmitSupportedBlameType,
|
||||||
@@ -228,18 +231,20 @@ import {
|
|||||||
import {
|
import {
|
||||||
asObjectRecord,
|
asObjectRecord,
|
||||||
asVehicleInquiryRows,
|
asVehicleInquiryRows,
|
||||||
canReuseCachedFanavaranVehicleIds,
|
|
||||||
collectPartyVinCandidates,
|
collectPartyVinCandidates,
|
||||||
completePlaqueParts,
|
completePlaqueParts,
|
||||||
fanavaranIdOrConfigDefault,
|
|
||||||
fanavaranPayloadMatchesDamagedIdentity,
|
|
||||||
fanavaranVehicleMatchesDamagedIdentity,
|
fanavaranVehicleMatchesDamagedIdentity,
|
||||||
normalizeVin,
|
normalizeVin,
|
||||||
|
applyFanavaranManualPayloadOverrides,
|
||||||
|
FANAVARAN_BASE_MANUAL_OVERRIDE_KEYS,
|
||||||
|
FANAVARAN_DAMAGE_MANUAL_OVERRIDE_KEYS,
|
||||||
|
FANAVARAN_EXPERTISE_MANUAL_OVERRIDE_KEYS,
|
||||||
parseFanavaranId,
|
parseFanavaranId,
|
||||||
pickFanavaranPlaqueLookupFields,
|
pickFanavaranPlaqueLookupFields,
|
||||||
pickFanavaranVehicleFromInquiry,
|
pickFanavaranVehicleFromInquiry,
|
||||||
pickVehicleId,
|
pickVehicleId,
|
||||||
pickVinFromPartyVehicle,
|
pickVinFromPartyVehicle,
|
||||||
|
resolveDamageCasePlaqueLookupIds,
|
||||||
selectFanavaranVehicleForDamageCase,
|
selectFanavaranVehicleForDamageCase,
|
||||||
type FanavaranDamagedVehicleIdentity,
|
type FanavaranDamagedVehicleIdentity,
|
||||||
} from "src/lookups/fanavaran-last-car-policy";
|
} from "src/lookups/fanavaran-last-car-policy";
|
||||||
@@ -4336,17 +4341,6 @@ export class ClaimRequestManagementService {
|
|||||||
}
|
}
|
||||||
const cachedDamage =
|
const cachedDamage =
|
||||||
(input.claimCase as any)?.fanavaranSync?.damageCase ?? {};
|
(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(
|
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"}`,
|
`[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,
|
product,
|
||||||
);
|
);
|
||||||
const apiPlaque = pickFanavaranPlaqueLookupFields(apiVehicle);
|
const apiPlaque = pickFanavaranPlaqueLookupFields(apiVehicle);
|
||||||
const inquiryVehicle =
|
const plaqueLookup = resolveDamageCasePlaqueLookupIds(apiVehicle);
|
||||||
asObjectRecord(inquiryMapped) ?? asObjectRecord(inquiryRaw);
|
const plaqueKindId = plaqueLookup.kindId;
|
||||||
const inquiryPlaque = fanavaranVehicleMatchesDamagedIdentity(
|
const plaqueSampleId = plaqueLookup.sampleId;
|
||||||
inquiryVehicle,
|
const plaqueKindSource = plaqueLookup.source;
|
||||||
damagedIdentity,
|
const plaqueSampleSource = plaqueLookup.source;
|
||||||
)
|
if (plaqueLookup.source !== "vehicle-inquiry") {
|
||||||
? 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) {
|
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
`[buildFanavaranDamageCasePayload] PlaqueKindId API miss; using config default ${plaqueKindId}`,
|
`[buildFanavaranDamageCasePayload] PlaqueKindId not on a matched Fanavaran vehicle; using national-plate default ${plaqueKindId}/${plaqueSampleId}`,
|
||||||
);
|
|
||||||
}
|
|
||||||
if (plaqueSampleFromApi == null) {
|
|
||||||
this.logger.log(
|
|
||||||
`[buildFanavaranDamageCasePayload] PlaqueSampleId API miss; using config default ${plaqueSampleId}`,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const builtYear =
|
const builtYear =
|
||||||
parseFanavaranId(apiVehicle?.BuiltYear) ?? builtYearFromInquiry;
|
parseFanavaranId(apiVehicle?.BuiltYear) ?? builtYearFromInquiry;
|
||||||
|
|
||||||
const carType = input.claimCase?.vehicle?.carType as string | undefined;
|
const carType = input.claimCase?.vehicle?.carType as string | undefined;
|
||||||
let vehicleKindId = this.firstPositiveFanavaranId(
|
let vehicleKindId = this.firstPositiveFanavaranId(apiVehicle?.VehicleKindId);
|
||||||
apiVehicle?.VehicleKindId,
|
|
||||||
canReuseCachedVehicleKind ? cachedDamage.vehicleKindId : null,
|
|
||||||
canReuseCachedVehicleKind ? lastDamagePayload?.VehicleKindId : null,
|
|
||||||
);
|
|
||||||
let vehicleKindSource: "vehicle-inquiry" | "car-type-lookup" | null =
|
let vehicleKindSource: "vehicle-inquiry" | "car-type-lookup" | null =
|
||||||
parseFanavaranId(apiVehicle?.VehicleKindId) != null
|
parseFanavaranId(apiVehicle?.VehicleKindId) != null
|
||||||
? "vehicle-inquiry"
|
|
||||||
: canReuseCachedVehicleKind
|
|
||||||
? "vehicle-inquiry"
|
? "vehicle-inquiry"
|
||||||
: null;
|
: null;
|
||||||
if (vehicleKindId == null) {
|
if (vehicleKindId == null) {
|
||||||
@@ -4423,28 +4379,24 @@ export class ClaimRequestManagementService {
|
|||||||
if (vehicleKindId != null) vehicleKindSource = "car-type-lookup";
|
if (vehicleKindId != null) vehicleKindSource = "car-type-lookup";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prefer claim-level cache, then party.person.fanavaranDriverId, then live inquiry, then last payload
|
let driverFanavaranId = await this.resolveDriverFanavaranIdFromBlameCase(
|
||||||
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(
|
|
||||||
input.clientKey,
|
input.clientKey,
|
||||||
input.blameCase?.parties ?? [],
|
input.blameCase?.parties ?? [],
|
||||||
person,
|
person,
|
||||||
input.claimCase,
|
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({
|
const registered = await this.registerFanavaranOtherPerson({
|
||||||
clientKey: input.clientKey,
|
clientKey: input.clientKey,
|
||||||
person,
|
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) {
|
if (driverFanavaranId && input.blameCase?._id && damagedParty) {
|
||||||
const partyIndex = (input.blameCase.parties ?? []).findIndex(
|
const partyIndex = (input.blameCase.parties ?? []).findIndex(
|
||||||
(p: any) => p === damagedParty ||
|
(p: any) => p === damagedParty ||
|
||||||
@@ -4471,36 +4422,19 @@ export class ClaimRequestManagementService {
|
|||||||
{ $set: { [`parties.${partyIndex}.person.fanavaranDriverId`]: driverFanavaranId } },
|
{ $set: { [`parties.${partyIndex}.person.fanavaranDriverId`]: driverFanavaranId } },
|
||||||
);
|
);
|
||||||
this.logger.log(
|
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 =
|
const insuranceCorpId =
|
||||||
cachedDamage.insuranceCorpId != null
|
await this.fanavaranLookupService.resolveInsuranceCorpId(
|
||||||
? Number(cachedDamage.insuranceCorpId)
|
|
||||||
: null;
|
|
||||||
if (insuranceCorpId == null) {
|
|
||||||
insuranceCorpId = await this.fanavaranLookupService.resolveInsuranceCorpId(
|
|
||||||
input.clientKey,
|
input.clientKey,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
const cachedUsedId =
|
let accidentVehicleUsedId: number | null = null;
|
||||||
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 usedSource: "vin-inquiry" | "policy-vehicle" | "vehicle-inquiry" | null =
|
let usedSource: "vin-inquiry" | "policy-vehicle" | "vehicle-inquiry" | null =
|
||||||
canReuseApiUsedId ? "vehicle-inquiry" : null;
|
null;
|
||||||
if (accidentVehicleUsedId == null && apiVehicle) {
|
if (accidentVehicleUsedId == null && apiVehicle) {
|
||||||
const fromDamagedVehicle =
|
const fromDamagedVehicle =
|
||||||
await this.resolveAccidentVehicleUsedFromVehicleRecord(
|
await this.resolveAccidentVehicleUsedFromVehicleRecord(
|
||||||
@@ -4597,10 +4531,7 @@ export class ClaimRequestManagementService {
|
|||||||
PolicyCINumber: policyCINumber,
|
PolicyCINumber: policyCINumber,
|
||||||
};
|
};
|
||||||
|
|
||||||
const persistedPayload = this.keepPreviousPositiveIds(lastDamagePayload, payload, [
|
const persistedPayload = payload;
|
||||||
"DriverId",
|
|
||||||
"InsuranceCorpId",
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Persist Fanavaran-sourced IDs + last payload for reuse on later preview/submit
|
// Persist Fanavaran-sourced IDs + last payload for reuse on later preview/submit
|
||||||
if (input.claimCase?._id) {
|
if (input.claimCase?._id) {
|
||||||
@@ -5502,12 +5433,9 @@ export class ClaimRequestManagementService {
|
|||||||
/**
|
/**
|
||||||
* Build Fanavaran GEN.03 payload from local claim/blame data.
|
* Build Fanavaran GEN.03 payload from local claim/blame data.
|
||||||
*
|
*
|
||||||
* Fanavaran-sourced fields (PolicyId) use resolve-once caching:
|
* HTTP preview/submit always pass forceRefreshPolicy + forceRefreshUsedId,
|
||||||
* - If `fanavaranSync.baseClaim.policyId` exists → reuse (no policy HTTP).
|
* so PolicyId and AccidentVehicleUsedId are re-inquired every call.
|
||||||
* - Else → live inquiry-my-policies once, then persist policyId + lastPayload.
|
|
||||||
*
|
|
||||||
* Auth still uses the shared midnight-TTL token cache (not a new login per preview).
|
* 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(
|
async previewFanavaranSubmitV2(
|
||||||
claimCaseId: string,
|
claimCaseId: string,
|
||||||
@@ -5577,7 +5505,7 @@ export class ClaimRequestManagementService {
|
|||||||
},
|
},
|
||||||
failureReason: null as string | null,
|
failureReason: null as string | null,
|
||||||
note:
|
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);
|
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({
|
const payload = await this.buildFanavaranDamageCasePayload({
|
||||||
claimCase,
|
claimCase,
|
||||||
blameCase,
|
blameCase,
|
||||||
@@ -5935,7 +5878,7 @@ export class ClaimRequestManagementService {
|
|||||||
claimId: claimCase.claimId ?? null,
|
claimId: claimCase.claimId ?? null,
|
||||||
dmgCaseId: claimCase.dmgCaseId ?? null,
|
dmgCaseId: claimCase.dmgCaseId ?? null,
|
||||||
submitUrl: claimCase.claimId
|
submitUrl: claimCase.claimId
|
||||||
? `${fanavaranClaimsBaseUrl(fanavaranClaimProductFromBlameType(blameCase.type))}/${claimCase.claimId}/dmg-cases`
|
? fanavaranClaimStageUrl(product, "damage-case", claimCase.claimId)
|
||||||
: null,
|
: null,
|
||||||
warning: warnings.length ? warnings.join(" ") : undefined,
|
warning: warnings.length ? warnings.join(" ") : undefined,
|
||||||
payload,
|
payload,
|
||||||
@@ -6775,15 +6718,14 @@ export class ClaimRequestManagementService {
|
|||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
const submittedAt = reply?.submittedAt ?? new Date();
|
const submittedAt = reply?.submittedAt ?? new Date();
|
||||||
|
const product = await this.resolveFanavaranClaimProduct(
|
||||||
|
input.claimCase?._id ? String(input.claimCase._id) : undefined,
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
const payload: Record<string, unknown> = {
|
||||||
replyKey: active?.replyKey,
|
|
||||||
warnings,
|
|
||||||
payload: {
|
|
||||||
ClaimExpertId: claimExpertId,
|
ClaimExpertId: claimExpertId,
|
||||||
ComponentReplacementCost: componentReplacementCost,
|
ComponentReplacementCost: componentReplacementCost,
|
||||||
DmgAssessmentDate: this.convertToPersianDate(submittedAt),
|
DmgAssessmentDate: this.convertToPersianDate(submittedAt),
|
||||||
DmgCaseId: input.claimCase.dmgCaseId ?? null,
|
|
||||||
InspectionPlaceId: inspectionPlaceId,
|
InspectionPlaceId: inspectionPlaceId,
|
||||||
InspectionTime: this.getTime24Hour(submittedAt),
|
InspectionTime: this.getTime24Hour(submittedAt),
|
||||||
RepairWage: repairWage,
|
RepairWage: repairWage,
|
||||||
@@ -6795,18 +6737,28 @@ export class ClaimRequestManagementService {
|
|||||||
DamagedVehicleCurrentPrice:
|
DamagedVehicleCurrentPrice:
|
||||||
this.parseFanavaranMoney(priceDrop?.carPrice) || null,
|
this.parseFanavaranMoney(priceDrop?.carPrice) || null,
|
||||||
DmgSections: dmgSections,
|
DmgSections: dmgSections,
|
||||||
},
|
};
|
||||||
|
if (fanavaranHasDamageCaseStage(product)) {
|
||||||
|
payload.DmgCaseId = input.claimCase.dmgCaseId ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
replyKey: active?.replyKey,
|
||||||
|
warnings,
|
||||||
|
payload,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private assertFanavaranExpertisePayloadReady(
|
private assertFanavaranExpertisePayloadReady(
|
||||||
payload: Record<string, unknown>,
|
payload: Record<string, unknown>,
|
||||||
warnings: string[],
|
warnings: string[],
|
||||||
|
options?: { requireDmgCaseId?: boolean },
|
||||||
): void {
|
): void {
|
||||||
const sections = Array.isArray(payload.DmgSections)
|
const sections = Array.isArray(payload.DmgSections)
|
||||||
? 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)
|
if (!payload.InspectionPlaceId)
|
||||||
warnings.push("InspectionPlaceId is required.");
|
warnings.push("InspectionPlaceId is required.");
|
||||||
if (!payload.DropAmountStatus)
|
if (!payload.DropAmountStatus)
|
||||||
@@ -6876,17 +6828,19 @@ export class ClaimRequestManagementService {
|
|||||||
);
|
);
|
||||||
const claimCase = await this.claimCaseDbService.findById(claimCaseId);
|
const claimCase = await this.claimCaseDbService.findById(claimCaseId);
|
||||||
if (!claimCase) throw new NotFoundException("Claim case not found");
|
if (!claimCase) throw new NotFoundException("Claim case not found");
|
||||||
let payload: Record<string, unknown>;
|
const product = await this.resolveFanavaranClaimProduct(claimCaseId);
|
||||||
if (bodyOverride) {
|
|
||||||
payload = bodyOverride;
|
|
||||||
} else {
|
|
||||||
const built = await this.buildFanavaranExpertisePayload({
|
const built = await this.buildFanavaranExpertisePayload({
|
||||||
claimCase,
|
claimCase,
|
||||||
clientKey,
|
clientKey,
|
||||||
});
|
});
|
||||||
payload = built.payload;
|
const payload = applyFanavaranManualPayloadOverrides(
|
||||||
this.assertFanavaranExpertisePayloadReady(payload, built.warnings);
|
built.payload,
|
||||||
}
|
bodyOverride,
|
||||||
|
FANAVARAN_EXPERTISE_MANUAL_OVERRIDE_KEYS,
|
||||||
|
);
|
||||||
|
this.assertFanavaranExpertisePayloadReady(payload, built.warnings, {
|
||||||
|
requireDmgCaseId: fanavaranHasDamageCaseStage(product),
|
||||||
|
});
|
||||||
return await this.executeFanavaranExpertiseSubmit(
|
return await this.executeFanavaranExpertiseSubmit(
|
||||||
claimCaseId,
|
claimCaseId,
|
||||||
clientKey,
|
clientKey,
|
||||||
@@ -6910,7 +6864,11 @@ export class ClaimRequestManagementService {
|
|||||||
auditSource: FanavaranAuditSource,
|
auditSource: FanavaranAuditSource,
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
let claimCase = await this.claimCaseDbService.findById(claimCaseId);
|
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(
|
await this.ensureFanavaranDamageCase(
|
||||||
claimCaseId,
|
claimCaseId,
|
||||||
clientKey,
|
clientKey,
|
||||||
@@ -7063,7 +7021,8 @@ export class ClaimRequestManagementService {
|
|||||||
await this.autoSubmitToFanavaranV2OnClaimCreated(claimCaseId);
|
await this.autoSubmitToFanavaranV2OnClaimCreated(claimCaseId);
|
||||||
claimCase = await this.claimCaseDbService.findById(claimCaseId);
|
claimCase = await this.claimCaseDbService.findById(claimCaseId);
|
||||||
}
|
}
|
||||||
if (!claimCase?.dmgCaseId) {
|
const product = await this.resolveFanavaranClaimProduct(claimCaseId);
|
||||||
|
if (fanavaranHasDamageCaseStage(product) && !claimCase?.dmgCaseId) {
|
||||||
await this.autoSubmitFanavaranDamageCaseOnOuterPartsSelected(
|
await this.autoSubmitFanavaranDamageCaseOnOuterPartsSelected(
|
||||||
claimCaseId,
|
claimCaseId,
|
||||||
claimCase?.damage?.selectedParts ?? [],
|
claimCase?.damage?.selectedParts ?? [],
|
||||||
@@ -7075,7 +7034,9 @@ export class ClaimRequestManagementService {
|
|||||||
claimCase,
|
claimCase,
|
||||||
clientKey,
|
clientKey,
|
||||||
});
|
});
|
||||||
this.assertFanavaranExpertisePayloadReady(payload, warnings);
|
this.assertFanavaranExpertisePayloadReady(payload, warnings, {
|
||||||
|
requireDmgCaseId: fanavaranHasDamageCaseStage(product),
|
||||||
|
});
|
||||||
|
|
||||||
const fanavaranResponse = await this.executeFanavaranExpertiseSubmit(
|
const fanavaranResponse = await this.executeFanavaranExpertiseSubmit(
|
||||||
claimCaseId,
|
claimCaseId,
|
||||||
@@ -7245,7 +7206,11 @@ export class ClaimRequestManagementService {
|
|||||||
clientKey: FanavaranClientKey,
|
clientKey: FanavaranClientKey,
|
||||||
auditSource: FanavaranAuditSource,
|
auditSource: FanavaranAuditSource,
|
||||||
selectedParts?: unknown[],
|
selectedParts?: unknown[],
|
||||||
): Promise<{ claimId: number; dmgCaseId: number; created: boolean }> {
|
): Promise<{
|
||||||
|
claimId: number;
|
||||||
|
dmgCaseId: number | null;
|
||||||
|
created: boolean;
|
||||||
|
}> {
|
||||||
await this.ensureFanavaranBaseClaim(claimCaseId, clientKey, auditSource);
|
await this.ensureFanavaranBaseClaim(claimCaseId, clientKey, auditSource);
|
||||||
|
|
||||||
const existing = await this.claimCaseDbService.findById(claimCaseId);
|
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(
|
this.logger.log(
|
||||||
`[Fanavaran ${clientKey}] Soft-ensuring damage case before next stage claimCaseId=${claimCaseId}`,
|
`[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(
|
this.logger.log(
|
||||||
`[executeFanavaranDamageCaseSubmit] Already have dmgCaseId=${claimCase.dmgCaseId}; skipping Fanavaran POST`,
|
`[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,
|
claimCase,
|
||||||
blameCase,
|
blameCase,
|
||||||
selectedParts: selectedParts ?? claimCase.damage?.selectedParts ?? [],
|
selectedParts: selectedParts ?? claimCase.damage?.selectedParts ?? [],
|
||||||
@@ -7430,11 +7436,15 @@ export class ClaimRequestManagementService {
|
|||||||
defaults: profile.defaults,
|
defaults: profile.defaults,
|
||||||
registerMissingPerson: true,
|
registerMissingPerson: true,
|
||||||
});
|
});
|
||||||
|
const payload = applyFanavaranManualPayloadOverrides(
|
||||||
|
builtPayload,
|
||||||
|
bodyOverride,
|
||||||
|
FANAVARAN_DAMAGE_MANUAL_OVERRIDE_KEYS,
|
||||||
|
);
|
||||||
|
|
||||||
// Guardrail: never send empty/null licence fields even if manual overrides arrive.
|
// Guardrail: never send empty/null licence fields even if manual overrides arrive.
|
||||||
payload.LicenceNo = this.ensureFanavaranNonEmptyLicenceNo(payload.LicenceNo);
|
payload.LicenceNo = this.ensureFanavaranNonEmptyLicenceNo(payload.LicenceNo);
|
||||||
|
|
||||||
const url = `${fanavaranClaimsBaseUrl(fanavaranClaimProductFromBlameType(blameCase.type))}/${claimCase.claimId}/dmg-cases`;
|
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
`[executeFanavaranDamageCaseSubmit] claimCaseId=${claimCaseId} claimId=${claimCase.claimId} payload.DriverId=${payload.DriverId ?? "NULL"} payload keys=${Object.keys(payload).join(", ")}`,
|
`[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,
|
clientKey,
|
||||||
selectedParts,
|
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;
|
const dmgCaseId = fanavaranResponse?.Id;
|
||||||
|
|
||||||
await this.claimCaseDbService.findByIdAndUpdate(claimCaseId, {
|
await this.claimCaseDbService.findByIdAndUpdate(claimCaseId, {
|
||||||
@@ -8208,7 +8228,7 @@ export class ClaimRequestManagementService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const existing = await this.claimCaseDbService.findById(claimCaseId);
|
const existing = await this.claimCaseDbService.findById(claimCaseId);
|
||||||
if (existing?.claimId != null && !bodyOverride) {
|
if (existing?.claimId != null) {
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
`${logPrefix} Base claim already exists claimId=${existing.claimId}; skipping Fanavaran POST`,
|
`${logPrefix} Base claim already exists claimId=${existing.claimId}; skipping Fanavaran POST`,
|
||||||
);
|
);
|
||||||
@@ -8227,14 +8247,19 @@ export class ClaimRequestManagementService {
|
|||||||
claimCaseId,
|
claimCaseId,
|
||||||
};
|
};
|
||||||
|
|
||||||
const fanavaranData =
|
const built =
|
||||||
bodyOverride ??
|
await this.previewFanavaranSubmitV2(claimCaseId, clientKey, {
|
||||||
(await this.previewFanavaranSubmitV2(claimCaseId, clientKey, {
|
|
||||||
requirePolicyId: true,
|
requirePolicyId: true,
|
||||||
auditSession,
|
auditSession,
|
||||||
persistPayload: true,
|
persistPayload: true,
|
||||||
|
forceRefreshPolicy: true,
|
||||||
forceRefreshUsedId: 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.
|
// Guardrail: never send empty/null licence fields even if manual overrides arrive.
|
||||||
fanavaranData.CulpritLicenceNo = this.ensureFanavaranNonEmptyLicenceNo(
|
fanavaranData.CulpritLicenceNo = this.ensureFanavaranNonEmptyLicenceNo(
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { PartyRole } from "src/request-management/entities/schema/partyRole.enum
|
|||||||
import {
|
import {
|
||||||
fanavaranClaimProductFromBlameType,
|
fanavaranClaimProductFromBlameType,
|
||||||
fanavaranClaimsBaseUrl,
|
fanavaranClaimsBaseUrl,
|
||||||
|
fanavaranClaimStageUrl,
|
||||||
|
fanavaranHasDamageCaseStage,
|
||||||
fanavaranPartyInquirySources,
|
fanavaranPartyInquirySources,
|
||||||
isFanavaranSubmitSupportedBlameType,
|
isFanavaranSubmitSupportedBlameType,
|
||||||
pickCarBodyPolicyNationalCode,
|
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", () => {
|
it("allows Fanavaran submit for both car products", () => {
|
||||||
expect(isFanavaranSubmitSupportedBlameType("THIRD_PARTY")).toBe(true);
|
expect(isFanavaranSubmitSupportedBlameType("THIRD_PARTY")).toBe(true);
|
||||||
expect(isFanavaranSubmitSupportedBlameType("CAR_BODY")).toBe(true);
|
expect(isFanavaranSubmitSupportedBlameType("CAR_BODY")).toBe(true);
|
||||||
|
|||||||
@@ -46,6 +46,35 @@ export function fanavaranClaimsBaseUrl(
|
|||||||
return `${FANAVARAN_CLAIMS_HOST}/${fanavaranClaimsResourcePath(product)}`;
|
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(
|
export function fanavaranInsuranceLineIdForProduct(
|
||||||
product: FanavaranClaimProduct,
|
product: FanavaranClaimProduct,
|
||||||
): number {
|
): number {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
} from "@nestjs/common";
|
} from "@nestjs/common";
|
||||||
import {
|
import {
|
||||||
ApiBearerAuth,
|
ApiBearerAuth,
|
||||||
|
ApiBody,
|
||||||
ApiOperation,
|
ApiOperation,
|
||||||
ApiParam,
|
ApiParam,
|
||||||
ApiQuery,
|
ApiQuery,
|
||||||
@@ -57,7 +58,7 @@ export class FanavaranController {
|
|||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: "Preview Fanavaran base claim create payload",
|
summary: "Preview Fanavaran base claim create payload",
|
||||||
description:
|
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({
|
@ApiParam({
|
||||||
name: "client",
|
name: "client",
|
||||||
@@ -76,34 +77,30 @@ export class FanavaranController {
|
|||||||
@ApiQuery({
|
@ApiQuery({
|
||||||
name: "forceRefreshPolicy",
|
name: "forceRefreshPolicy",
|
||||||
required: false,
|
required: false,
|
||||||
description:
|
deprecated: true,
|
||||||
"When true, ignores cached PolicyId and performs a live Fanavaran policy inquiry again. Do not pass this from normal UI loads.",
|
description: "Ignored. Preview always re-inquires PolicyId.",
|
||||||
})
|
})
|
||||||
@ApiQuery({
|
@ApiQuery({
|
||||||
name: "resolvePolicy",
|
name: "resolvePolicy",
|
||||||
required: false,
|
required: false,
|
||||||
deprecated: true,
|
deprecated: true,
|
||||||
description:
|
description: "Ignored. Preview always re-inquires PolicyId.",
|
||||||
"Deprecated. Ignored for cache-busting. PolicyId is resolve-once from fanavaranSync.baseClaim.policyId; use forceRefreshPolicy=true only to re-inquire.",
|
|
||||||
})
|
})
|
||||||
async preview(
|
async preview(
|
||||||
@Param("client") client: string,
|
@Param("client") client: string,
|
||||||
@Param("claimCaseId") claimCaseId: string,
|
@Param("claimCaseId") claimCaseId: string,
|
||||||
@Query("debug") debug?: string,
|
@Query("debug") debug?: string,
|
||||||
@Query("forceRefreshPolicy") forceRefreshPolicy?: string,
|
@Query("forceRefreshPolicy") _forceRefreshPolicy?: string,
|
||||||
@Query("resolvePolicy") _resolvePolicy?: string,
|
@Query("resolvePolicy") _resolvePolicy?: string,
|
||||||
) {
|
) {
|
||||||
const clientKey = this.parseClientParam(client);
|
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(
|
return await this.claimRequestManagementService.previewFanavaranSubmitV2(
|
||||||
claimCaseId,
|
claimCaseId,
|
||||||
clientKey,
|
clientKey,
|
||||||
{
|
{
|
||||||
debug: debug === "1" || debug === "true",
|
debug: debug === "1" || debug === "true",
|
||||||
forceRefreshPolicy:
|
forceRefreshPolicy: true,
|
||||||
forceRefreshPolicy === "1" || forceRefreshPolicy === "true",
|
forceRefreshUsedId: true,
|
||||||
requirePolicyId: false,
|
requirePolicyId: false,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -113,7 +110,7 @@ export class FanavaranController {
|
|||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: "Submit Fanavaran base claim create request",
|
summary: "Submit Fanavaran base claim create request",
|
||||||
description:
|
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({
|
@ApiParam({
|
||||||
name: "client",
|
name: "client",
|
||||||
@@ -124,6 +121,12 @@ export class FanavaranController {
|
|||||||
name: "claimCaseId",
|
name: "claimCaseId",
|
||||||
description: "Claim case MongoDB ObjectId",
|
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(
|
async submit(
|
||||||
@Param("client") client: string,
|
@Param("client") client: string,
|
||||||
@Param("claimCaseId") claimCaseId: string,
|
@Param("claimCaseId") claimCaseId: string,
|
||||||
@@ -141,7 +144,7 @@ export class FanavaranController {
|
|||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: "Preview Fanavaran damage-case payload",
|
summary: "Preview Fanavaran damage-case payload",
|
||||||
description:
|
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({
|
@ApiParam({
|
||||||
name: "client",
|
name: "client",
|
||||||
@@ -167,7 +170,7 @@ export class FanavaranController {
|
|||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: "Submit Fanavaran damage-case request",
|
summary: "Submit Fanavaran damage-case request",
|
||||||
description:
|
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({
|
@ApiParam({
|
||||||
name: "client",
|
name: "client",
|
||||||
@@ -178,6 +181,12 @@ export class FanavaranController {
|
|||||||
name: "claimCaseId",
|
name: "claimCaseId",
|
||||||
description: "Claim case MongoDB ObjectId",
|
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(
|
async submitDamageCase(
|
||||||
@Param("client") client: string,
|
@Param("client") client: string,
|
||||||
@Param("claimCaseId") claimCaseId: string,
|
@Param("claimCaseId") claimCaseId: string,
|
||||||
@@ -247,7 +256,7 @@ export class FanavaranController {
|
|||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: "Preview Fanavaran expertise payload",
|
summary: "Preview Fanavaran expertise payload",
|
||||||
description:
|
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({
|
@ApiParam({
|
||||||
name: "client",
|
name: "client",
|
||||||
@@ -273,7 +282,7 @@ export class FanavaranController {
|
|||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: "Submit Fanavaran expertise request",
|
summary: "Submit Fanavaran expertise request",
|
||||||
description:
|
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({
|
@ApiParam({
|
||||||
name: "client",
|
name: "client",
|
||||||
@@ -284,6 +293,12 @@ export class FanavaranController {
|
|||||||
name: "claimCaseId",
|
name: "claimCaseId",
|
||||||
description: "Claim case MongoDB ObjectId",
|
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(
|
async submitExpertise(
|
||||||
@Param("client") client: string,
|
@Param("client") client: string,
|
||||||
@Param("claimCaseId") claimCaseId: string,
|
@Param("claimCaseId") claimCaseId: string,
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import {
|
|||||||
fanavaranPayloadMatchesDamagedIdentity,
|
fanavaranPayloadMatchesDamagedIdentity,
|
||||||
fanavaranVehicleMatchesDamagedIdentity,
|
fanavaranVehicleMatchesDamagedIdentity,
|
||||||
filterPoliciesByLine,
|
filterPoliciesByLine,
|
||||||
|
applyFanavaranManualPayloadOverrides,
|
||||||
|
resolveDamageCasePlaqueLookupIds,
|
||||||
insuranceLineLabel,
|
insuranceLineLabel,
|
||||||
parseLastCarPolicyInput,
|
parseLastCarPolicyInput,
|
||||||
pickFanavaranPlaqueLookupFields,
|
pickFanavaranPlaqueLookupFields,
|
||||||
@@ -465,4 +467,38 @@ describe("fanavaran last car policy", () => {
|
|||||||
),
|
),
|
||||||
).toBe(true);
|
).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";
|
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.
|
* Pick the VIN-inquiry / vehicle-GET row that is the damaged car.
|
||||||
* Newest VersionNo wins among matches; PlaqueKindId is a tie-break only.
|
* Newest VersionNo wins among matches; PlaqueKindId is a tie-break only.
|
||||||
|
|||||||
Reference in New Issue
Block a user