forked from Yara724/api
refresh blame inquirys script
This commit is contained in:
606
scripts/refresh-blame-inquiries.js
Normal file
606
scripts/refresh-blame-inquiries.js
Normal file
@@ -0,0 +1,606 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/*
|
||||||
|
* One-time script to replace mocked blameCases party vehicle inquiry data with
|
||||||
|
* Tejarat block inquiry responses.
|
||||||
|
*
|
||||||
|
* Defaults to DRY_RUN=true. Set DRY_RUN=false to write changes.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
const mongoose = require("mongoose");
|
||||||
|
|
||||||
|
const loadedEnvFiles = loadEnvFiles(process.env.ENV_FILE || ".env");
|
||||||
|
|
||||||
|
const LETTER_TO_NUMBER = {
|
||||||
|
"الف": 1,
|
||||||
|
"ب": 2,
|
||||||
|
"ت": 3,
|
||||||
|
"ج": 4,
|
||||||
|
"د": 5,
|
||||||
|
"س": 6,
|
||||||
|
"ص": 7,
|
||||||
|
"ط": 8,
|
||||||
|
"ع": 9,
|
||||||
|
"ق": 10,
|
||||||
|
"ل": 11,
|
||||||
|
"م": 12,
|
||||||
|
"ن": 13,
|
||||||
|
"و": 14,
|
||||||
|
"ه": 15,
|
||||||
|
"ی": 16,
|
||||||
|
"ر": 17,
|
||||||
|
"ک": 18,
|
||||||
|
"ژ": 19,
|
||||||
|
"پ": 20,
|
||||||
|
"ظ": 24,
|
||||||
|
"ض": 25,
|
||||||
|
"ز": 41,
|
||||||
|
"ش": 42,
|
||||||
|
"گ": 43,
|
||||||
|
"ث": 44,
|
||||||
|
D: 45,
|
||||||
|
S: 46,
|
||||||
|
"ح": 47,
|
||||||
|
"ف": 48,
|
||||||
|
};
|
||||||
|
|
||||||
|
const NUMBER_TO_LETTER = Object.fromEntries(
|
||||||
|
Object.entries(LETTER_TO_NUMBER).map(([letter, number]) => [String(number), letter]),
|
||||||
|
);
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
mongoUri: requiredEnv("MONGO_URL", "MONGODB_URI", "DATABASE_URL"),
|
||||||
|
collectionName: process.env.BLAME_COLLECTION || "blameCases",
|
||||||
|
mongoDbName: process.env.MONGO_DB_NAME || "",
|
||||||
|
thirdPartyUrl:
|
||||||
|
process.env.TEJARAT_THIRD_PARTY_URL ||
|
||||||
|
"http://82.99.202.245:3027/block-inquiry-tejarat",
|
||||||
|
carBodyUrl:
|
||||||
|
process.env.TEJARAT_CAR_BODY_URL ||
|
||||||
|
"http://82.99.202.245:3027/block-inquiry-tejarat/badane",
|
||||||
|
thirdPartyToken:
|
||||||
|
process.env.TEJARAT_THIRD_PARTY_TOKEN || process.env.TEJARAT_TOKEN || "",
|
||||||
|
carBodyToken:
|
||||||
|
process.env.TEJARAT_CAR_BODY_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),
|
||||||
|
retryDelayMs: Number(process.env.RETRY_DELAY_MS || 2000),
|
||||||
|
dryRun: String(process.env.DRY_RUN ?? "true").toLowerCase() !== "false",
|
||||||
|
limit: process.env.LIMIT ? Number(process.env.LIMIT) : 0,
|
||||||
|
publicId: process.env.PUBLIC_ID || "",
|
||||||
|
};
|
||||||
|
|
||||||
|
let lastRequestAt = 0;
|
||||||
|
|
||||||
|
main().catch(async (error) => {
|
||||||
|
console.error("[fatal]", error && error.stack ? error.stack : error);
|
||||||
|
await mongoose.disconnect().catch(() => undefined);
|
||||||
|
process.exitCode = 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
validateConfig();
|
||||||
|
|
||||||
|
console.log("script runned successfully");
|
||||||
|
console.log(
|
||||||
|
"[config] envFiles=" +
|
||||||
|
(loadedEnvFiles.length ? loadedEnvFiles.join(",") : "none") +
|
||||||
|
", dbName=" +
|
||||||
|
(config.mongoDbName || "from-url-or-driver-default") +
|
||||||
|
", collection=" +
|
||||||
|
config.collectionName +
|
||||||
|
", dryRun=" +
|
||||||
|
config.dryRun +
|
||||||
|
", rateLimitPerMinute=" +
|
||||||
|
config.rateLimitPerMinute +
|
||||||
|
", retryEnabled=" +
|
||||||
|
config.retryEnabled +
|
||||||
|
", retryCount=" +
|
||||||
|
config.retryCount,
|
||||||
|
);
|
||||||
|
|
||||||
|
await mongoose.connect(config.mongoUri, {
|
||||||
|
autoIndex: false,
|
||||||
|
dbName: config.mongoDbName || undefined,
|
||||||
|
});
|
||||||
|
const collection = mongoose.connection.collection(config.collectionName);
|
||||||
|
|
||||||
|
const query = {
|
||||||
|
type: { $in: ["THIRD_PARTY", "CAR_BODY"] },
|
||||||
|
};
|
||||||
|
if (config.publicId) query.publicId = config.publicId;
|
||||||
|
|
||||||
|
const totalDocs = await collection.countDocuments(query);
|
||||||
|
console.log(`total docs that we have to edit: ${totalDocs}`);
|
||||||
|
|
||||||
|
const cursor = collection
|
||||||
|
.find(query, {
|
||||||
|
projection: {
|
||||||
|
publicId: 1,
|
||||||
|
requestNo: 1,
|
||||||
|
type: 1,
|
||||||
|
parties: 1,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.sort({ createdAt: 1, _id: 1 });
|
||||||
|
|
||||||
|
if (config.limit > 0) cursor.limit(config.limit);
|
||||||
|
|
||||||
|
const summary = {
|
||||||
|
docsSeen: 0,
|
||||||
|
docsChanged: 0,
|
||||||
|
partiesInquired: 0,
|
||||||
|
partiesSkipped: 0,
|
||||||
|
partiesFailed: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
for await (const doc of cursor) {
|
||||||
|
summary.docsSeen += 1;
|
||||||
|
const label = doc.publicId || doc.requestNo || String(doc._id);
|
||||||
|
const requestId = String(doc._id);
|
||||||
|
console.log(`currently inquiry for doc with ${label} and requestId ${requestId}`);
|
||||||
|
|
||||||
|
const parties = Array.isArray(doc.parties) ? clone(doc.parties) : [];
|
||||||
|
let docChanged = false;
|
||||||
|
|
||||||
|
for (let index = 0; index < parties.length; index += 1) {
|
||||||
|
const party = parties[index];
|
||||||
|
const partyLabel = `${label} parties[${index}] role=${party && party.role ? party.role : "-"}`;
|
||||||
|
|
||||||
|
const input = buildInquiryInput(doc, party);
|
||||||
|
if (!input.ok) {
|
||||||
|
summary.partiesSkipped += 1;
|
||||||
|
console.warn(`[skip] ${partyLabel}: ${input.reason}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[request] ${partyLabel} body=${JSON.stringify(input.body)}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const inquiryResponse = await inquiryWithRetry(doc.type, input.body);
|
||||||
|
const successful = isInquirySuccessful(doc.type, inquiryResponse);
|
||||||
|
console.log(
|
||||||
|
`[response] ${partyLabel} successful=${successful} body=${JSON.stringify(inquiryResponse)}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!successful) {
|
||||||
|
summary.partiesFailed += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
parties[index] = applyInquiryToParty(doc.type, party, inquiryResponse, input.plate);
|
||||||
|
summary.partiesInquired += 1;
|
||||||
|
docChanged = true;
|
||||||
|
} catch (error) {
|
||||||
|
summary.partiesFailed += 1;
|
||||||
|
console.error(`[error] ${partyLabel}: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!docChanged) {
|
||||||
|
console.log(`[doc] ${label} no changes`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.dryRun) {
|
||||||
|
console.log(`[dry-run] ${label} would update parties array`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await collection.updateOne(
|
||||||
|
{ _id: doc._id },
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
parties,
|
||||||
|
updatedAt: new Date(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
summary.docsChanged += result.modifiedCount;
|
||||||
|
console.log(`[update] ${label} matched=${result.matchedCount} modified=${result.modifiedCount}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await mongoose.disconnect();
|
||||||
|
console.log(`[done] ${JSON.stringify(summary)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildInquiryInput(doc, party) {
|
||||||
|
if (!party || typeof party !== "object") return { ok: false, reason: "party is empty" };
|
||||||
|
if (!party.vehicle || typeof party.vehicle !== "object") {
|
||||||
|
return { ok: false, reason: "party.vehicle is missing" };
|
||||||
|
}
|
||||||
|
|
||||||
|
const plate = extractPlate(party);
|
||||||
|
const nationalCode =
|
||||||
|
cleanString(party.person && party.person.nationalCodeOfInsurer) ||
|
||||||
|
cleanString(party.person && party.person.nationalCodeOfDriver);
|
||||||
|
|
||||||
|
if (!plate) return { ok: false, reason: "Plk1/Plk2/Plk3/PlkSrl not found" };
|
||||||
|
if (!nationalCode) return { ok: false, reason: "nationalCodeOfInsurer/nationalCodeOfDriver missing" };
|
||||||
|
|
||||||
|
const serialLetter = NUMBER_TO_LETTER[String(plate.Plk2)] || cleanString(plate.Plk2);
|
||||||
|
if (!serialLetter) return { ok: false, reason: `no Persian letter mapping for Plk2=${plate.Plk2}` };
|
||||||
|
|
||||||
|
if (doc.type === "CAR_BODY") {
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
plate,
|
||||||
|
body: {
|
||||||
|
part1: toNumber(plate.Plk1),
|
||||||
|
part2: serialLetter,
|
||||||
|
part3: toNumber(plate.Plk3),
|
||||||
|
part4: toNumber(plate.PlkSrl),
|
||||||
|
nationalCode,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doc.type === "THIRD_PARTY") {
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
plate,
|
||||||
|
body: {
|
||||||
|
leftTwoDigits: String(plate.Plk1),
|
||||||
|
serialLetter,
|
||||||
|
threeDigits: String(plate.Plk3),
|
||||||
|
rightTwoDigits: String(plate.PlkSrl),
|
||||||
|
nationalCode,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ok: false, reason: `unsupported type=${doc.type}` };
|
||||||
|
}
|
||||||
|
|
||||||
|
function parsePlateId(plateId) {
|
||||||
|
const value = cleanString(plateId);
|
||||||
|
if (!value) return null;
|
||||||
|
|
||||||
|
const parts = value.split("-").map((part) => normalizePlateNumber(part));
|
||||||
|
if (parts.length !== 4) return null;
|
||||||
|
|
||||||
|
const thirdPartIsLetter = LETTER_TO_NUMBER[parts[2]] !== undefined;
|
||||||
|
const fourthPartIsLetter = LETTER_TO_NUMBER[parts[3]] !== undefined;
|
||||||
|
|
||||||
|
if (thirdPartIsLetter) {
|
||||||
|
return {
|
||||||
|
Plk1: parts[1],
|
||||||
|
Plk2: String(LETTER_TO_NUMBER[parts[2]]),
|
||||||
|
Plk3: parts[3],
|
||||||
|
PlkSrl: parts[0],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fourthPartIsLetter) {
|
||||||
|
return {
|
||||||
|
Plk1: parts[2],
|
||||||
|
Plk2: String(LETTER_TO_NUMBER[parts[3]]),
|
||||||
|
Plk3: parts[1],
|
||||||
|
PlkSrl: parts[0],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractPlate(party) {
|
||||||
|
const plateFromPlateId = parsePlateId(party.vehicle && party.vehicle.plateId);
|
||||||
|
if (plateFromPlateId) return plateFromPlateId;
|
||||||
|
|
||||||
|
const candidates = [
|
||||||
|
party.vehicle && party.vehicle.inquiry && party.vehicle.inquiry.mapped,
|
||||||
|
party.vehicle && party.vehicle.inquiry && party.vehicle.inquiry.raw,
|
||||||
|
party.vehicle && party.vehicle.inquiry,
|
||||||
|
party.vehicle,
|
||||||
|
].filter(Boolean);
|
||||||
|
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
const Plk1 = firstPresent(candidate.Plk1, candidate.platePartOne);
|
||||||
|
const Plk2 = firstPresent(candidate.Plk2, candidate.plateLetterid, candidate.plateLetterId);
|
||||||
|
const Plk3 = firstPresent(candidate.Plk3, candidate.platePartThree);
|
||||||
|
const PlkSrl = firstPresent(candidate.PlkSrl, candidate.plkSrl, candidate.plateSerialNumber);
|
||||||
|
|
||||||
|
if (
|
||||||
|
Plk1 !== undefined &&
|
||||||
|
Plk2 !== undefined &&
|
||||||
|
Plk3 !== undefined &&
|
||||||
|
PlkSrl !== undefined
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
Plk1: normalizePlateNumber(Plk1),
|
||||||
|
Plk2: normalizePlateNumber(Plk2),
|
||||||
|
Plk3: normalizePlateNumber(Plk3),
|
||||||
|
PlkSrl: normalizePlateNumber(PlkSrl),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function inquiryWithRetry(type, body) {
|
||||||
|
const url = type === "CAR_BODY" ? config.carBodyUrl : config.thirdPartyUrl;
|
||||||
|
const token = type === "CAR_BODY" ? config.carBodyToken : config.thirdPartyToken;
|
||||||
|
let lastError;
|
||||||
|
|
||||||
|
const maxAttempts = config.retryEnabled ? config.retryCount : 1;
|
||||||
|
|
||||||
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
||||||
|
await waitForRateLimit();
|
||||||
|
try {
|
||||||
|
console.log("[http] " + type + " attempt=" + attempt + "/" + maxAttempts);
|
||||||
|
return await postJson(url, token, body);
|
||||||
|
} catch (error) {
|
||||||
|
lastError = error;
|
||||||
|
console.error(`[retry] ${type} attempt=${attempt} failed: ${error.message}`);
|
||||||
|
if (attempt < maxAttempts) {
|
||||||
|
await sleep(config.retryDelayMs * attempt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw lastError;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function postJson(url, token, body) {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
accept: "application/json",
|
||||||
|
authorization: `Bearer ${token}`,
|
||||||
|
"content-type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
});
|
||||||
|
|
||||||
|
const text = await response.text();
|
||||||
|
let data;
|
||||||
|
try {
|
||||||
|
data = text ? JSON.parse(text) : null;
|
||||||
|
} catch {
|
||||||
|
data = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP ${response.status}: ${JSON.stringify(data)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isInquirySuccessful(type, response) {
|
||||||
|
if (type === "CAR_BODY") return response && response.isSuccess === true && response.data;
|
||||||
|
return response && response.resultStatus === true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyInquiryToParty(type, party, response, originalPlate) {
|
||||||
|
const next = clone(party);
|
||||||
|
if (!next.vehicle) next.vehicle = {};
|
||||||
|
if (!next.insurance) next.insurance = {};
|
||||||
|
|
||||||
|
const mapped = type === "CAR_BODY"
|
||||||
|
? normalizeCarBodyResponse(response, originalPlate)
|
||||||
|
: normalizeThirdPartyResponse(response, originalPlate);
|
||||||
|
const plateId = buildPlateId(originalPlate);
|
||||||
|
|
||||||
|
next.vehicle.plateId = plateId || next.vehicle.plateId;
|
||||||
|
|
||||||
|
next.vehicle.inquiry = {
|
||||||
|
source: type === "CAR_BODY" ? "TEJARAT_CAR_BODY_BLOCK_INQUIRY" : "TEJARAT_BLOCK_INQUIRY",
|
||||||
|
raw: type === "CAR_BODY" ? normalizeCarBodyRaw(response, originalPlate) : response,
|
||||||
|
mapped,
|
||||||
|
refreshedAt: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (type === "CAR_BODY") {
|
||||||
|
next.vehicle.inquiry.responseMeta = {
|
||||||
|
isSuccess: response.isSuccess,
|
||||||
|
statusCode: response.statusCode,
|
||||||
|
message: response.message,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === "CAR_BODY") {
|
||||||
|
next.vehicle.name = mapped.vehicleSystemTitle || next.vehicle.name;
|
||||||
|
next.vehicle.type = mapped.vehicleGroupTitle || next.vehicle.type;
|
||||||
|
next.insurance.policyNumber = mapped.printNumber || next.insurance.policyNumber;
|
||||||
|
next.insurance.company = mapped.companyName || next.insurance.company;
|
||||||
|
next.insurance.startDate = mapped.beginDate || next.insurance.startDate;
|
||||||
|
next.insurance.endDate = mapped.endDate || next.insurance.endDate;
|
||||||
|
next.insurance.carBodyInsurance = {
|
||||||
|
policyNumber: mapped.printNumber,
|
||||||
|
startDate: mapped.beginDate,
|
||||||
|
endDate: mapped.endDate,
|
||||||
|
insurerCompany: mapped.companyName,
|
||||||
|
coverages: Array.isArray(mapped.coverages) ? mapped.coverages : [],
|
||||||
|
};
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
next.vehicle.name = mapped.vehiclePersianName || mapped.MapTypNam || "اطلاعات این گزینه در استعلام موجود نیست";
|
||||||
|
next.vehicle.type = mapped.persianCarType || mapped.MapUsageName || next.vehicle.type;
|
||||||
|
next.insurance.policyNumber = mapped.insuranceNumber || next.insurance.policyNumber;
|
||||||
|
next.insurance.company = mapped.companyPersianName || next.insurance.company;
|
||||||
|
next.insurance.financialCeiling = mapped.financeCoverage || next.insurance.financialCeiling;
|
||||||
|
next.insurance.startDate = mapped.persianStartDate || next.insurance.startDate;
|
||||||
|
next.insurance.endDate = mapped.persianEndDate || next.insurance.endDate;
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildPlateId(plate) {
|
||||||
|
if (!plate) return "";
|
||||||
|
const serialLetter = NUMBER_TO_LETTER[String(plate.Plk2)] || cleanString(plate.Plk2);
|
||||||
|
if (!serialLetter) return "";
|
||||||
|
return (
|
||||||
|
cleanString(plate.PlkSrl) +
|
||||||
|
"-" +
|
||||||
|
cleanString(plate.Plk1) +
|
||||||
|
"-" +
|
||||||
|
serialLetter +
|
||||||
|
"-" +
|
||||||
|
cleanString(plate.Plk3)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeThirdPartyResponse(response, originalPlate) {
|
||||||
|
return {
|
||||||
|
...response,
|
||||||
|
Plk1: toNumber(originalPlate.Plk1),
|
||||||
|
Plk2: toNumber(originalPlate.Plk2),
|
||||||
|
Plk3: toNumber(originalPlate.Plk3),
|
||||||
|
PlkSrl: toNumber(originalPlate.PlkSrl),
|
||||||
|
CompanyName: response.companyPersianName,
|
||||||
|
CompanyCode: response.companyId,
|
||||||
|
LastCompanyDocumentNumber: response.insuranceNumber,
|
||||||
|
FinancialCvrCptl: response.financeCoverage,
|
||||||
|
IssueDate: response.hIsuDte || response.persianStartDate,
|
||||||
|
SatrtDate: response.persianStartDate,
|
||||||
|
EndDate: response.persianEndDate,
|
||||||
|
MapTypNam: response.vehiclePersianName,
|
||||||
|
MapUsageName: response.MapUsageName,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeCarBodyRaw(response, originalPlate) {
|
||||||
|
const data = response.data || {};
|
||||||
|
return {
|
||||||
|
...data,
|
||||||
|
Plk1: toNumber(originalPlate.Plk1),
|
||||||
|
Plk2: toNumber(originalPlate.Plk2),
|
||||||
|
Plk3: toNumber(originalPlate.Plk3),
|
||||||
|
PlkSrl: toNumber(originalPlate.PlkSrl),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeCarBodyResponse(response, originalPlate) {
|
||||||
|
const data = response.data || {};
|
||||||
|
return {
|
||||||
|
...data,
|
||||||
|
Plk1: toNumber(originalPlate.Plk1),
|
||||||
|
Plk2: toNumber(originalPlate.Plk2),
|
||||||
|
Plk3: toNumber(originalPlate.Plk3),
|
||||||
|
PlkSrl: toNumber(originalPlate.PlkSrl),
|
||||||
|
CompanyName: data.companyName,
|
||||||
|
CompanyCode: data.companyId,
|
||||||
|
LastCompanyDocumentNumber: data.printNumber,
|
||||||
|
IssueDate: data.issueDate,
|
||||||
|
SatrtDate: data.beginDate,
|
||||||
|
EndDate: data.endDate,
|
||||||
|
MtrNum: data.motorNumber,
|
||||||
|
ShsNum: data.chassisNumber,
|
||||||
|
VinNumberField: data.vin,
|
||||||
|
MapTypNam: data.vehicleSystemTitle,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function waitForRateLimit() {
|
||||||
|
const minDelayMs = Math.ceil(60000 / config.rateLimitPerMinute);
|
||||||
|
const elapsed = Date.now() - lastRequestAt;
|
||||||
|
if (lastRequestAt > 0 && elapsed < minDelayMs) {
|
||||||
|
const waitMs = minDelayMs - elapsed;
|
||||||
|
console.log(`[rate-limit] waiting ${waitMs}ms`);
|
||||||
|
await sleep(waitMs);
|
||||||
|
}
|
||||||
|
lastRequestAt = Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateConfig() {
|
||||||
|
if (!config.mongoUri) throw new Error("MONGO_URL is required");
|
||||||
|
if (!Number.isFinite(config.rateLimitPerMinute) || config.rateLimitPerMinute <= 0) {
|
||||||
|
throw new Error("RATE_LIMIT_PER_MINUTE must be a positive number");
|
||||||
|
}
|
||||||
|
if (!Number.isFinite(config.retryCount) || config.retryCount <= 0) {
|
||||||
|
throw new Error("RETRY_COUNT must be a positive number");
|
||||||
|
}
|
||||||
|
if (!config.thirdPartyToken) {
|
||||||
|
throw new Error("TEJARAT_THIRD_PARTY_TOKEN or TEJARAT_TOKEN is required");
|
||||||
|
}
|
||||||
|
if (!config.carBodyToken) {
|
||||||
|
throw new Error("TEJARAT_CAR_BODY_TOKEN or TEJARAT_TOKEN is required");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadEnvFiles(filePath) {
|
||||||
|
const candidates = path.isAbsolute(filePath)
|
||||||
|
? [filePath]
|
||||||
|
: [
|
||||||
|
path.resolve(process.cwd(), filePath),
|
||||||
|
path.resolve(__dirname, "..", filePath),
|
||||||
|
];
|
||||||
|
|
||||||
|
const loaded = [];
|
||||||
|
for (const candidate of [...new Set(candidates)]) {
|
||||||
|
if (!fs.existsSync(candidate)) continue;
|
||||||
|
loadEnvFile(candidate);
|
||||||
|
loaded.push(candidate);
|
||||||
|
}
|
||||||
|
return loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadEnvFile(filePath) {
|
||||||
|
const resolved = path.resolve(process.cwd(), filePath);
|
||||||
|
if (!fs.existsSync(resolved)) return;
|
||||||
|
|
||||||
|
const lines = fs.readFileSync(resolved, "utf8").split(/\r?\n/);
|
||||||
|
for (const line of lines) {
|
||||||
|
const trimmed = line.trim();
|
||||||
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
||||||
|
const equalIndex = trimmed.indexOf("=");
|
||||||
|
if (equalIndex === -1) continue;
|
||||||
|
|
||||||
|
const key = trimmed.slice(0, equalIndex).trim();
|
||||||
|
let value = trimmed.slice(equalIndex + 1).trim();
|
||||||
|
if (
|
||||||
|
(value.startsWith('"') && value.endsWith('"')) ||
|
||||||
|
(value.startsWith("'") && value.endsWith("'"))
|
||||||
|
) {
|
||||||
|
value = value.slice(1, -1);
|
||||||
|
}
|
||||||
|
if (key && process.env[key] === undefined) process.env[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function requiredEnv(...names) {
|
||||||
|
for (const name of names) {
|
||||||
|
if (process.env[name]) return process.env[name];
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function firstPresent(...values) {
|
||||||
|
return values.find((value) => value !== undefined && value !== null && value !== "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizePlateNumber(value) {
|
||||||
|
const cleaned = normalizeDigits(cleanString(value));
|
||||||
|
return cleaned === "" ? value : cleaned;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeDigits(value) {
|
||||||
|
return cleanString(value).replace(/./g, (char) => {
|
||||||
|
const code = char.charCodeAt(0);
|
||||||
|
if (code >= 0x06f0 && code <= 0x06f9) return String(code - 0x06f0);
|
||||||
|
if (code >= 0x0660 && code <= 0x0669) return String(code - 0x0660);
|
||||||
|
return char;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function toNumber(value) {
|
||||||
|
const number = Number(value);
|
||||||
|
return Number.isFinite(number) ? number : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanString(value) {
|
||||||
|
return value === undefined || value === null ? "" : String(value).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function clone(value) {
|
||||||
|
return JSON.parse(JSON.stringify(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
function sleep(ms) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user