asu_tools_service/router/reports/parseExcel.handler.js
2026-03-03 16:28:25 +03:00

158 lines
4.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { BadRequestError } from "#root/errors/index.js";
import db from "#root/db.js";
import xlsx from "xlsx";
import pLimit from "p-limit";
import { serializeError } from "serialize-error";
const sheetName = "Отчеты";
const limit = pLimit(10);
/** @type {xlsx.ParsingOptions} */
const xlsxParseOptions = {
sheets: sheetName,
cellHTML: false,
cellFormula: false,
cellDates: true,
dateNF: "yyyy-mm-dd"
};
/**
* URL: /api/reports_excel_file_parse
* Method: POST
* Description: Принимает на вход файл отчетов в формате Excel, парсит его, проверяет связи с БД,
* отдает отчет в формате JSON частями.
*
* @type {HTTPRouteHandler}
*/
export default async (ctx) => {
const { res } = ctx;
if (!ctx.data) throw new BadRequestError("Cannot proceed uploaded file");
const [, files] = ctx.data;
const excelFilePath = files?.report?.at(0)?.filepath;
/** @type {ReportModel[]} */
const reports = [];
let parseResultCount = { equipments: 0, applicants: 0, executors: 0 };
let isReqClosed = false;
res.res.setHeaders(
new Headers({
Connection: "keep-alive",
"Cache-Control": "no-cache",
"Content-Type": "text/event-stream"
})
);
res.res.on("close", () => (isReqClosed = true));
if (excelFilePath) {
const workbook = xlsx.readFile(excelFilePath, xlsxParseOptions);
const sheet = workbook.Sheets[sheetName];
if (!sheet) throw new Error("Invalid reports workbook.", { cause: { statusCode: 400 } });
const data = xlsx.utils.sheet_to_json(sheet, { raw: false, blankrows: true });
for (let i = 0; i < data.length; i++) {
const cols = Object.keys(data[i]);
if (i === 0) continue;
/** Несинхронизированные помеченные отчеты */
if ("__EMPTY_3" in data[i] && data[i]["__EMPTY_3"] === "r") {
reports.push({
date: data[i][cols[0]],
equipment: data[i][cols[1]],
reason_call: data[i][cols[3]].split("\r\r\n")[1] || "",
job_description: data[i][cols[4]].split("\r\r\n")[1] || "",
root_cause: data[i]["__EMPTY_2"] || "",
applicantName: data[i][cols[3]].split("\r\r\n")[0] || "",
executorNames: data[i][cols[4]].split("\r\r\n")[0] || "",
isMarked: true
});
continue;
}
/** Остальные отчеты */
reports.push({
date: data[i][cols[0]],
equipment: data[i][cols[1]],
reason_call: parseReasonCallAndJobDesc(data[i][cols[3]]).trim(),
job_description: parseReasonCallAndJobDesc(data[i][cols[4]]).trim(),
root_cause: data[i]["__EMPTY_2"] || "",
applicantName: parseNames(data[i][cols[3]]).trim(),
executorNames: parseNames(data[i][cols[4]]).trim()
});
}
/** @param {ReportModel} report */
const checkReportAssigments = async (report) => {
if (isReqClosed) {
return Promise.reject("Client closed the connection.");
}
const equipment = await db
.request()
.query(`select id from dbo.asu_system_api_subsystemlist where name like N'%${report.equipment}%';`);
const applicant = await db
.request()
.query(
`select id from dbo.gpp_report_api_applicant where name like N'${report.applicantName.split(" ")[0]}%';`
);
if (equipment.recordset.length !== 0) parseResultCount.equipments++;
if (applicant.recordset.length !== 0) parseResultCount.applicants++;
res.sendSSEJson(
{
report: {
...report,
equipment: equipment.recordset.at(0)?.id || null,
applicant: applicant.recordset.at(0)?.id || null
}
},
"progress"
);
return equipment;
};
const requests = reports.map((r) => limit(() => checkReportAssigments(r)));
await Promise.all(requests)
.then(() => res.sendSSEJson(parseResultCount, "done"))
.catch((error) => {
console.error(error);
res.sendSSEJson({ error: serializeError(error) }, "error");
})
.finally(() => res.res.end());
} else {
throw new BadRequestError();
}
};
function parseReasonCallAndJobDesc(str = "") {
const splitted = splitLongSpacedSentence(str);
if (splitted.length > 1) return splitted.slice(1).join(" ");
return str.split("\r\n").slice(1).join(" ");
}
function parseNames(str = "") {
const splitted = splitLongSpacedSentence(str);
if (splitted.length > 1) return splitted[0];
return str.split("\r\n")[0];
}
/**
* @param {string} str
* @param {number} min
*/
function splitLongSpacedSentence(str, min = 5) {
return str.split(new RegExp(`\\s{${min},}`));
}