forked from Yara724/api
Merge pull request 'script updated , inquiry field added to blame case and claim case' (#106) from s.hajizadeh/yara724api:main into main
Reviewed-on: Yara724/api#106
This commit is contained in:
@@ -3,7 +3,8 @@
|
||||
/*
|
||||
* One-time script to replace mocked blameCases party vehicle inquiry data.
|
||||
* THIRD_PARTY cases use Tejarat block inquiry; CAR_BODY cases use both
|
||||
* Tejarat third-party block inquiry and car-body inquiry.
|
||||
* Tejarat third-party block inquiry and car-body inquiry. All cases also
|
||||
* refresh available party personal inquiries into inquiries.person.
|
||||
*
|
||||
* Defaults to DRY_RUN=true. Set DRY_RUN=false to write changes.
|
||||
*/
|
||||
@@ -61,10 +62,15 @@ const config = {
|
||||
carBodyUrl:
|
||||
process.env.TEJARAT_CAR_BODY_URL ||
|
||||
"http://82.99.202.245:3027/block-inquiry-tejarat/badane",
|
||||
personUrl:
|
||||
process.env.TEJARAT_PERSON_URL ||
|
||||
"http://82.99.202.245:3027/personal-inquiry/tejarat-no",
|
||||
thirdPartyToken:
|
||||
process.env.TEJARAT_THIRD_PARTY_TOKEN || process.env.TEJARAT_TOKEN || "",
|
||||
carBodyToken:
|
||||
process.env.TEJARAT_CAR_BODY_TOKEN || process.env.TEJARAT_TOKEN || "",
|
||||
personToken:
|
||||
process.env.TEJARAT_PERSON_TOKEN || process.env.TEJARAT_TOKEN || "",
|
||||
rateLimitPerMinute: Number(process.env.RATE_LIMIT_PER_MINUTE || 5),
|
||||
retryEnabled: String(process.env.RETRY_ENABLED ?? "true").toLowerCase() !== "false",
|
||||
retryCount: Number(process.env.RETRY_COUNT || 3),
|
||||
@@ -124,6 +130,7 @@ async function main() {
|
||||
requestNo: 1,
|
||||
type: 1,
|
||||
parties: 1,
|
||||
inquiries: 1,
|
||||
},
|
||||
})
|
||||
.sort({ createdAt: 1, _id: 1 });
|
||||
@@ -136,6 +143,9 @@ async function main() {
|
||||
partiesInquired: 0,
|
||||
partiesSkipped: 0,
|
||||
partiesFailed: 0,
|
||||
personInquired: 0,
|
||||
personSkipped: 0,
|
||||
personFailed: 0,
|
||||
};
|
||||
|
||||
for await (const doc of cursor) {
|
||||
@@ -145,7 +155,9 @@ async function main() {
|
||||
console.log(`currently inquiry for doc with ${label} and requestId ${requestId}`);
|
||||
|
||||
const parties = Array.isArray(doc.parties) ? clone(doc.parties) : [];
|
||||
const inquiries = doc.inquiries && typeof doc.inquiries === "object" ? clone(doc.inquiries) : {};
|
||||
let docChanged = false;
|
||||
let inquiriesChanged = false;
|
||||
|
||||
for (let index = 0; index < parties.length; index += 1) {
|
||||
const party = parties[index];
|
||||
@@ -155,9 +167,7 @@ async function main() {
|
||||
if (!input.ok) {
|
||||
summary.partiesSkipped += 1;
|
||||
console.warn(`[skip] ${partyLabel}: ${input.reason}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
} else {
|
||||
let nextParty = party;
|
||||
let partyChanged = false;
|
||||
|
||||
@@ -193,23 +203,65 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
if (!docChanged) {
|
||||
const personInput = buildPersonInquiryInput(party);
|
||||
if (!personInput.ok) {
|
||||
summary.personSkipped += 1;
|
||||
console.warn(`[skip] ${partyLabel} person: ${personInput.reason}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(`[request] ${partyLabel} type=PERSON body=${JSON.stringify(personInput.body)}`);
|
||||
try {
|
||||
const personResponse = await inquiryWithRetry("PERSON", personInput.body);
|
||||
const successful = isInquirySuccessful("PERSON", personResponse);
|
||||
console.log(
|
||||
`[response] ${partyLabel} type=PERSON successful=${successful} body=${JSON.stringify(personResponse)}`,
|
||||
);
|
||||
|
||||
if (!successful) {
|
||||
summary.personFailed += 1;
|
||||
applyPersonInquiryToCaseInquiries(inquiries, party, index, false, {}, personResponse);
|
||||
inquiriesChanged = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
applyPersonInquiryToCaseInquiries(
|
||||
inquiries,
|
||||
party,
|
||||
index,
|
||||
true,
|
||||
normalizePersonResponse(personResponse),
|
||||
);
|
||||
summary.personInquired += 1;
|
||||
inquiriesChanged = true;
|
||||
} catch (error) {
|
||||
summary.personFailed += 1;
|
||||
applyPersonInquiryToCaseInquiries(inquiries, party, index, false, {}, error);
|
||||
inquiriesChanged = true;
|
||||
console.error(`[error] ${partyLabel} type=PERSON: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!docChanged && !inquiriesChanged) {
|
||||
console.log(`[doc] ${label} no changes`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (config.dryRun) {
|
||||
console.log(`[dry-run] ${label} would update parties array`);
|
||||
console.log(`[dry-run] ${label} would update parties/inquiries`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const updateSet = {
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
if (docChanged) updateSet.parties = parties;
|
||||
if (inquiriesChanged) updateSet.inquiries = inquiries;
|
||||
|
||||
const result = await collection.updateOne(
|
||||
{ _id: doc._id },
|
||||
{
|
||||
$set: {
|
||||
parties,
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
$set: updateSet,
|
||||
},
|
||||
);
|
||||
summary.docsChanged += result.modifiedCount;
|
||||
@@ -285,6 +337,40 @@ function buildCarBodyRequest(plate, serialLetter, nationalCode) {
|
||||
};
|
||||
}
|
||||
|
||||
function buildPersonInquiryInput(party) {
|
||||
if (!party || typeof party !== "object") return { ok: false, reason: "party is empty" };
|
||||
const person = party.person && typeof party.person === "object" ? party.person : null;
|
||||
if (!person) return { ok: false, reason: "party.person is missing" };
|
||||
|
||||
const nationalCode =
|
||||
cleanString(person.nationalCodeOfInsurer) ||
|
||||
cleanString(person.nationalCodeOfDriver);
|
||||
const birthDate = firstPresent(
|
||||
person.insurerBirthday,
|
||||
person.driverBirthday,
|
||||
person.birthday,
|
||||
);
|
||||
const gregorianBirthdate = jalaliToGregorianDate(birthDate);
|
||||
|
||||
if (!nationalCode) {
|
||||
return { ok: false, reason: "nationalCodeOfInsurer/nationalCodeOfDriver missing" };
|
||||
}
|
||||
if (!gregorianBirthdate) {
|
||||
return {
|
||||
ok: false,
|
||||
reason: `invalid insurerBirthday/driverBirthday=${cleanString(birthDate)}`,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
body: {
|
||||
nationalCode,
|
||||
birthdate: gregorianBirthdate,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function parsePlateId(plateId) {
|
||||
const value = cleanString(plateId);
|
||||
if (!value) return null;
|
||||
@@ -352,8 +438,10 @@ function extractPlate(party) {
|
||||
}
|
||||
|
||||
async function inquiryWithRetry(type, body) {
|
||||
const url = type === "CAR_BODY" ? config.carBodyUrl : config.thirdPartyUrl;
|
||||
const token = type === "CAR_BODY" ? config.carBodyToken : config.thirdPartyToken;
|
||||
const endpoint = getInquiryEndpoint(type);
|
||||
const url = endpoint.url;
|
||||
const token = endpoint.token;
|
||||
const accept = endpoint.accept;
|
||||
let lastError;
|
||||
|
||||
const maxAttempts = config.retryEnabled ? config.retryCount : 1;
|
||||
@@ -362,7 +450,7 @@ async function inquiryWithRetry(type, body) {
|
||||
await waitForRateLimit();
|
||||
try {
|
||||
console.log("[http] " + type + " attempt=" + attempt + "/" + maxAttempts);
|
||||
return await postJson(url, token, body);
|
||||
return await postJson(url, token, body, accept);
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
console.error(`[retry] ${type} attempt=${attempt} failed: ${error.message}`);
|
||||
@@ -375,11 +463,11 @@ async function inquiryWithRetry(type, body) {
|
||||
throw lastError;
|
||||
}
|
||||
|
||||
async function postJson(url, token, body) {
|
||||
async function postJson(url, token, body, accept = "application/json") {
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
accept,
|
||||
authorization: `Bearer ${token}`,
|
||||
"content-type": "application/json",
|
||||
},
|
||||
@@ -395,7 +483,10 @@ async function postJson(url, token, body) {
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${JSON.stringify(data)}`);
|
||||
const error = new Error(`HTTP ${response.status}: ${JSON.stringify(data)}`);
|
||||
error.status = response.status;
|
||||
error.data = data;
|
||||
throw error;
|
||||
}
|
||||
|
||||
return data;
|
||||
@@ -403,9 +494,20 @@ async function postJson(url, token, body) {
|
||||
|
||||
function isInquirySuccessful(type, response) {
|
||||
if (type === "CAR_BODY") return response && response.isSuccess === true && response.data;
|
||||
if (type === "PERSON") return response && response.status === 200 && response.data;
|
||||
return response && response.resultStatus === true;
|
||||
}
|
||||
|
||||
function getInquiryEndpoint(type) {
|
||||
if (type === "CAR_BODY") {
|
||||
return { url: config.carBodyUrl, token: config.carBodyToken, accept: "application/json" };
|
||||
}
|
||||
if (type === "PERSON") {
|
||||
return { url: config.personUrl, token: config.personToken, accept: "*/*" };
|
||||
}
|
||||
return { url: config.thirdPartyUrl, token: config.thirdPartyToken, accept: "application/json" };
|
||||
}
|
||||
|
||||
function applyInquiryToParty(type, party, response, originalPlate) {
|
||||
if (type === "CAR_BODY") return applyCarBodyInquiryToParty(party, response, originalPlate);
|
||||
return applyThirdPartyInquiryToParty(party, response, originalPlate);
|
||||
@@ -486,6 +588,51 @@ function applyCarBodyInquiryToParty(party, response, originalPlate) {
|
||||
return next;
|
||||
}
|
||||
|
||||
function applyPersonInquiryToCaseInquiries(inquiries, party, index, has, data, error) {
|
||||
if (!inquiries.person || typeof inquiries.person !== "object") {
|
||||
inquiries.person = {};
|
||||
}
|
||||
|
||||
const existingData =
|
||||
inquiries.person.data && typeof inquiries.person.data === "object" && !Array.isArray(inquiries.person.data)
|
||||
? inquiries.person.data
|
||||
: {};
|
||||
const existingError =
|
||||
inquiries.person.error && typeof inquiries.person.error === "object" && !Array.isArray(inquiries.person.error)
|
||||
? inquiries.person.error
|
||||
: {};
|
||||
const roleKey = party && party.role ? party.role : `party_${index}`;
|
||||
const nextData = { ...existingData };
|
||||
const nextError = { ...existingError };
|
||||
|
||||
if (has) {
|
||||
nextData[roleKey] = data || {};
|
||||
delete nextError[roleKey];
|
||||
} else {
|
||||
nextError[roleKey] = normalizeInquiryError(error);
|
||||
}
|
||||
|
||||
inquiries.person = {
|
||||
has: Object.keys(nextData).length > 0,
|
||||
data: nextData,
|
||||
...(Object.keys(nextError).length > 0 ? { error: nextError } : {}),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
}
|
||||
|
||||
function normalizePersonResponse(response) {
|
||||
return response && response.data ? response.data : response;
|
||||
}
|
||||
|
||||
function normalizeInquiryError(error) {
|
||||
if (!error) return undefined;
|
||||
return {
|
||||
message: error.message || String(error),
|
||||
status: error.status,
|
||||
data: error.data,
|
||||
};
|
||||
}
|
||||
|
||||
function buildPlateId(plate) {
|
||||
if (!plate) return "";
|
||||
const serialLetter = NUMBER_TO_LETTER[String(plate.Plk2)] || cleanString(plate.Plk2);
|
||||
@@ -555,6 +702,111 @@ function normalizeCarBodyResponse(response, originalPlate) {
|
||||
};
|
||||
}
|
||||
|
||||
function jalaliToGregorianDate(input) {
|
||||
if (input === null || input === undefined) return null;
|
||||
|
||||
const raw = normalizeDigits(typeof input === "number" ? String(input) : String(input).trim());
|
||||
if (!raw) return null;
|
||||
|
||||
let year = 0;
|
||||
let month = 0;
|
||||
let day = 0;
|
||||
|
||||
const separated = raw.match(/^(\d{4})[\-/](\d{1,2})[\-/](\d{1,2})$/);
|
||||
if (separated) {
|
||||
year = parseInt(separated[1], 10);
|
||||
month = parseInt(separated[2], 10);
|
||||
day = parseInt(separated[3], 10);
|
||||
} else {
|
||||
const digits = raw.replace(/\D/g, "");
|
||||
if (digits.length !== 8) return null;
|
||||
year = parseInt(digits.slice(0, 4), 10);
|
||||
month = parseInt(digits.slice(4, 6), 10);
|
||||
day = parseInt(digits.slice(6, 8), 10);
|
||||
}
|
||||
|
||||
if (!year || !month || !day) return null;
|
||||
|
||||
if (year >= 1900) {
|
||||
const mm = String(month).padStart(2, "0");
|
||||
const dd = String(day).padStart(2, "0");
|
||||
const result = `${year}-${mm}-${dd}`;
|
||||
return isNaN(new Date(result).getTime()) ? null : result;
|
||||
}
|
||||
|
||||
return jalaliPartsToGregorian(year, month, day);
|
||||
}
|
||||
|
||||
function jalaliPartsToGregorian(jYear, jMonth, jDay) {
|
||||
const jy = jYear - 979;
|
||||
const jm = jMonth - 1;
|
||||
const jd = jDay - 1;
|
||||
|
||||
let jDayNo =
|
||||
365 * jy + Math.floor(jy / 33) * 8 + Math.floor(((jy % 33) + 3) / 4);
|
||||
|
||||
const jalaliMonthDays = [31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29];
|
||||
for (let i = 0; i < jm; i += 1) {
|
||||
jDayNo += jalaliMonthDays[i];
|
||||
}
|
||||
jDayNo += jd;
|
||||
|
||||
let gDayNo = jDayNo + 79;
|
||||
|
||||
let gy = 1600 + 400 * Math.floor(gDayNo / 146097);
|
||||
gDayNo %= 146097;
|
||||
|
||||
let leap = true;
|
||||
if (gDayNo >= 36525) {
|
||||
gDayNo -= 1;
|
||||
gy += 100 * Math.floor(gDayNo / 36524);
|
||||
gDayNo %= 36524;
|
||||
if (gDayNo >= 365) gDayNo += 1;
|
||||
else leap = false;
|
||||
}
|
||||
|
||||
gy += 4 * Math.floor(gDayNo / 1461);
|
||||
gDayNo %= 1461;
|
||||
|
||||
if (gDayNo >= 366) {
|
||||
leap = false;
|
||||
gDayNo -= 1;
|
||||
gy += Math.floor(gDayNo / 365);
|
||||
gDayNo %= 365;
|
||||
}
|
||||
|
||||
const gregorianMonthDays = [
|
||||
31,
|
||||
leap ? 29 : 28,
|
||||
31,
|
||||
30,
|
||||
31,
|
||||
30,
|
||||
31,
|
||||
31,
|
||||
30,
|
||||
31,
|
||||
30,
|
||||
31,
|
||||
];
|
||||
|
||||
let gm = 0;
|
||||
for (let i = 0; i < 12; i += 1) {
|
||||
if (gDayNo < gregorianMonthDays[i]) {
|
||||
gm = i + 1;
|
||||
break;
|
||||
}
|
||||
gDayNo -= gregorianMonthDays[i];
|
||||
}
|
||||
|
||||
const gd = gDayNo + 1;
|
||||
const mm = String(gm).padStart(2, "0");
|
||||
const dd = String(gd).padStart(2, "0");
|
||||
const result = `${gy}-${mm}-${dd}`;
|
||||
|
||||
return isNaN(new Date(result).getTime()) ? null : result;
|
||||
}
|
||||
|
||||
async function waitForRateLimit() {
|
||||
const minDelayMs = Math.ceil(60000 / config.rateLimitPerMinute);
|
||||
const elapsed = Date.now() - lastRequestAt;
|
||||
@@ -580,6 +832,9 @@ function validateConfig() {
|
||||
if (!config.carBodyToken) {
|
||||
throw new Error("TEJARAT_CAR_BODY_TOKEN or TEJARAT_TOKEN is required");
|
||||
}
|
||||
if (!config.personToken) {
|
||||
throw new Error("TEJARAT_PERSON_TOKEN or TEJARAT_TOKEN is required");
|
||||
}
|
||||
}
|
||||
|
||||
function loadEnvFiles(filePath) {
|
||||
|
||||
@@ -3973,6 +3973,7 @@ export class ClaimRequestManagementService {
|
||||
blameRequestNo: blameRequest.requestNo,
|
||||
status: ClaimCaseStatus.SELECTING_OUTER_PARTS,
|
||||
claimStatus: ClaimStatus.PENDING,
|
||||
inquiries: (blameRequest as any).inquiries ?? {},
|
||||
workflow: {
|
||||
currentStep: ClaimWorkflowStep.SELECT_OUTER_PARTS,
|
||||
nextStep: ClaimWorkflowStep.SELECT_OTHER_PARTS,
|
||||
@@ -4108,6 +4109,7 @@ export class ClaimRequestManagementService {
|
||||
blameRequestNo: blameRequest.requestNo,
|
||||
status: ClaimCaseStatus.SELECTING_OUTER_PARTS,
|
||||
claimStatus: ClaimStatus.PENDING,
|
||||
inquiries: (blameRequest as any).inquiries ?? {},
|
||||
workflow: {
|
||||
currentStep: ClaimWorkflowStep.SELECT_OUTER_PARTS,
|
||||
nextStep: ClaimWorkflowStep.SELECT_OTHER_PARTS,
|
||||
@@ -4258,6 +4260,7 @@ export class ClaimRequestManagementService {
|
||||
blameRequestNo: blameRequest.requestNo,
|
||||
status: ClaimCaseStatus.SELECTING_OUTER_PARTS,
|
||||
claimStatus: ClaimStatus.PENDING,
|
||||
inquiries: (blameRequest as any).inquiries ?? {},
|
||||
workflow: {
|
||||
currentStep: ClaimWorkflowStep.SELECT_OUTER_PARTS,
|
||||
nextStep: ClaimWorkflowStep.SELECT_OTHER_PARTS,
|
||||
|
||||
@@ -3,7 +3,10 @@ import { HydratedDocument, Schema as MongooseSchema, Types } from "mongoose";
|
||||
import { ClaimRequiredDocumentType } from "src/Types&Enums/claim-request-management/required-document-type.enum";
|
||||
import { ClaimStatus } from "src/Types&Enums/claim-request-management/claimStatus.enum";
|
||||
import { ClaimCaseStatus } from "src/Types&Enums/claim-request-management/claim-case-status.enum";
|
||||
import { HistoryEvent, HistoryEventSchema } from "src/request-management/entities/schema/historyEvent.type";
|
||||
import {
|
||||
HistoryEvent,
|
||||
HistoryEventSchema,
|
||||
} from "src/request-management/entities/schema/historyEvent.type";
|
||||
import {
|
||||
ClaimDamageSelection,
|
||||
ClaimDamageSelectionSchema,
|
||||
@@ -25,8 +28,15 @@ import {
|
||||
ClaimCaseSnapshot,
|
||||
ClaimCaseSnapshotSchema,
|
||||
} from "./claim-case.snapshot.schema";
|
||||
import { ClaimWorkflow, ClaimWorkflowSchema } from "./claim-case.workflow.schema";
|
||||
import {
|
||||
ClaimWorkflow,
|
||||
ClaimWorkflowSchema,
|
||||
} from "./claim-case.workflow.schema";
|
||||
import { UserClaimRating } from "./claim-request-management.schema";
|
||||
import {
|
||||
CaseInquiries,
|
||||
CaseInquiriesSchema,
|
||||
} from "src/common/schema/case-inquiries.schema";
|
||||
|
||||
@Schema({ _id: false })
|
||||
export class RequiredDocumentRef {
|
||||
@@ -45,7 +55,8 @@ export class RequiredDocumentRef {
|
||||
@Prop({ type: Date })
|
||||
uploadedAt?: Date;
|
||||
}
|
||||
export const RequiredDocumentRefSchema = SchemaFactory.createForClass(RequiredDocumentRef);
|
||||
export const RequiredDocumentRefSchema =
|
||||
SchemaFactory.createForClass(RequiredDocumentRef);
|
||||
|
||||
@Schema({
|
||||
collection: "claimCases",
|
||||
@@ -67,7 +78,12 @@ export class ClaimCase {
|
||||
/**
|
||||
* Overall case status (user flow + expert flow progression)
|
||||
*/
|
||||
@Prop({ required: true, type: String, enum: ClaimCaseStatus, default: ClaimCaseStatus.CREATED })
|
||||
@Prop({
|
||||
required: true,
|
||||
type: String,
|
||||
enum: ClaimCaseStatus,
|
||||
default: ClaimCaseStatus.CREATED,
|
||||
})
|
||||
status: ClaimCaseStatus;
|
||||
|
||||
/**
|
||||
@@ -119,6 +135,9 @@ export class ClaimCase {
|
||||
@Prop({ type: ClaimEvaluationSchema, default: () => ({}) })
|
||||
evaluation?: ClaimEvaluation;
|
||||
|
||||
@Prop({ type: CaseInquiriesSchema, default: () => ({}) })
|
||||
inquiries?: CaseInquiries;
|
||||
|
||||
/**
|
||||
* Optional “read-optimized” copy of fields from blame/request side.
|
||||
* Source of truth remains `blameRequestId`.
|
||||
@@ -160,4 +179,3 @@ export class ClaimCase {
|
||||
export type ClaimCaseDocument = HydratedDocument<ClaimCase>;
|
||||
export const ClaimCaseSchema = SchemaFactory.createForClass(ClaimCase);
|
||||
ClaimCaseSchema.index({ status: 1, "workflow.locked": 1 });
|
||||
|
||||
|
||||
34
src/common/schema/case-inquiries.schema.ts
Normal file
34
src/common/schema/case-inquiries.schema.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
|
||||
import { Schema as MongooseSchema } from "mongoose";
|
||||
|
||||
@Schema({ _id: false })
|
||||
export class InquiryStatus {
|
||||
@Prop({ type: Boolean, default: false })
|
||||
has?: boolean;
|
||||
|
||||
@Prop({ type: MongooseSchema.Types.Mixed, default: () => ({}) })
|
||||
data?: any;
|
||||
|
||||
@Prop({ type: MongooseSchema.Types.Mixed })
|
||||
error?: any;
|
||||
|
||||
@Prop({ type: Date })
|
||||
updatedAt?: Date;
|
||||
}
|
||||
export const InquiryStatusSchema = SchemaFactory.createForClass(InquiryStatus);
|
||||
|
||||
@Schema({ _id: false, strict: false })
|
||||
export class CaseInquiries {
|
||||
@Prop({ type: InquiryStatusSchema })
|
||||
thirdParty?: InquiryStatus;
|
||||
|
||||
@Prop({ type: InquiryStatusSchema })
|
||||
carBody?: InquiryStatus;
|
||||
|
||||
@Prop({ type: InquiryStatusSchema })
|
||||
person?: InquiryStatus;
|
||||
|
||||
@Prop({ type: InquiryStatusSchema })
|
||||
drivingLicence?: InquiryStatus;
|
||||
}
|
||||
export const CaseInquiriesSchema = SchemaFactory.createForClass(CaseInquiries);
|
||||
@@ -8,6 +8,10 @@ import { HistoryEvent, HistoryEventSchema } from "./historyEvent.type";
|
||||
import { CaseStatus } from "src/Types&Enums/blame-request-management/caseStatus.enum";
|
||||
import { BlameStatus } from "src/Types&Enums/blame-request-management/blameStatus.enum";
|
||||
import { CreationMethod, FilledBy } from "./request-management.schema";
|
||||
import {
|
||||
CaseInquiries,
|
||||
CaseInquiriesSchema,
|
||||
} from "src/common/schema/case-inquiries.schema";
|
||||
|
||||
/** CAR_BODY only: mocked insurance inquiry result (legacy top-level; prefer parties[].insurance.carBodyInsurance) */
|
||||
@Schema({ _id: false })
|
||||
@@ -18,7 +22,9 @@ export class CarBodyInsuranceDetail {
|
||||
@Prop() insurerCompany?: string;
|
||||
@Prop({ type: [String] }) coverages?: string[];
|
||||
}
|
||||
export const CarBodyInsuranceDetailSchema = SchemaFactory.createForClass(CarBodyInsuranceDetail);
|
||||
export const CarBodyInsuranceDetailSchema = SchemaFactory.createForClass(
|
||||
CarBodyInsuranceDetail,
|
||||
);
|
||||
|
||||
@Schema({ _id: false })
|
||||
export class BlameRequestSnapshot {
|
||||
@@ -28,7 +34,6 @@ export class BlameRequestSnapshot {
|
||||
export const BlameRequestSnapshotSchema =
|
||||
SchemaFactory.createForClass(BlameRequestSnapshot);
|
||||
|
||||
|
||||
@Schema({ collection: "blameCases", timestamps: true, id: true })
|
||||
export class BlameRequest {
|
||||
@Prop({ required: true, unique: true, index: true, trim: true })
|
||||
@@ -44,7 +49,12 @@ export class BlameRequest {
|
||||
@Prop({ required: true, type: String, enum: BlameRequestType })
|
||||
type: BlameRequestType;
|
||||
|
||||
@Prop({ required: true, type: String, enum: CaseStatus, default: CaseStatus.OPEN })
|
||||
@Prop({
|
||||
required: true,
|
||||
type: String,
|
||||
enum: CaseStatus,
|
||||
default: CaseStatus.OPEN,
|
||||
})
|
||||
status: CaseStatus;
|
||||
|
||||
@Prop({ type: String, enum: BlameStatus, default: BlameStatus.UNKNOWN })
|
||||
@@ -56,6 +66,9 @@ export class BlameRequest {
|
||||
@Prop({ type: [PartySchema], default: [] })
|
||||
parties: Party[];
|
||||
|
||||
@Prop({ type: CaseInquiriesSchema, default: () => ({}) })
|
||||
inquiries?: CaseInquiries;
|
||||
|
||||
@Prop({ type: ExpertSectionSchema })
|
||||
expert?: ExpertSection;
|
||||
|
||||
|
||||
@@ -123,6 +123,64 @@ export class RequestManagementService {
|
||||
: -1;
|
||||
}
|
||||
|
||||
private normalizeInquiryError(err: any): any {
|
||||
if (!err) return undefined;
|
||||
return {
|
||||
message: err?.message || String(err),
|
||||
status: err?.response?.status,
|
||||
data: err?.response?.data,
|
||||
};
|
||||
}
|
||||
|
||||
private recordCaseInquiryStatus(
|
||||
req: any,
|
||||
key: "thirdParty" | "carBody" | "person" | "drivingLicence" | string,
|
||||
has: boolean,
|
||||
data: any = {},
|
||||
error?: any,
|
||||
): void {
|
||||
const existingInquiries =
|
||||
req.inquiries?.toObject?.() ??
|
||||
(req.inquiries && typeof req.inquiries === "object" ? req.inquiries : {});
|
||||
req.inquiries = {
|
||||
...existingInquiries,
|
||||
[key]: {
|
||||
has,
|
||||
data: data ?? {},
|
||||
...(error ? { error: this.normalizeInquiryError(error) } : {}),
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private recordPartyCaseInquiryStatus(
|
||||
req: any,
|
||||
key: "thirdParty" | "carBody" | "person" | "drivingLicence" | string,
|
||||
role: PartyRole,
|
||||
has: boolean,
|
||||
data: any = {},
|
||||
error?: any,
|
||||
): void {
|
||||
const existingData =
|
||||
req?.inquiries?.[key]?.data?.toObject?.() ?? req?.inquiries?.[key]?.data;
|
||||
const dataByRole =
|
||||
existingData &&
|
||||
typeof existingData === "object" &&
|
||||
!Array.isArray(existingData)
|
||||
? existingData
|
||||
: {};
|
||||
this.recordCaseInquiryStatus(
|
||||
req,
|
||||
key,
|
||||
has,
|
||||
{
|
||||
...dataByRole,
|
||||
[role]: data ?? {},
|
||||
},
|
||||
error,
|
||||
);
|
||||
}
|
||||
|
||||
private assertPartyOwner(party: any, user: any, errMsg: string) {
|
||||
const partyUserId = party?.person?.userId
|
||||
? String(party.person.userId)
|
||||
@@ -967,6 +1025,11 @@ export class RequestManagementService {
|
||||
this.logger.log(
|
||||
`[TEJARAT] block inquiry mapped for request=${req._id}: ${JSON.stringify(inquiryMapped)}`,
|
||||
);
|
||||
this.recordPartyCaseInquiryStatus(req, "thirdParty", role, true, {
|
||||
source: "TEJARAT_BLOCK_INQUIRY",
|
||||
raw: inquiryRaw,
|
||||
mapped: inquiryMapped,
|
||||
});
|
||||
} catch (err: any) {
|
||||
this.logger.error(
|
||||
`[TEJARAT] block inquiry failed for request=${req._id}: ${err?.message || err}`,
|
||||
@@ -978,6 +1041,15 @@ export class RequestManagementService {
|
||||
}, data=${JSON.stringify(err.response.data)}`,
|
||||
);
|
||||
}
|
||||
this.recordPartyCaseInquiryStatus(
|
||||
req,
|
||||
"thirdParty",
|
||||
role,
|
||||
false,
|
||||
{},
|
||||
err,
|
||||
);
|
||||
await (req as any).save();
|
||||
throw new HttpException("Inquiry failed", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
@@ -987,6 +1059,12 @@ export class RequestManagementService {
|
||||
inquiryMapped.Error,
|
||||
)}`,
|
||||
);
|
||||
this.recordPartyCaseInquiryStatus(req, "thirdParty", role, false, {
|
||||
source: "TEJARAT_BLOCK_INQUIRY",
|
||||
raw: inquiryRaw,
|
||||
mapped: inquiryMapped,
|
||||
});
|
||||
await (req as any).save();
|
||||
throw new HttpException(
|
||||
inquiryMapped.Error.Message || "Inquiry returned error",
|
||||
HttpStatus.BAD_REQUEST,
|
||||
@@ -1020,6 +1098,13 @@ export class RequestManagementService {
|
||||
personalNationalCode,
|
||||
personalBirthDate,
|
||||
);
|
||||
this.recordPartyCaseInquiryStatus(
|
||||
req,
|
||||
"person",
|
||||
role,
|
||||
true,
|
||||
personalInquiry,
|
||||
);
|
||||
this.logger.log(
|
||||
`[SANDHUB] personal inquiry success request=${req._id} nationalCode=${personalNationalCode}: ${JSON.stringify(
|
||||
personalInquiry,
|
||||
@@ -1029,6 +1114,8 @@ export class RequestManagementService {
|
||||
this.logger.error(
|
||||
`[SANDHUB] personal inquiry failed request=${req._id} nationalCode=${personalNationalCode}: ${err?.message || err}`,
|
||||
);
|
||||
this.recordPartyCaseInquiryStatus(req, "person", role, false, {}, err);
|
||||
await (req as any).save();
|
||||
throw new HttpException(
|
||||
"Personal identity inquiry failed",
|
||||
HttpStatus.BAD_REQUEST,
|
||||
@@ -1120,10 +1207,28 @@ export class RequestManagementService {
|
||||
|
||||
// CAR BODY EXTERNAL API INQUIRY
|
||||
if (req.type === BlameRequestType.CAR_BODY && role === PartyRole.FIRST) {
|
||||
const carBodyInfo = await this.sandHubService.getTejaratCarBodyInquiry({
|
||||
let carBodyInfo: any;
|
||||
try {
|
||||
carBodyInfo = await this.sandHubService.getTejaratCarBodyInquiry({
|
||||
nationalCodeOfInsurer: body.nationalCodeOfInsurer,
|
||||
plate: body.plate,
|
||||
});
|
||||
this.recordPartyCaseInquiryStatus(req, "carBody", role, true, {
|
||||
source: "TEJARAT_CAR_BODY_INQUIRY",
|
||||
raw: carBodyInfo.raw,
|
||||
mapped: carBodyInfo.mapped,
|
||||
});
|
||||
} catch (err: any) {
|
||||
this.logger.error(
|
||||
`[TEJARAT] car body inquiry failed for request=${req._id}: ${err?.message || err}`,
|
||||
);
|
||||
this.recordPartyCaseInquiryStatus(req, "carBody", role, false, {}, err);
|
||||
await (req as any).save();
|
||||
throw new HttpException(
|
||||
"Car body inquiry failed",
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
|
||||
// Raw + mapped stored under vehicle (same pattern as third-party inquiry)
|
||||
party.vehicle.inquiry = {
|
||||
@@ -4829,8 +4934,24 @@ export class RequestManagementService {
|
||||
nationalCodeOfInsurer: firstPartyPlate.nationalCodeOfInsurer,
|
||||
});
|
||||
sandHubReport = sandHubResponse["_doc"] || sandHubResponse;
|
||||
this.recordPartyCaseInquiryStatus(
|
||||
req,
|
||||
"thirdParty",
|
||||
PartyRole.FIRST,
|
||||
true,
|
||||
sandHubReport,
|
||||
);
|
||||
} catch (e) {
|
||||
this.logger.error("SandHub error in expertCompleteCarBodyFormV2:", e);
|
||||
this.recordPartyCaseInquiryStatus(
|
||||
req,
|
||||
"thirdParty",
|
||||
PartyRole.FIRST,
|
||||
false,
|
||||
{},
|
||||
e,
|
||||
);
|
||||
await (req as any).save();
|
||||
throw new InternalServerErrorException(
|
||||
"Failed to process plate information.",
|
||||
);
|
||||
@@ -4864,6 +4985,13 @@ export class RequestManagementService {
|
||||
firstPartyPlate.plate,
|
||||
firstPartyPlate.nationalCodeOfInsurer,
|
||||
);
|
||||
this.recordPartyCaseInquiryStatus(
|
||||
req,
|
||||
"carBody",
|
||||
PartyRole.FIRST,
|
||||
true,
|
||||
carBodyInfo,
|
||||
);
|
||||
|
||||
const firstParty: any = {
|
||||
role: PartyRole.FIRST,
|
||||
@@ -5010,11 +5138,32 @@ export class RequestManagementService {
|
||||
desc: string,
|
||||
role: PartyRole,
|
||||
) => {
|
||||
const sandHubResponse = await this.sandHubService.getSandHubResponse({
|
||||
let sandHubResponse: any;
|
||||
try {
|
||||
sandHubResponse = await this.sandHubService.getSandHubResponse({
|
||||
plate: plateDto.plate,
|
||||
nationalCodeOfInsurer: plateDto.nationalCodeOfInsurer,
|
||||
});
|
||||
} catch (err: any) {
|
||||
this.recordPartyCaseInquiryStatus(
|
||||
req,
|
||||
"thirdParty",
|
||||
role,
|
||||
false,
|
||||
{},
|
||||
err,
|
||||
);
|
||||
await (req as any).save();
|
||||
throw err;
|
||||
}
|
||||
const sandHubReport = (sandHubResponse["_doc"] || sandHubResponse) as any;
|
||||
this.recordPartyCaseInquiryStatus(
|
||||
req,
|
||||
"thirdParty",
|
||||
role,
|
||||
true,
|
||||
sandHubReport,
|
||||
);
|
||||
const clientName =
|
||||
sandHubReport?.CompanyName || sandHubReport?.LastCompanyName;
|
||||
const companyCode = sandHubReport?.CompanyCode;
|
||||
|
||||
Reference in New Issue
Block a user