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