forked from Yara724/api
Merge pull request 'main' (#152) from s.hajizadeh/yara724api:main into main
Reviewed-on: Yara724/api#152
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -169,6 +169,13 @@ import {
|
|||||||
getFanavaranClientProfile,
|
getFanavaranClientProfile,
|
||||||
resolveFanavaranClientKey,
|
resolveFanavaranClientKey,
|
||||||
} from "src/core/config/fanavaran-client.config";
|
} 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;
|
||||||
@@ -181,9 +188,6 @@ export interface FanavaranAutoSubmitResult {
|
|||||||
fanavaranResponse?: unknown;
|
fanavaranResponse?: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Placeholder for Fanavaran fields not yet mapped from claim/blame data. */
|
|
||||||
const FANAVARAN_UNMAPPED_FIELD = "empty";
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ClaimRequestManagementService {
|
export class ClaimRequestManagementService {
|
||||||
private readonly logger = new Logger(ClaimRequestManagementService.name);
|
private readonly logger = new Logger(ClaimRequestManagementService.name);
|
||||||
@@ -262,6 +266,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[] {
|
||||||
@@ -3358,15 +3363,15 @@ export class ClaimRequestManagementService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private applyFanavaranDefaultFields(result: Record<string, unknown>): void {
|
private applyFanavaranDefaultFields(result: Record<string, unknown>): void {
|
||||||
result.ActualPremium = FANAVARAN_UNMAPPED_FIELD;
|
result.ActualPremium = null;
|
||||||
result.ArchiveNo = FANAVARAN_UNMAPPED_FIELD;
|
result.ArchiveNo = null;
|
||||||
result.AuthorityCulpritId = FANAVARAN_UNMAPPED_FIELD;
|
result.AuthorityCulpritId = null;
|
||||||
result.AccidentCulpritId = null;
|
result.AccidentCulpritId = null;
|
||||||
result.ClaimCompletionDate = FANAVARAN_UNMAPPED_FIELD;
|
result.ClaimCompletionDate = null;
|
||||||
result.CostSeparationToDmgSections = FANAVARAN_UNMAPPED_FIELD;
|
result.CostSeparationToDmgSections = null;
|
||||||
result.CouponNo = null;
|
result.CouponNo = null;
|
||||||
result.CourtArchiveNo = null;
|
result.CourtArchiveNo = null;
|
||||||
result.CustomerFaultPercent = FANAVARAN_UNMAPPED_FIELD;
|
result.CustomerFaultPercent = null;
|
||||||
result.CulpritLicenceCityId = null;
|
result.CulpritLicenceCityId = null;
|
||||||
result.CulpritLicenceCountryId = null;
|
result.CulpritLicenceCountryId = null;
|
||||||
result.CulpritLicenceForeignCityName = null;
|
result.CulpritLicenceForeignCityName = null;
|
||||||
@@ -3375,14 +3380,14 @@ export class ClaimRequestManagementService {
|
|||||||
result.DamagedCount = 1;
|
result.DamagedCount = 1;
|
||||||
result.DmgAssessorFirstCreationTime = null;
|
result.DmgAssessorFirstCreationTime = null;
|
||||||
result.EntryDate = null;
|
result.EntryDate = null;
|
||||||
result.GlassBreakReasonId = FANAVARAN_UNMAPPED_FIELD;
|
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 = FANAVARAN_UNMAPPED_FIELD;
|
result.IsOwnerChanged = null;
|
||||||
result.IsPlaqueChanged = 0;
|
result.IsPlaqueChanged = 0;
|
||||||
result.IsSurplusArticleEighthLaw = FANAVARAN_UNMAPPED_FIELD;
|
result.IsSurplusArticleEighthLaw = null;
|
||||||
result.PlaqueCityId = null;
|
result.PlaqueCityId = null;
|
||||||
result.PlaqueKindId = null;
|
result.PlaqueKindId = null;
|
||||||
result.PlaqueLeftNo = null;
|
result.PlaqueLeftNo = null;
|
||||||
@@ -3397,7 +3402,7 @@ export class ClaimRequestManagementService {
|
|||||||
result.PoliceReportSerial = null;
|
result.PoliceReportSerial = null;
|
||||||
result.PolicyId = null;
|
result.PolicyId = null;
|
||||||
result.PreviousPolicyEndDate = "";
|
result.PreviousPolicyEndDate = "";
|
||||||
result.StatusChangeDate = FANAVARAN_UNMAPPED_FIELD;
|
result.StatusChangeDate = null;
|
||||||
result.TrackingCode = null;
|
result.TrackingCode = null;
|
||||||
result.IsLicenseReplacement = 0;
|
result.IsLicenseReplacement = 0;
|
||||||
result.UnknownCulpritCauseId = null;
|
result.UnknownCulpritCauseId = null;
|
||||||
@@ -3663,7 +3668,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,
|
||||||
@@ -3699,9 +3716,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 =
|
||||||
@@ -3710,9 +3752,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,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3725,7 +3777,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,
|
||||||
@@ -3750,7 +3814,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 ||
|
||||||
@@ -3767,9 +3830,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 =
|
||||||
@@ -3778,9 +3866,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,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3796,12 +3894,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}`,
|
||||||
@@ -3827,15 +3944,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}`,
|
||||||
);
|
);
|
||||||
@@ -3956,10 +4113,28 @@ export class ClaimRequestManagementService {
|
|||||||
async previewFanavaranSubmitV2(
|
async previewFanavaranSubmitV2(
|
||||||
claimCaseId: string,
|
claimCaseId: string,
|
||||||
clientKey: FanavaranClientKey,
|
clientKey: FanavaranClientKey,
|
||||||
options?: { debug?: boolean },
|
options?: {
|
||||||
|
debug?: boolean;
|
||||||
|
auditSession?: FanavaranAuditSession;
|
||||||
|
},
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
const profile = getFanavaranClientProfile(clientKey);
|
const profile = getFanavaranClientProfile(clientKey);
|
||||||
const logPrefix = `[Fanavaran ${clientKey} V2] claimCaseId=${claimCaseId}`;
|
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 = {
|
||||||
@@ -4088,6 +4263,7 @@ export class ClaimRequestManagementService {
|
|||||||
nationalCodeOfInsurer,
|
nationalCodeOfInsurer,
|
||||||
profile.auth,
|
profile.auth,
|
||||||
logPrefix,
|
logPrefix,
|
||||||
|
auditSession,
|
||||||
);
|
);
|
||||||
debug.values.policyId = policyId;
|
debug.values.policyId = policyId;
|
||||||
debug.steps.policyInquirySucceeded = policyId !== null;
|
debug.steps.policyInquirySucceeded = policyId !== null;
|
||||||
|
|||||||
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;
|
||||||
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
import { Module } from "@nestjs/common";
|
import { Module } from "@nestjs/common";
|
||||||
import { ClaimRequestManagementModule } from "src/claim-request-management/claim-request-management.module";
|
import { ClaimRequestManagementModule } from "src/claim-request-management/claim-request-management.module";
|
||||||
|
import { FanavaranAuditModule } from "./fanavaran-audit.module";
|
||||||
import { FanavaranController } from "./fanavaran.controller";
|
import { FanavaranController } from "./fanavaran.controller";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [ClaimRequestManagementModule],
|
imports: [ClaimRequestManagementModule, FanavaranAuditModule],
|
||||||
controllers: [FanavaranController],
|
controllers: [FanavaranController],
|
||||||
})
|
})
|
||||||
export class FanavaranModule {}
|
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 });
|
||||||
Reference in New Issue
Block a user