forked from Yara724/api
Merge pull request 'main' (#325) from s.hajizadeh/yara724api:main into main
Reviewed-on: Yara724/api#325
This commit is contained in:
@@ -49,13 +49,31 @@ import {
|
|||||||
pickStoredCarBodyPolicyId,
|
pickStoredCarBodyPolicyId,
|
||||||
type FanavaranClaimProduct,
|
type FanavaranClaimProduct,
|
||||||
} from "./fanavaran-claim-product";
|
} from "./fanavaran-claim-product";
|
||||||
|
import {
|
||||||
|
actualPremiumFromHullPolicyRecord,
|
||||||
|
customerIdFromHullPolicyRecord,
|
||||||
|
FANAVARAN_DEFAULT_HULL_CULPRIT_TYPE_ID,
|
||||||
|
fanavaranHullNestedClaimId,
|
||||||
|
toFanavaranHullBaseClaimPayload,
|
||||||
|
} from "./fanavaran-hull-base-claim";
|
||||||
import {
|
import {
|
||||||
collectFanavaranExpertiseReadinessWarnings,
|
collectFanavaranExpertiseReadinessWarnings,
|
||||||
hullExpertiseAssertFields,
|
hullExpertiseAssertFields,
|
||||||
|
mergeHullVehicleIdentityFromFanavaranVehicle,
|
||||||
pickHullVehicleIdentity,
|
pickHullVehicleIdentity,
|
||||||
toFanavaranHullExpertiseDmgSection,
|
toFanavaranHullExpertiseDmgSection,
|
||||||
toFanavaranHullExpertisePayload,
|
toFanavaranHullExpertisePayload,
|
||||||
|
vehicleCurrentValueFromHullPolicyRecord,
|
||||||
} from "./fanavaran-hull-expertise";
|
} from "./fanavaran-hull-expertise";
|
||||||
|
import {
|
||||||
|
asHullDmgAccessoryRows,
|
||||||
|
FANAVARAN_DEFAULT_HULL_DMG_COST_KIND_ID,
|
||||||
|
FANAVARAN_DEFAULT_HULL_DMG_KIND_ID,
|
||||||
|
FANAVARAN_HULL_DMG_COST_KIND_CAPTION,
|
||||||
|
FANAVARAN_HULL_DMG_KIND_CAPTION,
|
||||||
|
findLookupIdByCaption,
|
||||||
|
resolveVehicleHullAccessoryId,
|
||||||
|
} from "./fanavaran-hull-expertise-lookups";
|
||||||
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 {
|
||||||
@@ -123,6 +141,13 @@ import { PublicIdService } from "src/utils/public-id/public-id.service";
|
|||||||
import { ImageRequiredModel } from "./entites/schema/image-required.schema";
|
import { ImageRequiredModel } from "./entites/schema/image-required.schema";
|
||||||
import { DamageExpertDbService } from "src/users/entities/db-service/damage-expert.db.service";
|
import { DamageExpertDbService } from "src/users/entities/db-service/damage-expert.db.service";
|
||||||
import { FileMakerDbService } from "src/users/entities/db-service/file-maker.db.service";
|
import { FileMakerDbService } from "src/users/entities/db-service/file-maker.db.service";
|
||||||
|
import { FileReviewerDbService } from "src/users/entities/db-service/file-reviewer.db.service";
|
||||||
|
import {
|
||||||
|
assertFileMakerCanCreateBlameType,
|
||||||
|
assertFileReviewerCanReviewBlameType,
|
||||||
|
resolveFileMakerStageOneExpertId,
|
||||||
|
resolveFileReviewerExpertiseExpertId,
|
||||||
|
} from "src/fanavaran/fanavaran-file-role-expert-ids";
|
||||||
import { FieldExpertDbService } from "src/users/entities/db-service/field-expert.db.service";
|
import { FieldExpertDbService } from "src/users/entities/db-service/field-expert.db.service";
|
||||||
import { FanavaranLocationService } from "src/fanavaran/fanavaran-location.service";
|
import { FanavaranLocationService } from "src/fanavaran/fanavaran-location.service";
|
||||||
import { ExpertFileActivityDbService } from "src/users/entities/db-service/expert-file-activity.db.service";
|
import { ExpertFileActivityDbService } from "src/users/entities/db-service/expert-file-activity.db.service";
|
||||||
@@ -456,6 +481,7 @@ export class ClaimRequestManagementService {
|
|||||||
private readonly fanavaranLookupService: FanavaranLookupService,
|
private readonly fanavaranLookupService: FanavaranLookupService,
|
||||||
private readonly smsOrchestrationService: SmsOrchestrationService,
|
private readonly smsOrchestrationService: SmsOrchestrationService,
|
||||||
private readonly fileMakerDbService: FileMakerDbService,
|
private readonly fileMakerDbService: FileMakerDbService,
|
||||||
|
private readonly fileReviewerDbService: FileReviewerDbService,
|
||||||
private readonly fieldExpertDbService: FieldExpertDbService,
|
private readonly fieldExpertDbService: FieldExpertDbService,
|
||||||
private readonly plateNormalizer: PlateNormalizerService,
|
private readonly plateNormalizer: PlateNormalizerService,
|
||||||
private readonly fanavaranLocationService: FanavaranLocationService,
|
private readonly fanavaranLocationService: FanavaranLocationService,
|
||||||
@@ -4219,6 +4245,307 @@ export class ClaimRequestManagementService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** GEN.03 hull: TotalPremium + parties inquiry (AccidentCulpritId) before hull field whitelist. */
|
||||||
|
private async enrichCarBodyHullBaseClaimPayload(input: {
|
||||||
|
clientKey: FanavaranClientKey;
|
||||||
|
payload: Record<string, unknown>;
|
||||||
|
blameCase: { parties?: unknown[] };
|
||||||
|
/** Blame `Party` row (FIRST party for CAR_BODY); person shape is read via pickPerson* helpers. */
|
||||||
|
firstParty?: { person?: unknown };
|
||||||
|
logPrefix: string;
|
||||||
|
}): Promise<void> {
|
||||||
|
const policyId = parseFanavaranId(input.payload.PolicyId);
|
||||||
|
if (policyId == null) return;
|
||||||
|
|
||||||
|
let policyRecord: Record<string, unknown> | null = null;
|
||||||
|
try {
|
||||||
|
policyRecord = asObjectRecord(
|
||||||
|
await this.fanavaranLookupService.bodyPolicyById(
|
||||||
|
input.clientKey,
|
||||||
|
policyId,
|
||||||
|
{
|
||||||
|
contractIdOverride: resolveFanavaranProductContractId(
|
||||||
|
input.clientKey,
|
||||||
|
"car-body",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.warn(
|
||||||
|
`${input.logPrefix} hull base claim body policy GET ${policyId} failed: ${
|
||||||
|
error instanceof Error ? error.message : error
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const actualPremium = actualPremiumFromHullPolicyRecord(policyRecord);
|
||||||
|
if (actualPremium != null) {
|
||||||
|
input.payload.ActualPremium = actualPremium;
|
||||||
|
}
|
||||||
|
|
||||||
|
const person = input.firstParty?.person as
|
||||||
|
| {
|
||||||
|
driverIsInsurer?: boolean;
|
||||||
|
nationalCodeOfDriver?: unknown;
|
||||||
|
nationalCodeOfInsurer?: unknown;
|
||||||
|
nationalCode?: unknown;
|
||||||
|
birthday?: unknown;
|
||||||
|
driverBirthday?: unknown;
|
||||||
|
insurerBirthday?: unknown;
|
||||||
|
}
|
||||||
|
| undefined;
|
||||||
|
|
||||||
|
const nationalCode =
|
||||||
|
pickPersonNationalCode(person) ??
|
||||||
|
pickCarBodyPolicyNationalCode(
|
||||||
|
input.blameCase as { parties?: unknown[] },
|
||||||
|
);
|
||||||
|
const birthday = pickPersonBirthday(person);
|
||||||
|
const driverIsInsurer = person?.driverIsInsurer ?? true;
|
||||||
|
|
||||||
|
let accidentCulpritId: number | null = null;
|
||||||
|
if (nationalCode && birthday) {
|
||||||
|
accidentCulpritId = await this.resolveDriverFanavaranId(
|
||||||
|
input.clientKey,
|
||||||
|
nationalCode,
|
||||||
|
birthday,
|
||||||
|
driverIsInsurer,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (accidentCulpritId == null) {
|
||||||
|
accidentCulpritId = customerIdFromHullPolicyRecord(policyRecord);
|
||||||
|
if (accidentCulpritId != null) {
|
||||||
|
this.logger.log(
|
||||||
|
`${input.logPrefix} AccidentCulpritId from policy CustomerId=${accidentCulpritId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (accidentCulpritId != null) {
|
||||||
|
input.payload.AccidentCulpritId = accidentCulpritId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const culpritTypeId =
|
||||||
|
parseFanavaranId(input.payload.CulpritTypeId) ??
|
||||||
|
FANAVARAN_DEFAULT_HULL_CULPRIT_TYPE_ID;
|
||||||
|
if (
|
||||||
|
input.payload.CustomerFaultPercent == null &&
|
||||||
|
culpritTypeId === 300
|
||||||
|
) {
|
||||||
|
input.payload.CustomerFaultPercent = 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** GEN.06: VehicleCurrentValue from hull policy when price-drop / frontend value is missing. */
|
||||||
|
private async resolveHullVehicleCurrentValueFromPolicy(
|
||||||
|
clientKey: FanavaranClientKey,
|
||||||
|
claimCase: {
|
||||||
|
fanavaranSync?: {
|
||||||
|
baseClaim?: { policyId?: unknown; lastPayload?: { PolicyId?: unknown } };
|
||||||
|
};
|
||||||
|
},
|
||||||
|
): Promise<number | null> {
|
||||||
|
const policyId = parseFanavaranId(
|
||||||
|
claimCase?.fanavaranSync?.baseClaim?.policyId ??
|
||||||
|
claimCase?.fanavaranSync?.baseClaim?.lastPayload?.PolicyId,
|
||||||
|
);
|
||||||
|
if (policyId == null) return null;
|
||||||
|
try {
|
||||||
|
const policy = asObjectRecord(
|
||||||
|
await this.fanavaranLookupService.bodyPolicyById(
|
||||||
|
clientKey,
|
||||||
|
policyId,
|
||||||
|
{
|
||||||
|
contractIdOverride: resolveFanavaranProductContractId(
|
||||||
|
clientKey,
|
||||||
|
"car-body",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return vehicleCurrentValueFromHullPolicyRecord(policy);
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.warn(
|
||||||
|
`[resolveHullVehicleCurrentValueFromPolicy] body policy GET ${policyId} failed: ${
|
||||||
|
error instanceof Error ? error.message : error
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseHullClaimPolicyId(claimCase: {
|
||||||
|
fanavaranSync?: {
|
||||||
|
baseClaim?: { policyId?: unknown; lastPayload?: { PolicyId?: unknown } };
|
||||||
|
};
|
||||||
|
}): number | null {
|
||||||
|
return parseFanavaranId(
|
||||||
|
claimCase?.fanavaranSync?.baseClaim?.policyId ??
|
||||||
|
claimCase?.fanavaranSync?.baseClaim?.lastPayload?.PolicyId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async buildCarBodyFanavaranExpertisePayload(input: {
|
||||||
|
clientKey: FanavaranClientKey;
|
||||||
|
claimCase: any;
|
||||||
|
blame: { parties?: unknown[] } | null;
|
||||||
|
profile: ReturnType<typeof getFanavaranClientProfile>;
|
||||||
|
parts: any[];
|
||||||
|
claimExpertId: number;
|
||||||
|
submittedAt: Date;
|
||||||
|
priceDropTotal: number;
|
||||||
|
priceDropCarPrice: unknown;
|
||||||
|
warnings: string[];
|
||||||
|
replyKey?: string;
|
||||||
|
}): Promise<{
|
||||||
|
payload: Record<string, unknown>;
|
||||||
|
warnings: string[];
|
||||||
|
replyKey?: string;
|
||||||
|
}> {
|
||||||
|
const policyId = this.parseHullClaimPolicyId(input.claimCase);
|
||||||
|
const contractOptions = {
|
||||||
|
contractIdOverride: resolveFanavaranProductContractId(
|
||||||
|
input.clientKey,
|
||||||
|
"car-body",
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const [dmgKindRows, dmgCostKindRows, policyRecord, accessoriesRaw] =
|
||||||
|
await Promise.all([
|
||||||
|
this.getFanavaranLookupRows(input.clientKey, "vehicle-hull-dmg-kind"),
|
||||||
|
this.getFanavaranLookupRows(
|
||||||
|
input.clientKey,
|
||||||
|
"vehicle-hull-dmg-cost-kinds",
|
||||||
|
),
|
||||||
|
policyId != null
|
||||||
|
? this.fanavaranLookupService.bodyPolicyById(
|
||||||
|
input.clientKey,
|
||||||
|
policyId,
|
||||||
|
contractOptions,
|
||||||
|
)
|
||||||
|
: Promise.resolve(null),
|
||||||
|
policyId != null
|
||||||
|
? this.fanavaranLookupService.vehicleHullDmgAccessoriesByPolicyId(
|
||||||
|
input.clientKey,
|
||||||
|
policyId,
|
||||||
|
contractOptions,
|
||||||
|
)
|
||||||
|
: Promise.resolve([]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const dmgKindId =
|
||||||
|
findLookupIdByCaption(dmgKindRows, FANAVARAN_HULL_DMG_KIND_CAPTION) ??
|
||||||
|
FANAVARAN_DEFAULT_HULL_DMG_KIND_ID;
|
||||||
|
const dmgCostKindId =
|
||||||
|
findLookupIdByCaption(
|
||||||
|
dmgCostKindRows,
|
||||||
|
FANAVARAN_HULL_DMG_COST_KIND_CAPTION,
|
||||||
|
) ?? FANAVARAN_DEFAULT_HULL_DMG_COST_KIND_ID;
|
||||||
|
|
||||||
|
const accessories = asHullDmgAccessoryRows(accessoriesRaw);
|
||||||
|
if (policyId != null && accessories.length === 0) {
|
||||||
|
input.warnings.push(
|
||||||
|
`No hull dmg-accessories for policy ${policyId}; VehicleHullAccessoryId may be missing.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let vehicle = pickHullVehicleIdentity(input.blame);
|
||||||
|
let colorId: number | null = null;
|
||||||
|
const policy = asObjectRecord(policyRecord);
|
||||||
|
const vehicleId = pickVehicleId(policy?.VehicleId ?? policy?.vehicleId);
|
||||||
|
const versionNo = parseFanavaranId(policy?.VehicleVersionNo);
|
||||||
|
if (vehicleId != null) {
|
||||||
|
try {
|
||||||
|
const vehicleRow = asObjectRecord(
|
||||||
|
await this.fanavaranLookupService.vehicleById(
|
||||||
|
input.clientKey,
|
||||||
|
vehicleId,
|
||||||
|
versionNo ?? undefined,
|
||||||
|
contractOptions,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
vehicle = mergeHullVehicleIdentityFromFanavaranVehicle(
|
||||||
|
vehicle,
|
||||||
|
vehicleRow,
|
||||||
|
);
|
||||||
|
colorId = parseFanavaranId(vehicleRow.ColorId);
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.warn(
|
||||||
|
`[buildCarBodyFanavaranExpertisePayload] vehicle GET ${vehicleId} failed: ${
|
||||||
|
error instanceof Error ? error.message : error
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const wage = input.parts.reduce(
|
||||||
|
(sum, part) => sum + this.parseFanavaranMoney(part?.salary),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
const componentReplacementCost = input.parts.reduce(
|
||||||
|
(sum, part) => sum + this.parseFanavaranMoney(part?.price),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
const wasteValue = input.parts.reduce(
|
||||||
|
(sum, part) => sum + this.fanavaranDaghiWasteValue(part?.daghi),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
const hullSections = input.parts.map((part: any) => {
|
||||||
|
const accessoryKindId = parseCatalogPartIdInput(part?.partId);
|
||||||
|
const vehicleHullAccessoryId = resolveVehicleHullAccessoryId(
|
||||||
|
accessories,
|
||||||
|
accessoryKindId,
|
||||||
|
);
|
||||||
|
if (vehicleHullAccessoryId == null) {
|
||||||
|
input.warnings.push(
|
||||||
|
`No VehicleHullAccessoryId for part "${this.fanavaranPartLabel(part)}" (AccessoryKindId=${accessoryKindId ?? "none"}).`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return toFanavaranHullExpertiseDmgSection({
|
||||||
|
partId: part?.partId,
|
||||||
|
desc: this.fanavaranPartLabel(part),
|
||||||
|
wasteValue: this.fanavaranDaghiWasteValue(part?.daghi),
|
||||||
|
amount:
|
||||||
|
this.parseFanavaranMoney(part?.price) +
|
||||||
|
this.parseFanavaranMoney(part?.salary),
|
||||||
|
dmgKindId,
|
||||||
|
dmgCostKindId,
|
||||||
|
vehicleHullAccessoryId,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let vehicleCurrentValue =
|
||||||
|
this.parseFanavaranMoney(input.priceDropCarPrice) || null;
|
||||||
|
if (vehicleCurrentValue == null) {
|
||||||
|
vehicleCurrentValue = vehicleCurrentValueFromHullPolicyRecord(policy);
|
||||||
|
}
|
||||||
|
|
||||||
|
const repairDuration =
|
||||||
|
typeof input.profile.defaults.HullExpertiseRepairDuration === "number"
|
||||||
|
? input.profile.defaults.HullExpertiseRepairDuration
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
replyKey: input.replyKey,
|
||||||
|
warnings: input.warnings,
|
||||||
|
payload: toFanavaranHullExpertisePayload({
|
||||||
|
claimExpertId: input.claimExpertId,
|
||||||
|
dmgAssessmentDate: this.convertToPersianDate(input.submittedAt),
|
||||||
|
inspectionTime: this.getTime24Hour(input.submittedAt),
|
||||||
|
wage,
|
||||||
|
componentReplacementCost,
|
||||||
|
wasteValue,
|
||||||
|
dropAmount: input.priceDropTotal || 0,
|
||||||
|
vehicleCurrentValue,
|
||||||
|
vehicle,
|
||||||
|
colorId,
|
||||||
|
repairDuration,
|
||||||
|
dmgSections: hullSections,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private async buildFanavaranDamageCasePayload(input: {
|
private async buildFanavaranDamageCasePayload(input: {
|
||||||
claimCase: any;
|
claimCase: any;
|
||||||
blameCase?: any;
|
blameCase?: any;
|
||||||
@@ -4860,6 +5187,55 @@ export class ClaimRequestManagementService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* V4/V5 FileMaker flows: GEN.03 ClaimExpertId from file-maker; GEN.06 from file-reviewer.
|
||||||
|
* Returns null for non–file-maker blames (tenant defaults apply).
|
||||||
|
*/
|
||||||
|
private async resolveFanavaranFileRoleExpertIdsForBlame(blame: {
|
||||||
|
isMadeByFileMaker?: boolean;
|
||||||
|
type?: BlameRequestType;
|
||||||
|
initiatedByFieldExpertId?: unknown;
|
||||||
|
assignedFileReviewerId?: unknown;
|
||||||
|
}): Promise<{
|
||||||
|
baseClaimExpertId: number | null;
|
||||||
|
expertiseClaimExpertId: number | null;
|
||||||
|
} | null> {
|
||||||
|
if (!blame?.isMadeByFileMaker || blame.type == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let baseClaimExpertId: number | null = null;
|
||||||
|
let expertiseClaimExpertId: number | null = null;
|
||||||
|
|
||||||
|
if (blame.initiatedByFieldExpertId) {
|
||||||
|
const fileMaker = await this.fileMakerDbService.findById(
|
||||||
|
String(blame.initiatedByFieldExpertId),
|
||||||
|
);
|
||||||
|
if (fileMaker) {
|
||||||
|
assertFileMakerCanCreateBlameType(fileMaker, blame.type);
|
||||||
|
baseClaimExpertId = resolveFileMakerStageOneExpertId(
|
||||||
|
fileMaker,
|
||||||
|
blame.type,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blame.assignedFileReviewerId) {
|
||||||
|
const fileReviewer = await this.fileReviewerDbService.findById(
|
||||||
|
String(blame.assignedFileReviewerId),
|
||||||
|
);
|
||||||
|
if (fileReviewer) {
|
||||||
|
assertFileReviewerCanReviewBlameType(fileReviewer, blame.type);
|
||||||
|
expertiseClaimExpertId = resolveFileReviewerExpertiseExpertId(
|
||||||
|
fileReviewer,
|
||||||
|
blame.type,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { baseClaimExpertId, expertiseClaimExpertId };
|
||||||
|
}
|
||||||
|
|
||||||
private async getPolicyIdFromNationalCode(
|
private async getPolicyIdFromNationalCode(
|
||||||
nationalCodeOfInsurer: string,
|
nationalCodeOfInsurer: string,
|
||||||
config: {
|
config: {
|
||||||
@@ -5743,6 +6119,32 @@ export class ClaimRequestManagementService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fanavaranClaimProductFromBlameType(blameCase.type) === "car-body") {
|
||||||
|
await this.enrichCarBodyHullBaseClaimPayload({
|
||||||
|
clientKey,
|
||||||
|
payload,
|
||||||
|
blameCase,
|
||||||
|
firstParty,
|
||||||
|
logPrefix,
|
||||||
|
});
|
||||||
|
const hull = toFanavaranHullBaseClaimPayload(payload);
|
||||||
|
for (const key of Object.keys(payload)) delete payload[key];
|
||||||
|
Object.assign(payload, hull);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileRoleExpertIds =
|
||||||
|
await this.resolveFanavaranFileRoleExpertIdsForBlame(blameCase);
|
||||||
|
if (fileRoleExpertIds) {
|
||||||
|
if (fileRoleExpertIds.baseClaimExpertId == null) {
|
||||||
|
throw new BadRequestException({
|
||||||
|
code: "FILE_MAKER_FANAVARAN_EXPERT_CODE_MISSING",
|
||||||
|
message:
|
||||||
|
"This FileMaker file cannot be submitted to Fanavaran: stage-1 claim expert id is missing for this product line.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
payload.ClaimExpertId = fileRoleExpertIds.baseClaimExpertId;
|
||||||
|
}
|
||||||
|
|
||||||
if (persistPayload) {
|
if (persistPayload) {
|
||||||
await this.claimCaseDbService.findByIdAndUpdate(claimCaseId, {
|
await this.claimCaseDbService.findByIdAndUpdate(claimCaseId, {
|
||||||
$set: {
|
$set: {
|
||||||
@@ -6153,7 +6555,34 @@ export class ClaimRequestManagementService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = `${await this.fanavaranClaimsUrlForClaim(claimCaseId)}/${claimCase.claimId}/files`;
|
await this.syncFanavaranHullBaseClaim(claimCaseId, clientKey);
|
||||||
|
claimCase = await this.claimCaseDbService.findById(claimCaseId);
|
||||||
|
const nestedClaimId = fanavaranHullNestedClaimId({
|
||||||
|
claimId: claimCase?.claimId,
|
||||||
|
claimNo: claimCase?.claimNo,
|
||||||
|
});
|
||||||
|
if (nestedClaimId == null) {
|
||||||
|
return {
|
||||||
|
clientKey,
|
||||||
|
claimCaseId,
|
||||||
|
totalLocalImages: candidates.length,
|
||||||
|
skippedAlreadySubmitted: candidates.length - pending.length,
|
||||||
|
attempted: 0,
|
||||||
|
submitted: 0,
|
||||||
|
failed: 0,
|
||||||
|
skipped: pending.length,
|
||||||
|
warning: "Fanavaran base claimId is missing",
|
||||||
|
results: pending.map((c) => ({
|
||||||
|
attempted: false,
|
||||||
|
submitted: false,
|
||||||
|
skipped: true,
|
||||||
|
skipReason: "Fanavaran base claimId is missing",
|
||||||
|
fileName: c.fileName,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = `${await this.fanavaranClaimsUrlForClaim(claimCaseId)}/${nestedClaimId}/files`;
|
||||||
const fileTypeId = profile.defaults.ClaimFileTypeId;
|
const fileTypeId = profile.defaults.ClaimFileTypeId;
|
||||||
const product = await this.resolveFanavaranClaimProduct(claimCaseId);
|
const product = await this.resolveFanavaranClaimProduct(claimCaseId);
|
||||||
|
|
||||||
@@ -6166,7 +6595,7 @@ export class ClaimRequestManagementService {
|
|||||||
const content = {
|
const content = {
|
||||||
FileName: candidate.fileName,
|
FileName: candidate.fileName,
|
||||||
FileTypeId: fileTypeId,
|
FileTypeId: fileTypeId,
|
||||||
...(product === "car-body" ? { ClaimId: claimCase.claimId } : {}),
|
...(product === "car-body" ? { ClaimId: nestedClaimId } : {}),
|
||||||
};
|
};
|
||||||
|
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
@@ -6188,7 +6617,7 @@ export class ClaimRequestManagementService {
|
|||||||
results.push({
|
results.push({
|
||||||
attempted: true,
|
attempted: true,
|
||||||
submitted: true,
|
submitted: true,
|
||||||
claimId: claimCase.claimId,
|
claimId: nestedClaimId,
|
||||||
fileName: candidate.fileName,
|
fileName: candidate.fileName,
|
||||||
submitUrl: url,
|
submitUrl: url,
|
||||||
fanavaranResponse: response.data,
|
fanavaranResponse: response.data,
|
||||||
@@ -6203,7 +6632,7 @@ export class ClaimRequestManagementService {
|
|||||||
submitted: false,
|
submitted: false,
|
||||||
warning,
|
warning,
|
||||||
fileName: candidate.fileName,
|
fileName: candidate.fileName,
|
||||||
claimId: claimCase.claimId,
|
claimId: nestedClaimId,
|
||||||
submitUrl: url,
|
submitUrl: url,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -6689,10 +7118,35 @@ export class ClaimRequestManagementService {
|
|||||||
const profile = getFanavaranClientProfile(input.clientKey);
|
const profile = getFanavaranClientProfile(input.clientKey);
|
||||||
const active = getActiveV2ExpertReply(input.claimCase as any);
|
const active = getActiveV2ExpertReply(input.claimCase as any);
|
||||||
|
|
||||||
// GEN.08 ClaimExpertId = tenant ExpertiseClaimExpertId (assessor role).
|
const blameForExpertIds = input.claimCase?.blameRequestId
|
||||||
// Tejaratno: 2709. Parsian: 29. Must NOT reuse GEN.03 ClaimExpertId
|
? await this.blameRequestDbService.findById(
|
||||||
// (مسئول پرونده مالی — Tejaratno 4543092 / Parsian 154).
|
String(input.claimCase.blameRequestId),
|
||||||
const claimExpertId = profile.defaults.ExpertiseClaimExpertId;
|
)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
// GEN.06 ClaimExpertId = tenant ExpertiseClaimExpertId (assessor role), unless
|
||||||
|
// V4/V5 FileMaker flow → file-reviewer expertise code for this product line.
|
||||||
|
let claimExpertId = profile.defaults.ExpertiseClaimExpertId;
|
||||||
|
if (blameForExpertIds) {
|
||||||
|
const roleExpertIds =
|
||||||
|
await this.resolveFanavaranFileRoleExpertIdsForBlame(
|
||||||
|
blameForExpertIds as {
|
||||||
|
isMadeByFileMaker?: boolean;
|
||||||
|
type?: BlameRequestType;
|
||||||
|
initiatedByFieldExpertId?: unknown;
|
||||||
|
assignedFileReviewerId?: unknown;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (roleExpertIds?.expertiseClaimExpertId != null) {
|
||||||
|
claimExpertId = roleExpertIds.expertiseClaimExpertId;
|
||||||
|
} else if (roleExpertIds) {
|
||||||
|
throw new BadRequestException({
|
||||||
|
code: "FILE_REVIEWER_FANAVARAN_EXPERT_CODE_MISSING",
|
||||||
|
message:
|
||||||
|
"Fanavaran expertise cannot be submitted: assign a FileReviewer with an expertise claim expert id for this product line.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
const warnings: string[] = [];
|
const warnings: string[] = [];
|
||||||
|
|
||||||
if (!active?.parts?.length) {
|
if (!active?.parts?.length) {
|
||||||
@@ -6747,38 +7201,20 @@ export class ClaimRequestManagementService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (product === "car-body") {
|
if (product === "car-body") {
|
||||||
const blame = input.claimCase?.blameRequestId
|
const blame = blameForExpertIds;
|
||||||
? await this.blameRequestDbService.findById(
|
return this.buildCarBodyFanavaranExpertisePayload({
|
||||||
String(input.claimCase.blameRequestId),
|
clientKey: input.clientKey,
|
||||||
)
|
claimCase: input.claimCase,
|
||||||
: null;
|
blame,
|
||||||
const hullSections = parts.map((part: any) =>
|
profile,
|
||||||
toFanavaranHullExpertiseDmgSection({
|
parts,
|
||||||
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,
|
claimExpertId,
|
||||||
dmgAssessmentDate: this.convertToPersianDate(submittedAt),
|
submittedAt,
|
||||||
inspectionTime: this.getTime24Hour(submittedAt),
|
priceDropTotal,
|
||||||
wage: repairWage,
|
priceDropCarPrice: priceDrop?.carPrice,
|
||||||
componentReplacementCost,
|
warnings,
|
||||||
wasteValue,
|
replyKey: active?.replyKey,
|
||||||
dropAmount: priceDropTotal || 0,
|
});
|
||||||
vehicleCurrentValue:
|
|
||||||
this.parseFanavaranMoney(priceDrop?.carPrice) || null,
|
|
||||||
vehicle: pickHullVehicleIdentity(blame),
|
|
||||||
dmgSections: hullSections,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const payload: Record<string, unknown> = {
|
const payload: Record<string, unknown> = {
|
||||||
@@ -6940,7 +7376,18 @@ export class ClaimRequestManagementService {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const url = `${await this.fanavaranClaimsUrlForClaim(claimCaseId)}/${claimCase.claimId}/expertise`;
|
await this.syncFanavaranHullBaseClaim(claimCaseId, clientKey);
|
||||||
|
claimCase = await this.claimCaseDbService.findById(claimCaseId);
|
||||||
|
const nestedClaimId = fanavaranHullNestedClaimId({
|
||||||
|
claimId: claimCase?.claimId,
|
||||||
|
claimNo: claimCase?.claimNo,
|
||||||
|
});
|
||||||
|
if (nestedClaimId == null) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
"Fanavaran claimId is required before submitting expertise",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const url = `${await this.fanavaranClaimsUrlForClaim(claimCaseId)}/${nestedClaimId}/expertise`;
|
||||||
const startedAt = Date.now();
|
const startedAt = Date.now();
|
||||||
const auditSession: FanavaranAuditSession = {
|
const auditSession: FanavaranAuditSession = {
|
||||||
trackingCode: this.fanavaranAuditService.generateTrackingCode(),
|
trackingCode: this.fanavaranAuditService.generateTrackingCode(),
|
||||||
@@ -7332,6 +7779,70 @@ export class ClaimRequestManagementService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async putFanavaranJson(
|
||||||
|
url: string,
|
||||||
|
payload: Record<string, unknown>,
|
||||||
|
clientKey: FanavaranClientKey,
|
||||||
|
auditSession?: FanavaranAuditSession,
|
||||||
|
claimCaseId?: string,
|
||||||
|
) {
|
||||||
|
this.fanavaranAuthService.assertNotInBackoff(clientKey);
|
||||||
|
const headers = await this.getFanavaranAuthHeaders(
|
||||||
|
clientKey,
|
||||||
|
auditSession,
|
||||||
|
claimCaseId,
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await firstValueFrom(
|
||||||
|
this.httpService.put(url, payload, {
|
||||||
|
headers: {
|
||||||
|
...headers,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
this.fanavaranAuthService.clearBackoff(clientKey);
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
this.fanavaranAuthService.registerFailure(clientKey, error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nested hull files/expertise look up a بدنه پرونده. Create often echoed a ثالث-shaped
|
||||||
|
* body; PUT the GEN.03 hull shape onto the existing Id before follow-up stages.
|
||||||
|
*/
|
||||||
|
private async syncFanavaranHullBaseClaim(
|
||||||
|
claimCaseId: string,
|
||||||
|
clientKey: FanavaranClientKey,
|
||||||
|
): Promise<void> {
|
||||||
|
const product = await this.resolveFanavaranClaimProduct(claimCaseId);
|
||||||
|
if (product !== "car-body") return;
|
||||||
|
const claimCase = await this.claimCaseDbService.findById(claimCaseId);
|
||||||
|
const nestedId = fanavaranHullNestedClaimId({
|
||||||
|
claimId: claimCase?.claimId,
|
||||||
|
claimNo: claimCase?.claimNo,
|
||||||
|
});
|
||||||
|
if (nestedId == null) return;
|
||||||
|
|
||||||
|
const built = await this.previewFanavaranSubmitV2(claimCaseId, clientKey, {
|
||||||
|
requirePolicyId: true,
|
||||||
|
persistPayload: true,
|
||||||
|
});
|
||||||
|
const hull = toFanavaranHullBaseClaimPayload(
|
||||||
|
built && typeof built === "object" && "payload" in built
|
||||||
|
? (built as { payload: Record<string, unknown> }).payload
|
||||||
|
: built,
|
||||||
|
);
|
||||||
|
const url = `${fanavaranClaimsBaseUrl("car-body")}/${nestedId}`;
|
||||||
|
this.logger.log(
|
||||||
|
`[Fanavaran hull sync] PUT ${url} keys=${Object.keys(hull).join(",")}`,
|
||||||
|
);
|
||||||
|
await this.putFanavaranJson(url, hull, clientKey, undefined, claimCaseId);
|
||||||
|
}
|
||||||
|
|
||||||
private async postFanavaranMultipart(
|
private async postFanavaranMultipart(
|
||||||
url: string,
|
url: string,
|
||||||
content: Record<string, unknown>,
|
content: Record<string, unknown>,
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import {
|
||||||
|
actualPremiumFromHullPolicyRecord,
|
||||||
|
customerIdFromHullPolicyRecord,
|
||||||
|
FANAVARAN_DEFAULT_HULL_ACCIDENT_TYPE_ID,
|
||||||
|
fanavaranHullNestedClaimId,
|
||||||
|
toFanavaranHullBaseClaimPayload,
|
||||||
|
} from "./fanavaran-hull-base-claim";
|
||||||
|
|
||||||
|
describe("fanavaran hull base claim", () => {
|
||||||
|
it("reads ActualPremium and CustomerId from hull policy GET", () => {
|
||||||
|
expect(
|
||||||
|
actualPremiumFromHullPolicyRecord({ TotalPremium: 21613552 }),
|
||||||
|
).toBe(21613552);
|
||||||
|
expect(customerIdFromHullPolicyRecord({ CustomerId: 1230091 })).toBe(
|
||||||
|
1230091,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("defaults CustomerFaultPercent to 100 when CulpritTypeId is 300", () => {
|
||||||
|
const hull = toFanavaranHullBaseClaimPayload({
|
||||||
|
PolicyId: 1,
|
||||||
|
CulpritTypeId: 300,
|
||||||
|
});
|
||||||
|
expect(hull.CustomerFaultPercent).toBe(100);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses GEN.03 Id for nested files/expertise, not ClaimNo", () => {
|
||||||
|
expect(
|
||||||
|
fanavaranHullNestedClaimId({ Id: 5023617, ClaimNo: 2268 }),
|
||||||
|
).toBe(5023617);
|
||||||
|
expect(
|
||||||
|
fanavaranHullNestedClaimId({ claimId: 5023617, claimNo: 2268 }),
|
||||||
|
).toBe(5023617);
|
||||||
|
expect(fanavaranHullNestedClaimId({ ClaimNo: 2268 })).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("strips ثالث-only fields from a live hull create echo", () => {
|
||||||
|
const hull = toFanavaranHullBaseClaimPayload({
|
||||||
|
AccidentCauseId: 6,
|
||||||
|
AccidentCityId: 701,
|
||||||
|
AccidentDate: "1405/06/24",
|
||||||
|
AccidentLocationAddress: "استان تهران شهر تهران",
|
||||||
|
AccidentReportTypeId: 155,
|
||||||
|
AccidentTime: "10:33",
|
||||||
|
AccidentVehicleUsedId: 1,
|
||||||
|
ActualPremium: 57270462,
|
||||||
|
AnnouncementDate: "1405/06/24",
|
||||||
|
ClaimExpertId: 154,
|
||||||
|
ClaimNo: 2268,
|
||||||
|
CompensationReferenceId: 167,
|
||||||
|
CulpritLicenceNo: "9705463515",
|
||||||
|
CulpritTypeId: 337,
|
||||||
|
CustomerFaultPercent: 100,
|
||||||
|
DamagedCount: 1,
|
||||||
|
EstimateAmount: 16000000,
|
||||||
|
HasOtherCulprit: 0,
|
||||||
|
Id: 5023617,
|
||||||
|
IsFatalAccident: 0,
|
||||||
|
IsLicenseReplacement: null,
|
||||||
|
IsPlaqueChanged: 0,
|
||||||
|
PolicyId: 13764408,
|
||||||
|
PreviousPolicyEndDate: "1404/10/23",
|
||||||
|
SanhabVersion: 6,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(hull.PolicyId).toBe(13764408);
|
||||||
|
expect(hull.AccidentTypeId).toBe(FANAVARAN_DEFAULT_HULL_ACCIDENT_TYPE_ID);
|
||||||
|
expect(hull.IsLicenseReplaced).toBeNull();
|
||||||
|
expect(hull.CostSeparationToDmgSections).toBe(0);
|
||||||
|
expect(hull.IsOwnerChanged).toBe(0);
|
||||||
|
expect(hull.CulpritTypeId).toBe(301);
|
||||||
|
expect(hull.DamagedCount).toBeUndefined();
|
||||||
|
expect(hull.HasOtherCulprit).toBeUndefined();
|
||||||
|
expect(hull.CompensationReferenceId).toBeUndefined();
|
||||||
|
expect(hull.SanhabVersion).toBeUndefined();
|
||||||
|
expect(hull.Id).toBeUndefined();
|
||||||
|
expect(hull.ClaimNo).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
140
src/claim-request-management/fanavaran-hull-base-claim.ts
Normal file
140
src/claim-request-management/fanavaran-hull-base-claim.ts
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
/** GEN.03 VHUD input fields. Do not send ثالث-only keys (DamagedCount, HasOtherCulprit, plaques, …). */
|
||||||
|
export const FANAVARAN_HULL_BASE_CLAIM_KEYS = [
|
||||||
|
"ArchiveNo",
|
||||||
|
"AccidentDate",
|
||||||
|
"AccidentTime",
|
||||||
|
"AnnouncementDate",
|
||||||
|
"AccidentLocationAddress",
|
||||||
|
"ClaimExpertId",
|
||||||
|
"EntryDate",
|
||||||
|
"CulpritLicenceNo",
|
||||||
|
"CulpritLicenceIssuDate",
|
||||||
|
"CulpritLicenceForeignCityName",
|
||||||
|
"PoliceReportSeri",
|
||||||
|
"PoliceReportSerial",
|
||||||
|
"PoliceReportDesc",
|
||||||
|
"CustomerFaultPercent",
|
||||||
|
"CulpritLevelTwoLicenceIssuDate",
|
||||||
|
"ActualPremium",
|
||||||
|
"EstimateAmount",
|
||||||
|
"TrackingCode",
|
||||||
|
"CourtArchiveNo",
|
||||||
|
"PlaqueReplacementDate",
|
||||||
|
"StatusChangeDate",
|
||||||
|
"ClaimCompletionDate",
|
||||||
|
"DmgAssessorFirstCreationTime",
|
||||||
|
"PolicyId",
|
||||||
|
"IsSurplusArticleEighthLaw",
|
||||||
|
"AccidentCityId",
|
||||||
|
"AccidentCauseId",
|
||||||
|
"AccidentTypeId",
|
||||||
|
"GlassBreakReasonId",
|
||||||
|
"CulpritTypeId",
|
||||||
|
"AuthorityCulpritId",
|
||||||
|
"AccidentCulpritId",
|
||||||
|
"CulpritLicenceTypeId",
|
||||||
|
"IsLicenseReplaced",
|
||||||
|
"CulpritLicenceCountryId",
|
||||||
|
"CulpritLicenceCityId",
|
||||||
|
"AccidentReportTypeId",
|
||||||
|
"PoliceOfficerId",
|
||||||
|
"IsAccidentOutOfBorder",
|
||||||
|
"AccidentVehicleUsedId",
|
||||||
|
"IsOwnerChanged",
|
||||||
|
"CostSeparationToDmgSections",
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
/** GEN.03 default: تصادف(حادثه). Lookup: GET /lookups/vehicle-hull-accident-types */
|
||||||
|
export const FANAVARAN_DEFAULT_HULL_ACCIDENT_TYPE_ID = 2;
|
||||||
|
|
||||||
|
/** GEN.03 ans (0=خیر). Used for CostSeparationToDmgSections, IsOwnerChanged, IsLicenseReplaced. */
|
||||||
|
export const FANAVARAN_HULL_ANS_NO = 0;
|
||||||
|
|
||||||
|
/** GEN.03 hull culprit type when third-party is at fault. Lookup: vehicle-hull-accident-culprit-type */
|
||||||
|
export const FANAVARAN_DEFAULT_HULL_CULPRIT_TYPE_ID = 301;
|
||||||
|
|
||||||
|
/** GEN.03 ActualPremium from GET car/vehicle-hull-policies/{PolicyId} → TotalPremium. */
|
||||||
|
export function actualPremiumFromHullPolicyRecord(
|
||||||
|
policy: Record<string, unknown> | null | undefined,
|
||||||
|
): number | null {
|
||||||
|
if (!policy) return null;
|
||||||
|
const raw = policy.TotalPremium ?? policy.totalPremium;
|
||||||
|
const n = typeof raw === "number" ? raw : Number(raw);
|
||||||
|
return Number.isFinite(n) && n >= 0 ? n : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Fallback AccidentCulpritId when parties inquiry is unavailable. */
|
||||||
|
export function customerIdFromHullPolicyRecord(
|
||||||
|
policy: Record<string, unknown> | null | undefined,
|
||||||
|
): number | null {
|
||||||
|
if (!policy) return null;
|
||||||
|
const raw = policy.CustomerId ?? policy.customerId;
|
||||||
|
const n = typeof raw === "number" ? raw : Number(raw);
|
||||||
|
return Number.isFinite(n) && n > 0 ? n : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toFanavaranHullBaseClaimPayload(
|
||||||
|
built: Record<string, unknown>,
|
||||||
|
): Record<string, unknown> {
|
||||||
|
const next: Record<string, unknown> = {};
|
||||||
|
for (const key of FANAVARAN_HULL_BASE_CLAIM_KEYS) {
|
||||||
|
if (key === "IsLicenseReplaced") {
|
||||||
|
next[key] =
|
||||||
|
built.IsLicenseReplaced ?? built.IsLicenseReplacement ?? null;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (key === "AccidentTypeId") {
|
||||||
|
next[key] =
|
||||||
|
built.AccidentTypeId ?? FANAVARAN_DEFAULT_HULL_ACCIDENT_TYPE_ID;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (key === "CostSeparationToDmgSections") {
|
||||||
|
next[key] =
|
||||||
|
built.CostSeparationToDmgSections ?? FANAVARAN_HULL_ANS_NO;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (key === "IsOwnerChanged") {
|
||||||
|
next[key] = built.IsOwnerChanged ?? FANAVARAN_HULL_ANS_NO;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (key === "CulpritTypeId") {
|
||||||
|
const raw = built.CulpritTypeId;
|
||||||
|
// ثالث profile default 337 is not in vehicle-hull-accident-culprit-type.
|
||||||
|
if (raw == null || raw === 337) {
|
||||||
|
next[key] = FANAVARAN_DEFAULT_HULL_CULPRIT_TYPE_ID;
|
||||||
|
} else {
|
||||||
|
next[key] = raw;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (key === "CustomerFaultPercent") {
|
||||||
|
const culpritType =
|
||||||
|
built.CulpritTypeId ?? FANAVARAN_DEFAULT_HULL_CULPRIT_TYPE_ID;
|
||||||
|
if (built.CustomerFaultPercent != null) {
|
||||||
|
next[key] = built.CustomerFaultPercent;
|
||||||
|
} else if (culpritType === 300) {
|
||||||
|
next[key] = 100;
|
||||||
|
} else {
|
||||||
|
next[key] = null;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
next[key] = Object.prototype.hasOwnProperty.call(built, key)
|
||||||
|
? built[key]
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Nested hull files/expertise use GEN.03 `Id` (کد رایانه), never `ClaimNo` (شماره پرونده). */
|
||||||
|
export function fanavaranHullNestedClaimId(input: {
|
||||||
|
Id?: unknown;
|
||||||
|
ClaimNo?: unknown;
|
||||||
|
claimId?: unknown;
|
||||||
|
claimNo?: unknown;
|
||||||
|
}): number | null {
|
||||||
|
const id = input.claimId ?? input.Id;
|
||||||
|
if (typeof id === "number" && Number.isFinite(id) && id > 0) return id;
|
||||||
|
if (typeof id === "string" && /^\d+$/.test(id.trim())) return Number(id);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import { parseFanavaranId } from "src/lookups/fanavaran-last-car-policy";
|
||||||
|
|
||||||
|
export const FANAVARAN_HULL_DMG_KIND_CAPTION = "تخریب";
|
||||||
|
export const FANAVARAN_HULL_DMG_COST_KIND_CAPTION = "مجموع لوازم";
|
||||||
|
|
||||||
|
/** Fallback when lookup fetch fails (Parsian `vehicle-hull-dmg-kind`). */
|
||||||
|
export const FANAVARAN_DEFAULT_HULL_DMG_KIND_ID = 5485;
|
||||||
|
/** Fallback when lookup fetch fails (`vehicle-hull-dmg-cost-kinds` Id). */
|
||||||
|
export const FANAVARAN_DEFAULT_HULL_DMG_COST_KIND_ID = 1;
|
||||||
|
|
||||||
|
export type FanavaranHullDmgAccessoryRow = {
|
||||||
|
Id?: unknown;
|
||||||
|
AccessoryKindId?: unknown;
|
||||||
|
AccessoryDesc?: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function asLookupRows(value: unknown): Record<string, unknown>[] {
|
||||||
|
if (!Array.isArray(value)) return [];
|
||||||
|
return value.filter(
|
||||||
|
(row): row is Record<string, unknown> =>
|
||||||
|
!!row && typeof row === "object" && !Array.isArray(row),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function asHullDmgAccessoryRows(value: unknown): FanavaranHullDmgAccessoryRow[] {
|
||||||
|
return asLookupRows(value) as FanavaranHullDmgAccessoryRow[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findLookupIdByCaption(
|
||||||
|
rows: unknown,
|
||||||
|
caption: string,
|
||||||
|
): number | null {
|
||||||
|
const target = caption.trim();
|
||||||
|
for (const row of asLookupRows(rows)) {
|
||||||
|
if (String(row.Caption ?? "").trim() !== target) continue;
|
||||||
|
const id = parseFanavaranId(row.Id);
|
||||||
|
if (id != null) return id;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isFactoryDefaultAccessoryRow(row: FanavaranHullDmgAccessoryRow): boolean {
|
||||||
|
const desc = String(row.AccessoryDesc ?? "").trim();
|
||||||
|
return (
|
||||||
|
desc.includes("کليه قطعات فابريک") ||
|
||||||
|
desc.includes("کلیه قطعات فابریک") ||
|
||||||
|
desc.includes("کليه قطعات") ||
|
||||||
|
desc.includes("فابريک")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GEN.06 VehicleHullAccessoryId from policy dmg-accessories.
|
||||||
|
* 1) Match AccessoryKindId to car-components part id.
|
||||||
|
* 2) Else factory bundle row (app default outer parts map here on Parsian).
|
||||||
|
*/
|
||||||
|
export function resolveVehicleHullAccessoryId(
|
||||||
|
accessories: FanavaranHullDmgAccessoryRow[],
|
||||||
|
accessoryKindId: number | null,
|
||||||
|
): number | null {
|
||||||
|
if (accessories.length === 0) return null;
|
||||||
|
|
||||||
|
if (accessoryKindId != null) {
|
||||||
|
const exact = accessories.find(
|
||||||
|
(row) => parseFanavaranId(row.AccessoryKindId) === accessoryKindId,
|
||||||
|
);
|
||||||
|
const exactId = parseFanavaranId(exact?.Id);
|
||||||
|
if (exactId != null) return exactId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const factory = accessories.find(isFactoryDefaultAccessoryRow);
|
||||||
|
const factoryId = parseFanavaranId(factory?.Id);
|
||||||
|
if (factoryId != null) return factoryId;
|
||||||
|
|
||||||
|
return parseFanavaranId(accessories[0]?.Id);
|
||||||
|
}
|
||||||
@@ -5,9 +5,16 @@ import {
|
|||||||
pickHullVehicleIdentity,
|
pickHullVehicleIdentity,
|
||||||
toFanavaranHullExpertiseDmgSection,
|
toFanavaranHullExpertiseDmgSection,
|
||||||
toFanavaranHullExpertisePayload,
|
toFanavaranHullExpertisePayload,
|
||||||
|
vehicleCurrentValueFromHullPolicyRecord,
|
||||||
} from "./fanavaran-hull-expertise";
|
} from "./fanavaran-hull-expertise";
|
||||||
|
|
||||||
describe("fanavaran hull expertise", () => {
|
describe("fanavaran hull expertise", () => {
|
||||||
|
it("reads VehicleCurrentValue from hull policy VehicleValue", () => {
|
||||||
|
expect(
|
||||||
|
vehicleCurrentValueFromHullPolicyRecord({ VehicleValue: 11000000000 }),
|
||||||
|
).toBe(11000000000);
|
||||||
|
});
|
||||||
|
|
||||||
it("maps car-body inquiry identity onto GEN.06 vehicle fields", () => {
|
it("maps car-body inquiry identity onto GEN.06 vehicle fields", () => {
|
||||||
const vehicle = pickHullVehicleIdentity({
|
const vehicle = pickHullVehicleIdentity({
|
||||||
parties: [
|
parties: [
|
||||||
@@ -71,6 +78,9 @@ describe("fanavaran hull expertise", () => {
|
|||||||
desc: "bumper",
|
desc: "bumper",
|
||||||
wasteValue: 0,
|
wasteValue: 0,
|
||||||
amount: 3000,
|
amount: 3000,
|
||||||
|
dmgKindId: 5485,
|
||||||
|
dmgCostKindId: 1,
|
||||||
|
vehicleHullAccessoryId: 3043330,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@@ -89,10 +99,10 @@ describe("fanavaran hull expertise", () => {
|
|||||||
Desc: "bumper",
|
Desc: "bumper",
|
||||||
WasteValue: 0,
|
WasteValue: 0,
|
||||||
AccessoryKindId: 9,
|
AccessoryKindId: 9,
|
||||||
DmgKindId: null,
|
DmgKindId: 5485,
|
||||||
VehicleHullAccessoryId: null,
|
VehicleHullAccessoryId: 3043330,
|
||||||
DmgSectionCosts: [
|
DmgSectionCosts: [
|
||||||
{ Caption: "bumper", Amount: 3000, DmgCostKindId: null },
|
{ Caption: "bumper", Amount: 3000, DmgCostKindId: 1 },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
@@ -122,12 +132,18 @@ describe("fanavaran hull expertise", () => {
|
|||||||
desc: "سپر جلو",
|
desc: "سپر جلو",
|
||||||
wasteValue: 0,
|
wasteValue: 0,
|
||||||
amount: 11000000,
|
amount: 11000000,
|
||||||
|
dmgKindId: 5485,
|
||||||
|
dmgCostKindId: 1,
|
||||||
|
vehicleHullAccessoryId: 3043330,
|
||||||
}),
|
}),
|
||||||
toFanavaranHullExpertiseDmgSection({
|
toFanavaranHullExpertiseDmgSection({
|
||||||
partId: 11,
|
partId: 11,
|
||||||
desc: "آينه سمت راننده",
|
desc: "آينه سمت راننده",
|
||||||
wasteValue: 0,
|
wasteValue: 0,
|
||||||
amount: 5000000,
|
amount: 5000000,
|
||||||
|
dmgKindId: 5485,
|
||||||
|
dmgCostKindId: 1,
|
||||||
|
vehicleHullAccessoryId: 3043330,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,6 +13,29 @@ export type FanavaranHullVehicleIdentity = {
|
|||||||
builtYear: number | null;
|
builtYear: number | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function mergeHullVehicleIdentityFromFanavaranVehicle(
|
||||||
|
base: FanavaranHullVehicleIdentity,
|
||||||
|
vehicle: Record<string, unknown> | null | undefined,
|
||||||
|
): FanavaranHullVehicleIdentity {
|
||||||
|
if (!vehicle) return base;
|
||||||
|
const readText = (key: string, fallback: string | null) => {
|
||||||
|
const raw = vehicle[key];
|
||||||
|
if (raw == null || String(raw).trim() === "") return fallback;
|
||||||
|
return String(raw).trim();
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
motorNo: readText("MotorNo", base.motorNo),
|
||||||
|
chassisNo: readText("ChassisNo", base.chassisNo),
|
||||||
|
vin: readText("VIN", base.vin),
|
||||||
|
plaqueNo: readText("PlaqueNo", base.plaqueNo),
|
||||||
|
plaqueSerial: readText("PlaqueSerial", base.plaqueSerial),
|
||||||
|
builtYear:
|
||||||
|
parseFanavaranId(vehicle.BuiltYear) ??
|
||||||
|
parseFanavaranId(vehicle.builtYear) ??
|
||||||
|
base.builtYear,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function pickHullVehicleIdentity(
|
export function pickHullVehicleIdentity(
|
||||||
blame?: { parties?: unknown[] } | null,
|
blame?: { parties?: unknown[] } | null,
|
||||||
): FanavaranHullVehicleIdentity {
|
): FanavaranHullVehicleIdentity {
|
||||||
@@ -48,6 +71,24 @@ function text(value: unknown): string | null {
|
|||||||
return next ? next : null;
|
return next ? next : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
FANAVARAN_DEFAULT_HULL_DMG_COST_KIND_ID,
|
||||||
|
FANAVARAN_DEFAULT_HULL_DMG_KIND_ID,
|
||||||
|
FANAVARAN_HULL_DMG_COST_KIND_CAPTION,
|
||||||
|
FANAVARAN_HULL_DMG_KIND_CAPTION,
|
||||||
|
} from "./fanavaran-hull-expertise-lookups";
|
||||||
|
|
||||||
|
/** GEN.06 VehicleCurrentValue from GET car/vehicle-hull-policies/{PolicyId} → VehicleValue. */
|
||||||
|
export function vehicleCurrentValueFromHullPolicyRecord(
|
||||||
|
policy: Record<string, unknown> | null | undefined,
|
||||||
|
): number | null {
|
||||||
|
if (!policy) return null;
|
||||||
|
const raw =
|
||||||
|
policy.VehicleValue ?? policy.vehicleValue ?? policy.VehicleCurrentValue;
|
||||||
|
const n = typeof raw === "number" ? raw : Number(raw);
|
||||||
|
return Number.isFinite(n) && n > 0 ? n : null;
|
||||||
|
}
|
||||||
|
|
||||||
export function toFanavaranHullExpertisePayload(input: {
|
export function toFanavaranHullExpertisePayload(input: {
|
||||||
claimExpertId: number;
|
claimExpertId: number;
|
||||||
dmgAssessmentDate: string;
|
dmgAssessmentDate: string;
|
||||||
@@ -58,6 +99,8 @@ export function toFanavaranHullExpertisePayload(input: {
|
|||||||
dropAmount: number;
|
dropAmount: number;
|
||||||
vehicleCurrentValue: number | null;
|
vehicleCurrentValue: number | null;
|
||||||
vehicle: FanavaranHullVehicleIdentity;
|
vehicle: FanavaranHullVehicleIdentity;
|
||||||
|
colorId?: number | null;
|
||||||
|
repairDuration?: number | null;
|
||||||
dmgSections: Record<string, unknown>[];
|
dmgSections: Record<string, unknown>[];
|
||||||
}): Record<string, unknown> {
|
}): Record<string, unknown> {
|
||||||
return {
|
return {
|
||||||
@@ -74,7 +117,7 @@ export function toFanavaranHullExpertisePayload(input: {
|
|||||||
ComponentReplacementCost: input.componentReplacementCost,
|
ComponentReplacementCost: input.componentReplacementCost,
|
||||||
WasteValue: input.wasteValue,
|
WasteValue: input.wasteValue,
|
||||||
CarryAndRescueCost: null,
|
CarryAndRescueCost: null,
|
||||||
RepairDuration: null,
|
RepairDuration: input.repairDuration ?? null,
|
||||||
VehicleCurrentValue: input.vehicleCurrentValue,
|
VehicleCurrentValue: input.vehicleCurrentValue,
|
||||||
WreckHighestValue: null,
|
WreckHighestValue: null,
|
||||||
InspectionDeduction: null,
|
InspectionDeduction: null,
|
||||||
@@ -82,7 +125,7 @@ export function toFanavaranHullExpertisePayload(input: {
|
|||||||
WentDistanceByExpert: null,
|
WentDistanceByExpert: null,
|
||||||
IsDestruction: null,
|
IsDestruction: null,
|
||||||
DropAmount: input.dropAmount,
|
DropAmount: input.dropAmount,
|
||||||
ColorId: null,
|
ColorId: input.colorId ?? null,
|
||||||
PlaqueDesignId: null,
|
PlaqueDesignId: null,
|
||||||
PlaqueCityId: null,
|
PlaqueCityId: null,
|
||||||
AccidentPercent: null,
|
AccidentPercent: null,
|
||||||
@@ -96,19 +139,22 @@ export function toFanavaranHullExpertiseDmgSection(input: {
|
|||||||
desc: string;
|
desc: string;
|
||||||
wasteValue: number;
|
wasteValue: number;
|
||||||
amount: number;
|
amount: number;
|
||||||
|
dmgKindId: number;
|
||||||
|
dmgCostKindId: number;
|
||||||
|
vehicleHullAccessoryId: number | null;
|
||||||
}): Record<string, unknown> {
|
}): Record<string, unknown> {
|
||||||
return {
|
return {
|
||||||
Count: 1,
|
Count: 1,
|
||||||
Desc: input.desc,
|
Desc: input.desc,
|
||||||
WasteValue: input.wasteValue,
|
WasteValue: input.wasteValue,
|
||||||
AccessoryKindId: parseFanavaranId(input.partId),
|
AccessoryKindId: parseFanavaranId(input.partId),
|
||||||
DmgKindId: null,
|
DmgKindId: input.dmgKindId,
|
||||||
VehicleHullAccessoryId: null,
|
VehicleHullAccessoryId: input.vehicleHullAccessoryId,
|
||||||
DmgSectionCosts: [
|
DmgSectionCosts: [
|
||||||
{
|
{
|
||||||
Caption: input.desc,
|
Caption: input.desc,
|
||||||
Amount: input.amount,
|
Amount: input.amount,
|
||||||
DmgCostKindId: null,
|
DmgCostKindId: input.dmgCostKindId,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -57,6 +57,8 @@ export interface FanavaranPayloadDefaults {
|
|||||||
* (Parsian: 29; Tejaratno proven: 2709).
|
* (Parsian: 29; Tejaratno proven: 2709).
|
||||||
*/
|
*/
|
||||||
ExpertiseClaimExpertId: number;
|
ExpertiseClaimExpertId: number;
|
||||||
|
/** GEN.06 hull RepairDuration when configured per tenant (optional). */
|
||||||
|
HullExpertiseRepairDuration?: number | null;
|
||||||
CompensationReferenceId: number;
|
CompensationReferenceId: number;
|
||||||
CulpritLicenceTypeId: number;
|
CulpritLicenceTypeId: number;
|
||||||
CulpritTypeId: number;
|
CulpritTypeId: number;
|
||||||
|
|||||||
57
src/fanavaran/fanavaran-file-role-expert-ids.spec.ts
Normal file
57
src/fanavaran/fanavaran-file-role-expert-ids.spec.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { BlameRequestType } from "src/Types&Enums/blame-request-management/blameRequestType.enum";
|
||||||
|
import {
|
||||||
|
assertFileMakerCanCreateBlameType,
|
||||||
|
assertFileReviewerCanReviewBlameType,
|
||||||
|
resolveFileMakerStageOneExpertId,
|
||||||
|
resolveFileReviewerExpertiseExpertId,
|
||||||
|
} from "./fanavaran-file-role-expert-ids";
|
||||||
|
|
||||||
|
describe("fanavaran-file-role-expert-ids", () => {
|
||||||
|
it("resolves stage-1 and expertise ids by blame type", () => {
|
||||||
|
expect(
|
||||||
|
resolveFileMakerStageOneExpertId(
|
||||||
|
{
|
||||||
|
ThirdPartyClaimExpertId: "154",
|
||||||
|
CarBodyClaimExpertId: "29",
|
||||||
|
},
|
||||||
|
BlameRequestType.THIRD_PARTY,
|
||||||
|
),
|
||||||
|
).toBe(154);
|
||||||
|
expect(
|
||||||
|
resolveFileMakerStageOneExpertId(
|
||||||
|
{
|
||||||
|
ThirdPartyClaimExpertId: "154",
|
||||||
|
CarBodyClaimExpertId: "29",
|
||||||
|
},
|
||||||
|
BlameRequestType.CAR_BODY,
|
||||||
|
),
|
||||||
|
).toBe(29);
|
||||||
|
expect(
|
||||||
|
resolveFileReviewerExpertiseExpertId(
|
||||||
|
{
|
||||||
|
ThirdPartyExpertiseClaim: "2709",
|
||||||
|
CarBodyExpertiseClaim: "15",
|
||||||
|
},
|
||||||
|
BlameRequestType.CAR_BODY,
|
||||||
|
),
|
||||||
|
).toBe(15);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("blocks file maker create when the product code is missing", () => {
|
||||||
|
expect(() =>
|
||||||
|
assertFileMakerCanCreateBlameType(
|
||||||
|
{ ThirdPartyClaimExpertId: "154" },
|
||||||
|
BlameRequestType.CAR_BODY,
|
||||||
|
),
|
||||||
|
).toThrow(/stage-1 claim expert id/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("blocks file reviewer when the product code is missing", () => {
|
||||||
|
expect(() =>
|
||||||
|
assertFileReviewerCanReviewBlameType(
|
||||||
|
{ CarBodyExpertiseClaim: "15" },
|
||||||
|
BlameRequestType.THIRD_PARTY,
|
||||||
|
),
|
||||||
|
).toThrow(/expertise claim expert id/);
|
||||||
|
});
|
||||||
|
});
|
||||||
90
src/fanavaran/fanavaran-file-role-expert-ids.ts
Normal file
90
src/fanavaran/fanavaran-file-role-expert-ids.ts
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import { BadRequestException } from "@nestjs/common";
|
||||||
|
import { BlameRequestType } from "src/Types&Enums/blame-request-management/blameRequestType.enum";
|
||||||
|
import { parseFanavaranId } from "src/lookups/fanavaran-last-car-policy";
|
||||||
|
|
||||||
|
export type FileMakerFanavaranExpertCodes = {
|
||||||
|
ThirdPartyClaimExpertId?: string;
|
||||||
|
CarBodyClaimExpertId?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type FileReviewerFanavaranExpertCodes = {
|
||||||
|
ThirdPartyExpertiseClaim?: string;
|
||||||
|
CarBodyExpertiseClaim?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function parseFanavaranExpertId(raw?: string | null): number | null {
|
||||||
|
if (raw == null) return null;
|
||||||
|
const trimmed = String(raw).trim();
|
||||||
|
if (!trimmed) return null;
|
||||||
|
return parseFanavaranId(trimmed) ?? parseFanavaranId(Number(trimmed));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fileMakerStageOneExpertCode(
|
||||||
|
fileMaker: FileMakerFanavaranExpertCodes,
|
||||||
|
blameType: BlameRequestType,
|
||||||
|
): string | undefined {
|
||||||
|
return blameType === BlameRequestType.CAR_BODY
|
||||||
|
? fileMaker.CarBodyClaimExpertId
|
||||||
|
: fileMaker.ThirdPartyClaimExpertId;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fileReviewerExpertiseExpertCode(
|
||||||
|
fileReviewer: FileReviewerFanavaranExpertCodes,
|
||||||
|
blameType: BlameRequestType,
|
||||||
|
): string | undefined {
|
||||||
|
return blameType === BlameRequestType.CAR_BODY
|
||||||
|
? fileReviewer.CarBodyExpertiseClaim
|
||||||
|
: fileReviewer.ThirdPartyExpertiseClaim;
|
||||||
|
}
|
||||||
|
|
||||||
|
function blameTypeLabel(blameType: BlameRequestType): string {
|
||||||
|
return blameType === BlameRequestType.CAR_BODY
|
||||||
|
? "car body (بدنه)"
|
||||||
|
: "third party (ثالث)";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function assertFileMakerCanCreateBlameType(
|
||||||
|
fileMaker: FileMakerFanavaranExpertCodes,
|
||||||
|
blameType: BlameRequestType,
|
||||||
|
): void {
|
||||||
|
const raw = fileMakerStageOneExpertCode(fileMaker, blameType);
|
||||||
|
if (parseFanavaranExpertId(raw) != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new BadRequestException({
|
||||||
|
code: "FILE_MAKER_FANAVARAN_EXPERT_CODE_MISSING",
|
||||||
|
message: `Your FileMaker profile has no Fanavaran stage-1 claim expert id for ${blameTypeLabel(blameType)} files. You cannot create this file type.`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function assertFileReviewerCanReviewBlameType(
|
||||||
|
fileReviewer: FileReviewerFanavaranExpertCodes,
|
||||||
|
blameType: BlameRequestType,
|
||||||
|
): void {
|
||||||
|
const raw = fileReviewerExpertiseExpertCode(fileReviewer, blameType);
|
||||||
|
if (parseFanavaranExpertId(raw) != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new BadRequestException({
|
||||||
|
code: "FILE_REVIEWER_FANAVARAN_EXPERT_CODE_MISSING",
|
||||||
|
message: `Your FileReviewer profile has no Fanavaran expertise claim expert id for ${blameTypeLabel(blameType)} files. You cannot review this file type.`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveFileMakerStageOneExpertId(
|
||||||
|
fileMaker: FileMakerFanavaranExpertCodes,
|
||||||
|
blameType: BlameRequestType,
|
||||||
|
): number | null {
|
||||||
|
return parseFanavaranExpertId(
|
||||||
|
fileMakerStageOneExpertCode(fileMaker, blameType),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveFileReviewerExpertiseExpertId(
|
||||||
|
fileReviewer: FileReviewerFanavaranExpertCodes,
|
||||||
|
blameType: BlameRequestType,
|
||||||
|
): number | null {
|
||||||
|
return parseFanavaranExpertId(
|
||||||
|
fileReviewerExpertiseExpertCode(fileReviewer, blameType),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -16,6 +16,26 @@ export const FANAVARAN_REMOTE_LOOKUPS: FanavaranRemoteLookupDefinition[] = [
|
|||||||
url: `${FANAVARAN_LOOKUP_BASE_URL}/car/base-info/accident-causes`,
|
url: `${FANAVARAN_LOOKUP_BASE_URL}/car/base-info/accident-causes`,
|
||||||
cacheFile: "accident-causes.json",
|
cacheFile: "accident-causes.json",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "vehicle-hull-accident-types",
|
||||||
|
url: `${FANAVARAN_LOOKUP_BASE_URL}/car/base-info/vehicle-hull-accident-types`,
|
||||||
|
cacheFile: "vehicle-hull-accident-types.json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "vehicle-hull-accident-culprit-type",
|
||||||
|
url: `${FANAVARAN_LOOKUP_BASE_URL}/car/code-list/vehicle-hull-accident-culprit-type`,
|
||||||
|
cacheFile: "vehicle-hull-accident-culprit-type.json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "vehicle-hull-dmg-kind",
|
||||||
|
url: `${FANAVARAN_LOOKUP_BASE_URL}/car/code-list/vehicle-hull-dmg-kind`,
|
||||||
|
cacheFile: "vehicle-hull-dmg-kind.json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "vehicle-hull-dmg-cost-kinds",
|
||||||
|
url: `${FANAVARAN_LOOKUP_BASE_URL}/car/base-info/vehicle-hull-dmg-cost-kinds`,
|
||||||
|
cacheFile: "vehicle-hull-dmg-cost-kinds.json",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "accident-report-type",
|
name: "accident-report-type",
|
||||||
url: `${FANAVARAN_LOOKUP_BASE_URL}/car/code-list/accident-report-type`,
|
url: `${FANAVARAN_LOOKUP_BASE_URL}/car/code-list/accident-report-type`,
|
||||||
|
|||||||
@@ -277,6 +277,16 @@ export class FanavaranLookupService {
|
|||||||
return this.fetchFromFanavaran(clientKey, url, options);
|
return this.fetchFromFanavaran(clientKey, url, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** GEN.06 VehicleHullAccessoryId — requires hull PolicyId (کد رایانه بیمهنامه). */
|
||||||
|
async vehicleHullDmgAccessoriesByPolicyId(
|
||||||
|
clientKey: FanavaranClientKey,
|
||||||
|
policyId: number,
|
||||||
|
options?: { contractIdOverride?: string },
|
||||||
|
): Promise<unknown> {
|
||||||
|
const url = `${FANAVARAN_LOOKUP_BASE_URL}/car/vehicle-hull-policies/${policyId}/dmg-accessories`;
|
||||||
|
return this.fetchFromFanavaran(clientKey, url, options);
|
||||||
|
}
|
||||||
|
|
||||||
async vehicleById(
|
async vehicleById(
|
||||||
clientKey: FanavaranClientKey,
|
clientKey: FanavaranClientKey,
|
||||||
vehicleId: number,
|
vehicleId: number,
|
||||||
|
|||||||
@@ -36,6 +36,85 @@ export class LookupsController {
|
|||||||
return await this.lookupsService.getAccidentCauses();
|
return await this.lookupsService.getAccidentCauses();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Get("vehicle-hull-accident-types")
|
||||||
|
@ApiOperation({
|
||||||
|
summary: "Fanavaran GEN.03 hull accident type lookup",
|
||||||
|
description:
|
||||||
|
"Returns values for base-claim field AccidentTypeId from car/base-info/vehicle-hull-accident-types (not the Tejarat static accident-type list).",
|
||||||
|
})
|
||||||
|
@ApiOkResponse({
|
||||||
|
description: "Returns Fanavaran vehicle hull accident types",
|
||||||
|
schema: { type: "array", items: { type: "object" } },
|
||||||
|
})
|
||||||
|
async getVehicleHullAccidentTypes() {
|
||||||
|
return await this.lookupsService.getVehicleHullAccidentTypes();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get("vehicle-hull-accident-culprit-type")
|
||||||
|
@ApiOperation({
|
||||||
|
summary: "Fanavaran GEN.03 hull culprit type lookup",
|
||||||
|
description:
|
||||||
|
"Returns values for base-claim field CulpritTypeId from car/code-list/vehicle-hull-accident-culprit-type (not accident-culprit-type used for ثالث).",
|
||||||
|
})
|
||||||
|
@ApiOkResponse({
|
||||||
|
description: "Returns Fanavaran vehicle hull accident culprit types",
|
||||||
|
schema: { type: "array", items: { type: "object" } },
|
||||||
|
})
|
||||||
|
async getVehicleHullAccidentCulpritType() {
|
||||||
|
return await this.lookupsService.getVehicleHullAccidentCulpritType();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get("vehicle-hull-dmg-kind")
|
||||||
|
@ApiOperation({
|
||||||
|
summary: "Fanavaran GEN.06 hull damage kind lookup",
|
||||||
|
description:
|
||||||
|
"Returns values for DmgSections[].DmgKindId from car/code-list/vehicle-hull-dmg-kind.",
|
||||||
|
})
|
||||||
|
@ApiOkResponse({
|
||||||
|
description: "Returns Fanavaran vehicle hull damage kinds",
|
||||||
|
schema: { type: "array", items: { type: "object" } },
|
||||||
|
})
|
||||||
|
async getVehicleHullDmgKind() {
|
||||||
|
return await this.lookupsService.getVehicleHullDmgKind();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get("vehicle-hull-dmg-cost-kinds")
|
||||||
|
@ApiOperation({
|
||||||
|
summary: "Fanavaran GEN.06 hull damage cost kind lookup",
|
||||||
|
description:
|
||||||
|
"Returns values for DmgSections[].DmgSectionCosts[].DmgCostKindId from car/base-info/vehicle-hull-dmg-cost-kinds.",
|
||||||
|
})
|
||||||
|
@ApiOkResponse({
|
||||||
|
description: "Returns Fanavaran vehicle hull damage cost kinds",
|
||||||
|
schema: { type: "array", items: { type: "object" } },
|
||||||
|
})
|
||||||
|
async getVehicleHullDmgCostKinds() {
|
||||||
|
return await this.lookupsService.getVehicleHullDmgCostKinds();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get("vehicle-hull-dmg-accessories/:policyId")
|
||||||
|
@ApiOperation({
|
||||||
|
summary: "Fanavaran GEN.06 hull policy damage accessories",
|
||||||
|
description:
|
||||||
|
"Returns rows for DmgSections[].VehicleHullAccessoryId from car/vehicle-hull-policies/{policyId}/dmg-accessories. PolicyId is required (Fanavaran: کد رایانه ریسورس).",
|
||||||
|
})
|
||||||
|
@ApiParam({
|
||||||
|
name: "policyId",
|
||||||
|
description: "Fanavaran hull policy Id (same as GEN.03 PolicyId)",
|
||||||
|
example: 13764610,
|
||||||
|
})
|
||||||
|
@ApiOkResponse({
|
||||||
|
description: "Returns Fanavaran vehicle hull damage accessories for the policy",
|
||||||
|
schema: { type: "array", items: { type: "object" } },
|
||||||
|
})
|
||||||
|
async getVehicleHullDmgAccessoriesByPolicyId(
|
||||||
|
@Param("policyId", ParseIntPipe) policyId: number,
|
||||||
|
) {
|
||||||
|
return await this.lookupsService.getVehicleHullDmgAccessoriesByPolicyId(
|
||||||
|
policyId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Get("accident-report-type")
|
@Get("accident-report-type")
|
||||||
@ApiOkResponse({
|
@ApiOkResponse({
|
||||||
description: "Returns accident report type lookup data",
|
description: "Returns accident report type lookup data",
|
||||||
|
|||||||
@@ -106,6 +106,36 @@ export class LookupsService {
|
|||||||
return await this.getClientRemoteLookup("accident-causes");
|
return await this.getClientRemoteLookup("accident-causes");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getVehicleHullAccidentTypes(): Promise<any> {
|
||||||
|
return await this.getClientRemoteLookup("vehicle-hull-accident-types");
|
||||||
|
}
|
||||||
|
|
||||||
|
async getVehicleHullAccidentCulpritType(): Promise<any> {
|
||||||
|
return await this.getClientRemoteLookup("vehicle-hull-accident-culprit-type");
|
||||||
|
}
|
||||||
|
|
||||||
|
async getVehicleHullDmgKind(): Promise<any> {
|
||||||
|
return await this.getClientRemoteLookup("vehicle-hull-dmg-kind");
|
||||||
|
}
|
||||||
|
|
||||||
|
async getVehicleHullDmgCostKinds(): Promise<any> {
|
||||||
|
return await this.getClientRemoteLookup("vehicle-hull-dmg-cost-kinds");
|
||||||
|
}
|
||||||
|
|
||||||
|
async getVehicleHullDmgAccessoriesByPolicyId(policyId: number): Promise<unknown> {
|
||||||
|
const clientKey = this.activeClientKey();
|
||||||
|
return this.fanavaranLookupService.vehicleHullDmgAccessoriesByPolicyId(
|
||||||
|
clientKey,
|
||||||
|
policyId,
|
||||||
|
{
|
||||||
|
contractIdOverride: resolveFanavaranProductContractId(
|
||||||
|
clientKey,
|
||||||
|
"car-body",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async getAccidentReportType(): Promise<any> {
|
async getAccidentReportType(): Promise<any> {
|
||||||
return await this.getClientRemoteLookup("accident-report-type");
|
return await this.getClientRemoteLookup("accident-report-type");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,12 @@ import { StepsEnum } from "src/Types&Enums/blame-request-management/steps.enum";
|
|||||||
import { ExpertDbService } from "src/users/entities/db-service/expert.db.service";
|
import { ExpertDbService } from "src/users/entities/db-service/expert.db.service";
|
||||||
import { UserDbService } from "src/users/entities/db-service/user.db.service";
|
import { UserDbService } from "src/users/entities/db-service/user.db.service";
|
||||||
import { FanavaranLocationService } from "src/fanavaran/fanavaran-location.service";
|
import { FanavaranLocationService } from "src/fanavaran/fanavaran-location.service";
|
||||||
|
import {
|
||||||
|
assertFileMakerCanCreateBlameType,
|
||||||
|
assertFileReviewerCanReviewBlameType,
|
||||||
|
} from "src/fanavaran/fanavaran-file-role-expert-ids";
|
||||||
|
import { FileMakerDbService } from "src/users/entities/db-service/file-maker.db.service";
|
||||||
|
import { FileReviewerDbService } from "src/users/entities/db-service/file-reviewer.db.service";
|
||||||
import { isOtpExpiryActive } from "src/helpers/user-otp-expiry";
|
import { isOtpExpiryActive } from "src/helpers/user-otp-expiry";
|
||||||
import { parseIranLocalDateTime } from "src/helpers/iran-datetime";
|
import { parseIranLocalDateTime } from "src/helpers/iran-datetime";
|
||||||
import { applyListQueryV2 } from "src/helpers/list-query-v2";
|
import { applyListQueryV2 } from "src/helpers/list-query-v2";
|
||||||
@@ -1088,6 +1094,8 @@ export class RequestManagementService {
|
|||||||
private readonly hashService: HashService,
|
private readonly hashService: HashService,
|
||||||
private readonly userAuthService: UserAuthService,
|
private readonly userAuthService: UserAuthService,
|
||||||
private readonly fanavaranLocationService: FanavaranLocationService,
|
private readonly fanavaranLocationService: FanavaranLocationService,
|
||||||
|
private readonly fileMakerDbService: FileMakerDbService,
|
||||||
|
private readonly fileReviewerDbService: FileReviewerDbService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -5325,6 +5333,14 @@ export class RequestManagementService {
|
|||||||
"This file has been taken by another FileReviewer.",
|
"This file has been taken by another FileReviewer.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
const fileReviewer = await this.fileReviewerDbService.findById(
|
||||||
|
String(expert.sub),
|
||||||
|
);
|
||||||
|
if (!fileReviewer) {
|
||||||
|
throw new ForbiddenException("FileReviewer account not found.");
|
||||||
|
}
|
||||||
|
assertFileReviewerCanReviewBlameType(fileReviewer, req.type);
|
||||||
|
|
||||||
if (!assignedId) {
|
if (!assignedId) {
|
||||||
// Atomically claim — ignore if another reviewer won the race (they would have
|
// Atomically claim — ignore if another reviewer won the race (they would have
|
||||||
// been caught by the assignedId check above on their own first call).
|
// been caught by the assignedId check above on their own first call).
|
||||||
@@ -5633,6 +5649,13 @@ export class RequestManagementService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isFileMakerRole = (expert as any)?.role === RoleEnum.FILE_MAKER;
|
const isFileMakerRole = (expert as any)?.role === RoleEnum.FILE_MAKER;
|
||||||
|
if (isFileMakerRole) {
|
||||||
|
const fileMaker = await this.fileMakerDbService.findById(String(expert.sub));
|
||||||
|
if (!fileMaker) {
|
||||||
|
throw new ForbiddenException("FileMaker account not found.");
|
||||||
|
}
|
||||||
|
assertFileMakerCanCreateBlameType(fileMaker, type);
|
||||||
|
}
|
||||||
const created = await this.blameRequestDbService.create({
|
const created = await this.blameRequestDbService.create({
|
||||||
publicId,
|
publicId,
|
||||||
requestNo: publicId,
|
requestNo: publicId,
|
||||||
|
|||||||
Reference in New Issue
Block a user