'use strict'; /** * * * @author Mai * @date * @version */ const _ = require('lodash'); class ImportBaseTree { /** * 构造函数 * @param {Array} tempData - 清单模板数据 */ constructor (tempData) { // 常量 this.splitChar = '-'; // 索引 // 以code为索引 this.codeNodes = {}; this.items = []; this.roots = []; this.pos = []; this.tempData = []; // 缓存 this.finalNode = null; this.finalXmjNode = null; this.keyNodeId = 1; this._loadTemplateTree(tempData); } /** * 加载 清单模板 * @param {Array} data - 模板数据 * @private */ _loadTemplateTree(data) { for (const node of data) { node.ledger_id = node.id; node.ledger_pid = node.pid; delete node.id; delete node.pid; if (node.code) { this.codeNodes[node.code] = node; } this.items.push(node); if (node.ledger_pid === -1) { this.roots.push(node); } if (node.ledger_id >= this.keyNodeId) { this.keyNodeId = node.ledger_id + 1; } this.tempData.push(node); } for (const node of this.items) { node.children = this.items.filter(function (i) { return i.ledger_pid === node.ledger_id; }); } } /** * 根据 编号、名称 查找模板节点 * @param {Object} node - 要查找的节点 * @returns {*} */ findTempData(node) { return this.tempData.find(function (td) { return td.code === node.code && td.name === node.name; }); } /** * 根据 编号 查找 父项项目节 * @param {String} code - 子项编号 * @returns {*} */ findXmjParent(code) { const codePath = code.split(this.splitChar); if (codePath.length > 1) { codePath.splice(codePath.length - 1, 1); return this.codeNodes[codePath.join(this.splitChar)]; } } /** * 添加 树节点 并完善该节点的树结构 * @param {Object} node - 添加节点 * @param {Object} parent - 父项 * @returns {*} */ addNodeWithParent(node, parent) { node.ledger_id = this.keyNodeId; this.keyNodeId += 1; node.ledger_pid = parent ? parent.ledger_id : -1; node.level = parent ? parent.level + 1 : 1; node.order = parent ? parent.children.length + 1 : this.roots.length + 1; node.full_path = parent ? parent.full_path + '.' + node.ledger_id : '' + node.ledger_id; if (parent) { parent.children.push(node); } else { this.roots.push(node); } this.items.push(node); this.codeNodes[node.code] = node; this.defineCacheData(node); return node; } /** * 定义缓存节点(添加工程量清单、部位明细需使用缓存定位) * @param {Object} node - 当前添加的节点 */ defineCacheData(node) { this.finalNode = node; if (node.code) { this.finalXmjNode = node; } } /** * 添加 项目节 * @param {Object} node - 项目节 * @returns {*} */ addXmjNode(node) { node.children = []; if (node.code.split(this.splitChar).length > 1) { const temp = this.findTempData(node); if (temp) { this.defineCacheData(temp); return temp; } else { const parent = this.findXmjParent(node.code); return this.addNodeWithParent(node, parent); } } else { const n = this.codeNodes[node.code]; if (!n) { return this.addNodeWithParent(node, null); } else { this.defineCacheData(n); return n; } } } /** * 添加 工程量清单 * @param {Object} node - 工程量清单 */ addGclNode(node) { node.pos = []; if (this.finalXmjNode) { return this.addNodeWithParent(node, this.finalXmjNode); } } /** * 添加 部位明细 * @param {object} pos - 部位明细 * @returns {*} */ addPos (pos){ if (this.finalNode && this.finalNode.pos) { this.finalNode.pos.push(pos); this.pos.push(pos); return pos; } } /** * 第一部分的子节点,顺序重排 */ resortFirstPartChildren () { const splitChar = this.splitChar; const firstPart = this.roots[0]; firstPart.children.sort(function (a, b) { if (a.code === '') { return 1; } else if (b.code === '') { return -1; } const codeA = a.code.split(splitChar); const numA = _.toNumber(codeA[codeA.length -1]); const codeB = b.code.split(splitChar); const numB = _.toNumber(codeB[codeB.length -1]); return numA - numB; }); } calculateLeafWithPos () { for (const node of this.items) { if (node.children && node.children.length > 0) { continue; } if (!node.pos || node.pos.length === 0) { continue; } node.quantity = _.sum(_.map(node.pos, 'quantity')); if (node.quantity && node.unit_price) { node.total_price = node.quantity * node.unit_price; } else { node.total_price = null; } } } } class AnalysisExcelTree { /** * 构造函数 */ constructor() { this.colsDef = null; this.colHeaderMatch = { code: ['项目节编号', '预算项目节'], b_code: ['清单子目号', '清单编号', '子目号'], pos: ['部位明细'], name: ['名称'], unit: ['单位'], quantity: ['清单数量'], // 施工图复核数量 dgn_qty1: ['设计数量1'], dgn_qty2: ['设计数量2'], unit_price: ['单价'], drawing_code: ['图号'], memo: ['备注'], }; } toNumber (value) { if (value) { return _.isNumber(value) ? value : _.toNumber(value); } else { return null; } } /** * 读取项目节节点 * @param {Array} row - excel行数据 * @returns {*} * @private */ _loadXmjNode(row) { const node = {}; node.code = row[this.colsDef.code]; node.name = row[this.colsDef.name]; node.unit = row[this.colsDef.unit]; node.quantity = this.toNumber(row[this.colsDef.quantity]); node.dgn_qty1 = this.toNumber(row[this.colsDef.dgn_qty1]); node.dgn_qty2 = this.toNumber(row[this.colsDef.dgn_qty2]); node.unit_price = this.toNumber(row[this.colsDef.unit_price]); node.drawing_code = row[this.colsDef.drawing_code]; node.memo = row[this.colsDef.memo]; if (node.quantity && node.unit_price) { node.total_price = node.quantity * node.unit_price; } else { node.total_price = null; } return this.cacheTree.addXmjNode(node); } /** * 读取工程量清单数据 * @param {Array} row - excel行数据 * @returns {*} * @private */ _loadGclNode(row) { const node = {}; node.b_code = row[this.colsDef.b_code]; node.name = row[this.colsDef.name]; node.unit = row[this.colsDef.unit]; node.quantity = this.toNumber(row[this.colsDef.quantity]); node.unit_price = this.toNumber(row[this.colsDef.unit_price]); node.drawing_code = row[this.colsDef.drawing_code]; node.memo = row[this.colsDef.memo]; if (node.quantity && node.unit_price) { node.total_price = node.quantity * node.unit_price; } else { node.total_price = null; } return this.cacheTree.addGclNode(node); } /** * 读取部位明细数据 * @param {Array} row - excel行数据 * @returns {*} * @private */ _loadPos(row) { const pos = {}; pos.name = row[this.colsDef.name]; pos.quantity = this.toNumber(row[this.colsDef.quantity]); pos.drawing_code = row[this.colsDef.drawing_code]; return this.cacheTree.addPos(pos); } /** * 读取数据行 * @param {Array} row - excel数据行 * @param {Number} index - 行索引号 */ loadRowData(row, index) { let result; // 含code识别为项目节,含posCode识别为部位明细,其他识别为工程量清单 if (row[this.colsDef.code]) { // 第三部分(编号为'3')及之后的数据不读取 if (row[this.colsDef.code] === '3') { this.loadEnd = true; return; } result = this._loadXmjNode(row) } else if (row[this.colsDef.pos]) { result = this._loadPos(row); } else { result = this._loadGclNode(row); } // 读取失败则写入错误数据 todo 返回前端告知用户? if (!result) { this.errorData.push({ serialNo: index, data: row, }); } } /** * 读取表头并检查 * @param {Number} row - Excel数据行 */ checkColHeader(row) { const colsDef = {}; for (const iCol in row) { const text = row[iCol]; for (const head in this.colHeaderMatch) { const match = this.colHeaderMatch[head]; if (match.indexOf(text) >= 0) { colsDef[head] = iCol; } } } if (colsDef.code && colsDef.b_code && colsDef.pos) { this.colsDef = colsDef; } } /** * 将excel清单 平面数据 解析为 树结构数据 * @param {object} sheet - excel清单数据 * @param {Array} tempData - 新建项目使用的清单模板 * @returns {ImportBaseTree} */ analysisData(sheet, tempData) { this.colsDef = null; this.cacheTree = new ImportBaseTree(tempData); this.errorData = []; this.loadEnd = false; for (const iRow in sheet.rows) { const row = sheet.rows[iRow]; if (this.colsDef && !this.loadEnd) { this.loadRowData(row, iRow); } else { this.checkColHeader(row); } } this.cacheTree.resortFirstPartChildren(); this.cacheTree.calculateLeafWithPos(); return this.cacheTree; } } module.exports = AnalysisExcelTree;