экспорт по подсистеме

This commit is contained in:
Sergey Yarkov 2026-08-28 08:33:32 +03:00
parent 8139a711f4
commit 29ebc98d0e
8 changed files with 874 additions and 87 deletions

View File

@ -12,8 +12,11 @@ import { dbExporterService, XlsExporterAdapter } from "#root/services/dbExporter
export default async (ctx) => { export default async (ctx) => {
const { res, searchParams } = ctx; const { res, searchParams } = ctx;
const systemIdsParam = searchParams.get("systemIds")?.split(","); const systemIdsParam = searchParams.get("systemIds")?.split(",");
const subSystemIdsParam = searchParams.get("subSystemIds")?.split(",");
const filename = encodeURIComponent("АСУ промплощадка.xlsx"); const filename = encodeURIComponent("АСУ промплощадка.xlsx");
const exportResult = await dbExporterService.export(XlsExporterAdapter, { filter: { systemIds: systemIdsParam } }); const exportResult = await dbExporterService.export(XlsExporterAdapter, {
filter: { systemIds: systemIdsParam, subsystemIds: subSystemIdsParam }
});
res.res.setHeader("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); 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-Disposition", `attachment; filename=${filename}; filename*=UTF-8''${filename}`);

View File

@ -2,13 +2,108 @@ import { UOW } from "./index.js";
import sql from "mssql"; import sql from "mssql";
/** /**
* @typedef {{ name: string, nameModel?: string | null; }} EquipmentDbRecord; * @typedef {{
* * invNumber?: string | null;
* location?: string | null;
* purpose?: string | null;
* name?: string | null;
* adminLoginPass?: string | null;
* workerLoginPass?: string | null;
* promConn?: string | null;
* compConn?: string | null;
* locAndRemark?: string | null;
* upsReq?: string | null;
* upsAvail?: string | null;
* lastServiceDate?: string | null;
* osVersion?: string | null;
* indSoftInst?: string | null;
* softAdminLoginPass?: string | null;
* projectSource?: string | null;
* projectSourceLink?: string | null;
* comments?: string | null;
* projectArchieveLink?: string | null;
* config?: PCConfigDbRecord[] | null;
* }} PcEquipmentDbRecord
*/
/**
* @typedef {{
* nameModel?: string | null;
* partNum?: string | null;
* cabinet?: string | null;
* cabinetLocation?: string | null;
* purpose?: string | null;
* adminLoginPass?: string | null;
* workerLoginPass?: string | null;
* interface?: string | null;
* cableLocation?: string | null;
* projectName?: string | null;
* projectLink?: string | null;
* diskImage?: string | null;
* imageLink?: string | null;
* connectionSettings?: string | null;
* }} HmiModelDbRecord
*/
/**
* @typedef {{
* modelName?: string | null;
* partNum?: string | null;
* motherboard?: string | null;
* socket?: string | null;
* cpu?: string | null;
* mem?: string | null;
* memVolume?: string | null;
* hdd?: string | null;
* hddVolume?: string | null;
* expansionCards?: string | null;
* externalDevices?: string | null;
* caseFan?: string | null;
* cpuFan?: string | null;
* powerUnit?: string | null;
* }} PCConfigDbRecord
*/
/**
* @typedef {{
* nameModel?: string | null;
* partNum?: string | null;
* cabinet?: string | null;
* cabinetLocation?: string | null;
* purpose?: string | null;
* interface?: string | null;
* cableLocation?: string | null;
* projectName?: string | null;
* plcPass?: string | null;
* safetyPass?: string | null;
* projectLink?: string | null;
* nameId?: string | null;
* connectionSettings?: string | null;
* }} PlcModelDbRecord
*/
/**
* @typedef {{
* nameModel?: string | null;
* partNum?: string | null;
* cabinet?: string | null;
* cabinetLocation?: string | null;
* purpose?: string | null;
* interface?: string | null;
* cableLocation?: string | null;
* configName?: string | null;
* projectLink?: string | null;
* nameId?: string | null;
* connectionSettings?: string | null;
* }} FgModelDbRecord
*/
/**
* @typedef {{ id: string, name?: string, * @typedef {{ id: string, name?: string,
* docLink?: string | null, electrDiagLink?: string | null, * docLink?: string | null, electrDiagLink?: string | null,
* locationName?: string | null, plcs?: Array<EquipmentDbRecord>, * locationName?: string | null, plcs?: Array<PlcModelDbRecord>,
* fgs?: Array<EquipmentDbRecord>, hmis: Array<EquipmentDbRecord>, * fgs?: Array<FgModelDbRecord>, hmis: Array<HmiModelDbRecord>,
* pcs?: Array<EquipmentDbRecord> * pcs?: Array<PcEquipmentDbRecord>
* }} SubSystemDbRecord; * }} SubSystemDbRecord;
* *
* @typedef {{ id: string, name: string, baseLocationName: string, subsystems: Array<SubSystemDbRecord> }} SystemDbRecord; * @typedef {{ id: string, name: string, baseLocationName: string, subsystems: Array<SubSystemDbRecord> }} SystemDbRecord;
@ -16,23 +111,39 @@ import sql from "mssql";
export const equipmentRepository = { export const equipmentRepository = {
/** /**
* @param {{ filter?: { systemIds?: string[] | number[] } }} [options] * @param {{ filter?: { systemIds?: string[] | number[], subsystemIds? : string[] | number[] } }} [options]
* @returns {Promise<Array<SystemDbRecord>>} * @returns {Promise<Array<SystemDbRecord>>}
*/ */
async getAll(options) { async getAll(options) {
const request = UOW.request; const request = UOW.request;
const systemIds = options?.filter?.systemIds; const systemIds = options?.filter?.systemIds;
const subsystemIds = options?.filter?.subsystemIds;
const hasFilterBySystemIds = systemIds !== undefined && systemIds.length !== 0; const hasFilterBySystemIds = systemIds !== undefined && systemIds.length !== 0;
const hasFilterBySubsystemIds = subsystemIds !== undefined && subsystemIds.length !== 0;
let whereClause = ""; const whereParts = [];
if (hasFilterBySystemIds) { if (hasFilterBySystemIds) {
const idsString = systemIds.map(Number).filter(Boolean).join(","); const idsString = systemIds.map(Number).filter(Boolean).join(",");
request.input("sysIdsString", sql.VarChar, idsString); request.input("sysIdsString", sql.VarChar, idsString);
whereParts.push(`systemsTbl.id IN (SELECT value FROM STRING_SPLIT(@sysIdsString, ','))`);
whereClause = `WHERE systemsTbl.id IN (SELECT value FROM STRING_SPLIT(@sysIdsString, ','))`;
} }
if (hasFilterBySubsystemIds) {
const subIdsString = subsystemIds.map(Number).filter(Boolean).join(",");
request.input("subSysIdsString", sql.VarChar, subIdsString);
whereParts.push(`systemsTbl.id IN (
SELECT systemlist_id
FROM asu_system_api_systemlist_subsystem_name
WHERE subsystemlist_id IN (SELECT value FROM STRING_SPLIT(@subSysIdsString, ','))
)`);
} else {
request.input("subSysIdsString", sql.VarChar, null);
}
const whereClause = whereParts.length > 0 ? `WHERE ${whereParts.join(" AND ")}` : "";
const query = ` const query = `
SELECT systemsTbl.id as id, SELECT systemsTbl.id as id,
systemsTbl.name as name, systemsTbl.name as name,
@ -44,7 +155,20 @@ export const equipmentRepository = {
locTbl.name as locationName, locTbl.name as locationName,
(select (select
plcTbl.plc_name as name, plcTbl.plc_name as name,
plcModelTbl.name as nameModel plcModelTbl.name as nameModel,
plcModelTbl.part_num as partNum,
plcTbl.cabinet as cabinet,
plcTbl.cabinet_location as cabinetLocation,
plcTbl.purpose as purpose,
plcModelTbl.interface as interface,
plcTbl.cable_location as cableLocation,
plcTbl.project_name as projectName,
plcTbl.plc_pass as plcPass,
plcTbl.safety_pass as safetyPass,
plcTbl.project_link as projectLink,
plcTbl.connection_settings as connectionSettings
FROM asu_system_api_plc as plcTbl FROM asu_system_api_plc as plcTbl
left join asu_system_api_plc_model as plcModelTbl on plcModelTbl.id = plcTbl.name_id left join asu_system_api_plc_model as plcModelTbl on plcModelTbl.id = plcTbl.name_id
left join asu_system_api_subsystemlist_plc as jtPLC on jtPLC.plc_id = plcTbl.id left join asu_system_api_subsystemlist_plc as jtPLC on jtPLC.plc_id = plcTbl.id
@ -52,8 +176,22 @@ export const equipmentRepository = {
FOR JSON PATH) as plcs, FOR JSON PATH) as plcs,
(select (select
hmiTbl.hmi_name as name, hmiTbl.hmi_name as name,
hmiModelTbl.name as nameModel hmiModelTbl.name as nameModel,
hmiModelTbl.part_num as partNum,
hmiTbl.cabinet as cabinet,
hmiTbl.cabinet_location as cabinetLocation,
hmiTbl.purpose as purpose,
hmiTbl.admin_login_pass as adminLoginPass,
hmiTbl.worker_login_pass as workerLoginPass,
hmiModelTbl.interface as interface,
hmiTbl.cable_location as cableLocation,
hmiTbl.project_name as projectName,
hmiTbl.project_link as projectLink,
hmiTbl.disk_image as diskImage,
hmiTbl.image_link as imageLink,
hmiTbl.connection_settings as connectionSettings
FROM asu_system_api_hmi as hmiTbl FROM asu_system_api_hmi as hmiTbl
left join asu_system_api_hmi_model as hmiModelTbl on hmiModelTbl.id = hmiTbl.name_id left join asu_system_api_hmi_model as hmiModelTbl on hmiModelTbl.id = hmiTbl.name_id
left join asu_system_api_subsystemlist_hmi as jtHMI on jtHMI.hmi_id = hmiTbl.id left join asu_system_api_subsystemlist_hmi as jtHMI on jtHMI.hmi_id = hmiTbl.id
@ -61,15 +199,64 @@ export const equipmentRepository = {
FOR JSON PATH) as hmis, FOR JSON PATH) as hmis,
(select (select
fgTbl.fg_name as name, fgTbl.fg_name as name,
fgModelTbl.name as nameModel fgModelTbl.name as nameModel,
fgModelTbl.part_num as partNum,
fgModelTbl.interface as interface,
fgTbl.cabinet as cabinet,
fgTbl.cabinet_location as cabinetLocation,
fgTbl.purpose as purpose,
fgTbl.cable_location as cableLocation,
fgTbl.config_name as configName,
fgTbl.project_link as projectLink,
fgTbl.name_id as nameId,
fgTbl.connection_settings as connectionSettings
FROM asu_system_api_fg as fgTbl FROM asu_system_api_fg as fgTbl
left join asu_system_api_fg_model as fgModelTbl on fgModelTbl.id = fgTbl.name_id left join asu_system_api_fg_model as fgModelTbl on fgModelTbl.id = fgTbl.name_id
left join asu_system_api_subsystemlist_fg as jtFG on jtFG.fg_id = fgTbl.id left join asu_system_api_subsystemlist_fg as jtFG on jtFG.fg_id = fgTbl.id
where jtFG.subsystemlist_id = subSystemsTbl.id where jtFG.subsystemlist_id = subSystemsTbl.id
FOR JSON PATH) as fgs, FOR JSON PATH) as fgs,
(select pcTbl.pc_name as name (select pcTbl.inv_number as invNumber,
pcTbl.location as location,
pcTbl.purpose as purpose,
pcTbl.pc_name as name,
pcTbl.admin_login_pass as adminLoginPass,
pcTbl.worker_login_pass as workerLoginPass,
pcTbl.prom_conn as promConn,
pcTbl.comp_conn as compConn,
pcTbl.loc_and_remark as locAndRemark,
pcTbl.ups_req as upsReq,
pcTbl.ups_avail as upsAvail,
pcTbl.last_service_date as lastServiceDate,
pcTbl.os_version as osVersion,
pcTbl.ind_soft_inst as indSoftInst,
pcTbl.soft_admin_login_pass as softAdminLoginPass,
pcTbl.project_source as projectSource,
pcTbl.project_source_link as projectSourceLink,
pcTbl.comments as comments,
pcTbl.project_archieve_link as projectArchieveLink,
(select
pcConfigTbl.model as modelName,
pcConfigTbl.part_num as partNum,
pcConfigTbl.motherboard as motherboard,
pcConfigTbl.socket as socket,
pcConfigTbl.cpu as cpu,
pcConfigTbl.mem as mem,
pcConfigTbl.mem_volume as memVolume,
pcConfigTbl.hdd as hdd,
pcConfigTbl.hdd_volume as hddVolume,
pcConfigTbl.expansion_cards as expansionCards,
pcConfigTbl.external_devices as externalDevices,
pcConfigTbl.case_fan as caseFan,
pcConfigTbl.cpu_fan as cpuFan,
pcConfigTbl.power_unit as powerUnit
from asu_system_api_pcconfig as pcConfigTbl
left join asu_system_api_pc_model as jtPCModel on jtPCModel.pc_id = pcTbl.id
where pcConfigTbl.id = jtPCModel.pcconfig_id
FOR JSON PATH) as config
FROM asu_system_api_pc as pcTbl FROM asu_system_api_pc as pcTbl
left join asu_system_api_subsystemlist_pc as jtPC on jtPC.subsystemlist_id = subSystemsTbl.id left join asu_system_api_subsystemlist_pc as jtPC on jtPC.subsystemlist_id = subSystemsTbl.id
where jtPC.pc_id = pcTbl.id where jtPC.pc_id = pcTbl.id
@ -79,6 +266,7 @@ export const equipmentRepository = {
inner join asu_system_api_systemlist_subsystem_name as jt on subSystemsTbl.id = jt.subsystemlist_id 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 left join asu_system_api_location as locTbl on locTbl.id = subSystemsTbl.location_id
where jt.systemlist_id = systemsTbl.id where jt.systemlist_id = systemsTbl.id
and (@subSysIdsString IS NULL OR subSystemsTbl.id IN (SELECT value FROM STRING_SPLIT(@subSysIdsString, ',')))
FOR JSON PATH) as subsystems FOR JSON PATH) as subsystems
from asu_system_api_systemlist as systemsTbl from asu_system_api_systemlist as systemsTbl
inner join asu_system_api_baselocation as baseLocTbl on systemsTbl.location_id = baseLocTbl.id inner join asu_system_api_baselocation as baseLocTbl on systemsTbl.location_id = baseLocTbl.id

View File

@ -5,7 +5,7 @@ import { equipmentRepository } from "#root/repositories/equipment.repository.js"
export const dbExporterService = { export const dbExporterService = {
/** /**
* @param { new () => ExporterAdapterInterface } Adapter * @param { new () => ExporterAdapterInterface } Adapter
* @param {{ filter?: { systemIds?: number[] | string[] } }} options * @param {{ filter?: { systemIds?: number[] | string[], subsystemIds?: number[] | string[] } }} options
* */ * */
async export(Adapter, options) { async export(Adapter, options) {
const db = await equipmentRepository.getAll({ filter: options.filter }); const db = await equipmentRepository.getAll({ filter: options.filter });

View File

@ -0,0 +1,100 @@
export const EXCEL_MAIN_WS_HEADERS = [
"Наименование системы",
"Наименование внутренних подсистем.",
"Расположение",
"Документация",
"Электроплан",
"PLC",
"HMI",
"ПЧ",
"Промышленные ПК"
];
export const EXCEL_PC_WS_HEADERS = [
"Наименование АСУ",
"Вендер, модель ",
"Инвентарный номер ОС ПК или комплекса",
"Местоположение (цех\\АБК, ось\\ряд или кабинет)",
"Назначение",
"Имя ПК",
"Администратор в ОС (логин/Пароль)",
"Рабочий пользователь в ОС (логин/Пароль)",
"Подключение к промышленной сети. (ДА\\НЕТ) IP адрес, MAC адрес",
"Подключение к сети предприятия (ДА\\НЕТ) IP адрес, MAC адрес",
"Место установки. Замечания.",
"ИБП Потребность",
"ИБП Наличие",
"Последняя дата Обслуживания",
"Версия ОС",
"Установленное промышленное ПО",
"Администратор в ПО (логин/Пароль)",
"Исходник проекта ПО",
"Ссылка на исходник проекта",
"Резервная копия HDD, расположение"
];
export const EXCEL_PC_CONFIG_WS_HEADERS = [
"Наименование оборудования",
"Вендер, модель",
"Артикул",
"Процессор",
"Материнская плата",
"Сокет",
"Тип ОЗУ",
"Объем ОЗУ",
"Тип HDD",
"Объем HDD",
"Блок питания(мощность, тип, особенности)",
"Установленные карты расширения",
"Внешние устройства (тип)",
"Вентилятор ЦП",
"Доп.вентилятор на корпусе"
];
export const EXCEL_HMI_WS_HEADERS = [
"Наименование подсистемы",
"HMI",
"Артикул",
"Шкаф (по электроплану)",
"Расположение шкафа (физически)",
"Назначение",
"Администратор (логин/Пароль)",
"Рабочий пользователь (логин/Пароль)",
"Интерфейс для подключения (Интерфейс, протокол, коммуникация)",
"Настройки подключения",
"Месторасположения кабеля",
"Имя проекта",
"Ссылка на проект",
"Образ носителя",
"Ссылка на папку с образом"
];
export const EXCEL_PLC_WS_HEADERS = [
"Наименование подсистемы",
"PLC",
"Артикул",
"Шкаф",
"Расположение шкафа (физически)",
"Назначение",
"Интерфейс для подключения (Интерфейс, протокол, коммуникация)",
"Настройки подключения",
"Месторасположение кабеля",
"Имя проекта",
"Пароль PLC",
"Пароль Safety",
"Ссылка на проект"
];
export const EXCEL_FG_WS_HEADERS = [
"Наименование подсистемы",
"Наименование ПЧ",
"Артикул",
"Шкаф",
"Месторасположение шкафа",
"Назначение",
"Интерфейс для подключения (Интерфейс, протокол, коммуникация)",
"Настройки подключения",
"Месторасположение кабеля",
"Имя проекта конфигурации ПЧ (архива параметров)",
"Ссылка"
];

View File

@ -1,48 +1,405 @@
import ExcelJS from "exceljs"; import ExcelJS from "exceljs";
import {
EXCEL_FG_WS_HEADERS,
EXCEL_HMI_WS_HEADERS,
EXCEL_MAIN_WS_HEADERS,
EXCEL_PC_CONFIG_WS_HEADERS,
EXCEL_PC_WS_HEADERS,
EXCEL_PLC_WS_HEADERS
} from "./constants.js";
import * as utilNum from "#utils/num.js";
import * as utilStr from "#utils/str.js";
/** /**
* @import { ExporterAdapterInterface } from '../types.js' * @import { ExporterAdapterInterface } from '../types.js'
* @import { SystemDbRecord } from '#repositories/equipment.repository.js' * @import { FgModelDbRecord, HmiModelDbRecord, PCConfigDbRecord, PcEquipmentDbRecord, PlcModelDbRecord, SubSystemDbRecord, SystemDbRecord } from '#repositories/equipment.repository.js'
*/
/**
* @typedef {Map<string, { row: number }>} HeadingRowMap
* @typedef {Array<any[]>} WsData
*/ */
/** @implements {ExporterAdapterInterface} */ /** @implements {ExporterAdapterInterface} */
export class XlsExporterAdapter { export class XlsExporterAdapter {
/** @type {Partial<ExcelJS.AddWorksheetOptions>} */ /** @type {Partial<ExcelJS.AddWorksheetOptions>} */
static mainWsOptions = { views: [{ state: "frozen", ySplit: 1, zoomScale: 70 }] }; static worksheetsOptions = { views: [{ state: "frozen", ySplit: 1, zoomScale: 70 }] };
constructor() { constructor() {
this.wb = new ExcelJS.Workbook(); this.wb = new ExcelJS.Workbook();
this.ws = this.wb.addWorksheet("Перечень систем", XlsExporterAdapter.mainWsOptions); this.ws = this.wb.addWorksheet("Перечень систем", XlsExporterAdapter.worksheetsOptions);
this.PcWs = this.wb.addWorksheet("Перечень ПК", XlsExporterAdapter.worksheetsOptions);
this.pcConfigWs = this.wb.addWorksheet("Компоненты ПК", XlsExporterAdapter.worksheetsOptions);
this.hmiWs = this.wb.addWorksheet("HMI", XlsExporterAdapter.worksheetsOptions);
this.plcWs = this.wb.addWorksheet("PLC", XlsExporterAdapter.worksheetsOptions);
this.fgWs = this.wb.addWorksheet("ПЧ", XlsExporterAdapter.worksheetsOptions);
/** @type {Array<any[]>} */ /** @type {WsData} */
this.data = []; this.mainSheetData = [];
/** @type {WsData} */
this.pcSheetData = [];
/** @type {WsData} */
this.pcConfigSheetData = [];
/** @type {WsData} */
this.hmiSheetData = [];
/** @type {WsData} */
this.plcSheetData = [];
/** @type {WsData} */
this.fgSheetData = [];
/** @type {{ sheet: ExcelJS.Worksheet, colStart: string, colEnd: string, from: number; to: number }[]} */
this.mergeRanges = [];
} }
/** @param {SystemDbRecord[]} db */ /** @param {SystemDbRecord[]} db */
async export(db) { async export(db) {
/** @type {Map<string, { row: number }>} */ db.sort((a, b) => a.baseLocationName.localeCompare(b.baseLocationName, "ru", { sensitivity: "base" }));
const addedBaseLocationNames = new Map();
/** @type {{ colStart: string, colEnd: string, from: number; to: number }[]} */ const mainWsHeadings = this.fillMainWorksheet(db);
const mergeRanges = []; const pcWsHeadings = this.fillPcWorksheet(db);
const pcConfigWsHeadings = this.fillPcConfigWorksheet(db);
const hmiWsHeadings = this.fillHmisWorksheet(db);
const plcWsHeadings = this.fillPlcsWorksheet(db);
const fgWsHeadings = this.fillFgsWorksheet(db);
this.ws.getRow(1).values = EXCEL_MAIN_WS_HEADERS;
this.PcWs.getRow(1).values = EXCEL_PC_WS_HEADERS;
this.pcConfigWs.getRow(1).values = EXCEL_PC_CONFIG_WS_HEADERS;
this.hmiWs.getRow(1).values = EXCEL_HMI_WS_HEADERS;
this.plcWs.getRow(1).values = EXCEL_PLC_WS_HEADERS;
this.fgWs.getRow(1).values = EXCEL_FG_WS_HEADERS;
this.ws.autoFilter = `A1:I${this.mainSheetData.length + 1}`;
this.PcWs.autoFilter = `A1:T${this.pcSheetData.length + 1}`;
this.pcConfigWs.autoFilter = `A1:O${this.pcSheetData.length + 1}`;
this.hmiWs.autoFilter = `A1:O${this.pcSheetData.length + 1}`;
this.ws.addRows(this.mainSheetData);
this.PcWs.addRows(this.pcSheetData);
this.pcConfigWs.addRows(this.pcConfigSheetData);
this.hmiWs.addRows(this.hmiSheetData);
this.plcWs.addRows(this.plcSheetData);
this.fgWs.addRows(this.fgSheetData);
this.mergeRanges.forEach((r) => r.sheet.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.PcWs.getColumn(1).width = 30;
this.PcWs.getColumn(2).width = 40;
this.PcWs.getColumn(3).width = 20;
utilNum.range(4, 20).forEach((el) => (this.PcWs.getColumn(el).width = 18));
this.pcConfigWs.getColumn(1).width = 30;
this.pcConfigWs.getColumn(2).width = 35;
utilNum.range(3, 8).forEach((el) => (this.pcConfigWs.getColumn(el).width = 25));
this.pcConfigWs.getColumn(9).width = 40;
this.pcConfigWs.getColumn(10).width = 20;
utilNum.range(11, 15).forEach((el) => (this.pcConfigWs.getColumn(el).width = 30));
this.hmiWs.getColumn(1).width = 30;
this.hmiWs.getColumn(2).width = 45;
this.hmiWs.getColumn(3).width = 35;
utilNum.range(4, 15).forEach((el) => (this.hmiWs.getColumn(el).width = 16));
this.hmiWs.getColumn(9).width = 35;
this.plcWs.getColumn(1).width = 30;
this.plcWs.getColumn(2).width = 40;
this.plcWs.getColumn(3).width = 20;
utilNum.range(4, 13).forEach((el) => (this.plcWs.getColumn(el).width = 16));
this.fgWs.getColumn(6).width = 30;
this.fgWs.getColumn(7).width = 35;
this.fgWs.getColumn(1).width = 30;
this.fgWs.getColumn(2).width = 40;
this.fgWs.getColumn(3).width = 20;
utilNum.range(4, 11).forEach((el) => (this.fgWs.getColumn(el).width = 16));
this.fgWs.getColumn(6).width = 25;
this.fgWs.getColumn(7).width = 35;
this.setSheetCellStyles(this.ws);
this.setSheetCellStyles(this.PcWs);
this.setSheetCellStyles(this.pcConfigWs);
this.setSheetCellStyles(this.hmiWs);
this.setSheetCellStyles(this.plcWs);
this.setSheetCellStyles(this.fgWs);
this.setHeadingCellsStyles(this.ws, mainWsHeadings);
this.setHeadingCellsStyles(this.PcWs, pcWsHeadings);
this.setHeadingCellsStyles(this.pcConfigWs, pcConfigWsHeadings);
this.setHeadingCellsStyles(this.hmiWs, hmiWsHeadings);
this.setHeadingCellsStyles(this.plcWs, plcWsHeadings);
this.setHeadingCellsStyles(this.fgWs, fgWsHeadings);
const excelBuffer = await this.wb.xlsx.writeBuffer();
/** @type {Buffer} */
return excelBuffer;
}
/**
* @param {SystemDbRecord[]} db
*/
fillPcWorksheet(db) {
/** @type {HeadingRowMap} */
const headings = new Map();
let currRow = 2;
for (const system of db) {
const subsystems = system.subsystems || [];
const pcCount = subsystems.reduce((count, curr) => (curr?.pcs?.length || 0) + count, 0);
if (subsystems.length === 0) continue;
if (pcCount > 0) {
const isHeadingInserted = this.addBaseLocHeading({
ws: this.PcWs,
data: this.pcSheetData,
currRow,
headings,
name: system.baseLocationName,
colStart: "A",
colEnd: "T"
});
if (isHeadingInserted) currRow++;
}
for (const subsystem of subsystems) {
const pcItems = subsystem?.pcs || [];
if (pcItems.length === 0) continue;
pcItems.forEach((pc) => {
this.addPcRow(pc, system, subsystem);
currRow++;
});
}
}
return headings;
}
/**
* @param {SystemDbRecord[]} db
*/
fillPcConfigWorksheet(db) {
/** @type {HeadingRowMap} */
const headings = new Map();
let currRow = 2;
for (const system of db) {
const subsystems = system.subsystems || [];
const pcCount = subsystems.reduce((count, curr) => (curr?.pcs?.length || 0) + count, 0);
if (subsystems.length === 0) continue;
if (pcCount > 0) {
const isHeadingInserted = this.addBaseLocHeading({
ws: this.pcConfigWs,
data: this.pcConfigSheetData,
currRow,
headings,
name: system.baseLocationName,
colStart: "A",
colEnd: "O"
});
if (isHeadingInserted) currRow++;
}
for (const subsystem of subsystems) {
const pcItems = subsystem?.pcs || [];
if (pcItems.length === 0) continue;
pcItems.forEach((pc) => {
const configs = pc?.config || [];
if (configs.length > 0) {
configs.forEach((c) => {
this.addPcConfigRow(c, subsystem);
currRow++;
});
}
});
}
}
return headings;
}
/**
* @param {SystemDbRecord[]} db
*/
fillHmisWorksheet(db) {
/** @type {HeadingRowMap} */
const headings = new Map();
let currRow = 2;
for (const system of db) {
const subsystems = system.subsystems || [];
const pcCount = subsystems.reduce((count, curr) => (curr?.hmis?.length || 0) + count, 0);
if (subsystems.length === 0) continue;
if (pcCount > 0) {
const isHeadingInserted = this.addBaseLocHeading({
ws: this.hmiWs,
data: this.hmiSheetData,
currRow,
headings,
name: system.baseLocationName,
colStart: "A",
colEnd: "O"
});
if (isHeadingInserted) currRow++;
}
for (const subsystem of subsystems) {
const hmiItems = subsystem?.hmis || [];
if (hmiItems.length === 0) continue;
hmiItems.forEach((hmi) => {
// console.log(hmi);
this.addHmiRow(hmi, subsystem);
currRow++;
});
}
}
return headings;
}
/**
* @param {SystemDbRecord[]} db
*/
fillPlcsWorksheet(db) {
/** @type {HeadingRowMap} */
const headings = new Map();
let currRow = 2;
for (const system of db) {
const subsystems = system.subsystems || [];
const plcCount = subsystems.reduce((count, curr) => (curr?.plcs?.length || 0) + count, 0);
if (subsystems.length === 0) continue;
if (plcCount > 0) {
const isHeadingInserted = this.addBaseLocHeading({
ws: this.plcWs,
data: this.plcSheetData,
currRow,
headings,
name: system.baseLocationName,
colStart: "A",
colEnd: "M"
});
if (isHeadingInserted) currRow++;
}
for (const subsystem of subsystems) {
const plcItems = subsystem?.plcs || [];
if (plcItems.length === 0) continue;
plcItems.forEach((plc) => {
// console.log(hmi);
this.addPlcRow(plc, subsystem);
currRow++;
});
}
}
return headings;
}
/**
* @param {SystemDbRecord[]} db
*/
fillFgsWorksheet(db) {
/** @type {HeadingRowMap} */
const headings = new Map();
let currRow = 2;
for (const system of db) {
const subsystems = system.subsystems || [];
const fgCount = subsystems.reduce((count, curr) => (curr?.fgs?.length || 0) + count, 0);
if (subsystems.length === 0) continue;
if (fgCount > 0) {
const isHeadingInserted = this.addBaseLocHeading({
ws: this.fgWs,
data: this.fgSheetData,
currRow,
headings,
name: system.baseLocationName,
colStart: "A",
colEnd: "K"
});
if (isHeadingInserted) currRow++;
}
for (const subsystem of subsystems) {
const fgsItems = subsystem?.fgs || [];
if (fgsItems.length === 0) continue;
fgsItems.forEach((fg) => {
this.addFgRow(fg, subsystem);
currRow++;
});
}
}
return headings;
}
/**
* @param {SystemDbRecord[]} db
*/
fillMainWorksheet(db) {
/** @type {HeadingRowMap} */
const headings = new Map();
let currentRow = 2; let currentRow = 2;
for (const system of db) { for (const system of db) {
const subsystems = system.subsystems || []; const subsystems = system.subsystems || [];
// Подсистема не найдены, добавляет название системы с пустыми ячейками // Подсистемы не найдены, добавляет название системы с пустыми ячейками
if (subsystems.length === 0) { if (subsystems.length === 0) {
this.data.push([system.name, ...new Array(8).fill("", 0, 8)]); this.mainSheetData.push([system.name, ...new Array(8).fill("", 0, 8)]);
currentRow++; currentRow++;
continue; continue;
} }
// Заголовок для расположения систем // Заголовок для расположения систем
if (!addedBaseLocationNames.has(system.baseLocationName)) { if (!headings.has(system.baseLocationName)) {
addedBaseLocationNames.set(system.baseLocationName, { row: currentRow }); headings.set(system.baseLocationName, { row: currentRow });
this.data.push([system.baseLocationName]); this.mainSheetData.push([system.baseLocationName]);
mergeRanges.push({ colStart: "A", colEnd: "I", from: currentRow, to: currentRow }); this.mergeRanges.push({ sheet: this.ws, colStart: "A", colEnd: "I", from: currentRow, to: currentRow });
currentRow++; currentRow++;
} }
@ -57,7 +414,7 @@ export class XlsExporterAdapter {
const fgs = subsystem?.fgs || []; const fgs = subsystem?.fgs || [];
const pcs = subsystem?.pcs || []; const pcs = subsystem?.pcs || [];
this.data.push([ this.mainSheetData.push([
system.name, system.name,
subsystem.name, subsystem.name,
`${system.baseLocationName} ${subsystem.locationName}`, `${system.baseLocationName} ${subsystem.locationName}`,
@ -72,39 +429,191 @@ export class XlsExporterAdapter {
currentRow++; currentRow++;
}); });
mergeRanges.push({ colStart: "A", colEnd: "A", from: startRow, to: currentRow - 1 }); this.mergeRanges.push({ sheet: this.ws, colStart: "A", colEnd: "A", from: startRow, to: currentRow - 1 });
} }
this.ws.getRow(1).values = [ return headings;
"Наименование системы", }
"Наименование внутренних подсистем.",
"Расположение",
"Документация",
"Электроплан",
"PLC",
"HMI",
"ПЧ",
"Промышленные ПК"
];
this.ws.autoFilter = `A1:I${this.data.length + 1}`; /**
* @param {PCConfigDbRecord} config
* @param {SubSystemDbRecord} subsystem
*/
addPcConfigRow(config, subsystem) {
this.pcConfigSheetData.push([
subsystem.name || "",
config.modelName || "",
config.partNum || "",
config.cpu || "",
config.motherboard || "",
config.socket || "",
config.mem || "",
config.memVolume || "",
config.hdd || "",
config.hddVolume || "",
config.powerUnit || "",
config.expansionCards || "",
config.externalDevices || "",
config.cpuFan || "",
config.caseFan || ""
]);
}
this.ws.addRows(this.data); /**
*
* @param {PcEquipmentDbRecord} pc
* @param {SystemDbRecord} system
* @param {SubSystemDbRecord} subsystem
*/
addPcRow(pc, system, subsystem) {
const hasProjectSourceLink = Boolean(pc?.projectSourceLink) && utilStr.isStrLink(pc?.projectSourceLink || "");
const hasProjectArchieveLink = Boolean(pc?.projectArchieveLink) && utilStr.isStrLink(pc?.projectArchieveLink || "");
const modelNames = pc?.config?.map((c) => c.modelName).join("\n");
mergeRanges.forEach((r) => this.ws.mergeCells(`${r.colStart}${r.from}:${r.colEnd}${r.to}`)); this.pcSheetData.push([
subsystem.name,
modelNames || "",
pc.invNumber || "",
`${system.baseLocationName} ${subsystem.locationName}`,
pc.purpose || "",
pc.name || "",
pc.adminLoginPass || "",
pc.workerLoginPass || "",
pc.promConn || "",
pc.compConn || "",
pc.locAndRemark || "",
pc.upsReq || "",
pc.upsAvail || "",
pc.lastServiceDate || "",
pc.osVersion || "",
pc.indSoftInst || "",
pc.softAdminLoginPass || "",
pc.projectSource || "",
hasProjectSourceLink ? { text: "Ссылка", hyperlink: pc.projectSourceLink } : pc.projectSourceLink || "",
hasProjectArchieveLink ? { text: "Ссылка", hyperlink: pc.projectArchieveLink } : pc.projectArchieveLink || ""
]);
}
this.ws.getColumn(1).width = 50; /**
this.ws.getColumn(2).width = 50; * @param {HmiModelDbRecord} hmi
this.ws.getColumn(3).width = 35; * @param {SubSystemDbRecord} subsystem
this.ws.getColumn(4).width = 17; */
this.ws.getColumn(5).width = 17; addHmiRow(hmi, subsystem) {
this.ws.getColumn(6).width = 45; const hasProjectLink = Boolean(hmi?.projectLink) && utilStr.isStrLink(hmi?.projectLink || "");
this.ws.getColumn(7).width = 45; const hasImageLink = Boolean(hmi?.imageLink) && utilStr.isStrLink(hmi?.imageLink || "");
this.ws.getColumn(8).width = 45;
this.ws.getColumn(9).width = 30;
this.ws.getRow(1).height = 45;
this.ws.eachRow((row, rowNumber) => { this.hmiSheetData.push([
subsystem.name,
hmi.nameModel || "",
hmi.partNum || "",
hmi.cabinet || "",
hmi.cabinetLocation || "",
hmi.purpose || "",
hmi.adminLoginPass || "",
hmi.workerLoginPass || "",
hmi.interface || "",
hmi.connectionSettings || "",
hmi.cableLocation || "",
hmi.projectName || "",
hasProjectLink ? { text: "Ссылка", hyperlink: hmi.projectLink } : hmi.projectLink || "",
hmi.diskImage || "",
hasImageLink ? { text: "Ссылка", hyperlink: hmi.imageLink } : hmi.imageLink || ""
]);
}
/**
* @param {PlcModelDbRecord} plc
* @param {SubSystemDbRecord} subsystem
*/
addPlcRow(plc, subsystem) {
const hasProjectLink = Boolean(plc?.projectLink) && utilStr.isStrLink(plc?.projectLink || "");
this.plcSheetData.push([
subsystem.name,
plc.nameModel || "",
plc.partNum || "",
plc.cabinet || "",
plc.cabinetLocation || "",
plc.purpose || "",
plc.interface || "",
plc.connectionSettings || "",
plc.cableLocation || "",
plc.projectName || "",
plc.plcPass || "",
plc.safetyPass || "",
hasProjectLink ? { text: "Ссылка", hyperlink: plc.projectLink } : plc.projectLink || ""
]);
}
/**
* @param {FgModelDbRecord} fg
* @param {SubSystemDbRecord} subsystem
*/
addFgRow(fg, subsystem) {
const hasProjectLink = Boolean(fg?.projectLink) && utilStr.isStrLink(fg?.projectLink || "");
this.fgSheetData.push([
subsystem.name,
fg.nameModel || "",
fg.partNum || "",
fg.cabinet || "",
fg.cabinetLocation || "",
fg.purpose || "",
fg.interface || "",
fg.connectionSettings || "",
fg.cableLocation || "",
fg.configName || "",
hasProjectLink ? { text: "Ссылка", hyperlink: fg.projectLink } : fg.projectLink || ""
]);
}
/**
* @param {{ ws: ExcelJS.Worksheet, data: WsData, headings: HeadingRowMap, name: string, currRow: number, colStart: string, colEnd: string }} info
*/
addBaseLocHeading({ ws, data, headings, name, currRow, colStart, colEnd }) {
if (!headings.has(name)) {
headings.set(name, { row: currRow });
data.push([name]);
this.mergeRanges.push({ sheet: ws, colStart, colEnd, from: currRow, to: currRow });
return true;
}
return false;
}
/**
* @param {ExcelJS.Worksheet} ws
* @param {Map<string, { row: number }>} headings
*/
setHeadingCellsStyles(ws, headings) {
headings.forEach((h) => {
const row = ws.getRow(h.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" }
};
});
});
}
/**
* @param {ExcelJS.Worksheet} ws
*/
setSheetCellStyles(ws) {
ws.eachRow((row, rowNumber) => {
row.eachCell({ includeEmpty: true }, (cell) => { row.eachCell({ includeEmpty: true }, (cell) => {
cell.alignment = { cell.alignment = {
wrapText: true, wrapText: true,
@ -167,33 +676,5 @@ export class XlsExporterAdapter {
} }
}); });
}); });
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() {}
} }

View File

@ -1 +1,3 @@
export * from "./date.js"; export * from "./date.js";
export * from "./num.js";
export * from "./str.js";

11
utils/num.js Normal file
View File

@ -0,0 +1,11 @@
export function range(from = 0, to = 1) {
const ret = [];
to++;
for (const idx of Array(to - from).keys()) {
ret[idx] = from + idx;
}
return ret;
}

2
utils/str.js Normal file
View File

@ -0,0 +1,2 @@
/** @param {string} str */
export const isStrLink = (str) => RegExp(/^(https?:\/\/|file:\/\/)/gm).test(str);