update the fanavaran which accepts unknown person too

This commit is contained in:
2026-09-14 13:42:53 +03:30
parent 6e5b7cb1fd
commit b13b0211ad
9 changed files with 758 additions and 4 deletions

View File

@@ -185,7 +185,10 @@ import {
import { FanavaranAuditService } from "src/fanavaran/fanavaran-audit.service";
import { FanavaranAuthService } from "src/fanavaran/fanavaran-auth.service";
import { FanavaranLookupService } from "src/fanavaran/fanavaran-lookup.service";
import { FANAVARAN_REMOTE_LOOKUPS } from "src/fanavaran/fanavaran-lookup.config";
import {
FANAVARAN_LOOKUP_BASE_URL,
FANAVARAN_REMOTE_LOOKUPS,
} from "src/fanavaran/fanavaran-lookup.config";
import type { FanavaranAuditSession } from "src/fanavaran/fanavaran-audit.types";
import {
FanavaranAuditSource,
@@ -207,6 +210,10 @@ import {
pickPersonNationalCode,
selectFanavaranDriverId,
} from "./fanavaran-driver-inquiry";
import {
buildFanavaranOtherPeoplePayload,
pickFanavaranRecordId,
} from "./fanavaran-other-people";
import {
asObjectRecord,
collectPartyVinCandidates,
@@ -4127,6 +4134,8 @@ export class ClaimRequestManagementService {
DriverIsOwner: number;
FaultPercent: number;
};
/** GEN.44 write: register the damaged party in Fanavaran when inquiry misses. */
registerMissingPerson?: boolean;
}): Promise<Record<string, unknown>> {
const damagedParty = this.pickDamagedPartyForFanavaran(
input.blameCase,
@@ -4288,9 +4297,11 @@ export class ClaimRequestManagementService {
// Prefer claim-level cache, then party.person.fanavaranDriverId, then live inquiry, then last payload
let driverFanavaranId = this.firstPositiveFanavaranId(
cachedDamage.driverId,
cachedDamage.otherPersonId,
person.fanavaranDriverId,
lastDamagePayload?.DriverId,
);
let otherPersonId = this.firstPositiveFanavaranId(cachedDamage.otherPersonId);
if (driverFanavaranId) {
this.logger.log(
@@ -4304,6 +4315,20 @@ export class ClaimRequestManagementService {
input.claimCase,
);
if (!driverFanavaranId && input.registerMissingPerson) {
const registered = await this.registerFanavaranOtherPerson({
clientKey: input.clientKey,
person,
claimCaseId: input.claimCase?._id
? String(input.claimCase._id)
: undefined,
});
if (registered != null) {
driverFanavaranId = registered;
otherPersonId = registered;
}
}
// Persist the resolved ID back to the blame case party for future use
if (driverFanavaranId && input.blameCase?._id && damagedParty) {
const partyIndex = (input.blameCase.parties ?? []).findIndex(
@@ -4473,6 +4498,9 @@ export class ClaimRequestManagementService {
...(driverFanavaranId != null
? { "fanavaranSync.damageCase.driverId": driverFanavaranId }
: {}),
...(otherPersonId != null
? { "fanavaranSync.damageCase.otherPersonId": otherPersonId }
: {}),
...(vehicleKindId != null
? { "fanavaranSync.damageCase.vehicleKindId": vehicleKindId }
: {}),
@@ -5075,6 +5103,158 @@ export class ClaimRequestManagementService {
return null;
}
private async loadFanavaranOtherPeopleLookups(
clientKey: FanavaranClientKey,
): Promise<{
cities: unknown[];
gender: unknown[];
maritalStatus: unknown[];
ans: unknown[];
countries: unknown[];
personKind: unknown[];
}> {
const [cities, gender, maritalStatus, ans, countries, personKind] =
await Promise.all([
this.getFanavaranLookupRows(clientKey, "cities"),
this.getFanavaranLookupRows(clientKey, "gender"),
this.getFanavaranLookupRows(clientKey, "marital-status"),
this.getFanavaranLookupRows(clientKey, "ans"),
this.getFanavaranLookupRows(clientKey, "countries"),
this.getFanavaranLookupRows(clientKey, "person-kind"),
]);
return { cities, gender, maritalStatus, ans, countries, personKind };
}
/**
* GEN.44: create the damaged party in Fanavaran when parties inquiry has no row.
* Failures stay best-effort — GEN.12 submit continues without DriverId.
*/
private async registerFanavaranOtherPerson(input: {
clientKey: FanavaranClientKey;
person: any;
claimCaseId?: string;
}): Promise<number | null> {
const nationalCode = pickPersonNationalCode(input.person);
const birthday = pickPersonBirthday(input.person);
if (!nationalCode || !parseJalaliDateParts(birthday)) {
this.logger.warn(
`[registerFanavaranOtherPerson] SKIP: missing nationalCode or birthday`,
);
return null;
}
let user: {
fullName?: string;
mobile?: string;
city?: string;
address?: string;
gender?: string;
} | null = null;
const userId = input.person?.userId;
if (userId && Types.ObjectId.isValid(String(userId))) {
try {
user = await this.userDbService.findOne({
_id: new Types.ObjectId(String(userId)),
});
} catch (error) {
this.logger.warn(
`[registerFanavaranOtherPerson] user lookup failed: ${
error instanceof Error ? error.message : error
}`,
);
}
}
const lookups = await this.loadFanavaranOtherPeopleLookups(input.clientKey);
const payload = buildFanavaranOtherPeoplePayload(
{
nationalCode,
birthday,
fullName: input.person?.fullName ?? user?.fullName,
mobile: input.person?.phoneNumber ?? user?.mobile,
address: user?.address,
cityName: user?.city,
gender: user?.gender,
},
lookups,
);
if (!payload) {
this.logger.warn(
`[registerFanavaranOtherPerson] SKIP: could not build GEN.44 payload for nationalCode=${nationalCode}`,
);
return null;
}
const url = `${FANAVARAN_LOOKUP_BASE_URL}/common/other-people`;
const startedAt = Date.now();
const auditSession: FanavaranAuditSession = {
trackingCode: this.fanavaranAuditService.generateTrackingCode(),
clientKey: input.clientKey,
source: FanavaranAuditSource.AUTO_SUBMIT,
claimCaseId: input.claimCaseId,
};
await this.fanavaranAuditService.recordStep({
session: auditSession,
step: FanavaranAuditStep.CREATE_OTHER_PERSON,
status: FanavaranAuditStatus.STARTED,
requestUrl: url,
requestMethod: "POST",
requestBody: payload,
requestMeta: { nationalCode },
});
try {
const created = await this.fanavaranLookupService.createOtherPerson(
input.clientKey,
payload,
);
const personId = pickFanavaranRecordId(created);
await this.fanavaranAuditService.recordStep({
session: auditSession,
step: FanavaranAuditStep.CREATE_OTHER_PERSON,
status: FanavaranAuditStatus.SUCCESS,
requestUrl: url,
requestMethod: "POST",
requestBody: payload,
responseBody: created,
responseMeta: { otherPersonId: personId, nationalCode },
durationMs: Date.now() - startedAt,
});
if (personId != null) {
this.logger.log(
`[registerFanavaranOtherPerson] CREATED Id=${personId} for nationalCode=${nationalCode}`,
);
return personId;
}
this.logger.warn(
`[registerFanavaranOtherPerson] create returned no Id for nationalCode=${nationalCode}; retrying inquiry`,
);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
await this.fanavaranAuditService.recordStep({
session: auditSession,
step: FanavaranAuditStep.CREATE_OTHER_PERSON,
status: FanavaranAuditStatus.FAILURE,
requestUrl: url,
requestMethod: "POST",
requestBody: payload,
errorMessage: message,
durationMs: Date.now() - startedAt,
});
this.logger.warn(
`[registerFanavaranOtherPerson] CREATE failed for nationalCode=${nationalCode}: ${message}; retrying inquiry`,
);
}
return this.resolveDriverFanavaranId(
input.clientKey,
nationalCode,
birthday,
input.person?.driverIsInsurer,
);
}
/**
* زیان‌دیده for GEN.12: claim owner when linked to a blame party,
* else the party that is not the guilty one.
@@ -5521,6 +5701,16 @@ export class ClaimRequestManagementService {
defaults: profile.defaults,
});
const warnings: string[] = [];
if (!claimCase.claimId) {
warnings.push("Fanavaran base claimId is required before submit.");
}
if (payload.DriverId == null) {
warnings.push(
"Damaged party is not in Fanavaran yet; submit will register them via other-people (GEN.44).",
);
}
return {
clientKey,
claimCaseId,
@@ -5529,9 +5719,7 @@ export class ClaimRequestManagementService {
submitUrl: claimCase.claimId
? `${this.FANAVARAN_SUBMIT_URL}/${claimCase.claimId}/dmg-cases`
: null,
warning: claimCase.claimId
? undefined
: "Fanavaran base claimId is required before submit.",
warning: warnings.length ? warnings.join(" ") : undefined,
payload,
};
}
@@ -7021,6 +7209,7 @@ export class ClaimRequestManagementService {
selectedParts: selectedParts ?? claimCase.damage?.selectedParts ?? [],
clientKey,
defaults: profile.defaults,
registerMissingPerson: true,
});
// Guardrail: never send empty/null licence fields even if manual overrides arrive.