error handling

This commit is contained in:
Sergey Yarkov 2026-02-27 15:38:02 +03:00
parent dae69f46a5
commit 7955b224b0
4 changed files with 136 additions and 123 deletions

View File

@ -1,6 +1,6 @@
import { ctxAsyncLocalStorage } from "#root/context.js";
import handleExcelReportsFileParse from "./reportsFileParse.handler.js";
import handleReportsSyncParsed from "./reportsSyncParsed.handler.js";
import handleExcelReportsFileParse from "./reports/parseExcel.handler.js";
import handleReportsSyncParsed from "./reports/syncParsed.handler.js";
/**
* @type {HTTPRouteMap}
@ -24,17 +24,27 @@ export async function routeHandler(url) {
const ctx = ctxAsyncLocalStorage.getStore();
const { req, res } = ctx;
try {
if (url.pathname in ROUTE_MAP) {
if (req.headers["content-type"] === "application/json") {
ctx.data = await getJsonData();
}
const route = ROUTE_MAP[url.pathname];
if (route.method === req.method) route.handle(ctx);
if (req.headers["content-type"] === "application/json") ctx.data = await getJsonData();
if (route.method === req.method) await route.handle(ctx);
return;
}
res.sendText("Указанный URL не имеет обработчика.", 200);
} catch (error) {
console.error(error);
res.sendJson(
{
error: {
message: error?.message || "Internal Server Error",
stack: error?.stack || null,
},
},
error?.cause?.statusCode || 500,
);
}
}
function getJsonData() {
@ -49,4 +59,4 @@ function getJsonData() {
});
}
export * from "./reportsFileParse.handler.js";
export * from "./reports/parseExcel.handler.js";

View File

@ -0,0 +1,115 @@
import path from "node:path";
import xlsx from "xlsx";
import formidable from "formidable";
import db from "#root/db.js";
/**
* Читает Excel файл отчетов и возвращает его в формате JSON
*
* @type {HTTPRouteHandler}
*/
export default async ({ req, res }) => {
const sheetName = "Отчеты";
/** @type {{ markedReports: ReportModel[], reports: ReportModel[] }} */
const ret = { reports: [], markedReports: [] };
/** @type {xlsx.ParsingOptions} */
const xlsxParseOptions = {
sheets: sheetName,
cellHTML: false,
cellFormula: false,
cellDates: true,
dateNF: "yyyy-mm-dd",
};
const form = formidable({
maxFields: 1,
maxFiles: 1,
filename: (_, ext) => `gpp-reports${ext}`,
keepExtensions: true,
uploadDir: path.join(process.cwd(), "./uploads"),
filter: ({ mimetype }) => mimetype && mimetype.includes("application/vnd.ms-excel"),
});
const [, files] = await form.parse(req);
const excelFilePath = files?.report?.at(0)?.filepath;
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") {
ret.markedReports.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] || "",
});
continue;
}
const parseReasonCallAndJobDesc = (str = "") => {
const splitted = splitLongSpacedSentence(str);
if (splitted.length > 1) return splitted.slice(1).join(" ");
return str.split("\r\n").slice(1).join(" ");
};
const parseNames = (str = "") => {
const splitted = splitLongSpacedSentence(str);
if (splitted.length > 1) return splitted[0];
return str.split("\r\n")[0];
};
/** Остальные отчеты */
ret.reports.push({
date: data[i][cols[0]],
equipment: data[i][cols[1]],
reason_call: parseReasonCallAndJobDesc(data[i][cols[3]]),
job_description: parseReasonCallAndJobDesc(data[i][cols[4]]),
root_cause: data[i]["__EMPTY_2"] || "",
applicantName: parseNames(data[i][cols[3]]),
executorNames: parseNames(data[i][cols[4]]),
});
}
} else {
throw new Error("Bad request", { cause: { statusCode: 400 } });
}
res.sendJson({ data: ret });
};
// /**
// * @param {string} filepath
// */
// function reportsExcelFileToJson(filepath) {
// try {
// } catch (error) {
// }
// }
/**
* @param {string} str
* @param {number} min
*/
function splitLongSpacedSentence(str, min = 5) {
return str.split(new RegExp(`\\s{${min},}`));
}

View File

@ -1,112 +0,0 @@
import path from "node:path";
import xlsx from "xlsx";
import formidable from "formidable";
/**
* Читает Excel файл отчетов и возвращает его в формате JSON
*
* @type {HTTPRouteHandler}
*/
export default async ({ req, res }) => {
const sheetName = "Отчеты";
/** @type {{ markedReports: ReportModel[], reports: ReportModel[] }} */
const ret = { reports: [], markedReports: [] };
/** @type {xlsx.ParsingOptions} */
const xlsxParseOptions = {
sheets: sheetName,
cellHTML: false,
cellFormula: false,
cellDates: true,
dateNF: "yyyy-mm-dd",
};
const form = formidable({
maxFields: 1,
maxFiles: 1,
filename: (_, ext) => `gpp-reports${ext}`,
keepExtensions: true,
uploadDir: path.join(process.cwd(), "./uploads"),
filter: ({ mimetype }) => mimetype && mimetype.includes("application/vnd.ms-excel"),
});
try {
const [, files] = await form.parse(req);
const excelFilePath = files?.report?.at(0)?.filepath;
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") {
ret.markedReports.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] || "",
});
continue;
}
const parseReasonCallAndJobDesc = (str = "") => {
const splitted = splitLongSpacedSentence(str);
if (splitted.length > 1) return splitted.slice(1).join(" ");
return str.split("\r\n").slice(1).join(" ");
};
const parseNames = (str = "") => {
const splitted = splitLongSpacedSentence(str);
if (splitted.length > 1) return splitted[0];
return str.split("\r\n")[0];
};
/** Остальные отчеты */
ret.reports.push({
date: data[i][cols[0]],
equipment: data[i][cols[1]],
reason_call: parseReasonCallAndJobDesc(data[i][cols[3]]),
job_description: parseReasonCallAndJobDesc(data[i][cols[4]]),
root_cause: data[i]["__EMPTY_2"] || "",
applicantName: parseNames(data[i][cols[3]]),
executorNames: parseNames(data[i][cols[4]]),
});
}
} else {
throw new Error("Bad request", { cause: { statusCode: 400 } });
}
} catch (error) {
console.error(error);
res.sendJson(
{ error: error?.stack || error?.message || "Server error" },
error?.cause?.statusCode || 500,
);
return;
}
res.sendJson({ data: ret });
};
/**
* @param {string} str
* @param {number} min
*/
function splitLongSpacedSentence(str, min = 5) {
return str.split(new RegExp(`\\s{${min},}`));
}