From e9b5905117a14928158c0e9d7f20208b39305b01 Mon Sep 17 00:00:00 2001 From: suixy <2277317060@qq.com> Date: Thu, 28 May 2026 16:19:53 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=AE=A1=E9=87=8F=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E5=92=8C=E6=B5=8B=E6=8E=A7=E7=82=B9=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ems/measurementControlPointService.js | 178 ++++++++ src/services/ems/measuringDeviceService.js | 423 ++++++++++++++++++ 2 files changed, 601 insertions(+) create mode 100644 src/services/ems/measurementControlPointService.js create mode 100644 src/services/ems/measuringDeviceService.js diff --git a/src/services/ems/measurementControlPointService.js b/src/services/ems/measurementControlPointService.js new file mode 100644 index 0000000..3babc86 --- /dev/null +++ b/src/services/ems/measurementControlPointService.js @@ -0,0 +1,178 @@ +const { query, queryOne } = require('../../db'); +const { nextId } = require('../../id'); + +const TABLE = 'measurement_control_point'; + +function pageParams(input = {}) { + const rawPageNum = Number(input.pageNum || 1); + const rawPageSize = Number(input.pageSize || 10); + const pageNum = Number.isFinite(rawPageNum) ? Math.max(Math.trunc(rawPageNum), 1) : 1; + const pageSize = Number.isFinite(rawPageSize) ? Math.max(Math.trunc(rawPageSize), 1) : 10; + return { pageNum, pageSize, offset: (pageNum - 1) * pageSize }; +} + +function tableData(rows, total) { + return { code: 200, msg: '查询成功', rows, total }; +} + +function mapRow(row) { + if (!row) return null; + return { + id: row.id, + pointCode: row.point_code, + pointName: row.point_name, + measuringDeviceId: row.measuring_device_id, + measuringDeviceName: row.measuring_device_name, + parameterName: row.parameter_name, + parameterNameLabel: row.parameter_name_label, + standardUnit: row.standard_unit, + conversionFactor: row.conversion_factor === null || row.conversion_factor === undefined ? null : Number(row.conversion_factor), + originalUnit: row.original_unit, + collectionCycle: row.collection_cycle, + dataType: row.data_type, + registerAddress: row.register_address, + status: row.status, + createTime: row.create_time, + updateTime: row.update_time, + delFlag: row.del_flag + }; +} + +function normalizeBody(body = {}) { + const conversionFactor = body.conversionFactor === '' || body.conversionFactor === undefined || body.conversionFactor === null + ? null + : Number(body.conversionFactor); + if (conversionFactor !== null && !Number.isFinite(conversionFactor)) { + throw new Error('换算倍率必须是数字'); + } + if (!body.measuringDeviceId) throw new Error('所属计量器具不能为空'); + return { + pointCode: body.pointCode || null, + pointName: body.pointName || null, + measuringDeviceId: body.measuringDeviceId, + parameterName: body.parameterName || null, + standardUnit: body.standardUnit || null, + conversionFactor, + originalUnit: body.originalUnit || null, + collectionCycle: body.collectionCycle || null, + dataType: body.dataType || null, + registerAddress: body.registerAddress || null, + status: body.status || null + }; +} + +const selectSql = ` + select mcp.*, + md.device_name as measuring_device_name, + em.metric_name as parameter_name_label + from ${TABLE} mcp + left join measuring_device md on md.id = mcp.measuring_device_id + left join ( + select type, + collection_field, + substring_index(group_concat(metric_name order by id separator '||'), '||', 1) as metric_name + from energy_metric_library + where collection_field is not null + and collection_field <> '' + group by type, collection_field + ) em on em.type collate utf8mb4_general_ci = md.device_type collate utf8mb4_general_ci + and em.collection_field collate utf8mb4_general_ci = mcp.parameter_name collate utf8mb4_general_ci +`; + +async function list(params = {}) { + const page = pageParams(params); + const where = ["mcp.del_flag = '0'"]; + const sqlParams = {}; + if (params.pointCode) { + where.push('mcp.point_code like :pointCode'); + sqlParams.pointCode = `%${String(params.pointCode).trim()}%`; + } + if (params.pointName) { + where.push('mcp.point_name like :pointName'); + sqlParams.pointName = `%${String(params.pointName).trim()}%`; + } + if (params.measuringDeviceId) { + where.push('mcp.measuring_device_id = :measuringDeviceId'); + sqlParams.measuringDeviceId = String(params.measuringDeviceId); + } + if (params.parameterName) { + where.push('mcp.parameter_name = :parameterName'); + sqlParams.parameterName = String(params.parameterName); + } + const whereSql = `where ${where.join(' and ')}`; + const totalRow = await queryOne(`select count(*) as total from ${TABLE} mcp ${whereSql}`, sqlParams); + const rows = await query( + `${selectSql} + ${whereSql} + order by mcp.create_time desc, mcp.id desc + limit ${page.pageSize} offset ${page.offset}`, + sqlParams + ); + return tableData(rows.map(mapRow), Number(totalRow?.total || 0)); +} + +async function get(id) { + if (!id) throw new Error('ID不能为空'); + return mapRow(await queryOne( + `${selectSql} + where mcp.id = :id and mcp.del_flag = '0' + limit 1`, + { id } + )); +} + +async function add(body = {}) { + const id = body.id ? String(body.id) : nextId(); + const data = normalizeBody(body); + await query( + `insert into ${TABLE} + (id, point_code, point_name, measuring_device_id, parameter_name, standard_unit, conversion_factor, + original_unit, collection_cycle, data_type, register_address, status, create_time, update_time, del_flag) + values + (:id, :pointCode, :pointName, :measuringDeviceId, :parameterName, :standardUnit, :conversionFactor, + :originalUnit, :collectionCycle, :dataType, :registerAddress, :status, now(), now(), '0')`, + { id, ...data } + ); + return id; +} + +async function update(body = {}) { + if (!body.id) throw new Error('ID不能为空'); + const data = normalizeBody(body); + await query( + `update ${TABLE} + set point_code = :pointCode, + point_name = :pointName, + measuring_device_id = :measuringDeviceId, + parameter_name = :parameterName, + standard_unit = :standardUnit, + conversion_factor = :conversionFactor, + original_unit = :originalUnit, + collection_cycle = :collectionCycle, + data_type = :dataType, + register_address = :registerAddress, + status = :status, + update_time = now() + where id = :id and del_flag = '0'`, + { id: body.id, ...data } + ); +} + +async function remove(ids) { + const list = String(ids || '').split(',').map((item) => item.trim()).filter(Boolean); + if (!list.length) return; + const params = {}; + const placeholders = list.map((id, index) => { + params[`id${index}`] = id; + return `:id${index}`; + }); + await query( + `update ${TABLE} + set del_flag = '1', + update_time = now() + where id in (${placeholders.join(', ')})`, + params + ); +} + +module.exports = { list, get, add, update, remove }; diff --git a/src/services/ems/measuringDeviceService.js b/src/services/ems/measuringDeviceService.js new file mode 100644 index 0000000..72e51dd --- /dev/null +++ b/src/services/ems/measuringDeviceService.js @@ -0,0 +1,423 @@ +const { query, queryOne } = require('../../db'); +const { nextId } = require('../../id'); + +function pageParams(input = {}) { + const rawPageNum = Number(input.pageNum || 1); + const rawPageSize = Number(input.pageSize || 10); + const pageNum = Number.isFinite(rawPageNum) ? Math.max(Math.trunc(rawPageNum), 1) : 1; + const pageSize = Number.isFinite(rawPageSize) ? Math.max(Math.trunc(rawPageSize), 1) : 10; + return { + pageNum, + pageSize, + offset: (pageNum - 1) * pageSize + }; +} + +function tableData(rows, total) { + return { + code: 200, + msg: '查询成功', + rows, + total + }; +} + +function mapRow(row) { + if (!row) return null; + return { + id: row.id, + deviceId: row.device_id, + deviceName: row.device_name, + deviceCode: row.device_code, + installPosition: row.install_position, + installPositionName: row.install_position_name, + deviceType: row.device_type, + measuringUnit: row.measuring_unit_summary || row.measuring_unit, + standardUnit: row.standard_unit, + standardUnitName: row.standard_unit_name_summary || row.standard_unit_name, + conversionFactor: row.conversion_factor === null || row.conversion_factor === undefined ? null : Number(row.conversion_factor), + units: row.units || [], + specificationModel: row.specification_model, + manufacturer: row.manufacturer, + factoryNo: row.factory_no, + accuracyLevel: row.accuracy_level, + measureRange: row.measure_range, + communicationProtocol: row.communication_protocol, + communicationAddress: row.communication_address, + collectionCycle: row.collection_cycle, + status: row.status, + lastVerificationDate: row.last_verification_date, + responsibleDept: row.responsible_dept, + responsiblePerson: row.responsible_person, + remark: row.remark, + createTime: row.create_time, + updateTime: row.update_time, + delFlag: row.del_flag + }; +} + +function normalizeUnits(body = {}) { + const source = Array.isArray(body.units) && body.units.length + ? body.units + : [{ + measuringUnit: body.measuringUnit, + standardUnit: body.standardUnit, + conversionFactor: body.conversionFactor + }]; + return source + .map((item) => { + const conversionFactor = item.conversionFactor === '' || item.conversionFactor === undefined || item.conversionFactor === null + ? null + : Number(item.conversionFactor); + if (conversionFactor !== null && !Number.isFinite(conversionFactor)) { + throw new Error('换算倍率必须是数字'); + } + return { + id: item.id || nextId(), + measuringUnit: item.measuringUnit || null, + standardUnit: item.standardUnit || item.parameterName || null, + conversionFactor + }; + }) + .filter((item) => item.measuringUnit || item.standardUnit || item.conversionFactor !== null); +} + +function normalizeBody(body = {}) { + const units = normalizeUnits(body); + const firstUnit = units[0] || {}; + return { + deviceId: body.deviceId || null, + deviceName: body.deviceName || null, + deviceCode: body.deviceCode || null, + installPosition: body.installPosition || null, + deviceType: body.deviceType === undefined || body.deviceType === null || body.deviceType === '' ? null : String(body.deviceType), + measuringUnit: firstUnit.measuringUnit || null, + standardUnit: firstUnit.standardUnit || null, + conversionFactor: firstUnit.conversionFactor ?? null, + units, + specificationModel: body.specificationModel || null, + manufacturer: body.manufacturer || null, + factoryNo: body.factoryNo || null, + accuracyLevel: body.accuracyLevel || null, + measureRange: body.measureRange || null, + communicationProtocol: body.communicationProtocol || null, + communicationAddress: body.communicationAddress || null, + collectionCycle: body.collectionCycle || null, + status: body.status || null, + lastVerificationDate: body.lastVerificationDate || null, + responsibleDept: body.responsibleDept || null, + responsiblePerson: body.responsiblePerson || null, + remark: body.remark || null + }; +} + +const TABLE = 'measuring_device'; +const selectSql = `select md.* from ${TABLE} md`; + +async function loadFloorPathMap() { + const rows = await query( + `select id, pid, name + from floorInfo + where del_flag = '0'` + ); + const byId = new Map(rows.map((row) => [String(row.id), row])); + const pathMap = new Map(); + + function pathOf(id) { + const key = String(id || ''); + if (!key) return ''; + if (pathMap.has(key)) return pathMap.get(key); + const names = []; + let current = byId.get(key); + const visited = new Set(); + while (current && !visited.has(String(current.id))) { + visited.add(String(current.id)); + if (current.name) names.unshift(current.name); + current = byId.get(String(current.pid)); + } + const path = names.join(' / '); + pathMap.set(key, path); + return path; + } + + rows.forEach((row) => pathOf(row.id)); + return pathMap; +} + +async function loadMetricNameMap() { + const rows = await query( + `select type, metric_name, collection_field, unit + from energy_metric_library` + ); + return new Map(rows.map((row) => [ + `${String(row.type)}:${row.collection_field}`, + `${row.metric_name || row.collection_field || ''}${row.unit ? `(${row.unit})` : ''}` + ])); +} + +function unitLabel(unit, metricNameMap, deviceType) { + const standardUnitName = metricNameMap.get(`${String(deviceType)}:${unit.standard_unit}`) || unit.standard_unit || ''; + const measuringUnit = unit.measuring_unit || ''; + const factor = unit.conversion_factor === null || unit.conversion_factor === undefined ? '' : Number(unit.conversion_factor); + return `${measuringUnit}${standardUnitName ? ` -> ${standardUnitName}` : ''}${factor !== '' ? ` x ${factor}` : ''}`; +} + +async function loadUnitsMap(deviceIds, metricNameMap, deviceTypeMap) { + if (!deviceIds.length) return new Map(); + const params = {}; + const placeholders = deviceIds.map((id, index) => { + params[`id${index}`] = id; + return `:id${index}`; + }); + const rows = await query( + `select id, + measuring_device_id, + measuring_unit, + standard_unit, + conversion_factor + from measuring_device_unit + where measuring_device_id in (${placeholders.join(', ')}) + order by create_time asc, id asc`, + params + ); + const map = new Map(); + rows.forEach((row) => { + const deviceType = deviceTypeMap.get(String(row.measuring_device_id)); + const unit = { + id: row.id, + parameterName: row.standard_unit, + measuringUnit: row.measuring_unit, + standardUnit: row.standard_unit, + standardUnitName: metricNameMap.get(`${String(deviceType)}:${row.standard_unit}`) || row.standard_unit, + conversionFactor: row.conversion_factor === null || row.conversion_factor === undefined ? null : Number(row.conversion_factor) + }; + if (!map.has(String(row.measuring_device_id))) map.set(String(row.measuring_device_id), []); + map.get(String(row.measuring_device_id)).push({ + raw: row, + unit, + label: unitLabel(row, metricNameMap, deviceType) + }); + }); + return map; +} + +async function decorateRows(rows) { + const [floorPathMap, metricNameMap] = await Promise.all([loadFloorPathMap(), loadMetricNameMap()]); + const deviceTypeMap = new Map(rows.map((row) => [String(row.id), row.device_type])); + const unitsMap = await loadUnitsMap(rows.map((row) => row.id), metricNameMap, deviceTypeMap); + return rows.map((row) => ({ + ...row, + install_position_name: floorPathMap.get(String(row.install_position)) || '', + standard_unit_name: metricNameMap.get(`${String(row.device_type)}:${row.standard_unit}`) || row.standard_unit, + measuring_unit_summary: (unitsMap.get(String(row.id)) || []).map((item) => item.unit.measuringUnit).filter(Boolean).join('、'), + standard_unit_name_summary: (unitsMap.get(String(row.id)) || []).map((item) => item.label).filter(Boolean).join(';'), + units: (unitsMap.get(String(row.id)) || []).map((item) => item.unit) + })); +} + +async function saveUnits(deviceId, units = []) { + await query('delete from measuring_device_unit where measuring_device_id = :deviceId', { deviceId }); + for (const unit of units) { + await query( + `insert into measuring_device_unit + (id, measuring_device_id, measuring_unit, standard_unit, conversion_factor, create_time, update_time) + values + (:id, :deviceId, :measuringUnit, :standardUnit, :conversionFactor, now(), now())`, + { + id: unit.id || nextId(), + deviceId, + measuringUnit: unit.measuringUnit || null, + standardUnit: unit.standardUnit || null, + conversionFactor: unit.conversionFactor + } + ); + } +} + +async function syncInstallPositionByDeviceId() { + const [deviceRows, floorRows] = await Promise.all([ + query( + `select id, device_id, install_position + from ${TABLE} + where del_flag = '0' + and device_id is not null + and device_id <> ''` + ), + query( + `select id, + pid, + device_id, + is_device, + is_device_group + from floorInfo + where del_flag = '0'` + ) + ]); + const floorById = new Map(floorRows.map((row) => [String(row.id), row])); + const deviceNodeByDeviceId = new Map( + floorRows + .filter((row) => row.is_device === '1' && row.device_id) + .map((row) => [String(row.device_id), row]) + ); + + function nearestNormalNodeId(row) { + let current = row; + const visited = new Set(); + while (current && !visited.has(String(current.id))) { + visited.add(String(current.id)); + if (current.is_device !== '1' && current.is_device_group !== '1') { + return String(current.id); + } + current = floorById.get(String(current.pid)); + } + return null; + } + + for (const row of deviceRows) { + const deviceNode = deviceNodeByDeviceId.get(String(row.device_id)); + const normalNodeId = deviceNode ? nearestNormalNodeId(deviceNode) : null; + if (normalNodeId && String(row.install_position || '') !== normalNodeId) { + await query( + `update ${TABLE} + set install_position = :installPosition, + update_time = now() + where id = :id`, + { + id: row.id, + installPosition: normalNodeId + } + ); + } + } +} + +async function list(params = {}) { + await syncInstallPositionByDeviceId(); + const page = pageParams(params); + const where = ["md.del_flag = '0'"]; + const sqlParams = {}; + + if (params.deviceName) { + where.push('md.device_name like :deviceName'); + sqlParams.deviceName = `%${String(params.deviceName).trim()}%`; + } + if (params.deviceCode) { + where.push('md.device_code like :deviceCode'); + sqlParams.deviceCode = `%${String(params.deviceCode).trim()}%`; + } + if (params.deviceId) { + where.push('md.device_id like :deviceId'); + sqlParams.deviceId = `%${String(params.deviceId).trim()}%`; + } + if (params.deviceType !== undefined && params.deviceType !== '') { + where.push('md.device_type = :deviceType'); + sqlParams.deviceType = String(params.deviceType); + } + if (params.installPosition) { + where.push('md.install_position = :installPosition'); + sqlParams.installPosition = String(params.installPosition); + } + + const whereSql = `where ${where.join(' and ')}`; + const totalRow = await queryOne(`select count(*) as total from ${TABLE} md ${whereSql}`, sqlParams); + const rows = await query( + `${selectSql} + ${whereSql} + order by md.create_time desc, md.id desc + limit ${page.pageSize} offset ${page.offset}`, + sqlParams + ); + return tableData((await decorateRows(rows)).map(mapRow), Number(totalRow?.total || 0)); +} + +async function get(id) { + if (!id) throw new Error('ID不能为空'); + await syncInstallPositionByDeviceId(); + const row = await queryOne( + `${selectSql} + where md.id = :id and md.del_flag = '0' + limit 1`, + { id } + ); + return mapRow((await decorateRows(row ? [row] : []))[0]); +} + +async function add(body = {}) { + const id = body.id ? String(body.id) : nextId(); + const data = normalizeBody(body); + await query( + `insert into ${TABLE} + (id, device_id, device_name, device_code, install_position, device_type, measuring_unit, standard_unit, conversion_factor, + specification_model, manufacturer, factory_no, accuracy_level, measure_range, communication_protocol, communication_address, + collection_cycle, status, last_verification_date, responsible_dept, responsible_person, remark, create_time, update_time, del_flag) + values + (:id, :deviceId, :deviceName, :deviceCode, :installPosition, :deviceType, :measuringUnit, :standardUnit, :conversionFactor, + :specificationModel, :manufacturer, :factoryNo, :accuracyLevel, :measureRange, :communicationProtocol, :communicationAddress, + :collectionCycle, :status, :lastVerificationDate, :responsibleDept, :responsiblePerson, :remark, now(), now(), '0')`, + { id, ...data } + ); + await saveUnits(id, data.units); + return id; +} + +async function update(body = {}) { + if (!body.id) throw new Error('ID不能为空'); + const data = normalizeBody(body); + await query( + `update ${TABLE} + set device_id = :deviceId, + device_name = :deviceName, + device_code = :deviceCode, + install_position = :installPosition, + device_type = :deviceType, + measuring_unit = :measuringUnit, + standard_unit = :standardUnit, + conversion_factor = :conversionFactor, + specification_model = :specificationModel, + manufacturer = :manufacturer, + factory_no = :factoryNo, + accuracy_level = :accuracyLevel, + measure_range = :measureRange, + communication_protocol = :communicationProtocol, + communication_address = :communicationAddress, + collection_cycle = :collectionCycle, + status = :status, + last_verification_date = :lastVerificationDate, + responsible_dept = :responsibleDept, + responsible_person = :responsiblePerson, + remark = :remark, + update_time = now() + where id = :id and del_flag = '0'`, + { id: body.id, ...data } + ); + await saveUnits(body.id, data.units); +} + +async function remove(ids) { + const list = String(ids || '') + .split(',') + .map((item) => item.trim()) + .filter(Boolean); + if (!list.length) return; + const params = {}; + const placeholders = list.map((id, index) => { + params[`id${index}`] = id; + return `:id${index}`; + }); + await query( + `update ${TABLE} + set del_flag = '1', + update_time = now() + where id in (${placeholders.join(', ')})`, + params + ); + await query(`delete from measuring_device_unit where measuring_device_id in (${placeholders.join(', ')})`, params); +} + +module.exports = { + list, + get, + add, + update, + remove +};