forked from Yara724/api
merge upstream
This commit is contained in:
@@ -10,6 +10,7 @@ import { AuthModule } from "./auth/auth.module";
|
|||||||
import { ClaimRequestManagementModule } from "./claim-request-management/claim-request-management.module";
|
import { ClaimRequestManagementModule } from "./claim-request-management/claim-request-management.module";
|
||||||
import { ClientModule } from "./client/client.module";
|
import { ClientModule } from "./client/client.module";
|
||||||
import { ExpertBlameModule } from "./expert-blame/expert-blame.module";
|
import { ExpertBlameModule } from "./expert-blame/expert-blame.module";
|
||||||
|
import { FanavaranModule } from "./fanavaran/fanavaran.module";
|
||||||
import { ExpertClaimModule } from "./expert-claim/expert-claim.module";
|
import { ExpertClaimModule } from "./expert-claim/expert-claim.module";
|
||||||
import { ExpertInsurerModule } from "./expert-insurer/expert-insurer.module";
|
import { ExpertInsurerModule } from "./expert-insurer/expert-insurer.module";
|
||||||
import { CaseExpertReportModule } from "./case-expert-report/case-expert-report.module";
|
import { CaseExpertReportModule } from "./case-expert-report/case-expert-report.module";
|
||||||
@@ -50,6 +51,7 @@ import { AppConfigModule } from "./core/config/config.module";
|
|||||||
SystemSettingsModule,
|
SystemSettingsModule,
|
||||||
ExpertBlameModule,
|
ExpertBlameModule,
|
||||||
ClaimRequestManagementModule,
|
ClaimRequestManagementModule,
|
||||||
|
FanavaranModule,
|
||||||
ExpertClaimModule,
|
ExpertClaimModule,
|
||||||
CaseExpertReportModule,
|
CaseExpertReportModule,
|
||||||
AiModule,
|
AiModule,
|
||||||
|
|||||||
@@ -53,10 +53,12 @@ import { ClaimAccessGuard } from "src/auth/guards/claim-access.guard";
|
|||||||
import { JwtModule } from "@nestjs/jwt";
|
import { JwtModule } from "@nestjs/jwt";
|
||||||
import { MediaPolicyModule } from "src/media-policy/media-policy.module";
|
import { MediaPolicyModule } from "src/media-policy/media-policy.module";
|
||||||
import { HttpModule } from "@nestjs/axios";
|
import { HttpModule } from "@nestjs/axios";
|
||||||
|
import { FanavaranAuditModule } from "src/fanavaran/fanavaran-audit.module";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
HttpModule,
|
HttpModule,
|
||||||
|
FanavaranAuditModule,
|
||||||
PublicIdModule,
|
PublicIdModule,
|
||||||
UsersModule,
|
UsersModule,
|
||||||
RequestManagementModule,
|
RequestManagementModule,
|
||||||
|
|||||||
@@ -163,6 +163,19 @@ import {
|
|||||||
import { HttpService } from "@nestjs/axios";
|
import { HttpService } from "@nestjs/axios";
|
||||||
import { firstValueFrom } from "rxjs";
|
import { firstValueFrom } from "rxjs";
|
||||||
import { buildEnrichedDamagedParts } from "src/expert-claim/dto/claim-damaged-part.enricher";
|
import { buildEnrichedDamagedParts } from "src/expert-claim/dto/claim-damaged-part.enricher";
|
||||||
|
import {
|
||||||
|
type FanavaranClientKey,
|
||||||
|
fanavaranSubmitPath,
|
||||||
|
getFanavaranClientProfile,
|
||||||
|
resolveFanavaranClientKey,
|
||||||
|
} from "src/core/config/fanavaran-client.config";
|
||||||
|
import { FanavaranAuditService } from "src/fanavaran/fanavaran-audit.service";
|
||||||
|
import type { FanavaranAuditSession } from "src/fanavaran/fanavaran-audit.types";
|
||||||
|
import {
|
||||||
|
FanavaranAuditSource,
|
||||||
|
FanavaranAuditStatus,
|
||||||
|
FanavaranAuditStep,
|
||||||
|
} from "src/fanavaran/schema/fanavaran-audit-log.schema";
|
||||||
|
|
||||||
export interface FanavaranAutoSubmitResult {
|
export interface FanavaranAutoSubmitResult {
|
||||||
attempted: boolean;
|
attempted: boolean;
|
||||||
@@ -175,15 +188,17 @@ export interface FanavaranAutoSubmitResult {
|
|||||||
fanavaranResponse?: unknown;
|
fanavaranResponse?: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const FANAVARAN_ACCIDENT_LOCATION_ADDRESS = "استان تهران شهر تهران";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ClaimRequestManagementService {
|
export class ClaimRequestManagementService {
|
||||||
private readonly logger = new Logger(ClaimRequestManagementService.name);
|
private readonly logger = new Logger(ClaimRequestManagementService.name);
|
||||||
|
|
||||||
// Authentication URLs and credentials (same as lookups service)
|
// Authentication URLs and credentials (same as lookups service)
|
||||||
private readonly GET_APP_TOKEN_URL =
|
private readonly GET_APP_TOKEN_URL =
|
||||||
"https://insapm.ir/bimeapimanager/api/EITAuthentication/GetAppToken";
|
"https://apimanager.iraneit.com/BimeApiManager/api/EITAuthentication/GetAppToken";
|
||||||
private readonly LOGIN_URL =
|
private readonly LOGIN_URL =
|
||||||
"https://insapm.ir/bimeapimanager/api/EITAuthentication/Login";
|
"https://apimanager.iraneit.com/BimeApiManager/api/EITAuthentication/Login";
|
||||||
private readonly FANAVARAN_SUBMIT_URL =
|
private readonly FANAVARAN_SUBMIT_URL =
|
||||||
"https://apimanager.iraneit.com/BimeApiManager/api/BimeApi/v2.0/car/third-party-car-financial-claims";
|
"https://apimanager.iraneit.com/BimeApiManager/api/BimeApi/v2.0/car/third-party-car-financial-claims";
|
||||||
|
|
||||||
@@ -253,6 +268,7 @@ export class ClaimRequestManagementService {
|
|||||||
private readonly publicIdService: PublicIdService,
|
private readonly publicIdService: PublicIdService,
|
||||||
private readonly sandHubService: SandHubService,
|
private readonly sandHubService: SandHubService,
|
||||||
private readonly httpService: HttpService,
|
private readonly httpService: HttpService,
|
||||||
|
private readonly fanavaranAuditService: FanavaranAuditService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
private requiredDocumentKeysV2(isCarBody: boolean): string[] {
|
private requiredDocumentKeysV2(isCarBody: boolean): string[] {
|
||||||
@@ -3349,9 +3365,15 @@ export class ClaimRequestManagementService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private applyFanavaranDefaultFields(result: Record<string, unknown>): void {
|
private applyFanavaranDefaultFields(result: Record<string, unknown>): void {
|
||||||
|
result.ActualPremium = null;
|
||||||
|
result.ArchiveNo = null;
|
||||||
|
result.AuthorityCulpritId = null;
|
||||||
result.AccidentCulpritId = null;
|
result.AccidentCulpritId = null;
|
||||||
|
result.ClaimCompletionDate = null;
|
||||||
|
result.CostSeparationToDmgSections = null;
|
||||||
result.CouponNo = null;
|
result.CouponNo = null;
|
||||||
result.CourtArchiveNo = null;
|
result.CourtArchiveNo = null;
|
||||||
|
result.CustomerFaultPercent = null;
|
||||||
result.CulpritLicenceCityId = null;
|
result.CulpritLicenceCityId = null;
|
||||||
result.CulpritLicenceCountryId = null;
|
result.CulpritLicenceCountryId = null;
|
||||||
result.CulpritLicenceForeignCityName = null;
|
result.CulpritLicenceForeignCityName = null;
|
||||||
@@ -3360,11 +3382,14 @@ export class ClaimRequestManagementService {
|
|||||||
result.DamagedCount = 1;
|
result.DamagedCount = 1;
|
||||||
result.DmgAssessorFirstCreationTime = null;
|
result.DmgAssessorFirstCreationTime = null;
|
||||||
result.EntryDate = null;
|
result.EntryDate = null;
|
||||||
|
result.GlassBreakReasonId = null;
|
||||||
result.IsLicenseMatchWithVehicleKind = 1;
|
result.IsLicenseMatchWithVehicleKind = 1;
|
||||||
result.HasOtherCulprit = 0;
|
result.HasOtherCulprit = 0;
|
||||||
result.IsAccidentOutOfBorder = 0;
|
result.IsAccidentOutOfBorder = 0;
|
||||||
result.IsFatalAccident = 0;
|
result.IsFatalAccident = 0;
|
||||||
|
result.IsOwnerChanged = null;
|
||||||
result.IsPlaqueChanged = 0;
|
result.IsPlaqueChanged = 0;
|
||||||
|
result.IsSurplusArticleEighthLaw = null;
|
||||||
result.PlaqueCityId = null;
|
result.PlaqueCityId = null;
|
||||||
result.PlaqueKindId = null;
|
result.PlaqueKindId = null;
|
||||||
result.PlaqueLeftNo = null;
|
result.PlaqueLeftNo = null;
|
||||||
@@ -3379,6 +3404,7 @@ export class ClaimRequestManagementService {
|
|||||||
result.PoliceReportSerial = null;
|
result.PoliceReportSerial = null;
|
||||||
result.PolicyId = null;
|
result.PolicyId = null;
|
||||||
result.PreviousPolicyEndDate = "";
|
result.PreviousPolicyEndDate = "";
|
||||||
|
result.StatusChangeDate = null;
|
||||||
result.TrackingCode = null;
|
result.TrackingCode = null;
|
||||||
result.IsLicenseReplacement = 0;
|
result.IsLicenseReplacement = 0;
|
||||||
result.UnknownCulpritCauseId = null;
|
result.UnknownCulpritCauseId = null;
|
||||||
@@ -3425,12 +3451,7 @@ export class ClaimRequestManagementService {
|
|||||||
result.AccidentTime = this.getTime24Hour(input.createdAt);
|
result.AccidentTime = this.getTime24Hour(input.createdAt);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input.location?.lat && input.location?.lon) {
|
result.AccidentLocationAddress = FANAVARAN_ACCIDENT_LOCATION_ADDRESS;
|
||||||
result.AccidentLocationAddress = await this.getAddressFromCoordinates(
|
|
||||||
input.location.lat,
|
|
||||||
input.location.lon,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
result.EstimateAmount = input.damageParts
|
result.EstimateAmount = input.damageParts
|
||||||
? this.calculateEstimateAmount(input.damageParts)
|
? this.calculateEstimateAmount(input.damageParts)
|
||||||
@@ -3589,16 +3610,7 @@ export class ClaimRequestManagementService {
|
|||||||
result.AccidentTime = this.getTime24Hour(blameRequest.createdAt);
|
result.AccidentTime = this.getTime24Hour(blameRequest.createdAt);
|
||||||
}
|
}
|
||||||
|
|
||||||
// AccidentLocationAddress: Get from reverse geocoding API
|
result.AccidentLocationAddress = FANAVARAN_ACCIDENT_LOCATION_ADDRESS;
|
||||||
if (
|
|
||||||
blameRequest.firstPartyLocation?.lat &&
|
|
||||||
blameRequest.firstPartyLocation?.lon
|
|
||||||
) {
|
|
||||||
result.AccidentLocationAddress = await this.getAddressFromCoordinates(
|
|
||||||
blameRequest.firstPartyLocation.lat,
|
|
||||||
blameRequest.firstPartyLocation.lon,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// EstimateAmount: Sum of totalPayment from damageExpertReply parts
|
// EstimateAmount: Sum of totalPayment from damageExpertReply parts
|
||||||
const damageReply =
|
const damageReply =
|
||||||
@@ -3610,39 +3622,7 @@ export class ClaimRequestManagementService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Keep other fields from template with default values
|
// Keep other fields from template with default values
|
||||||
result.AccidentCulpritId = null; //todo
|
this.applyFanavaranDefaultFields(result);
|
||||||
result.CouponNo = null;
|
|
||||||
result.CourtArchiveNo = null;
|
|
||||||
result.CulpritLicenceCityId = null;
|
|
||||||
result.CulpritLicenceCountryId = null;
|
|
||||||
result.CulpritLicenceForeignCityName = null;
|
|
||||||
result.CulpritLicenceIssuDate = "1394/10/13"; //todo
|
|
||||||
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;
|
|
||||||
|
|
||||||
// Get PolicyId from external API
|
// Get PolicyId from external API
|
||||||
try {
|
try {
|
||||||
@@ -3676,7 +3656,19 @@ export class ClaimRequestManagementService {
|
|||||||
appName: string;
|
appName: string;
|
||||||
secret: string;
|
secret: string;
|
||||||
} = this.getTejaratnoFanavaranConfig(),
|
} = this.getTejaratnoFanavaranConfig(),
|
||||||
|
auditSession?: FanavaranAuditSession,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
|
const startedAt = Date.now();
|
||||||
|
if (auditSession) {
|
||||||
|
await this.fanavaranAuditService.recordStep({
|
||||||
|
session: auditSession,
|
||||||
|
step: FanavaranAuditStep.GET_APP_TOKEN,
|
||||||
|
status: FanavaranAuditStatus.STARTED,
|
||||||
|
requestUrl: this.GET_APP_TOKEN_URL,
|
||||||
|
requestMeta: { appName: config.appName },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const requestHeaders: any = {
|
const requestHeaders: any = {
|
||||||
appname: config.appName,
|
appname: config.appName,
|
||||||
@@ -3712,9 +3704,34 @@ export class ClaimRequestManagementService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (auditSession) {
|
||||||
|
await this.fanavaranAuditService.recordStep({
|
||||||
|
session: auditSession,
|
||||||
|
step: FanavaranAuditStep.GET_APP_TOKEN,
|
||||||
|
status: FanavaranAuditStatus.SUCCESS,
|
||||||
|
requestUrl: this.GET_APP_TOKEN_URL,
|
||||||
|
httpStatus: response.status,
|
||||||
|
responseMeta: { hasAppToken: true },
|
||||||
|
durationMs: Date.now() - startedAt,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.logger.log(`Successfully obtained appToken`);
|
this.logger.log(`Successfully obtained appToken`);
|
||||||
return appToken;
|
return appToken;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (auditSession) {
|
||||||
|
await this.fanavaranAuditService.recordStep({
|
||||||
|
session: auditSession,
|
||||||
|
step: FanavaranAuditStep.GET_APP_TOKEN,
|
||||||
|
status: FanavaranAuditStatus.FAILURE,
|
||||||
|
requestUrl: this.GET_APP_TOKEN_URL,
|
||||||
|
httpStatus: isAxiosError(error) ? error.response?.status : undefined,
|
||||||
|
errorMessage: this.fanavaranAuditService.extractErrorMessage(error),
|
||||||
|
errorDetails: this.fanavaranAuditService.sanitizeErrorDetails(error),
|
||||||
|
durationMs: Date.now() - startedAt,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.logger.error("Failed to get appToken", error);
|
this.logger.error("Failed to get appToken", error);
|
||||||
if (isAxiosError(error)) {
|
if (isAxiosError(error)) {
|
||||||
const errorMessage =
|
const errorMessage =
|
||||||
@@ -3723,9 +3740,19 @@ export class ClaimRequestManagementService {
|
|||||||
error.response?.data ||
|
error.response?.data ||
|
||||||
error.message ||
|
error.message ||
|
||||||
"Failed to get appToken from external API";
|
"Failed to get appToken from external API";
|
||||||
throw new BadGatewayException(errorMessage);
|
throw new BadGatewayException(
|
||||||
|
this.fanavaranAuditService.formatErrorWithTrackingCode(
|
||||||
|
String(errorMessage),
|
||||||
|
auditSession?.trackingCode,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
throw new BadGatewayException("Failed to get appToken from external API");
|
throw new BadGatewayException(
|
||||||
|
this.fanavaranAuditService.formatErrorWithTrackingCode(
|
||||||
|
"Failed to get appToken from external API",
|
||||||
|
auditSession?.trackingCode,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3738,7 +3765,19 @@ export class ClaimRequestManagementService {
|
|||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
} = this.getTejaratnoFanavaranConfig(),
|
} = this.getTejaratnoFanavaranConfig(),
|
||||||
|
auditSession?: FanavaranAuditSession,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
|
const startedAt = Date.now();
|
||||||
|
if (auditSession) {
|
||||||
|
await this.fanavaranAuditService.recordStep({
|
||||||
|
session: auditSession,
|
||||||
|
step: FanavaranAuditStep.LOGIN,
|
||||||
|
status: FanavaranAuditStatus.STARTED,
|
||||||
|
requestUrl: this.LOGIN_URL,
|
||||||
|
requestMeta: { userName: config.username },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const requestHeaders: any = {
|
const requestHeaders: any = {
|
||||||
appToken: appToken,
|
appToken: appToken,
|
||||||
@@ -3763,7 +3802,6 @@ export class ClaimRequestManagementService {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check response headers first (authenticationToken is in headers)
|
|
||||||
const authenticationToken =
|
const authenticationToken =
|
||||||
response.headers.authenticationtoken ||
|
response.headers.authenticationtoken ||
|
||||||
response.headers.authenticationToken ||
|
response.headers.authenticationToken ||
|
||||||
@@ -3780,9 +3818,34 @@ export class ClaimRequestManagementService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (auditSession) {
|
||||||
|
await this.fanavaranAuditService.recordStep({
|
||||||
|
session: auditSession,
|
||||||
|
step: FanavaranAuditStep.LOGIN,
|
||||||
|
status: FanavaranAuditStatus.SUCCESS,
|
||||||
|
requestUrl: this.LOGIN_URL,
|
||||||
|
httpStatus: response.status,
|
||||||
|
responseMeta: { hasAuthenticationToken: true },
|
||||||
|
durationMs: Date.now() - startedAt,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.logger.log(`Successfully obtained authenticationToken`);
|
this.logger.log(`Successfully obtained authenticationToken`);
|
||||||
return authenticationToken;
|
return authenticationToken;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (auditSession) {
|
||||||
|
await this.fanavaranAuditService.recordStep({
|
||||||
|
session: auditSession,
|
||||||
|
step: FanavaranAuditStep.LOGIN,
|
||||||
|
status: FanavaranAuditStatus.FAILURE,
|
||||||
|
requestUrl: this.LOGIN_URL,
|
||||||
|
httpStatus: isAxiosError(error) ? error.response?.status : undefined,
|
||||||
|
errorMessage: this.fanavaranAuditService.extractErrorMessage(error),
|
||||||
|
errorDetails: this.fanavaranAuditService.sanitizeErrorDetails(error),
|
||||||
|
durationMs: Date.now() - startedAt,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.logger.error("Failed to login", error);
|
this.logger.error("Failed to login", error);
|
||||||
if (isAxiosError(error)) {
|
if (isAxiosError(error)) {
|
||||||
const errorMessage =
|
const errorMessage =
|
||||||
@@ -3791,9 +3854,19 @@ export class ClaimRequestManagementService {
|
|||||||
error.response?.data ||
|
error.response?.data ||
|
||||||
error.message ||
|
error.message ||
|
||||||
"Failed to login to external API";
|
"Failed to login to external API";
|
||||||
throw new BadGatewayException(errorMessage);
|
throw new BadGatewayException(
|
||||||
|
this.fanavaranAuditService.formatErrorWithTrackingCode(
|
||||||
|
String(errorMessage),
|
||||||
|
auditSession?.trackingCode,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
throw new BadGatewayException("Failed to login to external API");
|
throw new BadGatewayException(
|
||||||
|
this.fanavaranAuditService.formatErrorWithTrackingCode(
|
||||||
|
"Failed to login to external API",
|
||||||
|
auditSession?.trackingCode,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3809,12 +3882,31 @@ export class ClaimRequestManagementService {
|
|||||||
location: string;
|
location: string;
|
||||||
},
|
},
|
||||||
logPrefix: string,
|
logPrefix: string,
|
||||||
|
auditSession?: FanavaranAuditSession,
|
||||||
): Promise<number | null> {
|
): Promise<number | null> {
|
||||||
try {
|
const policyInquiryUrl = `https://apimanager.iraneit.com/BimeApiManager/api/BimeApi/v2.0/common/Policies/inquiry-my-policies?InsuranceLineId=5&NationalCode=${nationalCodeOfInsurer}`;
|
||||||
const appToken = await this.getAppToken(config);
|
const startedAt = Date.now();
|
||||||
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}`;
|
if (auditSession) {
|
||||||
|
await this.fanavaranAuditService.recordStep({
|
||||||
|
session: auditSession,
|
||||||
|
step: FanavaranAuditStep.POLICY_INQUIRY,
|
||||||
|
status: FanavaranAuditStatus.STARTED,
|
||||||
|
requestUrl: policyInquiryUrl,
|
||||||
|
requestMeta: {
|
||||||
|
corpId: config.corpId,
|
||||||
|
contractId: config.contractId,
|
||||||
|
location: config.location,
|
||||||
|
nationalCode: this.fanavaranAuditService.maskNationalCode(
|
||||||
|
nationalCodeOfInsurer,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const appToken = await this.getAppToken(config, auditSession);
|
||||||
|
const authenticationToken = await this.login(appToken, config, auditSession);
|
||||||
|
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
`${logPrefix} Calling policy inquiry API for nationalCode: ${nationalCodeOfInsurer}`,
|
`${logPrefix} Calling policy inquiry API for nationalCode: ${nationalCodeOfInsurer}`,
|
||||||
@@ -3840,15 +3932,55 @@ export class ClaimRequestManagementService {
|
|||||||
this.logger.log(
|
this.logger.log(
|
||||||
`${logPrefix} Successfully retrieved PolicyId from last policy entry: ${policyId}`,
|
`${logPrefix} Successfully retrieved PolicyId from last policy entry: ${policyId}`,
|
||||||
);
|
);
|
||||||
|
if (auditSession) {
|
||||||
|
await this.fanavaranAuditService.recordStep({
|
||||||
|
session: auditSession,
|
||||||
|
step: FanavaranAuditStep.POLICY_INQUIRY,
|
||||||
|
status: FanavaranAuditStatus.SUCCESS,
|
||||||
|
requestUrl: policyInquiryUrl,
|
||||||
|
httpStatus: response.status,
|
||||||
|
responseMeta: {
|
||||||
|
policyId,
|
||||||
|
policyCount: response.data.length,
|
||||||
|
},
|
||||||
|
durationMs: Date.now() - startedAt,
|
||||||
|
});
|
||||||
|
}
|
||||||
return policyId;
|
return policyId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.warn(`${logPrefix} No PolicyId found in API response`);
|
this.logger.warn(`${logPrefix} No PolicyId found in API response`);
|
||||||
|
if (auditSession) {
|
||||||
|
await this.fanavaranAuditService.recordStep({
|
||||||
|
session: auditSession,
|
||||||
|
step: FanavaranAuditStep.POLICY_INQUIRY,
|
||||||
|
status: FanavaranAuditStatus.SUCCESS,
|
||||||
|
requestUrl: policyInquiryUrl,
|
||||||
|
httpStatus: response.status,
|
||||||
|
responseMeta: {
|
||||||
|
policyId: null,
|
||||||
|
policyCount: Array.isArray(response.data) ? response.data.length : 0,
|
||||||
|
},
|
||||||
|
durationMs: Date.now() - startedAt,
|
||||||
|
});
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage =
|
const errorMessage =
|
||||||
error instanceof Error ? error.message : "unknown policy inquiry error";
|
error instanceof Error ? error.message : "unknown policy inquiry error";
|
||||||
|
if (auditSession) {
|
||||||
|
await this.fanavaranAuditService.recordStep({
|
||||||
|
session: auditSession,
|
||||||
|
step: FanavaranAuditStep.POLICY_INQUIRY,
|
||||||
|
status: FanavaranAuditStatus.FAILURE,
|
||||||
|
requestUrl: policyInquiryUrl,
|
||||||
|
httpStatus: isAxiosError(error) ? error.response?.status : undefined,
|
||||||
|
errorMessage,
|
||||||
|
errorDetails: this.fanavaranAuditService.sanitizeErrorDetails(error),
|
||||||
|
durationMs: Date.now() - startedAt,
|
||||||
|
});
|
||||||
|
}
|
||||||
this.logger.warn(
|
this.logger.warn(
|
||||||
`${logPrefix} Policy inquiry failed; continuing with empty PolicyId: ${errorMessage}`,
|
`${logPrefix} Policy inquiry failed; continuing with empty PolicyId: ${errorMessage}`,
|
||||||
);
|
);
|
||||||
@@ -3964,14 +4096,37 @@ export class ClaimRequestManagementService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fanavaran Submit (Parsian / V2) - Map data from claimCases + blameCases
|
* Preview Fanavaran submit body (V2 claimCases + blameCases) for a specific client.
|
||||||
*/
|
*/
|
||||||
async fanavaranSubmitParsianV2(
|
async previewFanavaranSubmitV2(
|
||||||
claimCaseId: string,
|
claimCaseId: string,
|
||||||
options?: { debug?: boolean },
|
clientKey: FanavaranClientKey,
|
||||||
|
options?: {
|
||||||
|
debug?: boolean;
|
||||||
|
auditSession?: FanavaranAuditSession;
|
||||||
|
},
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
|
const profile = getFanavaranClientProfile(clientKey);
|
||||||
|
const logPrefix = `[Fanavaran ${clientKey} V2] claimCaseId=${claimCaseId}`;
|
||||||
|
const auditSession =
|
||||||
|
options?.auditSession ??
|
||||||
|
({
|
||||||
|
trackingCode: this.fanavaranAuditService.generateTrackingCode(),
|
||||||
|
clientKey,
|
||||||
|
source: FanavaranAuditSource.PREVIEW,
|
||||||
|
claimCaseId,
|
||||||
|
} satisfies FanavaranAuditSession);
|
||||||
|
const buildStartedAt = Date.now();
|
||||||
|
|
||||||
|
await this.fanavaranAuditService.recordStep({
|
||||||
|
session: auditSession,
|
||||||
|
step: FanavaranAuditStep.BUILD_PAYLOAD,
|
||||||
|
status: FanavaranAuditStatus.STARTED,
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const debug = {
|
const debug = {
|
||||||
|
clientKey,
|
||||||
claimCaseId,
|
claimCaseId,
|
||||||
steps: {
|
steps: {
|
||||||
claimCaseFound: false,
|
claimCaseFound: false,
|
||||||
@@ -4022,7 +4177,8 @@ export class ClaimRequestManagementService {
|
|||||||
|
|
||||||
const expertDecision = blameCase.expert?.decision;
|
const expertDecision = blameCase.expert?.decision;
|
||||||
debug.steps.expertDecisionFound = !!expertDecision;
|
debug.steps.expertDecisionFound = !!expertDecision;
|
||||||
const fallbackAccidentReason = claimCase.snapshot?.accident?.classification?.accidentReason;
|
const fallbackAccidentReason =
|
||||||
|
claimCase.snapshot?.accident?.classification?.accidentReason;
|
||||||
if (!expertDecision?.fields?.accidentReason && fallbackAccidentReason) {
|
if (!expertDecision?.fields?.accidentReason && fallbackAccidentReason) {
|
||||||
debug.steps.accidentReasonFallbackFromSnapshot = true;
|
debug.steps.accidentReasonFallbackFromSnapshot = true;
|
||||||
}
|
}
|
||||||
@@ -4051,8 +4207,8 @@ export class ClaimRequestManagementService {
|
|||||||
createdAt: (blameCase as { createdAt?: Date }).createdAt,
|
createdAt: (blameCase as { createdAt?: Date }).createdAt,
|
||||||
location: firstParty?.location,
|
location: firstParty?.location,
|
||||||
damageParts,
|
damageParts,
|
||||||
defaults: this.PARSIAN_FANAVARAN_DEFAULTS,
|
defaults: profile.defaults,
|
||||||
logPrefix: `[Fanavaran Parsian V2] claimCaseId=${claimCaseId}`,
|
logPrefix,
|
||||||
resolvePolicyId: async () => {
|
resolvePolicyId: async () => {
|
||||||
const guiltyPartyId = this.resolveGuiltyPartyIdV2(
|
const guiltyPartyId = this.resolveGuiltyPartyIdV2(
|
||||||
blameCase.parties ?? [],
|
blameCase.parties ?? [],
|
||||||
@@ -4065,16 +4221,17 @@ export class ClaimRequestManagementService {
|
|||||||
debug.values.guiltyPartyId = guiltyPartyId
|
debug.values.guiltyPartyId = guiltyPartyId
|
||||||
? guiltyPartyId.toString()
|
? guiltyPartyId.toString()
|
||||||
: null;
|
: null;
|
||||||
const nationalCodeOfInsurer = this.getNationalCodeOfInsurerForGuiltyPartyV2(
|
const nationalCodeOfInsurer =
|
||||||
blameCase.parties ?? [],
|
this.getNationalCodeOfInsurerForGuiltyPartyV2(
|
||||||
guiltyPartyId,
|
blameCase.parties ?? [],
|
||||||
);
|
guiltyPartyId,
|
||||||
|
);
|
||||||
debug.steps.nationalCodeOfInsurerFound = !!nationalCodeOfInsurer;
|
debug.steps.nationalCodeOfInsurerFound = !!nationalCodeOfInsurer;
|
||||||
debug.values.nationalCodeOfInsurer = nationalCodeOfInsurer;
|
debug.values.nationalCodeOfInsurer = nationalCodeOfInsurer;
|
||||||
|
|
||||||
if (!guiltyPartyId) {
|
if (!guiltyPartyId) {
|
||||||
this.logger.warn(
|
this.logger.warn(
|
||||||
`[Fanavaran Parsian V2] guiltyPartyId not found in expert.decision`,
|
`${logPrefix} guiltyPartyId not found in expert.decision`,
|
||||||
);
|
);
|
||||||
debug.failureReason = "expert.decision.guiltyPartyId is missing";
|
debug.failureReason = "expert.decision.guiltyPartyId is missing";
|
||||||
return null;
|
return null;
|
||||||
@@ -4082,7 +4239,7 @@ export class ClaimRequestManagementService {
|
|||||||
|
|
||||||
if (!nationalCodeOfInsurer) {
|
if (!nationalCodeOfInsurer) {
|
||||||
this.logger.warn(
|
this.logger.warn(
|
||||||
`[Fanavaran Parsian V2] nationalCodeOfInsurer not found for guiltyPartyId: ${guiltyPartyId}`,
|
`${logPrefix} nationalCodeOfInsurer not found for guiltyPartyId: ${guiltyPartyId}`,
|
||||||
);
|
);
|
||||||
debug.failureReason =
|
debug.failureReason =
|
||||||
"nationalCodeOfInsurer not found for guilty party in blameCase.parties";
|
"nationalCodeOfInsurer not found for guilty party in blameCase.parties";
|
||||||
@@ -4092,8 +4249,9 @@ export class ClaimRequestManagementService {
|
|||||||
debug.steps.policyInquiryAttempted = true;
|
debug.steps.policyInquiryAttempted = true;
|
||||||
const policyId = await this.getPolicyIdFromNationalCode(
|
const policyId = await this.getPolicyIdFromNationalCode(
|
||||||
nationalCodeOfInsurer,
|
nationalCodeOfInsurer,
|
||||||
this.PARSIAN_FANAVARAN_CONFIG,
|
profile.auth,
|
||||||
`[Fanavaran Parsian V2] claimCaseId=${claimCaseId}`,
|
logPrefix,
|
||||||
|
auditSession,
|
||||||
);
|
);
|
||||||
debug.values.policyId = policyId;
|
debug.values.policyId = policyId;
|
||||||
debug.steps.policyInquirySucceeded = policyId !== null;
|
debug.steps.policyInquirySucceeded = policyId !== null;
|
||||||
@@ -4108,6 +4266,7 @@ export class ClaimRequestManagementService {
|
|||||||
|
|
||||||
if (options?.debug) {
|
if (options?.debug) {
|
||||||
return {
|
return {
|
||||||
|
clientKey,
|
||||||
payload,
|
payload,
|
||||||
debug,
|
debug,
|
||||||
};
|
};
|
||||||
@@ -4115,34 +4274,52 @@ export class ClaimRequestManagementService {
|
|||||||
|
|
||||||
return payload;
|
return payload;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error("Error in fanavaranSubmitParsianV2", error);
|
this.logger.error(`Error in previewFanavaranSubmitV2 (${clientKey})`, error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @deprecated Use previewFanavaranSubmitV2(claimCaseId, "parsian", options) */
|
||||||
|
async fanavaranSubmitParsianV2(
|
||||||
|
claimCaseId: string,
|
||||||
|
options?: { debug?: boolean },
|
||||||
|
): Promise<any> {
|
||||||
|
return this.previewFanavaranSubmitV2(claimCaseId, "parsian", options);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Submit Parsian fanavaran data (V2 collections) to external API
|
* Submit Fanavaran data (V2 claimCases + blameCases) for a specific client.
|
||||||
*/
|
*/
|
||||||
async submitToFanavaranParsianV2(claimCaseId: string): Promise<any> {
|
async submitFanavaranV2(
|
||||||
|
claimCaseId: string,
|
||||||
|
clientKey: FanavaranClientKey,
|
||||||
|
): Promise<any> {
|
||||||
try {
|
try {
|
||||||
return await this.executeFanavaranParsianV2Submit(claimCaseId);
|
return await this.executeFanavaranV2Submit(claimCaseId, clientKey);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(
|
this.logger.error(
|
||||||
`[Fanavaran Parsian V2] Error submitting to Fanavaran`,
|
`[Fanavaran ${clientKey} V2] Error submitting to Fanavaran`,
|
||||||
error,
|
error,
|
||||||
);
|
);
|
||||||
throw new BadGatewayException(this.extractFanavaranErrorMessage(error));
|
throw new BadGatewayException(this.extractFanavaranErrorMessage(error));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @deprecated Use submitFanavaranV2(claimCaseId, "parsian") */
|
||||||
|
async submitToFanavaranParsianV2(claimCaseId: string): Promise<any> {
|
||||||
|
return this.submitFanavaranV2(claimCaseId, "parsian");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Auto-submit to Fanavaran when a claim case reaches COMPLETED.
|
* Auto-submit to Fanavaran when a claim case reaches COMPLETED.
|
||||||
|
* Uses FANAVARAN_CLIENT env (or CLIENT_ID fallback) to pick the tenant.
|
||||||
* Never throws — failures are returned as warnings so the main flow continues.
|
* Never throws — failures are returned as warnings so the main flow continues.
|
||||||
*/
|
*/
|
||||||
async autoSubmitToFanavaranParsianV2OnClaimCompleted(
|
async autoSubmitToFanavaranV2OnClaimCompleted(
|
||||||
claimCaseId: string,
|
claimCaseId: string,
|
||||||
): Promise<FanavaranAutoSubmitResult> {
|
): Promise<FanavaranAutoSubmitResult> {
|
||||||
const logPrefix = `[Fanavaran Parsian V2 Auto] claimCaseId=${claimCaseId}`;
|
const clientKey = resolveFanavaranClientKey();
|
||||||
|
const logPrefix = `[Fanavaran ${clientKey} V2 Auto] claimCaseId=${claimCaseId}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const claimCase = await this.claimCaseDbService.findById(claimCaseId);
|
const claimCase = await this.claimCaseDbService.findById(claimCaseId);
|
||||||
@@ -4166,8 +4343,9 @@ export class ClaimRequestManagementService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const fanavaranResponse = await this.executeFanavaranParsianV2Submit(
|
const fanavaranResponse = await this.executeFanavaranV2Submit(
|
||||||
claimCaseId,
|
claimCaseId,
|
||||||
|
clientKey,
|
||||||
);
|
);
|
||||||
|
|
||||||
await this.claimCaseDbService.findByIdAndUpdate(claimCaseId, {
|
await this.claimCaseDbService.findByIdAndUpdate(claimCaseId, {
|
||||||
@@ -4177,6 +4355,7 @@ export class ClaimRequestManagementService {
|
|||||||
actor: { actorType: "system" },
|
actor: { actorType: "system" },
|
||||||
timestamp: new Date(),
|
timestamp: new Date(),
|
||||||
metadata: {
|
metadata: {
|
||||||
|
clientKey,
|
||||||
claimNo: fanavaranResponse?.ClaimNo,
|
claimNo: fanavaranResponse?.ClaimNo,
|
||||||
claimId: fanavaranResponse?.Id,
|
claimId: fanavaranResponse?.Id,
|
||||||
},
|
},
|
||||||
@@ -4202,7 +4381,7 @@ export class ClaimRequestManagementService {
|
|||||||
type: "FANAVARAN_AUTO_SUBMIT_FAILED",
|
type: "FANAVARAN_AUTO_SUBMIT_FAILED",
|
||||||
actor: { actorType: "system" },
|
actor: { actorType: "system" },
|
||||||
timestamp: new Date(),
|
timestamp: new Date(),
|
||||||
metadata: { error: warning },
|
metadata: { clientKey, error: warning },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -4216,11 +4395,18 @@ export class ClaimRequestManagementService {
|
|||||||
return {
|
return {
|
||||||
attempted: true,
|
attempted: true,
|
||||||
submitted: false,
|
submitted: false,
|
||||||
warning: `${warning} Case was not sent to Fanavaran. Retry manually via POST /v2/claim-request-management/fanavaran-submit/parsian/${claimCaseId}.`,
|
warning: `${warning} Case was not sent to Fanavaran. Retry manually via POST ${fanavaranSubmitPath(clientKey, claimCaseId)}.`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @deprecated Use autoSubmitToFanavaranV2OnClaimCompleted */
|
||||||
|
async autoSubmitToFanavaranParsianV2OnClaimCompleted(
|
||||||
|
claimCaseId: string,
|
||||||
|
): Promise<FanavaranAutoSubmitResult> {
|
||||||
|
return this.autoSubmitToFanavaranV2OnClaimCompleted(claimCaseId);
|
||||||
|
}
|
||||||
|
|
||||||
private extractFanavaranErrorMessage(error: unknown): string {
|
private extractFanavaranErrorMessage(error: unknown): string {
|
||||||
if (isAxiosError(error)) {
|
if (isAxiosError(error)) {
|
||||||
return (
|
return (
|
||||||
@@ -4247,16 +4433,20 @@ export class ClaimRequestManagementService {
|
|||||||
return "Failed to submit data to Fanavaran API";
|
return "Failed to submit data to Fanavaran API";
|
||||||
}
|
}
|
||||||
|
|
||||||
private async executeFanavaranParsianV2Submit(
|
private async executeFanavaranV2Submit(
|
||||||
claimCaseId: string,
|
claimCaseId: string,
|
||||||
|
clientKey: FanavaranClientKey,
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
const config = this.PARSIAN_FANAVARAN_CONFIG;
|
const profile = getFanavaranClientProfile(clientKey);
|
||||||
const logPrefix = `[Fanavaran Parsian V2]`;
|
const logPrefix = `[Fanavaran ${clientKey} V2]`;
|
||||||
|
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
`${logPrefix} Starting submission for claimCaseId: ${claimCaseId}`,
|
`${logPrefix} Starting submission for claimCaseId: ${claimCaseId}`,
|
||||||
);
|
);
|
||||||
const fanavaranData = await this.fanavaranSubmitParsianV2(claimCaseId);
|
const fanavaranData = await this.previewFanavaranSubmitV2(
|
||||||
|
claimCaseId,
|
||||||
|
clientKey,
|
||||||
|
);
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
`${logPrefix} Mapped data prepared:`,
|
`${logPrefix} Mapped data prepared:`,
|
||||||
JSON.stringify(fanavaranData, null, 2),
|
JSON.stringify(fanavaranData, null, 2),
|
||||||
@@ -4275,16 +4465,16 @@ export class ClaimRequestManagementService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const appToken = await this.getAppToken(config);
|
const appToken = await this.getAppToken(profile.auth);
|
||||||
const authenticationToken = await this.login(appToken, config);
|
const authenticationToken = await this.login(appToken, profile.auth);
|
||||||
|
|
||||||
const response = await firstValueFrom(
|
const response = await firstValueFrom(
|
||||||
this.httpService.post(this.FANAVARAN_SUBMIT_URL, fanavaranData, {
|
this.httpService.post(this.FANAVARAN_SUBMIT_URL, fanavaranData, {
|
||||||
headers: {
|
headers: {
|
||||||
authenticationToken: authenticationToken,
|
authenticationToken: authenticationToken,
|
||||||
CorpId: config.corpId,
|
CorpId: profile.auth.corpId,
|
||||||
ContractId: config.contractId,
|
ContractId: profile.auth.contractId,
|
||||||
Location: config.location,
|
Location: profile.auth.location,
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@@ -4319,6 +4509,13 @@ export class ClaimRequestManagementService {
|
|||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @deprecated Use executeFanavaranV2Submit(claimCaseId, "parsian") */
|
||||||
|
private async executeFanavaranParsianV2Submit(
|
||||||
|
claimCaseId: string,
|
||||||
|
): Promise<any> {
|
||||||
|
return this.executeFanavaranV2Submit(claimCaseId, "parsian");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Submit fanavaran data to external API
|
* Submit fanavaran data to external API
|
||||||
*/
|
*/
|
||||||
@@ -7427,7 +7624,7 @@ export class ClaimRequestManagementService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const fanavaran =
|
const fanavaran =
|
||||||
await this.autoSubmitToFanavaranParsianV2OnClaimCompleted(claimRequestId);
|
await this.autoSubmitToFanavaranV2OnClaimCompleted(claimRequestId);
|
||||||
|
|
||||||
let message = "Your signature has been recorded. The claim is completed.";
|
let message = "Your signature has been recorded. The claim is completed.";
|
||||||
if (fanavaran.submitted) {
|
if (fanavaran.submitted) {
|
||||||
|
|||||||
@@ -1030,43 +1030,4 @@ Returns status of each item (uploaded/captured or not).
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get("fanavaran-submit/parsian/:claimCaseId")
|
|
||||||
@ApiOperation({
|
|
||||||
summary: "Preview Fanavaran submit body (Parsian / V2)",
|
|
||||||
description:
|
|
||||||
"Builds the third-party-car-financial-claims payload from claimCases + blameCases without calling Fanavaran.",
|
|
||||||
})
|
|
||||||
@ApiParam({
|
|
||||||
name: "claimCaseId",
|
|
||||||
description: "The claim case ID (MongoDB ObjectId)",
|
|
||||||
})
|
|
||||||
async fanavaranSubmitParsianV2(
|
|
||||||
@Param("claimCaseId") claimCaseId: string,
|
|
||||||
@Query("debug") debug?: string,
|
|
||||||
) {
|
|
||||||
return await this.claimRequestManagementService.fanavaranSubmitParsianV2(
|
|
||||||
claimCaseId,
|
|
||||||
{
|
|
||||||
debug: debug === "1" || debug === "true",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post("fanavaran-submit/parsian/:claimCaseId")
|
|
||||||
@ApiOperation({
|
|
||||||
summary: "Submit claim to Fanavaran (Parsian / V2)",
|
|
||||||
description:
|
|
||||||
"Authenticates with Parsian Fanavaran credentials and submits the mapped claimCases + blameCases payload.",
|
|
||||||
})
|
|
||||||
@ApiParam({
|
|
||||||
name: "claimCaseId",
|
|
||||||
description: "The claim case ID (MongoDB ObjectId)",
|
|
||||||
})
|
|
||||||
async submitToFanavaranParsianV2(
|
|
||||||
@Param("claimCaseId") claimCaseId: string,
|
|
||||||
) {
|
|
||||||
return await this.claimRequestManagementService.submitToFanavaranParsianV2(
|
|
||||||
claimCaseId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,25 @@
|
|||||||
export type FanavaranClientKey = "parsian" | "tejaratno";
|
export type FanavaranClientKey = "parsian" | "tejaratno";
|
||||||
|
|
||||||
|
export const FANAVARAN_CLIENT_KEYS: readonly FanavaranClientKey[] = [
|
||||||
|
"parsian",
|
||||||
|
"tejaratno",
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export function isFanavaranClientKey(value: string): value is FanavaranClientKey {
|
||||||
|
const normalized = value?.trim().toLowerCase();
|
||||||
|
return normalized === "parsian" || normalized === "tejaratno";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeFanavaranClientKey(value: string): FanavaranClientKey {
|
||||||
|
const normalized = value?.trim().toLowerCase();
|
||||||
|
if (normalized === "parsian" || normalized === "tejaratno") {
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
throw new Error(
|
||||||
|
`Invalid Fanavaran client "${value}". Expected one of: ${FANAVARAN_CLIENT_KEYS.join(", ")}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export interface FanavaranAuthConfig {
|
export interface FanavaranAuthConfig {
|
||||||
appName: string;
|
appName: string;
|
||||||
secret: string;
|
secret: string;
|
||||||
@@ -93,6 +113,30 @@ export function resolveFanavaranClientProfile(): FanavaranClientProfile {
|
|||||||
return FANAVARAN_CLIENT_PROFILES[resolveFanavaranClientKey()];
|
return FANAVARAN_CLIENT_PROFILES[resolveFanavaranClientKey()];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fanavaranManualSubmitPath(claimCaseId: string): string {
|
export function getFanavaranClientProfile(
|
||||||
return `/v2/claim-request-management/fanavaran-submit/${claimCaseId}`;
|
clientKey: FanavaranClientKey,
|
||||||
|
): FanavaranClientProfile {
|
||||||
|
return FANAVARAN_CLIENT_PROFILES[clientKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function listFanavaranClientProfiles(): FanavaranClientProfile[] {
|
||||||
|
return FANAVARAN_CLIENT_KEYS.map((key) => FANAVARAN_CLIENT_PROFILES[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fanavaranPreviewPath(
|
||||||
|
clientKey: FanavaranClientKey,
|
||||||
|
claimCaseId: string,
|
||||||
|
): string {
|
||||||
|
return `/v2/fanavaran/${clientKey}/claim-cases/${claimCaseId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fanavaranSubmitPath(
|
||||||
|
clientKey: FanavaranClientKey,
|
||||||
|
claimCaseId: string,
|
||||||
|
): string {
|
||||||
|
return `/v2/fanavaran/${clientKey}/claim-cases/${claimCaseId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fanavaranManualSubmitPath(claimCaseId: string): string {
|
||||||
|
return fanavaranSubmitPath(resolveFanavaranClientKey(), claimCaseId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { ExpertBlameV2Controller } from "./expert-blame.v2.controller";
|
|||||||
import { ExpertBlameService } from "./expert-blame.service";
|
import { ExpertBlameService } from "./expert-blame.service";
|
||||||
import { RequestManagementModule } from "src/request-management/request-management.module";
|
import { RequestManagementModule } from "src/request-management/request-management.module";
|
||||||
import { SmsOrchestrationModule } from "src/sms-orchestration/sms-orchestration.module";
|
import { SmsOrchestrationModule } from "src/sms-orchestration/sms-orchestration.module";
|
||||||
|
import { LookupsModule } from "src/lookups/lookups.module";
|
||||||
import { ClaimRequestManagementModule } from "src/claim-request-management/claim-request-management.module";
|
import { ClaimRequestManagementModule } from "src/claim-request-management/claim-request-management.module";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
@@ -17,6 +18,7 @@ import { ClaimRequestManagementModule } from "src/claim-request-management/claim
|
|||||||
PlatesModule,
|
PlatesModule,
|
||||||
SmsOrchestrationModule,
|
SmsOrchestrationModule,
|
||||||
ClaimRequestManagementModule,
|
ClaimRequestManagementModule,
|
||||||
|
LookupsModule,
|
||||||
],
|
],
|
||||||
controllers: [ExpertBlameController, ExpertBlameV2Controller],
|
controllers: [ExpertBlameController, ExpertBlameV2Controller],
|
||||||
providers: [ExpertBlameService],
|
providers: [ExpertBlameService],
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import {
|
|||||||
} from "src/helpers/unified-file-status";
|
} from "src/helpers/unified-file-status";
|
||||||
import { applyListQueryV2, isInListDateRange, parseListDateRange } from "src/helpers/list-query-v2";
|
import { applyListQueryV2, isInListDateRange, parseListDateRange } from "src/helpers/list-query-v2";
|
||||||
import { ClaimCaseDbService } from "src/claim-request-management/entites/db-service/claim-case.db.service";
|
import { ClaimCaseDbService } from "src/claim-request-management/entites/db-service/claim-case.db.service";
|
||||||
|
import { LookupsService } from "src/lookups/lookups.service";
|
||||||
import { ListQueryV2Dto } from "src/common/dto/list-query-v2.dto";
|
import { ListQueryV2Dto } from "src/common/dto/list-query-v2.dto";
|
||||||
import {
|
import {
|
||||||
UnifiedFileStatusReportDto,
|
UnifiedFileStatusReportDto,
|
||||||
@@ -127,6 +128,7 @@ export class ExpertBlameService {
|
|||||||
private readonly smsOrchestrationService: SmsOrchestrationService,
|
private readonly smsOrchestrationService: SmsOrchestrationService,
|
||||||
private readonly expertFileActivityDbService: ExpertFileActivityDbService,
|
private readonly expertFileActivityDbService: ExpertFileActivityDbService,
|
||||||
private readonly claimCaseDbService: ClaimCaseDbService,
|
private readonly claimCaseDbService: ClaimCaseDbService,
|
||||||
|
private readonly lookupsService: LookupsService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
private async loadClaimsByPublicIds(
|
private async loadClaimsByPublicIds(
|
||||||
@@ -2425,21 +2427,7 @@ export class ExpertBlameService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getAccidentField() {
|
async getAccidentField() {
|
||||||
try {
|
return await this.lookupsService.getAccidentFields();
|
||||||
const ac_reason = await readFile(
|
|
||||||
"src/static/ACCIDENT_REASON.json",
|
|
||||||
"utf-8",
|
|
||||||
);
|
|
||||||
const ac_type = await readFile("src/static/ACCIDENT_TYPE.json", "utf-8");
|
|
||||||
const ac_way = await readFile("src/static/ACCIDENT_WAY.json", "utf-8");
|
|
||||||
return {
|
|
||||||
accidentReason: JSON.parse(ac_reason),
|
|
||||||
accidentType: JSON.parse(ac_type),
|
|
||||||
accidentWay: JSON.parse(ac_way),
|
|
||||||
};
|
|
||||||
} catch (err) {
|
|
||||||
this.logger.error(err);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async inPersonVisit(requestId: string, actorDetail: any) {
|
async inPersonVisit(requestId: string, actorDetail: any) {
|
||||||
|
|||||||
@@ -2365,7 +2365,7 @@ export class ExpertClaimService {
|
|||||||
{ replyField },
|
{ replyField },
|
||||||
);
|
);
|
||||||
const fanavaran =
|
const fanavaran =
|
||||||
await this.claimRequestManagementService.autoSubmitToFanavaranParsianV2OnClaimCompleted(
|
await this.claimRequestManagementService.autoSubmitToFanavaranV2OnClaimCompleted(
|
||||||
claimRequestId,
|
claimRequestId,
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
@@ -2391,7 +2391,7 @@ export class ExpertClaimService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const fanavaran =
|
const fanavaran =
|
||||||
await this.claimRequestManagementService.autoSubmitToFanavaranParsianV2OnClaimCompleted(
|
await this.claimRequestManagementService.autoSubmitToFanavaranV2OnClaimCompleted(
|
||||||
claimRequestId,
|
claimRequestId,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
18
src/fanavaran/fanavaran-audit.module.ts
Normal file
18
src/fanavaran/fanavaran-audit.module.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { Module } from "@nestjs/common";
|
||||||
|
import { MongooseModule } from "@nestjs/mongoose";
|
||||||
|
import { FanavaranAuditService } from "./fanavaran-audit.service";
|
||||||
|
import {
|
||||||
|
FanavaranAuditLog,
|
||||||
|
FanavaranAuditLogSchema,
|
||||||
|
} from "./schema/fanavaran-audit-log.schema";
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
MongooseModule.forFeature([
|
||||||
|
{ name: FanavaranAuditLog.name, schema: FanavaranAuditLogSchema },
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
providers: [FanavaranAuditService],
|
||||||
|
exports: [FanavaranAuditService],
|
||||||
|
})
|
||||||
|
export class FanavaranAuditModule {}
|
||||||
137
src/fanavaran/fanavaran-audit.service.ts
Normal file
137
src/fanavaran/fanavaran-audit.service.ts
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
import { Injectable, Logger } from "@nestjs/common";
|
||||||
|
import { InjectModel } from "@nestjs/mongoose";
|
||||||
|
import { isAxiosError } from "axios";
|
||||||
|
import { randomBytes } from "node:crypto";
|
||||||
|
import { Model, Types } from "mongoose";
|
||||||
|
import {
|
||||||
|
FanavaranAuditLog,
|
||||||
|
FanavaranAuditLogDocument,
|
||||||
|
FanavaranAuditStatus,
|
||||||
|
FanavaranAuditStep,
|
||||||
|
} from "./schema/fanavaran-audit-log.schema";
|
||||||
|
import type { FanavaranAuditSession } from "./fanavaran-audit.types";
|
||||||
|
|
||||||
|
export interface RecordFanavaranAuditStepInput {
|
||||||
|
session: FanavaranAuditSession;
|
||||||
|
step: FanavaranAuditStep;
|
||||||
|
status: FanavaranAuditStatus;
|
||||||
|
requestUrl?: string;
|
||||||
|
httpStatus?: number;
|
||||||
|
requestMeta?: Record<string, unknown>;
|
||||||
|
responseMeta?: Record<string, unknown>;
|
||||||
|
errorMessage?: string;
|
||||||
|
errorDetails?: Record<string, unknown>;
|
||||||
|
durationMs?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class FanavaranAuditService {
|
||||||
|
private readonly logger = new Logger(FanavaranAuditService.name);
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@InjectModel(FanavaranAuditLog.name)
|
||||||
|
private readonly auditModel: Model<FanavaranAuditLogDocument>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
generateTrackingCode(): string {
|
||||||
|
const date = new Date().toISOString().slice(0, 10).replace(/-/g, "");
|
||||||
|
const suffix = randomBytes(3).toString("hex").toUpperCase();
|
||||||
|
return `FNV-${date}-${suffix}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
maskNationalCode(nationalCode: string): string {
|
||||||
|
const trimmed = nationalCode.trim();
|
||||||
|
if (trimmed.length <= 4) {
|
||||||
|
return "****";
|
||||||
|
}
|
||||||
|
return `${trimmed.slice(0, 3)}****${trimmed.slice(-2)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
sanitizeErrorDetails(error: unknown): Record<string, unknown> {
|
||||||
|
if (isAxiosError(error)) {
|
||||||
|
const data = error.response?.data;
|
||||||
|
return {
|
||||||
|
type: "axios",
|
||||||
|
status: error.response?.status,
|
||||||
|
statusText: error.response?.statusText,
|
||||||
|
data:
|
||||||
|
typeof data === "object" && data !== null
|
||||||
|
? data
|
||||||
|
: typeof data === "string"
|
||||||
|
? data.slice(0, 2000)
|
||||||
|
: data,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (error instanceof Error) {
|
||||||
|
return { type: "error", name: error.name, message: error.message };
|
||||||
|
}
|
||||||
|
return { type: "unknown", value: String(error) };
|
||||||
|
}
|
||||||
|
|
||||||
|
extractErrorMessage(error: unknown): string {
|
||||||
|
if (isAxiosError(error)) {
|
||||||
|
const data = error.response?.data as
|
||||||
|
| { Message?: string; message?: string }
|
||||||
|
| string
|
||||||
|
| undefined;
|
||||||
|
if (typeof data === "string") {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
data?.Message ||
|
||||||
|
data?.message ||
|
||||||
|
error.message ||
|
||||||
|
"Fanavaran request failed"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (error instanceof Error) {
|
||||||
|
return error.message;
|
||||||
|
}
|
||||||
|
return "Fanavaran request failed";
|
||||||
|
}
|
||||||
|
|
||||||
|
formatErrorWithTrackingCode(message: string, trackingCode?: string): string {
|
||||||
|
if (!trackingCode) {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
return `${message} (trackingCode: ${trackingCode})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async recordStep(input: RecordFanavaranAuditStepInput): Promise<void> {
|
||||||
|
try {
|
||||||
|
await this.auditModel.create({
|
||||||
|
trackingCode: input.session.trackingCode,
|
||||||
|
step: input.step,
|
||||||
|
status: input.status,
|
||||||
|
clientKey: input.session.clientKey,
|
||||||
|
source: input.session.source,
|
||||||
|
...(input.session.claimCaseId
|
||||||
|
? { claimCaseId: new Types.ObjectId(input.session.claimCaseId) }
|
||||||
|
: {}),
|
||||||
|
...(input.session.claimRequestId
|
||||||
|
? { claimRequestId: new Types.ObjectId(input.session.claimRequestId) }
|
||||||
|
: {}),
|
||||||
|
requestUrl: input.requestUrl,
|
||||||
|
httpStatus: input.httpStatus,
|
||||||
|
requestMeta: input.requestMeta,
|
||||||
|
responseMeta: input.responseMeta,
|
||||||
|
errorMessage: input.errorMessage,
|
||||||
|
errorDetails: input.errorDetails,
|
||||||
|
durationMs: input.durationMs,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(
|
||||||
|
`Failed to persist Fanavaran audit step ${input.step} (${input.status})`,
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async findByTrackingCode(trackingCode: string) {
|
||||||
|
return this.auditModel
|
||||||
|
.find({ trackingCode })
|
||||||
|
.sort({ createdAt: 1 })
|
||||||
|
.lean()
|
||||||
|
.exec();
|
||||||
|
}
|
||||||
|
}
|
||||||
10
src/fanavaran/fanavaran-audit.types.ts
Normal file
10
src/fanavaran/fanavaran-audit.types.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import type { FanavaranClientKey } from "src/core/config/fanavaran-client.config";
|
||||||
|
import { FanavaranAuditSource } from "./schema/fanavaran-audit-log.schema";
|
||||||
|
|
||||||
|
export interface FanavaranAuditSession {
|
||||||
|
trackingCode: string;
|
||||||
|
clientKey: FanavaranClientKey;
|
||||||
|
source: FanavaranAuditSource;
|
||||||
|
claimCaseId?: string;
|
||||||
|
claimRequestId?: string;
|
||||||
|
}
|
||||||
58
src/fanavaran/fanavaran-lookup.config.ts
Normal file
58
src/fanavaran/fanavaran-lookup.config.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import type { FanavaranClientKey } from "src/core/config/fanavaran-client.config";
|
||||||
|
|
||||||
|
export const FANAVARAN_LOOKUP_BASE_URL =
|
||||||
|
"https://apimanager.iraneit.com/BimeApiManager/api/BimeApi/v2.0";
|
||||||
|
|
||||||
|
export interface FanavaranRemoteLookupDefinition {
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
cacheFile: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Fanavaran API lookups synced per client (Parsian fetches on first call). */
|
||||||
|
export const FANAVARAN_REMOTE_LOOKUPS: FanavaranRemoteLookupDefinition[] = [
|
||||||
|
{
|
||||||
|
name: "accident-causes",
|
||||||
|
url: `${FANAVARAN_LOOKUP_BASE_URL}/car/base-info/accident-causes`,
|
||||||
|
cacheFile: "accident-causes.json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "accident-report-type",
|
||||||
|
url: `${FANAVARAN_LOOKUP_BASE_URL}/car/code-list/accident-report-type`,
|
||||||
|
cacheFile: "accident-report-type.json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "vehicle-use-types",
|
||||||
|
url: `${FANAVARAN_LOOKUP_BASE_URL}/car/base-info/vehicle-use-types`,
|
||||||
|
cacheFile: "vehicle-use-types.json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dmg-pay-method",
|
||||||
|
url: `${FANAVARAN_LOOKUP_BASE_URL}/car/code-list/dmg-pay-method`,
|
||||||
|
cacheFile: "dmg-pay-method.json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "driving-licence-types",
|
||||||
|
url: `${FANAVARAN_LOOKUP_BASE_URL}/car/base-info/driving-licence-types`,
|
||||||
|
cacheFile: "driving-licence-types.json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "accident-culprit-type",
|
||||||
|
url: `${FANAVARAN_LOOKUP_BASE_URL}/car/code-list/accident-culprit-type`,
|
||||||
|
cacheFile: "accident-culprit-type.json",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const TEJARAT_STATIC_ACCIDENT_FILES = {
|
||||||
|
accidentReason: "ACCIDENT_REASON.json",
|
||||||
|
accidentWay: "ACCIDENT_WAY.json",
|
||||||
|
accidentType: "ACCIDENT_TYPE.json",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export function fanavaranLookupCacheDir(clientKey: FanavaranClientKey): string {
|
||||||
|
return `${process.cwd()}/files/fanavaran-lookups/${clientKey}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function tejaratStaticAccidentFilePath(fileName: string): string {
|
||||||
|
return `${process.cwd()}/src/static/${fileName}`;
|
||||||
|
}
|
||||||
10
src/fanavaran/fanavaran-lookup.module.ts
Normal file
10
src/fanavaran/fanavaran-lookup.module.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { Module } from "@nestjs/common";
|
||||||
|
import { HttpModule } from "@nestjs/axios";
|
||||||
|
import { FanavaranLookupService } from "./fanavaran-lookup.service";
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [HttpModule],
|
||||||
|
providers: [FanavaranLookupService],
|
||||||
|
exports: [FanavaranLookupService],
|
||||||
|
})
|
||||||
|
export class FanavaranLookupModule {}
|
||||||
241
src/fanavaran/fanavaran-lookup.service.ts
Normal file
241
src/fanavaran/fanavaran-lookup.service.ts
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
||||||
|
import { join } from "node:path";
|
||||||
|
import { HttpService } from "@nestjs/axios";
|
||||||
|
import {
|
||||||
|
BadGatewayException,
|
||||||
|
Injectable,
|
||||||
|
Logger,
|
||||||
|
NotFoundException,
|
||||||
|
} from "@nestjs/common";
|
||||||
|
import { firstValueFrom } from "rxjs";
|
||||||
|
import { isAxiosError } from "axios";
|
||||||
|
import {
|
||||||
|
getFanavaranClientProfile,
|
||||||
|
type FanavaranClientKey,
|
||||||
|
} from "src/core/config/fanavaran-client.config";
|
||||||
|
import {
|
||||||
|
fanavaranLookupCacheDir,
|
||||||
|
tejaratStaticAccidentFilePath,
|
||||||
|
} from "./fanavaran-lookup.config";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class FanavaranLookupService {
|
||||||
|
private readonly logger = new Logger(FanavaranLookupService.name);
|
||||||
|
|
||||||
|
private readonly getAppTokenUrl =
|
||||||
|
"https://apimanager.iraneit.com/BimeApiManager/api/EITAuthentication/GetAppToken";
|
||||||
|
private readonly loginUrl =
|
||||||
|
"https://apimanager.iraneit.com/BimeApiManager/api/EITAuthentication/Login";
|
||||||
|
|
||||||
|
constructor(private readonly httpService: HttpService) {}
|
||||||
|
|
||||||
|
private cacheFilePath(clientKey: FanavaranClientKey, fileName: string): string {
|
||||||
|
return join(fanavaranLookupCacheDir(clientKey), fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
async readCacheFile<T>(
|
||||||
|
clientKey: FanavaranClientKey,
|
||||||
|
fileName: string,
|
||||||
|
): Promise<T | null> {
|
||||||
|
try {
|
||||||
|
const content = await readFile(
|
||||||
|
this.cacheFilePath(clientKey, fileName),
|
||||||
|
"utf-8",
|
||||||
|
);
|
||||||
|
return JSON.parse(content) as T;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async writeCacheFile(
|
||||||
|
clientKey: FanavaranClientKey,
|
||||||
|
fileName: string,
|
||||||
|
data: unknown,
|
||||||
|
): Promise<void> {
|
||||||
|
const dir = fanavaranLookupCacheDir(clientKey);
|
||||||
|
await mkdir(dir, { recursive: true });
|
||||||
|
await writeFile(
|
||||||
|
join(dir, fileName),
|
||||||
|
`${JSON.stringify(data, null, 2)}\n`,
|
||||||
|
"utf-8",
|
||||||
|
);
|
||||||
|
this.logger.log(
|
||||||
|
`Cached Fanavaran lookup ${fileName} for client ${clientKey}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async readTejaratStaticAccidentFile<T>(fileName: string): Promise<T> {
|
||||||
|
const cached = await readFile(tejaratStaticAccidentFilePath(fileName), "utf-8");
|
||||||
|
return JSON.parse(cached) as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getAppToken(config: {
|
||||||
|
appName: string;
|
||||||
|
secret: string;
|
||||||
|
}): Promise<string> {
|
||||||
|
const response = await firstValueFrom(
|
||||||
|
this.httpService.post(this.getAppTokenUrl, "", {
|
||||||
|
headers: {
|
||||||
|
appname: config.appName,
|
||||||
|
secret: config.secret,
|
||||||
|
"Content-Length": "0",
|
||||||
|
},
|
||||||
|
transformRequest: [
|
||||||
|
(_data, headers) => {
|
||||||
|
if (headers) {
|
||||||
|
delete headers["Content-Type"];
|
||||||
|
delete headers["content-type"];
|
||||||
|
}
|
||||||
|
return _data;
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const appToken =
|
||||||
|
response.headers.apptoken ||
|
||||||
|
response.headers.appToken ||
|
||||||
|
response.headers["apptoken"] ||
|
||||||
|
response.headers["appToken"];
|
||||||
|
|
||||||
|
if (!appToken) {
|
||||||
|
throw new BadGatewayException("Failed to get Fanavaran appToken");
|
||||||
|
}
|
||||||
|
|
||||||
|
return appToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async login(
|
||||||
|
appToken: string,
|
||||||
|
config: { username: string; password: string },
|
||||||
|
): Promise<string> {
|
||||||
|
const response = await firstValueFrom(
|
||||||
|
this.httpService.post(this.loginUrl, "", {
|
||||||
|
headers: {
|
||||||
|
appToken,
|
||||||
|
userName: config.username,
|
||||||
|
password: config.password,
|
||||||
|
"Content-Length": "0",
|
||||||
|
},
|
||||||
|
transformRequest: [
|
||||||
|
(_data, headers) => {
|
||||||
|
if (headers) {
|
||||||
|
delete headers["Content-Type"];
|
||||||
|
delete headers["content-type"];
|
||||||
|
}
|
||||||
|
return _data;
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const authenticationToken =
|
||||||
|
response.headers.authenticationtoken ||
|
||||||
|
response.headers.authenticationToken ||
|
||||||
|
response.headers["authenticationtoken"] ||
|
||||||
|
response.headers["authenticationToken"] ||
|
||||||
|
response.data?.authenticationtoken ||
|
||||||
|
response.data?.authenticationToken ||
|
||||||
|
response.data?.authentication_token;
|
||||||
|
|
||||||
|
if (!authenticationToken) {
|
||||||
|
throw new BadGatewayException("Failed to get Fanavaran authenticationToken");
|
||||||
|
}
|
||||||
|
|
||||||
|
return authenticationToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchFromFanavaran(
|
||||||
|
clientKey: FanavaranClientKey,
|
||||||
|
url: string,
|
||||||
|
): Promise<unknown> {
|
||||||
|
const profile = getFanavaranClientProfile(clientKey);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const appToken = await this.getAppToken(profile.auth);
|
||||||
|
const authenticationToken = await this.login(appToken, profile.auth);
|
||||||
|
|
||||||
|
const response = await firstValueFrom(
|
||||||
|
this.httpService.get(url, {
|
||||||
|
headers: {
|
||||||
|
authenticationToken,
|
||||||
|
CorpId: profile.auth.corpId,
|
||||||
|
ContractId: profile.auth.contractId,
|
||||||
|
Location: profile.auth.location,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
timeout: 20000,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
const message = isAxiosError(error)
|
||||||
|
? error.response?.data?.Message ||
|
||||||
|
error.response?.data?.message ||
|
||||||
|
error.message
|
||||||
|
: error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: "Fanavaran lookup request failed";
|
||||||
|
|
||||||
|
this.logger.error(
|
||||||
|
`Fanavaran lookup fetch failed for ${clientKey} (${url}): ${message}`,
|
||||||
|
);
|
||||||
|
throw new BadGatewayException(String(message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRemoteLookup(
|
||||||
|
clientKey: FanavaranClientKey,
|
||||||
|
url: string,
|
||||||
|
cacheFile: string,
|
||||||
|
fallback?: () => Promise<unknown>,
|
||||||
|
): Promise<unknown> {
|
||||||
|
const cached = await this.readCacheFile(clientKey, cacheFile);
|
||||||
|
if (cached !== null) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await this.fetchFromFanavaran(clientKey, url);
|
||||||
|
await this.writeCacheFile(clientKey, cacheFile, data);
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
if (fallback) {
|
||||||
|
this.logger.warn(
|
||||||
|
`Fanavaran lookup fetch failed for ${clientKey}/${cacheFile}; using fallback`,
|
||||||
|
);
|
||||||
|
const data = await fallback();
|
||||||
|
await this.writeCacheFile(clientKey, cacheFile, data);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mapFanavaranAccidentCausesToReasonOptions(
|
||||||
|
causes: unknown,
|
||||||
|
): { id: number; label: string; fanavaran: number }[] {
|
||||||
|
if (!Array.isArray(causes)) {
|
||||||
|
throw new NotFoundException("Fanavaran accident-causes response is invalid");
|
||||||
|
}
|
||||||
|
|
||||||
|
return causes
|
||||||
|
.filter(
|
||||||
|
(item) =>
|
||||||
|
item &&
|
||||||
|
typeof item === "object" &&
|
||||||
|
(item as { IsActive?: number }).IsActive === 1 &&
|
||||||
|
typeof (item as { Id?: unknown }).Id === "number",
|
||||||
|
)
|
||||||
|
.map((item) => {
|
||||||
|
const row = item as { Id: number; Caption?: string };
|
||||||
|
return {
|
||||||
|
id: row.Id,
|
||||||
|
label: row.Caption ?? String(row.Id),
|
||||||
|
fanavaran: row.Id,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
124
src/fanavaran/fanavaran.controller.ts
Normal file
124
src/fanavaran/fanavaran.controller.ts
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
import {
|
||||||
|
BadRequestException,
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
Param,
|
||||||
|
Post,
|
||||||
|
Query,
|
||||||
|
UseGuards,
|
||||||
|
} from "@nestjs/common";
|
||||||
|
import {
|
||||||
|
ApiBearerAuth,
|
||||||
|
ApiOperation,
|
||||||
|
ApiParam,
|
||||||
|
ApiQuery,
|
||||||
|
ApiTags,
|
||||||
|
} from "@nestjs/swagger";
|
||||||
|
import { LocalActorAuthGuard } from "src/auth/guards/actor-local.guard";
|
||||||
|
import { RolesGuard } from "src/auth/guards/role.guard";
|
||||||
|
import { Roles } from "src/decorators/roles.decorator";
|
||||||
|
import { RoleEnum } from "src/Types&Enums/role.enum";
|
||||||
|
import { ClaimRequestManagementService } from "src/claim-request-management/claim-request-management.service";
|
||||||
|
import {
|
||||||
|
isFanavaranClientKey,
|
||||||
|
listFanavaranClientProfiles,
|
||||||
|
normalizeFanavaranClientKey,
|
||||||
|
resolveFanavaranClientKey,
|
||||||
|
} from "src/core/config/fanavaran-client.config";
|
||||||
|
|
||||||
|
@ApiTags("fanavaran")
|
||||||
|
@Controller("v2/fanavaran")
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@UseGuards(LocalActorAuthGuard, RolesGuard)
|
||||||
|
@Roles(RoleEnum.ADMIN, RoleEnum.FIELD_EXPERT)
|
||||||
|
export class FanavaranController {
|
||||||
|
constructor(
|
||||||
|
private readonly claimRequestManagementService: ClaimRequestManagementService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Get("clients")
|
||||||
|
@ApiOperation({
|
||||||
|
summary: "List supported Fanavaran insurance clients",
|
||||||
|
description:
|
||||||
|
"Returns configured Fanavaran tenants (parsian, tejaratno) and which client is active for this deployment.",
|
||||||
|
})
|
||||||
|
listClients() {
|
||||||
|
const activeClient = resolveFanavaranClientKey();
|
||||||
|
return {
|
||||||
|
activeClient,
|
||||||
|
clients: listFanavaranClientProfiles().map((profile) => ({
|
||||||
|
key: profile.key,
|
||||||
|
defaults: profile.defaults,
|
||||||
|
isActive: profile.key === activeClient,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(":client/claim-cases/:claimCaseId")
|
||||||
|
@ApiOperation({
|
||||||
|
summary: "Preview Fanavaran submit payload (V2)",
|
||||||
|
description:
|
||||||
|
"Builds the third-party-car-financial-claims body from claimCases + blameCases without calling Fanavaran.",
|
||||||
|
})
|
||||||
|
@ApiParam({
|
||||||
|
name: "client",
|
||||||
|
description: "Fanavaran tenant key",
|
||||||
|
enum: ["parsian", "tejaratno"],
|
||||||
|
})
|
||||||
|
@ApiParam({
|
||||||
|
name: "claimCaseId",
|
||||||
|
description: "Claim case MongoDB ObjectId",
|
||||||
|
})
|
||||||
|
@ApiQuery({
|
||||||
|
name: "debug",
|
||||||
|
required: false,
|
||||||
|
description: "When true, returns payload plus mapping debug steps",
|
||||||
|
})
|
||||||
|
async preview(
|
||||||
|
@Param("client") client: string,
|
||||||
|
@Param("claimCaseId") claimCaseId: string,
|
||||||
|
@Query("debug") debug?: string,
|
||||||
|
) {
|
||||||
|
const clientKey = this.parseClientParam(client);
|
||||||
|
return await this.claimRequestManagementService.previewFanavaranSubmitV2(
|
||||||
|
claimCaseId,
|
||||||
|
clientKey,
|
||||||
|
{ debug: debug === "1" || debug === "true" },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post(":client/claim-cases/:claimCaseId")
|
||||||
|
@ApiOperation({
|
||||||
|
summary: "Submit claim to Fanavaran (V2)",
|
||||||
|
description:
|
||||||
|
"Authenticates with the selected client credentials and submits the mapped claimCases + blameCases payload.",
|
||||||
|
})
|
||||||
|
@ApiParam({
|
||||||
|
name: "client",
|
||||||
|
description: "Fanavaran tenant key",
|
||||||
|
enum: ["parsian", "tejaratno"],
|
||||||
|
})
|
||||||
|
@ApiParam({
|
||||||
|
name: "claimCaseId",
|
||||||
|
description: "Claim case MongoDB ObjectId",
|
||||||
|
})
|
||||||
|
async submit(
|
||||||
|
@Param("client") client: string,
|
||||||
|
@Param("claimCaseId") claimCaseId: string,
|
||||||
|
) {
|
||||||
|
const clientKey = this.parseClientParam(client);
|
||||||
|
return await this.claimRequestManagementService.submitFanavaranV2(
|
||||||
|
claimCaseId,
|
||||||
|
clientKey,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseClientParam(client: string) {
|
||||||
|
if (!isFanavaranClientKey(client)) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
`Invalid Fanavaran client "${client}". Expected one of: parsian, tejaratno`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return normalizeFanavaranClientKey(client);
|
||||||
|
}
|
||||||
|
}
|
||||||
10
src/fanavaran/fanavaran.module.ts
Normal file
10
src/fanavaran/fanavaran.module.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { Module } from "@nestjs/common";
|
||||||
|
import { ClaimRequestManagementModule } from "src/claim-request-management/claim-request-management.module";
|
||||||
|
import { FanavaranAuditModule } from "./fanavaran-audit.module";
|
||||||
|
import { FanavaranController } from "./fanavaran.controller";
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [ClaimRequestManagementModule, FanavaranAuditModule],
|
||||||
|
controllers: [FanavaranController],
|
||||||
|
})
|
||||||
|
export class FanavaranModule {}
|
||||||
75
src/fanavaran/schema/fanavaran-audit-log.schema.ts
Normal file
75
src/fanavaran/schema/fanavaran-audit-log.schema.ts
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
|
||||||
|
import { HydratedDocument, Types } from "mongoose";
|
||||||
|
import type { FanavaranClientKey } from "src/core/config/fanavaran-client.config";
|
||||||
|
|
||||||
|
export enum FanavaranAuditStep {
|
||||||
|
GET_APP_TOKEN = "GET_APP_TOKEN",
|
||||||
|
LOGIN = "LOGIN",
|
||||||
|
POLICY_INQUIRY = "POLICY_INQUIRY",
|
||||||
|
BUILD_PAYLOAD = "BUILD_PAYLOAD",
|
||||||
|
SUBMIT_CLAIM = "SUBMIT_CLAIM",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum FanavaranAuditStatus {
|
||||||
|
STARTED = "started",
|
||||||
|
SUCCESS = "success",
|
||||||
|
FAILURE = "failure",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum FanavaranAuditSource {
|
||||||
|
PREVIEW = "preview",
|
||||||
|
SUBMIT = "submit",
|
||||||
|
AUTO_SUBMIT = "auto_submit",
|
||||||
|
LEGACY_SUBMIT = "legacy_submit",
|
||||||
|
}
|
||||||
|
|
||||||
|
@Schema({ collection: "fanavaranAuditLogs", timestamps: true })
|
||||||
|
export class FanavaranAuditLog {
|
||||||
|
@Prop({ type: String, required: true, index: true })
|
||||||
|
trackingCode: string;
|
||||||
|
|
||||||
|
@Prop({ type: String, required: true, enum: FanavaranAuditStep, index: true })
|
||||||
|
step: FanavaranAuditStep;
|
||||||
|
|
||||||
|
@Prop({ type: String, required: true, enum: FanavaranAuditStatus, index: true })
|
||||||
|
status: FanavaranAuditStatus;
|
||||||
|
|
||||||
|
@Prop({ type: String, required: true, index: true })
|
||||||
|
clientKey: FanavaranClientKey;
|
||||||
|
|
||||||
|
@Prop({ type: String, required: true, enum: FanavaranAuditSource, index: true })
|
||||||
|
source: FanavaranAuditSource;
|
||||||
|
|
||||||
|
@Prop({ type: Types.ObjectId, required: false, index: true })
|
||||||
|
claimCaseId?: Types.ObjectId;
|
||||||
|
|
||||||
|
@Prop({ type: Types.ObjectId, required: false, index: true })
|
||||||
|
claimRequestId?: Types.ObjectId;
|
||||||
|
|
||||||
|
@Prop({ type: String, required: false })
|
||||||
|
requestUrl?: string;
|
||||||
|
|
||||||
|
@Prop({ type: Number, required: false })
|
||||||
|
httpStatus?: number;
|
||||||
|
|
||||||
|
@Prop({ type: Object, required: false })
|
||||||
|
requestMeta?: Record<string, unknown>;
|
||||||
|
|
||||||
|
@Prop({ type: Object, required: false })
|
||||||
|
responseMeta?: Record<string, unknown>;
|
||||||
|
|
||||||
|
@Prop({ type: String, required: false })
|
||||||
|
errorMessage?: string;
|
||||||
|
|
||||||
|
@Prop({ type: Object, required: false })
|
||||||
|
errorDetails?: Record<string, unknown>;
|
||||||
|
|
||||||
|
@Prop({ type: Number, required: false })
|
||||||
|
durationMs?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FanavaranAuditLogDocument = HydratedDocument<FanavaranAuditLog>;
|
||||||
|
export const FanavaranAuditLogSchema =
|
||||||
|
SchemaFactory.createForClass(FanavaranAuditLog);
|
||||||
|
|
||||||
|
FanavaranAuditLogSchema.index({ trackingCode: 1, createdAt: 1 });
|
||||||
@@ -163,6 +163,7 @@ export class LookupsController {
|
|||||||
schema: {
|
schema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
|
client: { type: "string", example: "tejaratno" },
|
||||||
accidentWay: {
|
accidentWay: {
|
||||||
type: "array",
|
type: "array",
|
||||||
items: {
|
items: {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Module } from "@nestjs/common";
|
import { Module } from "@nestjs/common";
|
||||||
import { MongooseModule } from "@nestjs/mongoose";
|
import { MongooseModule } from "@nestjs/mongoose";
|
||||||
|
import { FanavaranLookupModule } from "src/fanavaran/fanavaran-lookup.module";
|
||||||
import { LookupDbService } from "./entities/db-service/lookup.db.service";
|
import { LookupDbService } from "./entities/db-service/lookup.db.service";
|
||||||
import { LookupModel, LookupSchema } from "./entities/schema/lookup.schema";
|
import { LookupModel, LookupSchema } from "./entities/schema/lookup.schema";
|
||||||
import { LookupsController } from "./lookups.controller";
|
import { LookupsController } from "./lookups.controller";
|
||||||
@@ -7,6 +8,7 @@ import { LookupsService } from "./lookups.service";
|
|||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
|
FanavaranLookupModule,
|
||||||
MongooseModule.forFeature([
|
MongooseModule.forFeature([
|
||||||
{ name: LookupModel.name, schema: LookupSchema },
|
{ name: LookupModel.name, schema: LookupSchema },
|
||||||
]),
|
]),
|
||||||
|
|||||||
@@ -1,15 +1,47 @@
|
|||||||
import { readFile } from "node:fs/promises";
|
|
||||||
import { Injectable, Logger, NotFoundException } from "@nestjs/common";
|
import { Injectable, Logger, NotFoundException } from "@nestjs/common";
|
||||||
|
import { resolveFanavaranClientKey } from "src/core/config/fanavaran-client.config";
|
||||||
|
import {
|
||||||
|
FANAVARAN_REMOTE_LOOKUPS,
|
||||||
|
TEJARAT_STATIC_ACCIDENT_FILES,
|
||||||
|
} from "src/fanavaran/fanavaran-lookup.config";
|
||||||
|
import { FanavaranLookupService } from "src/fanavaran/fanavaran-lookup.service";
|
||||||
import { LookupDbService } from "./entities/db-service/lookup.db.service";
|
import { LookupDbService } from "./entities/db-service/lookup.db.service";
|
||||||
|
|
||||||
|
type TejaratAccidentReasonRow = {
|
||||||
|
id: number;
|
||||||
|
persianLabel: string;
|
||||||
|
fanavaranID: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type TejaratAccidentLabelRow = {
|
||||||
|
id: number;
|
||||||
|
persianLabel: string;
|
||||||
|
};
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class LookupsService {
|
export class LookupsService {
|
||||||
private readonly logger = new Logger(LookupsService.name);
|
private readonly logger = new Logger(LookupsService.name);
|
||||||
|
|
||||||
constructor(private readonly lookupDbService: LookupDbService) {}
|
constructor(
|
||||||
|
private readonly lookupDbService: LookupDbService,
|
||||||
|
private readonly fanavaranLookupService: FanavaranLookupService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
private activeClientKey() {
|
||||||
|
return resolveFanavaranClientKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
private findRemoteLookup(name: string) {
|
||||||
|
const lookup = FANAVARAN_REMOTE_LOOKUPS.find((item) => item.name === name);
|
||||||
|
if (!lookup) {
|
||||||
|
throw new Error(`Unknown Fanavaran remote lookup: ${name}`);
|
||||||
|
}
|
||||||
|
return lookup;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns stored `response` for a lookup document by `name` in the lookups collection.
|
* Returns stored `response` for a lookup document by `name` in the lookups collection.
|
||||||
|
* Legacy Tejarat seed data — used as fallback when live fetch is unavailable.
|
||||||
*/
|
*/
|
||||||
async getLookup(lookupName: string): Promise<any> {
|
async getLookup(lookupName: string): Promise<any> {
|
||||||
const doc = await this.lookupDbService.findOne({ name: lookupName });
|
const doc = await this.lookupDbService.findOne({ name: lookupName });
|
||||||
@@ -20,42 +52,114 @@ export class LookupsService {
|
|||||||
return doc.response;
|
return doc.response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async getClientRemoteLookup(lookupName: string): Promise<unknown> {
|
||||||
|
const clientKey = this.activeClientKey();
|
||||||
|
const definition = this.findRemoteLookup(lookupName);
|
||||||
|
|
||||||
|
return this.fanavaranLookupService.getRemoteLookup(
|
||||||
|
clientKey,
|
||||||
|
definition.url,
|
||||||
|
definition.cacheFile,
|
||||||
|
clientKey === "tejaratno"
|
||||||
|
? async () => this.getLookup(lookupName)
|
||||||
|
: undefined,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async getAccidentCauses(): Promise<any> {
|
async getAccidentCauses(): Promise<any> {
|
||||||
return await this.getLookup("accident-causes");
|
return await this.getClientRemoteLookup("accident-causes");
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAccidentReportType(): Promise<any> {
|
async getAccidentReportType(): Promise<any> {
|
||||||
return await this.getLookup("accident-report-type");
|
return await this.getClientRemoteLookup("accident-report-type");
|
||||||
}
|
}
|
||||||
|
|
||||||
async getVehicleUseTypes(): Promise<any> {
|
async getVehicleUseTypes(): Promise<any> {
|
||||||
return await this.getLookup("vehicle-use-types");
|
return await this.getClientRemoteLookup("vehicle-use-types");
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDmgPayMethod(): Promise<any> {
|
async getDmgPayMethod(): Promise<any> {
|
||||||
return await this.getLookup("dmg-pay-method");
|
return await this.getClientRemoteLookup("dmg-pay-method");
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDrivingLicenceTypes(): Promise<any> {
|
async getDrivingLicenceTypes(): Promise<any> {
|
||||||
return await this.getLookup("driving-licence-types");
|
return await this.getClientRemoteLookup("driving-licence-types");
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAccidentCulpritType(): Promise<any> {
|
async getAccidentCulpritType(): Promise<any> {
|
||||||
return await this.getLookup("accident-culprit-type");
|
return await this.getClientRemoteLookup("accident-culprit-type");
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAccidentWay(): Promise<{ id: number; label: string }[]> {
|
async getAccidentWay(): Promise<{ id: number; label: string }[]> {
|
||||||
const raw: { id: number; persianLabel: string }[] = JSON.parse(
|
const clientKey = this.activeClientKey();
|
||||||
await readFile("src/static/ACCIDENT_WAY.json", "utf-8"),
|
const fileName = TEJARAT_STATIC_ACCIDENT_FILES.accidentWay;
|
||||||
);
|
|
||||||
|
const cached = await this.fanavaranLookupService.readCacheFile<
|
||||||
|
TejaratAccidentLabelRow[]
|
||||||
|
>(clientKey, fileName);
|
||||||
|
const raw =
|
||||||
|
cached ??
|
||||||
|
(clientKey === "tejaratno"
|
||||||
|
? await this.fanavaranLookupService.readTejaratStaticAccidentFile<
|
||||||
|
TejaratAccidentLabelRow[]
|
||||||
|
>(fileName)
|
||||||
|
: null);
|
||||||
|
|
||||||
|
if (!raw) {
|
||||||
|
this.logger.warn(
|
||||||
|
`No accident-way lookup for client ${clientKey}; falling back to tejarat static file`,
|
||||||
|
);
|
||||||
|
const fallback =
|
||||||
|
await this.fanavaranLookupService.readTejaratStaticAccidentFile<
|
||||||
|
TejaratAccidentLabelRow[]
|
||||||
|
>(fileName);
|
||||||
|
return fallback.map((item) => ({
|
||||||
|
id: item.id,
|
||||||
|
label: item.persianLabel,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
return raw.map((item) => ({ id: item.id, label: item.persianLabel }));
|
return raw.map((item) => ({ id: item.id, label: item.persianLabel }));
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAccidentReason(): Promise<
|
async getAccidentReason(): Promise<
|
||||||
{ id: number; label: string; fanavaran: number }[]
|
{ id: number; label: string; fanavaran: number }[]
|
||||||
> {
|
> {
|
||||||
const raw: { id: number; persianLabel: string; fanavaranID: number }[] =
|
const clientKey = this.activeClientKey();
|
||||||
JSON.parse(await readFile("src/static/ACCIDENT_REASON.json", "utf-8"));
|
|
||||||
|
if (clientKey === "parsian") {
|
||||||
|
const cacheFile = "accident-reason-options.json";
|
||||||
|
const cached =
|
||||||
|
await this.fanavaranLookupService.readCacheFile<
|
||||||
|
{ id: number; label: string; fanavaran: number }[]
|
||||||
|
>(clientKey, cacheFile);
|
||||||
|
if (cached) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
const causes = await this.getAccidentCauses();
|
||||||
|
const mapped =
|
||||||
|
this.fanavaranLookupService.mapFanavaranAccidentCausesToReasonOptions(
|
||||||
|
causes,
|
||||||
|
);
|
||||||
|
await this.fanavaranLookupService.writeCacheFile(
|
||||||
|
clientKey,
|
||||||
|
cacheFile,
|
||||||
|
mapped,
|
||||||
|
);
|
||||||
|
return mapped;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileName = TEJARAT_STATIC_ACCIDENT_FILES.accidentReason;
|
||||||
|
const cached = await this.fanavaranLookupService.readCacheFile<
|
||||||
|
TejaratAccidentReasonRow[]
|
||||||
|
>(clientKey, fileName);
|
||||||
|
const raw =
|
||||||
|
cached ??
|
||||||
|
(await this.fanavaranLookupService.readTejaratStaticAccidentFile<
|
||||||
|
TejaratAccidentReasonRow[]
|
||||||
|
>(fileName));
|
||||||
|
|
||||||
return raw.map((item) => ({
|
return raw.map((item) => ({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
label: item.persianLabel,
|
label: item.persianLabel,
|
||||||
@@ -64,9 +168,34 @@ export class LookupsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getAccidentType(): Promise<{ id: number; label: string }[]> {
|
async getAccidentType(): Promise<{ id: number; label: string }[]> {
|
||||||
const raw: { id: number; persianLabel: string }[] = JSON.parse(
|
const clientKey = this.activeClientKey();
|
||||||
await readFile("src/static/ACCIDENT_TYPE.json", "utf-8"),
|
const fileName = TEJARAT_STATIC_ACCIDENT_FILES.accidentType;
|
||||||
);
|
|
||||||
|
const cached = await this.fanavaranLookupService.readCacheFile<
|
||||||
|
TejaratAccidentLabelRow[]
|
||||||
|
>(clientKey, fileName);
|
||||||
|
const raw =
|
||||||
|
cached ??
|
||||||
|
(clientKey === "tejaratno"
|
||||||
|
? await this.fanavaranLookupService.readTejaratStaticAccidentFile<
|
||||||
|
TejaratAccidentLabelRow[]
|
||||||
|
>(fileName)
|
||||||
|
: null);
|
||||||
|
|
||||||
|
if (!raw) {
|
||||||
|
this.logger.warn(
|
||||||
|
`No accident-type lookup for client ${clientKey}; falling back to tejarat static file`,
|
||||||
|
);
|
||||||
|
const fallback =
|
||||||
|
await this.fanavaranLookupService.readTejaratStaticAccidentFile<
|
||||||
|
TejaratAccidentLabelRow[]
|
||||||
|
>(fileName);
|
||||||
|
return fallback.map((item) => ({
|
||||||
|
id: item.id,
|
||||||
|
label: item.persianLabel,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
return raw.map((item) => ({ id: item.id, label: item.persianLabel }));
|
return raw.map((item) => ({ id: item.id, label: item.persianLabel }));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,12 +203,18 @@ export class LookupsService {
|
|||||||
accidentWay: { id: number; label: string }[];
|
accidentWay: { id: number; label: string }[];
|
||||||
accidentReason: { id: number; label: string; fanavaran: number }[];
|
accidentReason: { id: number; label: string; fanavaran: number }[];
|
||||||
accidentType: { id: number; label: string }[];
|
accidentType: { id: number; label: string }[];
|
||||||
|
client: string;
|
||||||
}> {
|
}> {
|
||||||
const [accidentWay, accidentReason, accidentType] = await Promise.all([
|
const [accidentWay, accidentReason, accidentType] = await Promise.all([
|
||||||
this.getAccidentWay(),
|
this.getAccidentWay(),
|
||||||
this.getAccidentReason(),
|
this.getAccidentReason(),
|
||||||
this.getAccidentType(),
|
this.getAccidentType(),
|
||||||
]);
|
]);
|
||||||
return { accidentWay, accidentReason, accidentType };
|
return {
|
||||||
|
client: this.activeClientKey(),
|
||||||
|
accidentWay,
|
||||||
|
accidentReason,
|
||||||
|
accidentType,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user