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:
@@ -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,
|
||||
const policyId = await input.resolvePolicyId();
|
||||
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,39 +3878,11 @@ export class ClaimRequestManagementService {
|
||||
2,
|
||||
)}`,
|
||||
);
|
||||
if (lastPolicy && typeof lastPolicy === "object") {
|
||||
this.logger.log(
|
||||
`${logPrefix} Policy inquiry last policy keys: ${Object.keys(
|
||||
lastPolicy,
|
||||
).join(", ")}`,
|
||||
);
|
||||
}
|
||||
const selectedPolicy = selectLatestActiveFanavaranPolicy(response.data);
|
||||
this.logger.log(
|
||||
`${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,
|
||||
step: FanavaranAuditStep.POLICY_INQUIRY,
|
||||
status: FanavaranAuditStatus.SUCCESS,
|
||||
requestUrl: policyInquiryUrl,
|
||||
httpStatus: response.status,
|
||||
responseMeta: {
|
||||
policyId,
|
||||
policyCount: response.data.length,
|
||||
},
|
||||
durationMs: Date.now() - startedAt,
|
||||
});
|
||||
}
|
||||
return policyId;
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.warn(`${logPrefix} No PolicyId found in API response`);
|
||||
if (auditSession) {
|
||||
await this.fanavaranAuditService.recordStep({
|
||||
session: auditSession,
|
||||
@@ -3923,15 +3891,15 @@ export class ClaimRequestManagementService {
|
||||
requestUrl: policyInquiryUrl,
|
||||
httpStatus: response.status,
|
||||
responseMeta: {
|
||||
policyId: null,
|
||||
policyCount: Array.isArray(response.data)
|
||||
? response.data.length
|
||||
: 0,
|
||||
policyId: selectedPolicy.policyId,
|
||||
policyEndDate: selectedPolicy.endDate,
|
||||
policyEndDateGregorian: selectedPolicy.endDateGregorian,
|
||||
policyCount,
|
||||
},
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
95
src/claim-request-management/fanavaran-policy-selection.ts
Normal file
95
src/claim-request-management/fanavaran-policy-selection.ts
Normal 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 };
|
||||
}
|
||||
Reference in New Issue
Block a user