fanavaran duplication request problems fixed.

This commit is contained in:
2026-08-02 17:00:19 +03:30
parent c2f5c576fa
commit b345818d43
9 changed files with 884 additions and 123 deletions

View File

@@ -4290,20 +4290,24 @@ export class ClaimRequestManagementService {
const policyInquiryUrl = `https://apimanager.iraneit.com/BimeApiManager/api/BimeApi/v2.0/common/Policies/inquiry-my-policies?InsuranceLineId=5&NationalCode=${nationalCodeOfInsurer}`;
const startedAt = Date.now();
const requestMeta = {
corpId: config.corpId,
contractId: config.contractId,
location: config.location,
nationalCode: this.fanavaranAuditService.maskNationalCode(
nationalCodeOfInsurer,
),
InsuranceLineId: 5,
};
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,
),
},
requestMethod: "GET",
requestMeta,
});
}
@@ -4313,6 +4317,10 @@ export class ClaimRequestManagementService {
clientKey,
auditSession,
);
const requestHeaders = {
...headers,
"Content-Type": "application/json",
};
this.logger.log(
`${logPrefix} Calling policy inquiry API for nationalCode: ${nationalCodeOfInsurer}`,
@@ -4320,10 +4328,7 @@ export class ClaimRequestManagementService {
const response = await firstValueFrom(
this.httpService.get(policyInquiryUrl, {
headers: {
...headers,
"Content-Type": "application/json",
},
headers: requestHeaders,
timeout: 15000,
}),
);
@@ -4351,17 +4356,27 @@ export class ClaimRequestManagementService {
this.fanavaranAuthService.clearBackoff(clientKey);
if (auditSession) {
const exchange = this.fanavaranAuditService.captureAxiosExchange(
response,
requestHeaders,
);
await this.fanavaranAuditService.recordStep({
session: auditSession,
step: FanavaranAuditStep.POLICY_INQUIRY,
status: FanavaranAuditStatus.SUCCESS,
requestUrl: policyInquiryUrl,
requestMethod: "GET",
httpStatus: response.status,
requestHeaders: exchange.requestHeaders,
requestMeta,
responseHeaders: exchange.responseHeaders,
responseBody: exchange.responseBody,
responseMeta: {
policyId: selectedPolicy.policyId,
policyEndDate: selectedPolicy.endDate,
policyEndDateGregorian: selectedPolicy.endDateGregorian,
policyCount,
fromCache: false,
},
durationMs: Date.now() - startedAt,
});
@@ -4372,12 +4387,19 @@ export class ClaimRequestManagementService {
const errorMessage =
error instanceof Error ? error.message : "unknown policy inquiry error";
if (auditSession) {
const exchange =
this.fanavaranAuditService.captureAxiosExchange(error);
await this.fanavaranAuditService.recordStep({
session: auditSession,
step: FanavaranAuditStep.POLICY_INQUIRY,
status: FanavaranAuditStatus.FAILURE,
requestUrl: policyInquiryUrl,
httpStatus: isAxiosError(error) ? error.response?.status : undefined,
requestMethod: "GET",
httpStatus: exchange.httpStatus,
requestHeaders: exchange.requestHeaders,
requestMeta,
responseHeaders: exchange.responseHeaders,
responseBody: exchange.responseBody,
errorMessage,
errorDetails: this.fanavaranAuditService.sanitizeErrorDetails(error),
durationMs: Date.now() - startedAt,
@@ -4624,7 +4646,9 @@ export class ClaimRequestManagementService {
}
const cachedPolicyId =
(claimCase as any)?.fanavaranSync?.baseClaim?.policyId ?? null;
(claimCase as any)?.fanavaranSync?.baseClaim?.policyId ??
(claimCase as any)?.fanavaranSync?.baseClaim?.lastPayload?.PolicyId ??
null;
const payload = await this.buildFanavaranSubmitPayload({
accidentReason: selectedAccidentReason,
@@ -4674,6 +4698,7 @@ export class ClaimRequestManagementService {
if (cachedPolicyId != null && !forceRefreshPolicy) {
debug.steps.policyIdFromCache = true;
debug.values.policyId = Number(cachedPolicyId);
// Silent reuse — no Fanavaran HTTP and no audit noise on warm preview.
return Number(cachedPolicyId);
}
@@ -5018,7 +5043,37 @@ export class ClaimRequestManagementService {
}
const profile = getFanavaranClientProfile(clientKey);
const claimCase = await this.claimCaseDbService.findById(claimCaseId);
let claimCase = await this.claimCaseDbService.findById(claimCaseId);
if (!claimCase?.claimId) {
try {
await this.ensureFanavaranBaseClaim(
claimCaseId,
clientKey,
FanavaranAuditSource.SUBMIT,
);
claimCase = await this.claimCaseDbService.findById(claimCaseId);
} catch (error) {
const warning = this.extractFanavaranErrorMessage(error);
return {
clientKey,
claimCaseId,
totalLocalImages: candidates.length,
skippedAlreadySubmitted: candidates.length - pending.length,
attempted: 0,
submitted: 0,
failed: 0,
skipped: pending.length,
warning,
results: pending.map((c) => ({
attempted: false,
submitted: false,
skipped: true,
skipReason: warning,
fileName: c.fileName,
})),
};
}
}
if (!claimCase?.claimId) {
return {
clientKey,
@@ -5219,13 +5274,14 @@ export class ClaimRequestManagementService {
step: FanavaranAuditStep.SUBMIT_ATTACHMENT,
status: FanavaranAuditStatus.STARTED,
requestUrl: url,
requestMethod: "POST",
requestBody: content,
requestMeta: {
claimId: claimCase.claimId,
claimNo: claimCase.claimNo,
dmgCaseId: claimCase.dmgCaseId,
fileName,
source: file.source,
content,
},
});
@@ -5234,18 +5290,25 @@ export class ClaimRequestManagementService {
content,
[{ path: filePath, fileName }],
clientKey,
auditSession,
);
this.logger.log(`${logPrefix} Fanavaran response: ${JSON.stringify(response.data)}`);
const fileId = response.data?.Id;
const exchange =
this.fanavaranAuditService.captureAxiosExchange(response);
await this.fanavaranAuditService.recordStep({
session: auditSession,
step: FanavaranAuditStep.SUBMIT_ATTACHMENT,
status: FanavaranAuditStatus.SUCCESS,
requestUrl: url,
requestMethod: "POST",
httpStatus: response.status,
requestBody: content,
responseHeaders: exchange.responseHeaders,
responseBody: exchange.responseBody,
responseMeta: { claimId: claimCase.claimId, fileId, fileName },
durationMs: Date.now() - startedAt,
});
@@ -5693,6 +5756,11 @@ export class ClaimRequestManagementService {
bodyOverride?: Record<string, unknown>,
): Promise<any> {
try {
await this.ensureFanavaranDamageCase(
claimCaseId,
clientKey,
FanavaranAuditSource.SUBMIT,
);
const claimCase = await this.claimCaseDbService.findById(claimCaseId);
if (!claimCase) throw new NotFoundException("Claim case not found");
let payload: Record<string, unknown>;
@@ -5728,12 +5796,30 @@ export class ClaimRequestManagementService {
payload: Record<string, unknown>,
auditSource: FanavaranAuditSource,
): Promise<any> {
const claimCase = await this.claimCaseDbService.findById(claimCaseId);
let claimCase = await this.claimCaseDbService.findById(claimCaseId);
if (!claimCase?.claimId || claimCase.dmgCaseId == null) {
await this.ensureFanavaranDamageCase(
claimCaseId,
clientKey,
auditSource,
);
claimCase = await this.claimCaseDbService.findById(claimCaseId);
}
if (!claimCase?.claimId) {
throw new BadRequestException(
"Fanavaran claimId is required before submitting expertise",
);
}
if (claimCase.expertiseId != null) {
this.logger.log(
`[executeFanavaranExpertiseSubmit] Already have expertiseId=${claimCase.expertiseId}; skipping Fanavaran POST`,
);
return (
(claimCase as any)?.fanavaranSync?.expertise?.response ?? {
Id: claimCase.expertiseId,
}
);
}
const url = `${this.FANAVARAN_SUBMIT_URL}/${claimCase.claimId}/expertise`;
const startedAt = Date.now();
const auditSession: FanavaranAuditSession = {
@@ -5748,19 +5834,32 @@ export class ClaimRequestManagementService {
step: FanavaranAuditStep.SUBMIT_EXPERTISE,
status: FanavaranAuditStatus.STARTED,
requestUrl: url,
requestMeta: { claimId: claimCase.claimId, payload },
requestMethod: "POST",
requestBody: payload,
requestMeta: { claimId: claimCase.claimId },
});
try {
const response = await this.postFanavaranJson(url, payload, clientKey);
const response = await this.postFanavaranJson(
url,
payload,
clientKey,
auditSession,
);
const expertiseId = response.data?.Id;
const exchange =
this.fanavaranAuditService.captureAxiosExchange(response);
await this.fanavaranAuditService.recordStep({
session: auditSession,
step: FanavaranAuditStep.SUBMIT_EXPERTISE,
status: FanavaranAuditStatus.SUCCESS,
requestUrl: url,
requestMethod: "POST",
httpStatus: response.status,
requestBody: payload,
responseHeaders: exchange.responseHeaders,
responseBody: exchange.responseBody,
responseMeta: { claimId: claimCase.claimId, expertiseId },
durationMs: Date.now() - startedAt,
});
@@ -5781,12 +5880,17 @@ export class ClaimRequestManagementService {
return response.data;
} catch (error) {
const exchange = this.fanavaranAuditService.captureAxiosExchange(error);
await this.fanavaranAuditService.recordStep({
session: auditSession,
step: FanavaranAuditStep.SUBMIT_EXPERTISE,
status: FanavaranAuditStatus.FAILURE,
requestUrl: url,
httpStatus: isAxiosError(error) ? error.response?.status : undefined,
requestMethod: "POST",
httpStatus: exchange.httpStatus,
requestBody: payload,
responseHeaders: exchange.responseHeaders,
responseBody: exchange.responseBody,
errorMessage: this.fanavaranAuditService.extractErrorMessage(error),
errorDetails: this.fanavaranAuditService.sanitizeErrorDetails(error),
durationMs: Date.now() - startedAt,
@@ -5952,13 +6056,104 @@ export class ClaimRequestManagementService {
return null;
}
/**
* Soft-ensure Fanavaran base claim exists before a later stage (damage /
* attachments / expertise). Reuses claimId when present; otherwise submits
* GEN.03 once. Does not invent a fake claimId on failure.
*/
private async ensureFanavaranBaseClaim(
claimCaseId: string,
clientKey: FanavaranClientKey,
auditSource: FanavaranAuditSource,
): Promise<{ claimId: number; claimNo?: string; created: boolean }> {
const existing = await this.claimCaseDbService.findById(claimCaseId);
if (!existing) {
throw new NotFoundException("Claim case not found");
}
if (existing.claimId != null) {
return {
claimId: Number(existing.claimId),
claimNo:
existing.claimNo != null ? String(existing.claimNo) : undefined,
created: false,
};
}
this.logger.log(
`[Fanavaran ${clientKey}] Soft-ensuring base claim before next stage claimCaseId=${claimCaseId}`,
);
await this.executeFanavaranV2Submit(
claimCaseId,
clientKey,
undefined,
auditSource,
);
const refreshed = await this.claimCaseDbService.findById(claimCaseId);
if (refreshed?.claimId == null) {
throw new BadRequestException(
"Fanavaran base claim is missing and could not be created before the next stage. Fix base claim first.",
);
}
return {
claimId: Number(refreshed.claimId),
claimNo:
refreshed.claimNo != null ? String(refreshed.claimNo) : undefined,
created: true,
};
}
/**
* Soft-ensure damage case exists (creates base claim first if needed).
*/
private async ensureFanavaranDamageCase(
claimCaseId: string,
clientKey: FanavaranClientKey,
auditSource: FanavaranAuditSource,
selectedParts?: unknown[],
): Promise<{ claimId: number; dmgCaseId: number; created: boolean }> {
await this.ensureFanavaranBaseClaim(claimCaseId, clientKey, auditSource);
const existing = await this.claimCaseDbService.findById(claimCaseId);
if (existing?.dmgCaseId != null && existing.claimId != null) {
return {
claimId: Number(existing.claimId),
dmgCaseId: Number(existing.dmgCaseId),
created: false,
};
}
this.logger.log(
`[Fanavaran ${clientKey}] Soft-ensuring damage case before next stage claimCaseId=${claimCaseId}`,
);
await this.executeFanavaranDamageCaseSubmit(
claimCaseId,
clientKey,
selectedParts ?? existing?.damage?.selectedParts ?? [],
auditSource,
);
const refreshed = await this.claimCaseDbService.findById(claimCaseId);
if (refreshed?.claimId == null || refreshed.dmgCaseId == null) {
throw new BadRequestException(
"Fanavaran damage case is missing and could not be created before the next stage.",
);
}
return {
claimId: Number(refreshed.claimId),
dmgCaseId: Number(refreshed.dmgCaseId),
created: true,
};
}
private async postFanavaranJson(
url: string,
payload: Record<string, unknown>,
clientKey: FanavaranClientKey,
auditSession?: FanavaranAuditSession,
) {
this.fanavaranAuthService.assertNotInBackoff(clientKey);
const headers = await this.getFanavaranAuthHeaders(clientKey);
const headers = await this.getFanavaranAuthHeaders(clientKey, auditSession);
try {
const response = await firstValueFrom(
@@ -5982,9 +6177,10 @@ export class ClaimRequestManagementService {
content: Record<string, unknown>,
files: Array<{ path: string; fileName: string }>,
clientKey: FanavaranClientKey,
auditSession?: FanavaranAuditSession,
) {
this.fanavaranAuthService.assertNotInBackoff(clientKey);
const headers = await this.getFanavaranAuthHeaders(clientKey);
const headers = await this.getFanavaranAuthHeaders(clientKey, auditSession);
const form = new FormData();
form.append("Param", JSON.stringify(content), {
@@ -6043,13 +6239,27 @@ export class ClaimRequestManagementService {
bodyOverride?: Record<string, unknown>,
): Promise<any> {
const profile = getFanavaranClientProfile(clientKey);
const claimCase = await this.claimCaseDbService.findById(claimCaseId);
let claimCase = await this.claimCaseDbService.findById(claimCaseId);
if (!claimCase) {
throw new NotFoundException("Claim case not found");
}
if (!claimCase.claimId) {
throw new BadRequestException(
"Fanavaran claimId is required before submitting damage case",
await this.ensureFanavaranBaseClaim(claimCaseId, clientKey, auditSource);
claimCase = await this.claimCaseDbService.findById(claimCaseId);
if (!claimCase?.claimId) {
throw new BadRequestException(
"Fanavaran claimId is required before submitting damage case",
);
}
}
if (claimCase.dmgCaseId != null && !bodyOverride) {
this.logger.log(
`[executeFanavaranDamageCaseSubmit] Already have dmgCaseId=${claimCase.dmgCaseId}; skipping Fanavaran POST`,
);
return (
(claimCase as any)?.fanavaranSync?.damageCase?.response ?? {
Id: claimCase.dmgCaseId,
}
);
}
if (!claimCase.blameRequestId) {
@@ -6092,19 +6302,32 @@ export class ClaimRequestManagementService {
step: FanavaranAuditStep.SUBMIT_DAMAGE_CASE,
status: FanavaranAuditStatus.STARTED,
requestUrl: url,
requestMeta: { claimId: claimCase.claimId, payload },
requestMethod: "POST",
requestBody: payload,
requestMeta: { claimId: claimCase.claimId },
});
try {
const response = await this.postFanavaranJson(url, payload, clientKey);
const response = await this.postFanavaranJson(
url,
payload,
clientKey,
auditSession,
);
const dmgCaseId = response.data?.Id;
const exchange =
this.fanavaranAuditService.captureAxiosExchange(response);
await this.fanavaranAuditService.recordStep({
session: auditSession,
step: FanavaranAuditStep.SUBMIT_DAMAGE_CASE,
status: FanavaranAuditStatus.SUCCESS,
requestUrl: url,
requestMethod: "POST",
httpStatus: response.status,
requestBody: payload,
responseHeaders: exchange.responseHeaders,
responseBody: exchange.responseBody,
responseMeta: { dmgCaseId, claimId: claimCase.claimId },
durationMs: Date.now() - startedAt,
});
@@ -6134,12 +6357,17 @@ export class ClaimRequestManagementService {
this.logger.error(
`[executeFanavaranDamageCaseSubmit] FAILED claimCaseId=${claimCaseId} claimId=${claimCase.claimId} payload.DriverId=${payload.DriverId ?? "NULL"} error: ${errDetail}`,
);
const exchange = this.fanavaranAuditService.captureAxiosExchange(error);
await this.fanavaranAuditService.recordStep({
session: auditSession,
step: FanavaranAuditStep.SUBMIT_DAMAGE_CASE,
status: FanavaranAuditStatus.FAILURE,
requestUrl: url,
httpStatus: isAxiosError(error) ? error.response?.status : undefined,
requestMethod: "POST",
httpStatus: exchange.httpStatus,
requestBody: payload,
responseHeaders: exchange.responseHeaders,
responseBody: exchange.responseBody,
errorMessage: this.fanavaranAuditService.extractErrorMessage(error),
errorDetails: this.fanavaranAuditService.sanitizeErrorDetails(error),
durationMs: Date.now() - startedAt,
@@ -6830,6 +7058,19 @@ export class ClaimRequestManagementService {
`${logPrefix} Starting submission for claimCaseId: ${claimCaseId}`,
);
const existing = await this.claimCaseDbService.findById(claimCaseId);
if (existing?.claimId != null && !bodyOverride) {
this.logger.log(
`${logPrefix} Base claim already exists claimId=${existing.claimId}; skipping Fanavaran POST`,
);
return (
(existing as any)?.fanavaranSync?.baseClaim?.response ?? {
Id: existing.claimId,
ClaimNo: existing.claimNo,
}
);
}
const auditSession: FanavaranAuditSession = {
trackingCode: this.fanavaranAuditService.generateTrackingCode(),
clientKey,
@@ -6868,11 +7109,18 @@ export class ClaimRequestManagementService {
auditSession,
);
const requestHeaders = {
...headers,
"Content-Type": "application/json",
};
await this.fanavaranAuditService.recordStep({
session: auditSession,
step: FanavaranAuditStep.SUBMIT_CLAIM,
status: FanavaranAuditStatus.STARTED,
requestUrl: this.FANAVARAN_SUBMIT_URL,
requestMethod: "POST",
requestHeaders,
requestBody: fanavaranData,
requestMeta: { policyId: fanavaranData?.PolicyId ?? null },
});
const startedAt = Date.now();
@@ -6880,10 +7128,7 @@ export class ClaimRequestManagementService {
try {
const response = await firstValueFrom(
this.httpService.post(this.FANAVARAN_SUBMIT_URL, fanavaranData, {
headers: {
...headers,
"Content-Type": "application/json",
},
headers: requestHeaders,
}),
);
@@ -6895,12 +7140,21 @@ export class ClaimRequestManagementService {
this.fanavaranAuthService.clearBackoff(clientKey);
const exchange = this.fanavaranAuditService.captureAxiosExchange(
response,
requestHeaders,
);
await this.fanavaranAuditService.recordStep({
session: auditSession,
step: FanavaranAuditStep.SUBMIT_CLAIM,
status: FanavaranAuditStatus.SUCCESS,
requestUrl: this.FANAVARAN_SUBMIT_URL,
requestMethod: "POST",
httpStatus: response.status,
requestHeaders: exchange.requestHeaders,
requestBody: fanavaranData,
responseHeaders: exchange.responseHeaders,
responseBody: exchange.responseBody,
responseMeta: {
claimId: response.data?.Id,
claimNo: response.data?.ClaimNo,
@@ -6946,12 +7200,21 @@ export class ClaimRequestManagementService {
return response.data;
} catch (error) {
this.fanavaranAuthService.registerFailure(clientKey, error);
const exchange = this.fanavaranAuditService.captureAxiosExchange(
error,
requestHeaders,
);
await this.fanavaranAuditService.recordStep({
session: auditSession,
step: FanavaranAuditStep.SUBMIT_CLAIM,
status: FanavaranAuditStatus.FAILURE,
requestUrl: this.FANAVARAN_SUBMIT_URL,
httpStatus: isAxiosError(error) ? error.response?.status : undefined,
requestMethod: "POST",
httpStatus: exchange.httpStatus,
requestHeaders: exchange.requestHeaders,
requestBody: fanavaranData,
responseHeaders: exchange.responseHeaders,
responseBody: exchange.responseBody,
errorMessage: this.fanavaranAuditService.extractErrorMessage(error),
errorDetails: this.fanavaranAuditService.sanitizeErrorDetails(error),
durationMs: Date.now() - startedAt,