1
0
forked from Yara724/api

fix(fanavaran): select latest active policy by end date

Why:
- Fanavaran policy inquiry response order is inconsistent, so selecting the last item can choose an old or expired policy.

Changes:
- Select the policy with the latest Jalali EndDate.
- Reject empty policy responses, expired latest policies, and latest policies without a valid PolicyId.
- Stop Fanavaran submission when PolicyId cannot be resolved.

Impact:
- Fanavaran submit now fails clearly instead of continuing with PolicyId: null.
This commit is contained in:
2026-06-30 11:51:11 +03:30
parent f3686575ca
commit 8d396762a2
3 changed files with 187 additions and 55 deletions

View File

@@ -176,6 +176,7 @@ import {
FanavaranAuditStatus,
FanavaranAuditStep,
} from "src/fanavaran/schema/fanavaran-audit-log.schema";
import { selectLatestActiveFanavaranPolicy } from "./fanavaran-policy-selection";
export interface FanavaranAutoSubmitResult {
attempted: boolean;
@@ -3438,17 +3439,13 @@ export class ClaimRequestManagementService {
this.applyFanavaranDefaultFields(result);
try {
const policyId = await input.resolvePolicyId();
if (policyId !== undefined && policyId !== null) {
result.PolicyId = policyId;
}
} catch (error) {
this.logger.error(
`${input.logPrefix} Failed to get PolicyId from external API:`,
error,
if (policyId === undefined || policyId === null) {
throw new BadRequestException(
`${input.logPrefix} PolicyId is required for Fanavaran submit. Policy inquiry returned no valid policy; contact the administrator.`,
);
}
result.PolicyId = policyId;
return result;
}
@@ -3567,15 +3564,18 @@ export class ClaimRequestManagementService {
blameRequest,
claimRequestId,
);
if (policyId) {
result.PolicyId = policyId;
if (!policyId) {
throw new BadRequestException(
`[Fanavaran Submit] PolicyId is required for Fanavaran submit. Policy inquiry returned no valid policy; contact the administrator.`,
);
}
result.PolicyId = policyId;
} catch (error) {
this.logger.error(
`[Fanavaran Submit] Failed to get PolicyId from external API:`,
error,
);
// Continue without PolicyId if API call fails
throw error;
}
return result;
@@ -3866,11 +3866,7 @@ export class ClaimRequestManagementService {
}),
);
const policyCount = Array.isArray(response.data)
? response.data.length
: 0;
const lastPolicy =
policyCount > 0 ? response.data[policyCount - 1] : null;
const policyCount = Array.isArray(response.data) ? response.data.length : 0;
this.logger.log(
`${logPrefix} Policy inquiry response status=${response.status} count=${policyCount}`,
@@ -3882,20 +3878,11 @@ export class ClaimRequestManagementService {
2,
)}`,
);
if (lastPolicy && typeof lastPolicy === "object") {
const selectedPolicy = selectLatestActiveFanavaranPolicy(response.data);
this.logger.log(
`${logPrefix} Policy inquiry last policy keys: ${Object.keys(
lastPolicy,
).join(", ")}`,
`${logPrefix} Selected latest active policy PolicyId=${selectedPolicy.policyId} EndDate=${selectedPolicy.endDate}`,
);
}
if (Array.isArray(response.data) && response.data.length > 0) {
const policyId = lastPolicy?.PolicyId;
if (policyId !== undefined && policyId !== null) {
this.logger.log(
`${logPrefix} Successfully retrieved PolicyId from last policy entry: ${policyId}`,
);
if (auditSession) {
await this.fanavaranAuditService.recordStep({
session: auditSession,
@@ -3904,34 +3891,15 @@ export class ClaimRequestManagementService {
requestUrl: policyInquiryUrl,
httpStatus: response.status,
responseMeta: {
policyId,
policyCount: response.data.length,
policyId: selectedPolicy.policyId,
policyEndDate: selectedPolicy.endDate,
policyEndDateGregorian: selectedPolicy.endDateGregorian,
policyCount,
},
durationMs: Date.now() - startedAt,
});
}
return policyId;
}
}
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 selectedPolicy.policyId;
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : "unknown policy inquiry error";
@@ -3948,8 +3916,11 @@ export class ClaimRequestManagementService {
});
}
this.logger.warn(
`${logPrefix} Policy inquiry failed; continuing with empty PolicyId: ${errorMessage}`,
`${logPrefix} Policy inquiry failed: ${errorMessage}`,
);
if (error instanceof BadRequestException) {
throw error;
}
return null;
}
}
@@ -4011,6 +3982,9 @@ export class ClaimRequestManagementService {
`[Fanavaran Submit] Error getting PolicyId from external API:`,
error,
);
if (error instanceof BadRequestException) {
throw error;
}
return null;
}
}

View File

@@ -0,0 +1,63 @@
import { BadRequestException } from "@nestjs/common";
import { selectLatestActiveFanavaranPolicy } from "./fanavaran-policy-selection";
describe("selectLatestActiveFanavaranPolicy", () => {
const today = "2026-06-30";
it("selects the policy with the latest EndDate when newest is first", () => {
const selected = selectLatestActiveFanavaranPolicy(
[
{ PolicyId: 4826286, EndDate: "1405/09/23" },
{ PolicyId: 3719458, EndDate: "1404/09/06" },
{ PolicyId: 2800731, EndDate: "1403/09/05" },
],
today,
);
expect(selected.policyId).toBe(4826286);
expect(selected.endDate).toBe("1405/09/23");
});
it("selects the policy with the latest EndDate when newest is last", () => {
const selected = selectLatestActiveFanavaranPolicy(
[
{ PolicyId: 2800731, EndDate: "1403/09/05" },
{ PolicyId: 3719458, EndDate: "1404/09/06" },
{ PolicyId: 4826286, EndDate: "1405/09/23" },
],
today,
);
expect(selected.policyId).toBe(4826286);
});
it("rejects when no policies are returned", () => {
expect(() => selectLatestActiveFanavaranPolicy([], today)).toThrow(
BadRequestException,
);
});
it("rejects when the latest policy is expired", () => {
expect(() =>
selectLatestActiveFanavaranPolicy(
[
{ PolicyId: 3719458, EndDate: "1404/09/06" },
{ PolicyId: 2800731, EndDate: "1403/09/05" },
],
today,
),
).toThrow(BadRequestException);
});
it("rejects when the latest policy has no valid PolicyId", () => {
expect(() =>
selectLatestActiveFanavaranPolicy(
[
{ PolicyId: 3719458, EndDate: "1404/09/06" },
{ PolicyId: null, EndDate: "1405/09/23" },
],
today,
),
).toThrow(BadRequestException);
});
});

View File

@@ -0,0 +1,95 @@
import { BadRequestException } from "@nestjs/common";
import { jalaliToGregorianDate } from "src/helpers/date-jalali";
import { gregorianDateInIran } from "src/helpers/iran-datetime";
export type FanavaranPolicyInquiryRow = {
PolicyId?: unknown;
EndDate?: unknown;
};
export type SelectedFanavaranPolicy = {
policy: FanavaranPolicyInquiryRow;
policyId: number;
endDate: string;
endDateGregorian: string;
};
type DatedFanavaranPolicy = {
policy: FanavaranPolicyInquiryRow;
endDate: string;
endDateGregorian: string;
};
const NO_POLICY_MESSAGE =
"No Fanavaran policies were found for the insurer national code. PolicyId is required; contact the administrator.";
const EXPIRED_POLICY_MESSAGE =
"The latest insurance policy is expired and cannot be sent to Fanavaran. Contact the administrator.";
const INVALID_POLICY_MESSAGE =
"Fanavaran policy inquiry returned policies without a valid PolicyId or EndDate. PolicyId is required; contact the administrator.";
function parsePolicyId(value: unknown): number | null {
if (value === null || value === undefined) return null;
if (typeof value === "string" && value.trim() === "") return null;
const id = Number(value);
return Number.isFinite(id) && id > 0 ? id : null;
}
function normalizeEndDate(value: unknown): {
endDate: string;
endDateGregorian: string;
} | null {
if (value === null || value === undefined) return null;
const endDate = String(value).trim();
if (!endDate) return null;
const endDateGregorian = jalaliToGregorianDate(endDate);
if (!endDateGregorian) return null;
return { endDate, endDateGregorian };
}
export function selectLatestActiveFanavaranPolicy(
policies: unknown,
todayGregorian: string = gregorianDateInIran(new Date()),
): SelectedFanavaranPolicy {
if (!Array.isArray(policies) || policies.length === 0) {
throw new BadRequestException(NO_POLICY_MESSAGE);
}
const candidates = policies
.map((policy) => {
if (!policy || typeof policy !== "object") return null;
const row = policy as FanavaranPolicyInquiryRow;
const endDate = normalizeEndDate(row.EndDate);
if (!endDate) return null;
return {
policy: row,
endDate: endDate.endDate,
endDateGregorian: endDate.endDateGregorian,
};
})
.filter((policy): policy is DatedFanavaranPolicy => policy !== null);
if (candidates.length === 0) {
throw new BadRequestException(INVALID_POLICY_MESSAGE);
}
const latest = candidates.reduce((currentLatest, candidate) =>
candidate.endDateGregorian > currentLatest.endDateGregorian
? candidate
: currentLatest,
);
if (latest.endDateGregorian < todayGregorian) {
throw new BadRequestException(EXPIRED_POLICY_MESSAGE);
}
const policyId = parsePolicyId(latest.policy.PolicyId);
if (policyId === null) {
throw new BadRequestException(INVALID_POLICY_MESSAGE);
}
return { ...latest, policyId };
}