выгрузка БД в excel

This commit is contained in:
Sergey Yarkov 2026-08-25 10:36:59 +03:00
parent 44fd57ef2c
commit de712c847b
10 changed files with 1311 additions and 4 deletions

View File

@ -0,0 +1,202 @@
import { equipmentRepository } from "#root/repositories/equipment.repository.js";
import ExcelJS from "exceljs";
/** @import { HttpRouteHandler } from "#root/http-server/types/http-server.js" */
/**
* URL: /api/asu_table_xls
* Method: GET
* Description: Генерирует файл Excel на основе БД АСУ промплощадки
*
* @type {HttpRouteHandler}
*/
export default async (ctx) => {
const { res } = ctx;
const wb = new ExcelJS.Workbook();
const ws = wb.addWorksheet("Перечень систем", {
views: [{ state: "frozen", ySplit: 1, zoomScale: 70 }]
});
const filename = encodeURIComponent("АСУ промплощадка.xlsx");
const equipmentData = await equipmentRepository.getAll();
const excelData = [];
/** @type {Map<string, { row: number }} */
const addedBaseLocationNames = new Map();
/** @type {{ colStart: string, colEnd: string, from: number; to: number }[]} */
const mergeRanges = [];
let currentRow = 2;
for (const system of equipmentData) {
const subsystems = system.subsystems || [];
if (subsystems.length === 0) {
excelData.push([system.name, ...new Array(8).fill("", 0, 8)]);
currentRow++;
continue;
}
// Заголовок для расположения систем
if (!addedBaseLocationNames.has(system.baseLocationName)) {
addedBaseLocationNames.set(system.baseLocationName, { row: currentRow });
excelData.push([system.baseLocationName]);
mergeRanges.push({ colStart: "A", colEnd: "I", from: currentRow, to: currentRow });
currentRow++;
}
const startRow = currentRow;
// Добавление систем подсистем в таблицу excel
subsystems.forEach((subsystem) => {
const hasDocLink = Boolean(subsystem?.docLink);
const hasElectroPlan = Boolean(subsystem?.electrDiagLink);
const plcs = subsystem?.plcs || [];
const hmis = subsystem?.hmis || [];
const fgs = subsystem?.fgs || [];
const pcs = subsystem?.pcs || [];
excelData.push([
system.name,
subsystem.name,
`${system.baseLocationName} ${subsystem.locationName}`,
hasDocLink ? { text: "Документация", hyperlink: subsystem?.docLink } : "",
hasElectroPlan ? { text: "Электроплан", hyperlink: subsystem?.electrDiagLink } : "",
plcs.map((plc) => `${plc.name || ""}`).join("\n"),
hmis.map((hmi) => `${hmi.name || ""}`).join("\n"),
fgs.map((fg) => `${fg.name || ""}`).join("\n"),
pcs.map((pc) => `${pc.name || ""}`).join("\n")
]);
currentRow++;
});
mergeRanges.push({ colStart: "A", colEnd: "A", from: startRow, to: currentRow - 1 });
}
ws.getRow(1).values = [
"Наименование системы",
"Наименование внутренних подсистем.",
"Расположение",
"Документация",
"Электроплан",
"PLC",
"HMI",
"ПЧ",
"Промышленные ПК"
];
ws.autoFilter = `A1:I${excelData.length + 1}`;
ws.addRows(excelData);
mergeRanges.forEach((r) => ws.mergeCells(`${r.colStart}${r.from}:${r.colEnd}${r.to}`));
ws.getColumn(1).width = 50;
ws.getColumn(2).width = 50;
ws.getColumn(3).width = 35;
ws.getColumn(4).width = 17;
ws.getColumn(5).width = 17;
ws.getColumn(6).width = 45;
ws.getColumn(7).width = 45;
ws.getColumn(8).width = 45;
ws.getColumn(9).width = 30;
ws.getRow(1).height = 45;
ws.eachRow((row, rowNumber) => {
row.eachCell({ includeEmpty: true }, (cell) => {
cell.alignment = {
wrapText: true,
vertical: "middle",
horizontal: "center"
};
if (rowNumber > 1) {
cell.font = {
name: "Calibri",
size: 12
};
cell.border = {
top: { style: "thin", color: { argb: "FF000000" } },
left: { style: "thin", color: { argb: "FF000000" } },
right: { style: "thin", color: { argb: "FF000000" } },
bottom: { style: "thin", color: { argb: "FF000000" } }
};
cell.fill = {
type: "pattern",
pattern: "solid",
fgColor: { argb: "FFF4F4F4" }
};
if (cell.value === null || cell.value === undefined || cell.value === "") {
cell.fill = {
type: "pattern",
pattern: "solid",
fgColor: { argb: "FFFCE4D6" }
};
}
// Ссылки
if (cell.value && typeof cell.value === "object" && "hyperlink" in cell.value) {
cell.font = {
name: "Calibri",
size: 11,
color: { argb: "FF0563C1" },
underline: "single"
};
}
} else {
cell.font = {
name: "Calibri",
size: 11,
color: { argb: "FFFFFFFF" }
};
cell.border = {
top: { style: "thin", color: { argb: "FF000000" } },
right: { style: "thin", color: { argb: "FF000000" } },
bottom: { style: "thin", color: { argb: "FF000000" } }
};
cell.fill = {
type: "pattern",
pattern: "solid",
fgColor: { argb: "FFF27A2B" }
};
}
});
});
addedBaseLocationNames.forEach((baseLocationHeaderRow) => {
const row = ws.getRow(baseLocationHeaderRow.row);
row.height = 30;
row.eachCell((cell) => {
cell.font = {
name: "Tahoma",
bold: true,
size: 11
};
cell.border = {
top: { style: "medium", color: { argb: "FF000000" } },
bottom: { style: "medium", color: { argb: "FF000000" } }
};
cell.fill = {
type: "pattern",
pattern: "solid",
fgColor: { argb: "FF92D050" }
};
});
});
const excelBuffer = await wb.xlsx.writeBuffer();
res.res.setHeader("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
res.res.setHeader("Content-Disposition", `attachment; filename=${filename}; filename*=UTF-8''${filename}`);
res.res.setHeader("Content-Length", excelBuffer.byteLength);
res.res.end(excelBuffer);
return;
};

View File

@ -0,0 +1,3 @@
import getAsuTableHandler from "./getAsuTable.handler.js";
export const asuTableHandlers = { getAsuTableHandler };

View File

@ -1 +1,2 @@
export * from "./reports/index.js"; export * from "./reports/index.js";
export * from "./asu_table/index.js";

View File

@ -61,5 +61,5 @@ export interface HttpResponse {
res: http.ServerResponse; res: http.ServerResponse;
} }
export type HttpRouteHandler = (ctx: HttpContext) => any; export type HttpRouteHandler = (ctx: HttpContext) => Promise<unknown>;
export type HttpCreateServer = (options: HttpServerOptions) => HttpServer; export type HttpCreateServer = (options: HttpServerOptions) => HttpServer;

View File

@ -1,7 +1,7 @@
import path from "node:path"; import path from "node:path";
import fsp from "node:fs/promises"; import fsp from "node:fs/promises";
import { createServer } from "./http-server/index.js"; import { createServer } from "./http-server/index.js";
import { reportsHandlers } from "#handlers/index.js"; import { reportsHandlers, asuTableHandlers } from "#handlers/index.js";
import packageJson from "./package.json" with { type: "json" }; import packageJson from "./package.json" with { type: "json" };
import db from "./db.js"; import db from "./db.js";
@ -40,6 +40,7 @@ server.router.prefix("/api", (router) => {
} }
}); });
router.define("POST", "/reports_sync", reportsHandlers.syncParsedHandler); router.define("POST", "/reports_sync", reportsHandlers.syncParsedHandler);
router.define("GET", "/asu_table_xls", asuTableHandlers.getAsuTableHandler);
}); });
server.router.define("GET", "/", ({ res }) => res.sendText("Сервис инструментов таблицы АСУ.")); server.router.define("GET", "/", ({ res }) => res.sendText("Сервис инструментов таблицы АСУ."));

View File

@ -1,6 +1,7 @@
{ {
"compilerOptions": { "compilerOptions": {
"baseUrl": ".", "baseUrl": ".",
"ignoreDeprecations": "6.0",
"checkJs": true, "checkJs": true,
"allowJs": true, "allowJs": true,
"lib": ["ESNext"], "lib": ["ESNext"],

1048
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -14,11 +14,13 @@
"license": "ISC", "license": "ISC",
"description": "API сервис инструментов таблицы АСУ.", "description": "API сервис инструментов таблицы АСУ.",
"dependencies": { "dependencies": {
"exceljs": "^4.4.0",
"formidable": "^3.5.4", "formidable": "^3.5.4",
"mssql": "^12.2.0", "mssql": "^12.2.0",
"p-limit": "^7.3.0", "p-limit": "^7.3.0",
"serialize-error": "^13.0.1", "serialize-error": "^13.0.1",
"xlsx": "^0.18.5", "xlsx": "^0.18.5",
"xlsx-js-style": "^1.2.0",
"yup": "^1.7.1" "yup": "^1.7.1"
}, },
"imports": { "imports": {

View File

@ -0,0 +1,52 @@
import { UOW } from "./index.js";
export const equipmentRepository = {
async getAll() {
const request = UOW.request;
const result = await request.query(`
SELECT systemsTbl.id as id,
systemsTbl.name as name,
baseLocTbl.name as baseLocationName,
(select subSystemsTbl.id as id,
subSystemsTbl.name as name,
subSystemsTbl.doc_link as docLink,
subSystemsTbl.electr_diag_link as electrDiagLink,
locTbl.name as locationName,
(select plcTbl.plc_name as name
FROM asu_system_api_plc as plcTbl
left join asu_system_api_subsystemlist_plc as jtPLC on jtPLC.subsystemlist_id = subSystemsTbl.id
where jtPLC.plc_id = plcTbl.id
FOR JSON PATH) as plcs,
(select hmiTbl.hmi_name as name
FROM asu_system_api_hmi as hmiTbl
left join asu_system_api_subsystemlist_hmi as jtHMI on jtHMI.subsystemlist_id = subSystemsTbl.id
where jtHMI.hmi_id = hmiTbl.id
FOR JSON PATH) as hmis,
(select fgTbl.fg_name as name
FROM asu_system_api_fg as fgTbl
left join asu_system_api_subsystemlist_fg as jtFG on jtFG.subsystemlist_id = subSystemsTbl.id
where jtFG.fg_id = fgTbl.id
FOR JSON PATH) as fgs,
(select pcTbl.pc_name as name
FROM asu_system_api_pc as pcTbl
left join asu_system_api_subsystemlist_pc as jtPC on jtPC.subsystemlist_id = subSystemsTbl.id
where jtPC.pc_id = pcTbl.id
FOR JSON PATH) as pcs
from asu_system_api_subsystemlist as subSystemsTbl
inner join asu_system_api_systemlist_subsystem_name as jt on subSystemsTbl.id = jt.subsystemlist_id
left join asu_system_api_location as locTbl on locTbl.id = subSystemsTbl.location_id
where jt.systemlist_id = systemsTbl.id
FOR JSON PATH) as subsystems
from asu_system_api_systemlist as systemsTbl
inner join asu_system_api_baselocation as baseLocTbl on systemsTbl.location_id = baseLocTbl.id FOR JSON PATH
`);
const jsonStringData = result.recordset.map((row) => Object.values(row)[0]).join("");
return JSON.parse(jsonStringData || "[]");
}
};

View File

@ -1,2 +1,3 @@
export * from "./uow.js"; export * from "./uow.js";
export * from "./report.repository.js"; export * from "./report.repository.js";
export * from "./equipment.repository.js";