forked from Yara724/api
Merge upstream/main into main
Resolve conflicts while keeping field-expert lock/view fixes and user party-access query improvements from the fork.
This commit is contained in:
@@ -151,6 +151,17 @@ import { HttpService } from "@nestjs/axios";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
import { buildEnrichedDamagedParts } from "src/expert-claim/dto/claim-damaged-part.enricher";
|
||||
|
||||
export interface FanavaranAutoSubmitResult {
|
||||
attempted: boolean;
|
||||
submitted: boolean;
|
||||
skipped?: boolean;
|
||||
skipReason?: string;
|
||||
warning?: string;
|
||||
claimNo?: number;
|
||||
claimId?: number;
|
||||
fanavaranResponse?: unknown;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class ClaimRequestManagementService {
|
||||
private readonly logger = new Logger(ClaimRequestManagementService.name);
|
||||
@@ -174,6 +185,41 @@ export class ClaimRequestManagementService {
|
||||
private readonly CONTRACT_ID = "263";
|
||||
private readonly LOCATION = "100";
|
||||
|
||||
private readonly PARSIAN_FANAVARAN_CONFIG = {
|
||||
appName: "ParsianService",
|
||||
secret: "P@r30@n$erv!ce",
|
||||
username: "ParsianServiceUser",
|
||||
password: "P@r30@n123",
|
||||
corpId: "543",
|
||||
contractId: "28",
|
||||
location: "210050",
|
||||
} as const;
|
||||
|
||||
/** Tejaratno hardcoded Fanavaran lookup defaults (legacy). */
|
||||
private readonly TEJARATNO_FANAVARAN_DEFAULTS = {
|
||||
AccidentCityId: 701,
|
||||
AccidentReportTypeId: 155,
|
||||
AccidentVehicleUsedId: 1,
|
||||
ClaimExpertId: 1589,
|
||||
CompensationReferenceId: 167,
|
||||
CulpritLicenceTypeId: 2,
|
||||
CulpritTypeId: 337,
|
||||
} as const;
|
||||
|
||||
/** Parsian-validated Fanavaran lookup defaults (verified via direct submit). */
|
||||
private readonly PARSIAN_FANAVARAN_DEFAULTS = {
|
||||
AccidentCityId: 701,
|
||||
AccidentReportTypeId: 155,
|
||||
AccidentVehicleUsedId: 1,
|
||||
ClaimExpertId: 154,
|
||||
CompensationReferenceId: 167,
|
||||
CulpritLicenceTypeId: 2,
|
||||
CulpritTypeId: 337,
|
||||
} as const;
|
||||
|
||||
private readonly MAP_IR_API_KEY =
|
||||
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6ImI5ZDZjMThkNDRjZjc2OWI2Yjk1ODcyMGFjYmEzMmRiN2NhZjg0Zjk4OTRlMjZiZDg0Yzg3YjVlMzhlMTAyZDlkMWYxOGM5NjNmOTk4YjY2In0.eyJhdWQiOiIyMTcxOCIsImp0aSI6ImI5ZDZjMThkNDRjZjc2OWI2Yjk1ODcyMGFjYmEzMmRiN2NhZjg0Zjk4OTRlMjZiZDg0Yzg3YjVlMzhlMTAyZDlkMWYxOGM5NjNmOTk4YjY2IiwiaWF0IjoxNjgwNjA4NTkxLCJuYmYiOjE2ODA2MDg1OTEsImV4cCI6MTY4MzIwMDU5MSwic3ViIjoiIiwic2NvcGVzIjpbImJhc2ljIl19.rTviLd8b5yTHUDa3ODZyva593eMnL0d3XPg3sKkZxMOf_jNIH6lFQyIfbId-wsd1EAdsOdsL3CME_Y8t332PWJbxMNgnEq4Rf2IkClkvkSx6Sb5_4bmlhBM75zw2SmccvgbFUn4xkTOw0FT4vABC2Y3-MKctjMpmO8QOrVULSKt4psrmQhr7hBu7YRDnAAEc6muZ1VpRvdB1kqNKddoSIrfDaq6aDRJ-BNbGRAaFFvP_kH4cgSCKV4dU0TknL3mRKUiVy6_TDkjtzAN8fE2wsdvNo2pGTJPzKFsR2ipgGNTvB__g3bOnVpKsgFXPBH0e_Qa7ff1tZ3VGWy3jRNh9Lg";
|
||||
|
||||
constructor(
|
||||
private readonly blameDbService: RequestManagementDbService,
|
||||
private readonly claimDbService: ClaimRequestManagementDbService,
|
||||
@@ -3170,15 +3216,13 @@ export class ClaimRequestManagementService {
|
||||
lon: number,
|
||||
): Promise<string> {
|
||||
try {
|
||||
const apiKey =
|
||||
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6ImI5ZDZjMThkNDRjZjc2OWI2Yjk1ODcyMGFjYmEzMmRiN2NhZjg0Zjk4OTRlMjZiZDg0Yzg3YjVlMzhlMTAyZDlkMWYxOGM5NjNmOTk4YjY2In0.eyJhdWQiOiIyMTcxOCIsImp0aSI6ImI5ZDZjMThkNDRjZjc2OWI2Yjk1ODcyMGFjYmEzMmRiN2NhZjg0Zjk4OTRlMjZiZDg0Yzg3YjVlMzhlMTAyZDlkMWYxOGM5NjNmOTk4YjY2IiwiaWF0IjoxNjgwNjA4NTkxLCJuYmYiOjE2ODA2MDg1OTEsImV4cCI6MTY4MzIwMDU5MSwic3ViIjoiIiwic2NvcGVzIjpbImJhc2ljIl19.rTviLd8b5yTHUDa3ODZyva593eMnL0d3XPg3sKkZxMOf_jNIH6lFQyIfbId-wsd1EAdsOdsL3CME_Y8t332PWJbxMNgnEq4Rf2IkClkvkSx6Sb5_4bmlhBM75zw2SmccvgbFUn4xkTOw0FT4vABC2Y3-MKctjMpmO8QOrVULSKt4psrmQhr7hBu7YRDnAAEc6muZ1VpRvdB1kqNKddoSIrfDaq6aDRJ-BNbGRAaFFvP_kH4cgSCKV4dU0TknL3mRKUiVy6_TDkjtzAN8fE2wsdvNo2pGTJPzKFsR2ipgGNTvB__g3bOnVpKsgFXPBH0e_Qa7ff1tZ3VGWy3jRNh9Lg";
|
||||
const response = await firstValueFrom(
|
||||
this.httpService.get(
|
||||
`https://map.ir/fast-reverse?lat=${lat}&lon=${lon}`,
|
||||
{
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
"x-api-key": apiKey,
|
||||
"x-api-key": this.MAP_IR_API_KEY,
|
||||
},
|
||||
},
|
||||
),
|
||||
@@ -3226,6 +3270,140 @@ export class ClaimRequestManagementService {
|
||||
return ids;
|
||||
}
|
||||
|
||||
private getTejaratnoFanavaranConfig() {
|
||||
return {
|
||||
appName: this.APP_NAME,
|
||||
secret: this.SECRET,
|
||||
username: this.USERNAME,
|
||||
password: this.PASSWORD,
|
||||
corpId: this.CORP_ID,
|
||||
contractId: this.CONTRACT_ID,
|
||||
location: this.LOCATION,
|
||||
};
|
||||
}
|
||||
|
||||
private resolveAccidentCauseId(accidentReason?: {
|
||||
id?: string | number | null;
|
||||
fanavaran?: number | null;
|
||||
}): number | null {
|
||||
const fanavaranValue = accidentReason?.fanavaran;
|
||||
const idValue = accidentReason?.id;
|
||||
|
||||
if (fanavaranValue !== undefined && fanavaranValue !== null) {
|
||||
if (fanavaranValue === 0) {
|
||||
return idValue !== undefined && idValue !== null ? Number(idValue) : null;
|
||||
}
|
||||
return fanavaranValue;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private applyFanavaranDefaultFields(result: Record<string, unknown>): void {
|
||||
result.AccidentCulpritId = null;
|
||||
result.CouponNo = null;
|
||||
result.CourtArchiveNo = null;
|
||||
result.CulpritLicenceCityId = null;
|
||||
result.CulpritLicenceCountryId = null;
|
||||
result.CulpritLicenceForeignCityName = null;
|
||||
result.CulpritLicenceIssuDate = "1394/10/13";
|
||||
result.CulpritLicenceNo = "1124242";
|
||||
result.DamagedCount = 1;
|
||||
result.DmgAssessorFirstCreationTime = null;
|
||||
result.EntryDate = null;
|
||||
result.IsLicenseMatchWithVehicleKind = 1;
|
||||
result.HasOtherCulprit = 0;
|
||||
result.IsAccidentOutOfBorder = 0;
|
||||
result.IsFatalAccident = 0;
|
||||
result.IsPlaqueChanged = 0;
|
||||
result.PlaqueCityId = null;
|
||||
result.PlaqueKindId = null;
|
||||
result.PlaqueLeftNo = null;
|
||||
result.PlaqueMiddleCodeId = null;
|
||||
result.PlaqueNo = null;
|
||||
result.PlaqueRightNo = null;
|
||||
result.PlaqueSampleId = null;
|
||||
result.PlaqueSerial = null;
|
||||
result.PoliceOfficerId = 1;
|
||||
result.PoliceReportDesc = null;
|
||||
result.PoliceReportSeri = null;
|
||||
result.PoliceReportSerial = null;
|
||||
result.PolicyId = null;
|
||||
result.PreviousPolicyEndDate = "";
|
||||
result.TrackingCode = null;
|
||||
result.IsLicenseReplacement = 0;
|
||||
result.UnknownCulpritCauseId = null;
|
||||
}
|
||||
|
||||
private async buildFanavaranSubmitPayload(input: {
|
||||
accidentReason?: { id?: string | number | null; fanavaran?: number | null };
|
||||
createdAt?: Date | string;
|
||||
location?: { lat?: number; lon?: number };
|
||||
damageParts?: any[];
|
||||
resolvePolicyId: () => Promise<number | null>;
|
||||
logPrefix: string;
|
||||
defaults?: {
|
||||
AccidentCityId: number;
|
||||
AccidentReportTypeId: number;
|
||||
AccidentVehicleUsedId: number;
|
||||
ClaimExpertId: number;
|
||||
CompensationReferenceId: number;
|
||||
CulpritLicenceTypeId: number;
|
||||
CulpritTypeId: number;
|
||||
};
|
||||
}): Promise<Record<string, unknown>> {
|
||||
const lookupDefaults =
|
||||
input.defaults ?? this.TEJARATNO_FANAVARAN_DEFAULTS;
|
||||
const result: Record<string, unknown> = {
|
||||
...lookupDefaults,
|
||||
AccidentCauseId: this.resolveAccidentCauseId(input.accidentReason),
|
||||
};
|
||||
|
||||
if (input.accidentReason && result.AccidentCauseId === null) {
|
||||
this.logger.warn(
|
||||
`${input.logPrefix} accidentReason exists but fanavaran is null or undefined`,
|
||||
);
|
||||
} else if (!input.accidentReason) {
|
||||
this.logger.error(
|
||||
`${input.logPrefix} expert decision / accidentReason is missing`,
|
||||
);
|
||||
}
|
||||
|
||||
if (input.createdAt) {
|
||||
result.AccidentDate = this.convertToPersianDate(input.createdAt);
|
||||
result.AnnouncementDate = result.AccidentDate;
|
||||
result.DocReceivedDate = result.AccidentDate;
|
||||
result.AccidentTime = this.getTime24Hour(input.createdAt);
|
||||
}
|
||||
|
||||
if (input.location?.lat && input.location?.lon) {
|
||||
result.AccidentLocationAddress = await this.getAddressFromCoordinates(
|
||||
input.location.lat,
|
||||
input.location.lon,
|
||||
);
|
||||
}
|
||||
|
||||
result.EstimateAmount = input.damageParts
|
||||
? this.calculateEstimateAmount(input.damageParts)
|
||||
: 0;
|
||||
|
||||
this.applyFanavaranDefaultFields(result);
|
||||
|
||||
try {
|
||||
const policyId = await input.resolvePolicyId();
|
||||
if (policyId !== undefined && policyId !== null) {
|
||||
result.PolicyId = policyId;
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`${input.logPrefix} Failed to get PolicyId from external API:`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fanavaran Submit - Map data from claim-request-management and request-management to third-party-car-financial-claims format
|
||||
*/
|
||||
@@ -3444,11 +3622,16 @@ export class ClaimRequestManagementService {
|
||||
/**
|
||||
* Step 1: Get appToken from GetAppToken API
|
||||
*/
|
||||
private async getAppToken(): Promise<string> {
|
||||
private async getAppToken(
|
||||
config: {
|
||||
appName: string;
|
||||
secret: string;
|
||||
} = this.getTejaratnoFanavaranConfig(),
|
||||
): Promise<string> {
|
||||
try {
|
||||
const requestHeaders: any = {
|
||||
appname: this.APP_NAME,
|
||||
secret: this.SECRET,
|
||||
appname: config.appName,
|
||||
secret: config.secret,
|
||||
"Content-Length": "0",
|
||||
};
|
||||
delete requestHeaders["Content-Type"];
|
||||
@@ -3500,12 +3683,18 @@ export class ClaimRequestManagementService {
|
||||
/**
|
||||
* Step 2: Login to get authenticationToken
|
||||
*/
|
||||
private async login(appToken: string): Promise<string> {
|
||||
private async login(
|
||||
appToken: string,
|
||||
config: {
|
||||
username: string;
|
||||
password: string;
|
||||
} = this.getTejaratnoFanavaranConfig(),
|
||||
): Promise<string> {
|
||||
try {
|
||||
const requestHeaders: any = {
|
||||
appToken: appToken,
|
||||
userName: this.USERNAME,
|
||||
password: this.PASSWORD,
|
||||
userName: config.username,
|
||||
password: config.password,
|
||||
"Content-Length": "0",
|
||||
};
|
||||
delete requestHeaders["Content-Type"];
|
||||
@@ -3559,6 +3748,65 @@ export class ClaimRequestManagementService {
|
||||
}
|
||||
}
|
||||
|
||||
private async getPolicyIdFromNationalCode(
|
||||
nationalCodeOfInsurer: string,
|
||||
config: {
|
||||
appName: string;
|
||||
secret: string;
|
||||
username: string;
|
||||
password: string;
|
||||
corpId: string;
|
||||
contractId: string;
|
||||
location: string;
|
||||
},
|
||||
logPrefix: string,
|
||||
): Promise<number | null> {
|
||||
try {
|
||||
const appToken = await this.getAppToken(config);
|
||||
const authenticationToken = await this.login(appToken, config);
|
||||
|
||||
const policyInquiryUrl = `https://apimanager.iraneit.com/BimeApiManager/api/BimeApi/v2.0/common/Policies/inquiry-my-policies?InsuranceLineId=5&NationalCode=${nationalCodeOfInsurer}`;
|
||||
|
||||
this.logger.log(
|
||||
`${logPrefix} Calling policy inquiry API for nationalCode: ${nationalCodeOfInsurer}`,
|
||||
);
|
||||
|
||||
const response = await firstValueFrom(
|
||||
this.httpService.get(policyInquiryUrl, {
|
||||
headers: {
|
||||
authenticationToken: authenticationToken,
|
||||
CorpId: config.corpId,
|
||||
ContractId: config.contractId,
|
||||
Location: config.location,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
timeout: 15000,
|
||||
}),
|
||||
);
|
||||
|
||||
if (Array.isArray(response.data) && response.data.length > 0) {
|
||||
const lastPolicy = response.data[response.data.length - 1];
|
||||
const policyId = lastPolicy?.PolicyId;
|
||||
if (policyId !== undefined && policyId !== null) {
|
||||
this.logger.log(
|
||||
`${logPrefix} Successfully retrieved PolicyId from last policy entry: ${policyId}`,
|
||||
);
|
||||
return policyId;
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.warn(`${logPrefix} No PolicyId found in API response`);
|
||||
return null;
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : "unknown policy inquiry error";
|
||||
this.logger.warn(
|
||||
`${logPrefix} Policy inquiry failed; continuing with empty PolicyId: ${errorMessage}`,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get PolicyId from external API
|
||||
*/
|
||||
@@ -3606,63 +3854,422 @@ export class ClaimRequestManagementService {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get authenticationToken
|
||||
const appToken = await this.getAppToken();
|
||||
const authenticationToken = await this.login(appToken);
|
||||
|
||||
// Call external API to get PolicyId
|
||||
const policyInquiryUrl = `https://apimanager.iraneit.com/BimeApiManager/api/BimeApi/v2.0/common/Policies/inquiry-my-policies?InsuranceLineId=5&NationalCode=${nationalCodeOfInsurer}`;
|
||||
|
||||
this.logger.log(
|
||||
`[Fanavaran Submit] Calling policy inquiry API for nationalCode: ${nationalCodeOfInsurer}`,
|
||||
return await this.getPolicyIdFromNationalCode(
|
||||
nationalCodeOfInsurer,
|
||||
this.getTejaratnoFanavaranConfig(),
|
||||
`[Fanavaran Submit] claimRequestId=${claimRequestId}`,
|
||||
);
|
||||
|
||||
const response = await firstValueFrom(
|
||||
this.httpService.get(policyInquiryUrl, {
|
||||
headers: {
|
||||
authenticationToken: authenticationToken,
|
||||
CorpId: this.CORP_ID,
|
||||
ContractId: this.CONTRACT_ID,
|
||||
Location: this.LOCATION,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
if (
|
||||
Array.isArray(response.data) &&
|
||||
response.data.length > 0 &&
|
||||
response.data[0].PolicyId
|
||||
) {
|
||||
const policyId = response.data[0].PolicyId;
|
||||
this.logger.log(
|
||||
`[Fanavaran Submit] Successfully retrieved PolicyId: ${policyId}`,
|
||||
);
|
||||
return policyId;
|
||||
} else {
|
||||
this.logger.warn(
|
||||
`[Fanavaran Submit] No PolicyId found in API response`,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`[Fanavaran Submit] Error getting PolicyId from external API:`,
|
||||
error,
|
||||
);
|
||||
if (isAxiosError(error)) {
|
||||
const errorMessage =
|
||||
error.response?.data?.Message ||
|
||||
error.response?.data?.message ||
|
||||
error.response?.data ||
|
||||
error.message ||
|
||||
"Failed to get PolicyId from external API";
|
||||
this.logger.error(`[Fanavaran Submit] Error message: ${errorMessage}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private getNationalCodeOfInsurerForGuiltyPartyV2(
|
||||
parties: Array<{
|
||||
role?: PartyRole;
|
||||
person?: { userId?: Types.ObjectId; nationalCodeOfInsurer?: string };
|
||||
statement?: { admitsGuilt?: boolean };
|
||||
}>,
|
||||
guiltyPartyId?: Types.ObjectId | string | null,
|
||||
): string | null {
|
||||
if (!guiltyPartyId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const guiltyParty = parties.find(
|
||||
(party) => party.person?.userId?.toString() === guiltyPartyId.toString(),
|
||||
);
|
||||
|
||||
return guiltyParty?.person?.nationalCodeOfInsurer ?? null;
|
||||
}
|
||||
|
||||
private resolveGuiltyPartyIdV2(
|
||||
parties: Array<{
|
||||
role?: PartyRole;
|
||||
person?: { userId?: Types.ObjectId };
|
||||
statement?: { admitsGuilt?: boolean };
|
||||
}>,
|
||||
decisionGuiltyPartyId?: Types.ObjectId | string | null,
|
||||
): Types.ObjectId | string | null {
|
||||
if (decisionGuiltyPartyId) {
|
||||
return decisionGuiltyPartyId;
|
||||
}
|
||||
|
||||
const admitsGuiltParty = parties.find(
|
||||
(party) => party.statement?.admitsGuilt && party.person?.userId,
|
||||
);
|
||||
if (admitsGuiltParty?.person?.userId) {
|
||||
return admitsGuiltParty.person.userId;
|
||||
}
|
||||
|
||||
// Legacy-compatible safety fallback: when no explicit guilty marker exists,
|
||||
// use FIRST party if available (same heuristic used by older flows).
|
||||
const firstParty = parties.find(
|
||||
(party) => party.role === PartyRole.FIRST && party.person?.userId,
|
||||
);
|
||||
return firstParty?.person?.userId ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fanavaran Submit (Parsian / V2) - Map data from claimCases + blameCases
|
||||
*/
|
||||
async fanavaranSubmitParsianV2(
|
||||
claimCaseId: string,
|
||||
options?: { debug?: boolean },
|
||||
): Promise<any> {
|
||||
try {
|
||||
const debug = {
|
||||
claimCaseId,
|
||||
steps: {
|
||||
claimCaseFound: false,
|
||||
blameRequestLinked: false,
|
||||
blameCaseFound: false,
|
||||
expertDecisionFound: false,
|
||||
accidentReasonFound: false,
|
||||
firstPartyFound: false,
|
||||
firstPartyLocationFound: false,
|
||||
damageReplyFound: false,
|
||||
guiltyPartyIdFound: false,
|
||||
nationalCodeOfInsurerFound: false,
|
||||
policyInquiryAttempted: false,
|
||||
policyInquirySucceeded: false,
|
||||
accidentReasonFallbackFromSnapshot: false,
|
||||
guiltyPartyFallbackUsed: false,
|
||||
},
|
||||
values: {
|
||||
blameRequestId: null as string | null,
|
||||
guiltyPartyId: null as string | null,
|
||||
nationalCodeOfInsurer: null as string | null,
|
||||
policyId: null as number | null,
|
||||
estimateAmount: null as number | null,
|
||||
},
|
||||
failureReason: null as string | null,
|
||||
};
|
||||
|
||||
const claimCase = await this.claimCaseDbService.findById(claimCaseId);
|
||||
if (!claimCase) {
|
||||
throw new NotFoundException("Claim case not found");
|
||||
}
|
||||
debug.steps.claimCaseFound = true;
|
||||
|
||||
if (!claimCase.blameRequestId) {
|
||||
debug.failureReason = "claimCase.blameRequestId is missing";
|
||||
throw new BadRequestException("Blame case not linked to claim case");
|
||||
}
|
||||
debug.steps.blameRequestLinked = true;
|
||||
debug.values.blameRequestId = claimCase.blameRequestId.toString();
|
||||
|
||||
const blameCase = await this.blameRequestDbService.findById(
|
||||
claimCase.blameRequestId,
|
||||
);
|
||||
if (!blameCase) {
|
||||
throw new NotFoundException("Blame case not found");
|
||||
}
|
||||
debug.steps.blameCaseFound = true;
|
||||
|
||||
const expertDecision = blameCase.expert?.decision;
|
||||
debug.steps.expertDecisionFound = !!expertDecision;
|
||||
const fallbackAccidentReason = claimCase.snapshot?.accident?.classification?.accidentReason;
|
||||
if (!expertDecision?.fields?.accidentReason && fallbackAccidentReason) {
|
||||
debug.steps.accidentReasonFallbackFromSnapshot = true;
|
||||
}
|
||||
const selectedAccidentReason =
|
||||
expertDecision?.fields?.accidentReason ?? fallbackAccidentReason;
|
||||
debug.steps.accidentReasonFound = !!selectedAccidentReason;
|
||||
|
||||
const firstParty = blameCase.parties?.find(
|
||||
(party) => party.role === PartyRole.FIRST,
|
||||
);
|
||||
debug.steps.firstPartyFound = !!firstParty;
|
||||
debug.steps.firstPartyLocationFound = !!firstParty?.location;
|
||||
const activeExpertReply = getActiveV2ExpertReply(
|
||||
claimCase as unknown as { evaluation?: Record<string, unknown> },
|
||||
);
|
||||
const damageParts = activeExpertReply?.parts;
|
||||
debug.steps.damageReplyFound = !!damageParts?.length;
|
||||
if (options?.debug) {
|
||||
debug.values.estimateAmount = damageParts
|
||||
? this.calculateEstimateAmount(damageParts)
|
||||
: 0;
|
||||
}
|
||||
|
||||
const payload = await this.buildFanavaranSubmitPayload({
|
||||
accidentReason: selectedAccidentReason,
|
||||
createdAt: (blameCase as { createdAt?: Date }).createdAt,
|
||||
location: firstParty?.location,
|
||||
damageParts,
|
||||
defaults: this.PARSIAN_FANAVARAN_DEFAULTS,
|
||||
logPrefix: `[Fanavaran Parsian V2] claimCaseId=${claimCaseId}`,
|
||||
resolvePolicyId: async () => {
|
||||
const guiltyPartyId = this.resolveGuiltyPartyIdV2(
|
||||
blameCase.parties ?? [],
|
||||
expertDecision?.guiltyPartyId,
|
||||
);
|
||||
if (!expertDecision?.guiltyPartyId && guiltyPartyId) {
|
||||
debug.steps.guiltyPartyFallbackUsed = true;
|
||||
}
|
||||
debug.steps.guiltyPartyIdFound = !!guiltyPartyId;
|
||||
debug.values.guiltyPartyId = guiltyPartyId
|
||||
? guiltyPartyId.toString()
|
||||
: null;
|
||||
const nationalCodeOfInsurer = this.getNationalCodeOfInsurerForGuiltyPartyV2(
|
||||
blameCase.parties ?? [],
|
||||
guiltyPartyId,
|
||||
);
|
||||
debug.steps.nationalCodeOfInsurerFound = !!nationalCodeOfInsurer;
|
||||
debug.values.nationalCodeOfInsurer = nationalCodeOfInsurer;
|
||||
|
||||
if (!guiltyPartyId) {
|
||||
this.logger.warn(
|
||||
`[Fanavaran Parsian V2] guiltyPartyId not found in expert.decision`,
|
||||
);
|
||||
debug.failureReason = "expert.decision.guiltyPartyId is missing";
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!nationalCodeOfInsurer) {
|
||||
this.logger.warn(
|
||||
`[Fanavaran Parsian V2] nationalCodeOfInsurer not found for guiltyPartyId: ${guiltyPartyId}`,
|
||||
);
|
||||
debug.failureReason =
|
||||
"nationalCodeOfInsurer not found for guilty party in blameCase.parties";
|
||||
return null;
|
||||
}
|
||||
|
||||
debug.steps.policyInquiryAttempted = true;
|
||||
const policyId = await this.getPolicyIdFromNationalCode(
|
||||
nationalCodeOfInsurer,
|
||||
this.PARSIAN_FANAVARAN_CONFIG,
|
||||
`[Fanavaran Parsian V2] claimCaseId=${claimCaseId}`,
|
||||
);
|
||||
debug.values.policyId = policyId;
|
||||
debug.steps.policyInquirySucceeded = policyId !== null;
|
||||
if (policyId === null) {
|
||||
debug.failureReason =
|
||||
debug.failureReason ??
|
||||
"policy inquiry returned no PolicyId (timeout, network error, or empty response)";
|
||||
}
|
||||
return policyId;
|
||||
},
|
||||
});
|
||||
|
||||
if (options?.debug) {
|
||||
return {
|
||||
payload,
|
||||
debug,
|
||||
};
|
||||
}
|
||||
|
||||
return payload;
|
||||
} catch (error) {
|
||||
this.logger.error("Error in fanavaranSubmitParsianV2", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit Parsian fanavaran data (V2 collections) to external API
|
||||
*/
|
||||
async submitToFanavaranParsianV2(claimCaseId: string): Promise<any> {
|
||||
try {
|
||||
return await this.executeFanavaranParsianV2Submit(claimCaseId);
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`[Fanavaran Parsian V2] Error submitting to Fanavaran`,
|
||||
error,
|
||||
);
|
||||
throw new BadGatewayException(this.extractFanavaranErrorMessage(error));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-submit to Fanavaran when a claim case reaches COMPLETED.
|
||||
* Never throws — failures are returned as warnings so the main flow continues.
|
||||
*/
|
||||
async autoSubmitToFanavaranParsianV2OnClaimCompleted(
|
||||
claimCaseId: string,
|
||||
): Promise<FanavaranAutoSubmitResult> {
|
||||
const logPrefix = `[Fanavaran Parsian V2 Auto] claimCaseId=${claimCaseId}`;
|
||||
|
||||
try {
|
||||
const claimCase = await this.claimCaseDbService.findById(claimCaseId);
|
||||
if (!claimCase) {
|
||||
return {
|
||||
attempted: false,
|
||||
submitted: false,
|
||||
warning:
|
||||
"Claim case not found for Fanavaran auto-submit. Retry manually when available.",
|
||||
};
|
||||
}
|
||||
|
||||
if (claimCase.claimId != null || claimCase.claimNo != null) {
|
||||
return {
|
||||
attempted: false,
|
||||
submitted: false,
|
||||
skipped: true,
|
||||
skipReason: "Already submitted to Fanavaran",
|
||||
claimId: claimCase.claimId,
|
||||
claimNo: claimCase.claimNo,
|
||||
};
|
||||
}
|
||||
|
||||
const fanavaranResponse = await this.executeFanavaranParsianV2Submit(
|
||||
claimCaseId,
|
||||
);
|
||||
|
||||
await this.claimCaseDbService.findByIdAndUpdate(claimCaseId, {
|
||||
$push: {
|
||||
history: {
|
||||
type: "FANAVARAN_AUTO_SUBMIT_SUCCEEDED",
|
||||
actor: { actorType: "system" },
|
||||
timestamp: new Date(),
|
||||
metadata: {
|
||||
claimNo: fanavaranResponse?.ClaimNo,
|
||||
claimId: fanavaranResponse?.Id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
attempted: true,
|
||||
submitted: true,
|
||||
claimNo: fanavaranResponse?.ClaimNo,
|
||||
claimId: fanavaranResponse?.Id,
|
||||
fanavaranResponse,
|
||||
};
|
||||
} catch (error) {
|
||||
const warning = this.extractFanavaranErrorMessage(error);
|
||||
this.logger.warn(`${logPrefix} Auto-submit failed: ${warning}`);
|
||||
|
||||
try {
|
||||
await this.claimCaseDbService.findByIdAndUpdate(claimCaseId, {
|
||||
$push: {
|
||||
history: {
|
||||
type: "FANAVARAN_AUTO_SUBMIT_FAILED",
|
||||
actor: { actorType: "system" },
|
||||
timestamp: new Date(),
|
||||
metadata: { error: warning },
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (historyError) {
|
||||
this.logger.error(
|
||||
`${logPrefix} Failed to record Fanavaran auto-submit failure history`,
|
||||
historyError,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
attempted: true,
|
||||
submitted: false,
|
||||
warning: `${warning} Case was not sent to Fanavaran. Retry manually via POST /v2/claim-request-management/fanavaran-submit/parsian/${claimCaseId}.`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private extractFanavaranErrorMessage(error: unknown): string {
|
||||
if (isAxiosError(error)) {
|
||||
return (
|
||||
error.response?.data?.message ||
|
||||
error.response?.data?.Message ||
|
||||
(typeof error.response?.data === "string"
|
||||
? error.response.data
|
||||
: undefined) ||
|
||||
error.message ||
|
||||
"Failed to submit data to Fanavaran API"
|
||||
);
|
||||
}
|
||||
if (error instanceof HttpException) {
|
||||
const response = error.getResponse();
|
||||
if (typeof response === "string") return response;
|
||||
if (response && typeof response === "object" && "message" in response) {
|
||||
const message = (response as { message?: string | string[] }).message;
|
||||
return Array.isArray(message) ? message.join(", ") : String(message);
|
||||
}
|
||||
}
|
||||
if (error instanceof Error) {
|
||||
return error.message;
|
||||
}
|
||||
return "Failed to submit data to Fanavaran API";
|
||||
}
|
||||
|
||||
private async executeFanavaranParsianV2Submit(
|
||||
claimCaseId: string,
|
||||
): Promise<any> {
|
||||
const config = this.PARSIAN_FANAVARAN_CONFIG;
|
||||
const logPrefix = `[Fanavaran Parsian V2]`;
|
||||
|
||||
this.logger.log(
|
||||
`${logPrefix} Starting submission for claimCaseId: ${claimCaseId}`,
|
||||
);
|
||||
const fanavaranData = await this.fanavaranSubmitParsianV2(claimCaseId);
|
||||
this.logger.log(
|
||||
`${logPrefix} Mapped data prepared:`,
|
||||
JSON.stringify(fanavaranData, null, 2),
|
||||
);
|
||||
|
||||
if (
|
||||
fanavaranData.AccidentCauseId === undefined ||
|
||||
fanavaranData.AccidentCauseId === null
|
||||
) {
|
||||
this.logger.error(
|
||||
`${logPrefix} CRITICAL: AccidentCauseId is missing or null!`,
|
||||
);
|
||||
} else {
|
||||
this.logger.log(
|
||||
`${logPrefix} AccidentCauseId validated: ${fanavaranData.AccidentCauseId}`,
|
||||
);
|
||||
}
|
||||
|
||||
const appToken = await this.getAppToken(config);
|
||||
const authenticationToken = await this.login(appToken, config);
|
||||
|
||||
const response = await firstValueFrom(
|
||||
this.httpService.post(this.FANAVARAN_SUBMIT_URL, fanavaranData, {
|
||||
headers: {
|
||||
authenticationToken: authenticationToken,
|
||||
CorpId: config.corpId,
|
||||
ContractId: config.contractId,
|
||||
Location: config.location,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
this.logger.log(`${logPrefix} API Response Status: ${response.status}`);
|
||||
this.logger.log(
|
||||
`${logPrefix} API Response Data:`,
|
||||
JSON.stringify(response.data, null, 2),
|
||||
);
|
||||
|
||||
if (response.data) {
|
||||
const claimNo = response.data.ClaimNo;
|
||||
const claimId = response.data.Id;
|
||||
|
||||
if (claimNo !== undefined || claimId !== undefined) {
|
||||
const updateData: any = { $set: {} as Record<string, unknown> };
|
||||
if (claimNo !== undefined) {
|
||||
updateData.$set.claimNo = claimNo;
|
||||
}
|
||||
if (claimId !== undefined) {
|
||||
updateData.$set.claimId = claimId;
|
||||
}
|
||||
|
||||
await this.claimCaseDbService.findByIdAndUpdate(
|
||||
claimCaseId,
|
||||
updateData,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit fanavaran data to external API
|
||||
*/
|
||||
@@ -6306,6 +6913,7 @@ export class ClaimRequestManagementService {
|
||||
currentStep?: ClaimWorkflowStep;
|
||||
accepted: boolean;
|
||||
phase?: "PRICED_PARTS_FOR_FACTORS" | "FINAL_APPROVAL";
|
||||
fanavaran?: FanavaranAutoSubmitResult;
|
||||
}> {
|
||||
if (!Types.ObjectId.isValid(claimRequestId)) {
|
||||
throw new BadRequestException("Invalid claim request id");
|
||||
@@ -6558,14 +7166,26 @@ export class ClaimRequestManagementService {
|
||||
},
|
||||
});
|
||||
|
||||
const fanavaran =
|
||||
await this.autoSubmitToFanavaranParsianV2OnClaimCompleted(claimRequestId);
|
||||
|
||||
let message = "Your signature has been recorded. The claim is completed.";
|
||||
if (fanavaran.submitted) {
|
||||
message +=
|
||||
" The claim was sent to Fanavaran successfully.";
|
||||
} else if (fanavaran.warning) {
|
||||
message += ` ${fanavaran.warning}`;
|
||||
}
|
||||
|
||||
return {
|
||||
message: "Your signature has been recorded. The claim is completed.",
|
||||
message,
|
||||
claimRequestId,
|
||||
status: ClaimCaseStatus.COMPLETED,
|
||||
claimStatus: ClaimStatus.APPROVED,
|
||||
currentStep: ClaimWorkflowStep.CLAIM_COMPLETED,
|
||||
accepted: true,
|
||||
phase: "FINAL_APPROVAL",
|
||||
fanavaran,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user