fix: harden claim review and inquiry workflows

Preserve damage history and current vehicle price, restore depreciation mapping, normalize inquiry/report output, and support resumable expert review with paginated case retrieval.
This commit is contained in:
2026-09-18 16:04:33 +03:30
parent a84d83a135
commit 7d1a50db7b
38 changed files with 2345 additions and 370 deletions

View File

@@ -17,6 +17,7 @@ import {
ReinquiryInquiriesResponseDto,
ReinquiryPartyResultDto,
} from "./dto/reinquiry-inquiries.dto";
import { getInquiryErrorMessage } from "src/common/utils/inquiry-error";
type PlateParts = {
leftDigits: number;
@@ -48,7 +49,7 @@ export class InquiryRefreshService {
if (!body.publicId && !body.blameRequestId && limit === 0) {
throw new BadRequestException(
"Provide publicId, blameRequestId, or limit for bulk refresh.",
"برای اجرای مجدد استعلام، شناسه عمومی پرونده، شناسه پرونده یا تعداد پرونده‌ها را وارد کنید.",
);
}
@@ -58,14 +59,16 @@ export class InquiryRefreshService {
if (body.publicId) filter.publicId = body.publicId;
if (body.blameRequestId) {
if (!Types.ObjectId.isValid(body.blameRequestId)) {
throw new BadRequestException("Invalid blameRequestId");
throw new BadRequestException("شناسه پرونده معتبر نیست.");
}
filter._id = new Types.ObjectId(body.blameRequestId);
}
let docs = await this.blameRequestDbService.find(filter, { lean: true });
if (!docs.length) {
throw new NotFoundException("No matching blame cases found");
throw new NotFoundException(
"پرونده تقصیر مطابق اطلاعات واردشده یافت نشد.",
);
}
docs = limit > 0 ? docs.slice(0, limit) : docs;
@@ -111,8 +114,8 @@ export class InquiryRefreshService {
if (index === -1) {
partyResults.push({
role,
thirdParty: { ok: false, message: "party not found" },
person: { ok: false, message: "party not found" },
thirdParty: { ok: false, message: "طرف پرونده یافت نشد." },
person: { ok: false, message: "طرف پرونده یافت نشد." },
});
continue;
}
@@ -150,7 +153,9 @@ export class InquiryRefreshService {
blameRequestId: new Types.ObjectId(String(doc._id)),
});
claimsUpdated = linkedClaims.length;
this.logger.log(`[dry-run] ${label} would update blame + ${claimsUpdated} claim(s)`);
this.logger.log(
`[dry-run] ${label} would update blame + ${claimsUpdated} claim(s)`,
);
}
return {
@@ -208,9 +213,7 @@ export class InquiryRefreshService {
plateId: party?.vehicle?.plateId,
...(plate ? { plate } : {}),
...(nationalCode ? { nationalCode } : {}),
...(birthDate !== null && birthDate !== undefined
? { birthDate }
: {}),
...(birthDate !== null && birthDate !== undefined ? { birthDate } : {}),
};
if (dryRun) {
@@ -223,8 +226,8 @@ export class InquiryRefreshService {
result.thirdParty = {
ok: false,
message: !plate
? "plate not found on party"
: "nationalCodeOfInsurer/nationalCodeOfDriver missing",
? "پلاک برای این طرف پرونده ثبت نشده است."
: "کد ملی برای این طرف پرونده ثبت نشده است.",
};
} else {
await this.waitForRateLimit();
@@ -249,7 +252,9 @@ export class InquiryRefreshService {
inquiriesChanged = true;
result.thirdParty = {
ok: false,
message: inquiry.mapped.Error.Message || "third-party inquiry error",
message:
inquiry.mapped.Error.Message ||
getInquiryErrorMessage(inquiry.mapped, "thirdPartyPlate"),
};
} else {
nextParty = this.applyThirdPartyToParty(
@@ -292,11 +297,18 @@ export class InquiryRefreshService {
}
}
} catch (error: any) {
this.recordPartyInquiry(inquiries, "thirdParty", role, false, {}, error);
this.recordPartyInquiry(
inquiries,
"thirdParty",
role,
false,
{},
error,
);
inquiriesChanged = true;
result.thirdParty = {
ok: false,
message: error?.message || String(error),
message: getInquiryErrorMessage(error, "thirdPartyPlate"),
};
}
}
@@ -305,8 +317,8 @@ export class InquiryRefreshService {
result.person = {
ok: false,
message: !nationalCode
? "nationalCodeOfInsurer/nationalCodeOfDriver missing"
: "insurerBirthday/driverBirthday missing",
? "کد ملی برای این طرف پرونده ثبت نشده است."
: "تاریخ تولد برای این طرف پرونده ثبت نشده است.",
};
} else {
await this.waitForRateLimit();
@@ -333,7 +345,7 @@ export class InquiryRefreshService {
inquiriesChanged = true;
result.person = {
ok: false,
message: error?.message || String(error),
message: getInquiryErrorMessage(error, "personalIdentity"),
};
}
}
@@ -477,12 +489,14 @@ export class InquiryRefreshService {
): Promise<void> {
const blameDoc = await this.blameRequestDbService.findById(blameId);
if (!blameDoc) {
throw new NotFoundException(`Blame case ${blameId} not found`);
throw new NotFoundException("پرونده تقصیر یافت نشد.");
}
for (const role of roles) {
const memParty = updatedParties.find((party) => party?.role === role);
const docIdx = blameDoc.parties.findIndex((party) => party?.role === role);
const docIdx = blameDoc.parties.findIndex(
(party) => party?.role === role,
);
if (!memParty || docIdx === -1) continue;
const party = blameDoc.parties[docIdx];
@@ -514,7 +528,9 @@ export class InquiryRefreshService {
party.insurance.company = memParty.insurance.company;
}
if (memParty.insurance.financialCeiling !== undefined) {
party.insurance.financialCeiling = String(memParty.insurance.financialCeiling);
party.insurance.financialCeiling = String(
memParty.insurance.financialCeiling,
);
}
if (memParty.insurance.startDate !== undefined) {
party.insurance.startDate = memParty.insurance.startDate;
@@ -541,7 +557,8 @@ export class InquiryRefreshService {
});
const inquiryPatch: Record<string, unknown> = {};
if (inquiries.thirdParty) inquiryPatch["inquiries.thirdParty"] = inquiries.thirdParty;
if (inquiries.thirdParty)
inquiryPatch["inquiries.thirdParty"] = inquiries.thirdParty;
if (inquiries.person) inquiryPatch["inquiries.person"] = inquiries.person;
if (!Object.keys(inquiryPatch).length) return 0;
@@ -590,14 +607,16 @@ export class InquiryRefreshService {
private normalizeInquiryError(error: any): Record<string, unknown> {
return {
message: error?.message || String(error),
message: getInquiryErrorMessage(error, "generic"),
status: error?.status ?? error?.response?.status,
data: error?.data ?? error?.response?.data,
};
}
private resolvePartyPlate(party: Record<string, any>): PlateParts | null {
const fromPlateId = this.parsePlateFromCompactString(party?.vehicle?.plateId);
const fromPlateId = this.parsePlateFromCompactString(
party?.vehicle?.plateId,
);
if (fromPlateId) return fromPlateId;
const candidates = [
@@ -608,14 +627,24 @@ export class InquiryRefreshService {
].filter(Boolean);
for (const candidate of candidates) {
const leftDigits = this.firstPresent(candidate.Plk1, candidate.platePartOne);
const leftDigits = this.firstPresent(
candidate.Plk1,
candidate.platePartOne,
);
const centerAlphabet = this.firstPresent(
candidate.plateLetterid,
candidate.plateLetterId,
candidate.plateLetterTitle,
);
const centerDigits = this.firstPresent(candidate.Plk3, candidate.platePartThree);
const ir = this.firstPresent(candidate.PlkSrl, candidate.plkSrl, candidate.plateSerialNumber);
const centerDigits = this.firstPresent(
candidate.Plk3,
candidate.platePartThree,
);
const ir = this.firstPresent(
candidate.PlkSrl,
candidate.plkSrl,
candidate.plateSerialNumber,
);
if (
leftDigits !== undefined &&
@@ -623,7 +652,9 @@ export class InquiryRefreshService {
centerDigits !== undefined &&
ir !== undefined
) {
const plateLetter = this.plateNormalizer.normalizePlateText(String(centerAlphabet));
const plateLetter = this.plateNormalizer.normalizePlateText(
String(centerAlphabet),
);
const parsed: PlateParts = {
leftDigits: Number(leftDigits),
centerAlphabet: plateLetter,
@@ -653,7 +684,9 @@ export class InquiryRefreshService {
const ir = Number(irRaw);
const leftDigits = Number(leftRaw);
const centerDigits = Number(centerRaw);
const centerAlphabet = this.plateNormalizer.normalizePlateText(String(alphaRaw || ""));
const centerAlphabet = this.plateNormalizer.normalizePlateText(
String(alphaRaw || ""),
);
if (
!Number.isFinite(ir) ||
!Number.isFinite(leftDigits) ||
@@ -685,6 +718,8 @@ export class InquiryRefreshService {
}
private firstPresent(...values: unknown[]): unknown {
return values.find((value) => value !== undefined && value !== null && value !== "");
return values.find(
(value) => value !== undefined && value !== null && value !== "",
);
}
}