1
0
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:
2026-06-23 16:00:15 +03:30
7 changed files with 443 additions and 24 deletions

View File

@@ -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,

View File

@@ -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 appToken = await this.getAppToken(config);
const authenticationToken = await this.login(appToken, config);
const policyInquiryUrl = `https://apimanager.iraneit.com/BimeApiManager/api/BimeApi/v2.0/common/Policies/inquiry-my-policies?InsuranceLineId=5&NationalCode=${nationalCodeOfInsurer}`; const policyInquiryUrl = `https://apimanager.iraneit.com/BimeApiManager/api/BimeApi/v2.0/common/Policies/inquiry-my-policies?InsuranceLineId=5&NationalCode=${nationalCodeOfInsurer}`;
const startedAt = Date.now();
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;

View 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 {}

View 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();
}
}

View 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;
}

View File

@@ -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 {}

View 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 });