From 29ebc98d0ee7e67b6cfeff417594cf272ee71b35 Mon Sep 17 00:00:00 2001 From: sergeyyarkov Date: Fri, 28 Aug 2026 08:33:32 +0300 Subject: [PATCH] =?UTF-8?q?=D1=8D=D0=BA=D1=81=D0=BF=D0=BE=D1=80=D1=82=20?= =?UTF-8?q?=D0=BF=D0=BE=20=D0=BF=D0=BE=D0=B4=D1=81=D0=B8=D1=81=D1=82=D0=B5?= =?UTF-8?q?=D0=BC=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- handlers/asu_table/getAsuTable.handler.js | 5 +- repositories/equipment.repository.js | 218 +++++++- services/dbExporter.service.js | 2 +- services/exporters/constants.js | 100 ++++ services/exporters/excel.exporter.js | 621 +++++++++++++++++++--- utils/index.js | 2 + utils/num.js | 11 + utils/str.js | 2 + 8 files changed, 874 insertions(+), 87 deletions(-) create mode 100644 services/exporters/constants.js create mode 100644 utils/num.js create mode 100644 utils/str.js diff --git a/handlers/asu_table/getAsuTable.handler.js b/handlers/asu_table/getAsuTable.handler.js index 4087c74..ec765ec 100644 --- a/handlers/asu_table/getAsuTable.handler.js +++ b/handlers/asu_table/getAsuTable.handler.js @@ -12,8 +12,11 @@ import { dbExporterService, XlsExporterAdapter } from "#root/services/dbExporter export default async (ctx) => { const { res, searchParams } = ctx; const systemIdsParam = searchParams.get("systemIds")?.split(","); + const subSystemIdsParam = searchParams.get("subSystemIds")?.split(","); 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-Disposition", `attachment; filename=${filename}; filename*=UTF-8''${filename}`); diff --git a/repositories/equipment.repository.js b/repositories/equipment.repository.js index c1b41a1..0f03ea9 100644 --- a/repositories/equipment.repository.js +++ b/repositories/equipment.repository.js @@ -2,13 +2,108 @@ import { UOW } from "./index.js"; 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, * docLink?: string | null, electrDiagLink?: string | null, - * locationName?: string | null, plcs?: Array, - * fgs?: Array, hmis: Array, - * pcs?: Array + * locationName?: string | null, plcs?: Array, + * fgs?: Array, hmis: Array, + * pcs?: Array * }} SubSystemDbRecord; * * @typedef {{ id: string, name: string, baseLocationName: string, subsystems: Array }} SystemDbRecord; @@ -16,23 +111,39 @@ import sql from "mssql"; export const equipmentRepository = { /** - * @param {{ filter?: { systemIds?: string[] | number[] } }} [options] + * @param {{ filter?: { systemIds?: string[] | number[], subsystemIds? : string[] | number[] } }} [options] * @returns {Promise>} */ async getAll(options) { const request = UOW.request; const systemIds = options?.filter?.systemIds; + const subsystemIds = options?.filter?.subsystemIds; const hasFilterBySystemIds = systemIds !== undefined && systemIds.length !== 0; + const hasFilterBySubsystemIds = subsystemIds !== undefined && subsystemIds.length !== 0; - let whereClause = ""; + const whereParts = []; if (hasFilterBySystemIds) { const idsString = systemIds.map(Number).filter(Boolean).join(","); request.input("sysIdsString", sql.VarChar, idsString); - - whereClause = `WHERE systemsTbl.id IN (SELECT value FROM STRING_SPLIT(@sysIdsString, ','))`; + whereParts.push(`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 = ` SELECT systemsTbl.id as id, systemsTbl.name as name, @@ -44,7 +155,20 @@ export const equipmentRepository = { locTbl.name as locationName, (select 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 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 @@ -52,8 +176,22 @@ export const equipmentRepository = { FOR JSON PATH) as plcs, (select - hmiTbl.hmi_name as name, - hmiModelTbl.name as nameModel + hmiTbl.hmi_name as name, + 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 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 @@ -61,15 +199,64 @@ export const equipmentRepository = { FOR JSON PATH) as hmis, (select - fgTbl.fg_name as name, - fgModelTbl.name as nameModel + fgTbl.fg_name as name, + 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 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 where jtFG.subsystemlist_id = subSystemsTbl.id 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 left join asu_system_api_subsystemlist_pc as jtPC on jtPC.subsystemlist_id = subSystemsTbl.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 left join asu_system_api_location as locTbl on locTbl.id = subSystemsTbl.location_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 from asu_system_api_systemlist as systemsTbl inner join asu_system_api_baselocation as baseLocTbl on systemsTbl.location_id = baseLocTbl.id diff --git a/services/dbExporter.service.js b/services/dbExporter.service.js index f85fc95..f369e5c 100644 --- a/services/dbExporter.service.js +++ b/services/dbExporter.service.js @@ -5,7 +5,7 @@ import { equipmentRepository } from "#root/repositories/equipment.repository.js" export const dbExporterService = { /** * @param { new () => ExporterAdapterInterface } Adapter - * @param {{ filter?: { systemIds?: number[] | string[] } }} options + * @param {{ filter?: { systemIds?: number[] | string[], subsystemIds?: number[] | string[] } }} options * */ async export(Adapter, options) { const db = await equipmentRepository.getAll({ filter: options.filter }); diff --git a/services/exporters/constants.js b/services/exporters/constants.js new file mode 100644 index 0000000..6feffc1 --- /dev/null +++ b/services/exporters/constants.js @@ -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 = [ + "Наименование подсистемы", + "Наименование ПЧ", + "Артикул", + "Шкаф", + "Месторасположение шкафа", + "Назначение", + "Интерфейс для подключения (Интерфейс, протокол, коммуникация)", + "Настройки подключения", + "Месторасположение кабеля", + "Имя проекта конфигурации ПЧ (архива параметров)", + "Ссылка" +]; diff --git a/services/exporters/excel.exporter.js b/services/exporters/excel.exporter.js index b2e32f2..ff893d1 100644 --- a/services/exporters/excel.exporter.js +++ b/services/exporters/excel.exporter.js @@ -1,48 +1,405 @@ 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 { SystemDbRecord } from '#repositories/equipment.repository.js' + * @import { FgModelDbRecord, HmiModelDbRecord, PCConfigDbRecord, PcEquipmentDbRecord, PlcModelDbRecord, SubSystemDbRecord, SystemDbRecord } from '#repositories/equipment.repository.js' + */ + +/** + * @typedef {Map} HeadingRowMap + * @typedef {Array} WsData */ /** @implements {ExporterAdapterInterface} */ export class XlsExporterAdapter { /** @type {Partial} */ - static mainWsOptions = { views: [{ state: "frozen", ySplit: 1, zoomScale: 70 }] }; + static worksheetsOptions = { views: [{ state: "frozen", ySplit: 1, zoomScale: 70 }] }; constructor() { 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} */ - this.data = []; + /** @type {WsData} */ + 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 */ async export(db) { - /** @type {Map} */ - const addedBaseLocationNames = new Map(); + db.sort((a, b) => a.baseLocationName.localeCompare(b.baseLocationName, "ru", { sensitivity: "base" })); - /** @type {{ colStart: string, colEnd: string, from: number; to: number }[]} */ - const mergeRanges = []; + const mainWsHeadings = this.fillMainWorksheet(db); + 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; for (const system of db) { const subsystems = system.subsystems || []; - // Подсистема не найдены, добавляет название системы с пустыми ячейками + // Подсистемы не найдены, добавляет название системы с пустыми ячейками 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++; 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 }); + if (!headings.has(system.baseLocationName)) { + headings.set(system.baseLocationName, { row: currentRow }); + this.mainSheetData.push([system.baseLocationName]); + this.mergeRanges.push({ sheet: this.ws, colStart: "A", colEnd: "I", from: currentRow, to: currentRow }); currentRow++; } @@ -57,7 +414,7 @@ export class XlsExporterAdapter { const fgs = subsystem?.fgs || []; const pcs = subsystem?.pcs || []; - this.data.push([ + this.mainSheetData.push([ system.name, subsystem.name, `${system.baseLocationName} ${subsystem.locationName}`, @@ -72,39 +429,191 @@ export class XlsExporterAdapter { 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 = [ - "Наименование системы", - "Наименование внутренних подсистем.", - "Расположение", - "Документация", - "Электроплан", - "PLC", - "HMI", - "ПЧ", - "Промышленные ПК" - ]; + return headings; + } - 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; - 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; + /** + * @param {HmiModelDbRecord} hmi + * @param {SubSystemDbRecord} subsystem + */ + addHmiRow(hmi, subsystem) { + const hasProjectLink = Boolean(hmi?.projectLink) && utilStr.isStrLink(hmi?.projectLink || ""); + const hasImageLink = Boolean(hmi?.imageLink) && utilStr.isStrLink(hmi?.imageLink || ""); - 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} 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) => { cell.alignment = { 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() {} } diff --git a/utils/index.js b/utils/index.js index 82e6eff..025786e 100644 --- a/utils/index.js +++ b/utils/index.js @@ -1 +1,3 @@ export * from "./date.js"; +export * from "./num.js"; +export * from "./str.js"; diff --git a/utils/num.js b/utils/num.js new file mode 100644 index 0000000..6a1cf01 --- /dev/null +++ b/utils/num.js @@ -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; +} diff --git a/utils/str.js b/utils/str.js new file mode 100644 index 0000000..b3a20af --- /dev/null +++ b/utils/str.js @@ -0,0 +1,2 @@ +/** @param {string} str */ +export const isStrLink = (str) => RegExp(/^(https?:\/\/|file:\/\/)/gm).test(str);