mirror of
https://github.com/sergeyyarkov/asu_tools_service.git
synced 2026-10-11 10:29:22 +03:00
refactor
This commit is contained in:
parent
9cff9624a8
commit
53a8cab4fb
@ -1,5 +1,4 @@
|
||||
import { equipmentRepository } from "#root/repositories/equipment.repository.js";
|
||||
import ExcelJS from "exceljs";
|
||||
import { dbExporterService, XlsExporterAdapter } from "#root/services/dbExporter.service.js";
|
||||
|
||||
/** @import { HttpRouteHandler } from "#root/http-server/types/http-server.js" */
|
||||
|
||||
@ -12,193 +11,14 @@ import ExcelJS from "exceljs";
|
||||
*/
|
||||
export default async (ctx) => {
|
||||
const { res, searchParams } = ctx;
|
||||
|
||||
const systemIdsParam = searchParams.get("systemIds")?.split(",");
|
||||
|
||||
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({ filter: { systemIds: systemIdsParam } });
|
||||
|
||||
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.nameModel || ""}`).join("\n"),
|
||||
hmis.map((hmi) => `${hmi.nameModel || ""}`).join("\n"),
|
||||
fgs.map((fg) => `${fg.nameModel || ""}`).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();
|
||||
const exportResult = await dbExporterService.export(XlsExporterAdapter, { filter: { systemIds: systemIdsParam } });
|
||||
|
||||
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);
|
||||
res.res.setHeader("Content-Length", exportResult.byteLength);
|
||||
res.res.end(exportResult);
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
@ -1 +1,2 @@
|
||||
export * from "./report.model.js";
|
||||
export * from "./system.model.js";
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { BaseModel } from "./base.model.js";
|
||||
|
||||
/** @import { ReportColumns } from "#tables/report.table.js" */
|
||||
/** @import { ReportColumns } from "./types.js" */
|
||||
|
||||
export class ReportModel extends BaseModel {
|
||||
/**
|
||||
|
||||
20
models/system.model.js
Normal file
20
models/system.model.js
Normal file
@ -0,0 +1,20 @@
|
||||
import { BaseModel } from "./base.model.js";
|
||||
|
||||
/** @import { SystemModelTableCols } from "./types.js" */
|
||||
|
||||
/**
|
||||
* @implements { SystemModelTableCols }
|
||||
*/
|
||||
export class SystemModel extends BaseModel {
|
||||
id;
|
||||
location_id;
|
||||
name;
|
||||
|
||||
/** @param {SystemModelTableCols} columns */
|
||||
constructor(columns) {
|
||||
super();
|
||||
this.id = columns.id;
|
||||
this.location_id = columns.location_id;
|
||||
this.name = columns.name;
|
||||
}
|
||||
}
|
||||
17
models/types.d.ts
vendored
Normal file
17
models/types.d.ts
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
export type ReportColumns = {
|
||||
id?: number | string;
|
||||
date: Date;
|
||||
reason_call: string;
|
||||
job_description: string;
|
||||
root_cause: string | null;
|
||||
applicant_id: number | string | null;
|
||||
equipment_id: number | string | null;
|
||||
};
|
||||
|
||||
export type ReportExecutorTableCols = { id: string | number; report_id: string | number; user_id: string | number };
|
||||
|
||||
export type SystemModelTableCols = {
|
||||
id: string | number;
|
||||
name: string | null;
|
||||
location_id: string | number | null;
|
||||
};
|
||||
@ -1,9 +1,23 @@
|
||||
import { UOW } from "./index.js";
|
||||
import sql from "mssql";
|
||||
|
||||
/**
|
||||
* @typedef {{ name: string, nameModel?: string | null; }} EquipmentDbRecord;
|
||||
*
|
||||
* @typedef {{ id: string, name?: string,
|
||||
* docLink?: string | null, electrDiagLink?: string | null,
|
||||
* locationName?: string | null, plcs?: Array<EquipmentDbRecord>,
|
||||
* fgs?: Array<EquipmentDbRecord>, hmis: Array<EquipmentDbRecord>,
|
||||
* pcs?: Array<EquipmentDbRecord>
|
||||
* }} SubSystemDbRecord;
|
||||
*
|
||||
* @typedef {{ id: string, name: string, baseLocationName: string, subsystems: Array<SubSystemDbRecord> }} SystemDbRecord;
|
||||
*/
|
||||
|
||||
export const equipmentRepository = {
|
||||
/**
|
||||
* @param {{ filter?: { systemIds?: string[] | number[] } }} [options]
|
||||
* @returns {Promise<Array<SystemDbRecord>>}
|
||||
*/
|
||||
async getAll(options) {
|
||||
const request = UOW.request;
|
||||
|
||||
20
services/dbExporter.service.js
Normal file
20
services/dbExporter.service.js
Normal file
@ -0,0 +1,20 @@
|
||||
import { equipmentRepository } from "#root/repositories/equipment.repository.js";
|
||||
|
||||
/** @import { ExporterAdapterInterface } from './types.js' */
|
||||
|
||||
export const dbExporterService = {
|
||||
/**
|
||||
* @param { new () => ExporterAdapterInterface } Adapter
|
||||
* @param {{ filter?: { systemIds?: number[] | string[] } }} options
|
||||
* */
|
||||
async export(Adapter, options) {
|
||||
const db = await equipmentRepository.getAll({ filter: options.filter });
|
||||
const AdapterInstance = new Adapter();
|
||||
|
||||
/** @type {Buffer} */
|
||||
const data = await AdapterInstance.export(db);
|
||||
return data;
|
||||
}
|
||||
};
|
||||
|
||||
export * from "./exporters/index.js";
|
||||
199
services/exporters/excel.exporter.js
Normal file
199
services/exporters/excel.exporter.js
Normal file
@ -0,0 +1,199 @@
|
||||
import ExcelJS from "exceljs";
|
||||
|
||||
/**
|
||||
* @import { ExporterAdapterInterface } from '../types.js'
|
||||
* @import { SystemDbRecord } from '#repositories/equipment.repository.js'
|
||||
*/
|
||||
|
||||
/** @implements {ExporterAdapterInterface} */
|
||||
export class XlsExporterAdapter {
|
||||
/** @type {Partial<ExcelJS.AddWorksheetOptions>} */
|
||||
static mainWsOptions = { views: [{ state: "frozen", ySplit: 1, zoomScale: 70 }] };
|
||||
|
||||
constructor() {
|
||||
this.wb = new ExcelJS.Workbook();
|
||||
this.ws = this.wb.addWorksheet("Перечень систем", XlsExporterAdapter.mainWsOptions);
|
||||
|
||||
/** @type {Array<any[]>} */
|
||||
this.data = [];
|
||||
}
|
||||
|
||||
/** @param {SystemDbRecord[]} db */
|
||||
async export(db) {
|
||||
/** @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 db) {
|
||||
const subsystems = system.subsystems || [];
|
||||
|
||||
// Подсистема не найдены, добавляет название системы с пустыми ячейками
|
||||
if (subsystems.length === 0) {
|
||||
this.data.push([system.name, ...new Array(8).fill("", 0, 8)]);
|
||||
currentRow++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Заголовок для расположения систем
|
||||
if (!addedBaseLocationNames.has(system.baseLocationName)) {
|
||||
addedBaseLocationNames.set(system.baseLocationName, { row: currentRow });
|
||||
this.data.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 || [];
|
||||
|
||||
this.data.push([
|
||||
system.name,
|
||||
subsystem.name,
|
||||
`${system.baseLocationName} ${subsystem.locationName}`,
|
||||
hasDocLink ? { text: "Документация", hyperlink: subsystem?.docLink } : "",
|
||||
hasElectroPlan ? { text: "Электроплан", hyperlink: subsystem?.electrDiagLink } : "",
|
||||
plcs.map((plc) => `${plc.nameModel || ""}`).join("\n"),
|
||||
hmis.map((hmi) => `${hmi.nameModel || ""}`).join("\n"),
|
||||
fgs.map((fg) => `${fg.nameModel || ""}`).join("\n"),
|
||||
pcs.map((pc) => `${pc.name || ""}`).join("\n")
|
||||
]);
|
||||
|
||||
currentRow++;
|
||||
});
|
||||
|
||||
mergeRanges.push({ colStart: "A", colEnd: "A", from: startRow, to: currentRow - 1 });
|
||||
}
|
||||
|
||||
this.ws.getRow(1).values = [
|
||||
"Наименование системы",
|
||||
"Наименование внутренних подсистем.",
|
||||
"Расположение",
|
||||
"Документация",
|
||||
"Электроплан",
|
||||
"PLC",
|
||||
"HMI",
|
||||
"ПЧ",
|
||||
"Промышленные ПК"
|
||||
];
|
||||
|
||||
this.ws.autoFilter = `A1:I${this.data.length + 1}`;
|
||||
|
||||
this.ws.addRows(this.data);
|
||||
|
||||
mergeRanges.forEach((r) => this.ws.mergeCells(`${r.colStart}${r.from}:${r.colEnd}${r.to}`));
|
||||
|
||||
this.ws.getColumn(1).width = 50;
|
||||
this.ws.getColumn(2).width = 50;
|
||||
this.ws.getColumn(3).width = 35;
|
||||
this.ws.getColumn(4).width = 17;
|
||||
this.ws.getColumn(5).width = 17;
|
||||
this.ws.getColumn(6).width = 45;
|
||||
this.ws.getColumn(7).width = 45;
|
||||
this.ws.getColumn(8).width = 45;
|
||||
this.ws.getColumn(9).width = 30;
|
||||
this.ws.getRow(1).height = 45;
|
||||
|
||||
this.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 = this.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 this.wb.xlsx.writeBuffer();
|
||||
|
||||
/** @type {Buffer} */
|
||||
return excelBuffer;
|
||||
}
|
||||
|
||||
setupStyles() {}
|
||||
}
|
||||
1
services/exporters/index.js
Normal file
1
services/exporters/index.js
Normal file
@ -0,0 +1 @@
|
||||
export * from "./excel.exporter.js";
|
||||
@ -1 +1,2 @@
|
||||
export * from "./reports.service.js";
|
||||
export * from "./dbExporter.service.js";
|
||||
|
||||
5
services/types.d.ts
vendored
Normal file
5
services/types.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
import { SystemDbRecord } from "#root/repositories/equipment.repository.js";
|
||||
|
||||
export interface ExporterAdapterInterface {
|
||||
export: (database: Array<SystemDbRecord>) => Promise<any>;
|
||||
}
|
||||
@ -1,10 +1,6 @@
|
||||
import sql from "mssql";
|
||||
import { BaseTable } from "./base.table.js";
|
||||
|
||||
/**
|
||||
* @typedef {{ id?: number | string; date: Date; reason_call: string; job_description: string; root_cause: string | null; applicant_id: number | string | null; equipment_id: number | string | null; }} ReportColumns
|
||||
*/
|
||||
|
||||
export class ReportTable extends BaseTable {
|
||||
static tableName = "gpp_report_api_report";
|
||||
static table = new sql.Table(this.tableName);
|
||||
|
||||
@ -1,10 +1,6 @@
|
||||
import sql from "mssql";
|
||||
import { BaseTable } from "./base.table.js";
|
||||
|
||||
/**
|
||||
* @typedef {{ id: string | number; report_id: string | number; user_id: string | number; }} ReportExecutorTableCols
|
||||
*/
|
||||
|
||||
export class ReportExecutorTable extends BaseTable {
|
||||
static tableName = "gpp_report_api_report_executor";
|
||||
static table = new sql.Table(this.tableName);
|
||||
|
||||
16
tables/system.table.js
Normal file
16
tables/system.table.js
Normal file
@ -0,0 +1,16 @@
|
||||
import sql from "mssql";
|
||||
import { BaseTable } from "./base.table.js";
|
||||
|
||||
export class SystemTable extends BaseTable {
|
||||
static tableName = "asu_system_api_systemlist";
|
||||
static table = new sql.Table(this.tableName);
|
||||
|
||||
static columns = {
|
||||
id: { type: sql.BigInt, nullable: false },
|
||||
location_id: { type: sql.BigInt, nullable: true },
|
||||
name: { type: sql.VarChar, nullable: true }
|
||||
};
|
||||
}
|
||||
|
||||
SystemTable.table.create = false;
|
||||
SystemTable.init();
|
||||
Loading…
x
Reference in New Issue
Block a user