'use strict'; /** * * * @author Mai * @date * @version */ const _ = require('lodash'); const colDefineType = { match: 1, pos: 2, }; const mainReg = /^(GD*)?G?(\d\d)*\d$/ig; const subReg = /^(GD*)?G?[A-Z]{2}(\d\d)+$/i; const gdXmjPartReg = /^(GD*)?G?/; const specCode106 = { code: ['102', '103', '104'], reg: /^(GD*)?G?106/i }; const specCode109 = { code: ['102', '103', '104', '105', '106', '107', '108'], reg: /^(GD*)?G?109/i }; const aeUtils = { toNumber: function (value) { let num = _.toNumber(value); return _.isNaN(num) ? null : num; }, checkColHeader: function (row, colHeaderMatch) { const colsDef = {}; for (const iCol in row) { const text = row[iCol]; if (text === null) continue; for (const header in colHeaderMatch) { const match = colHeaderMatch[header]; switch (match.type) { case colDefineType.match: if (match.value.indexOf(text) >= 0) { colsDef[header] = iCol; } break; case colDefineType.pos: for (const v of match.value) { if (text.indexOf(v) >= 0) { colsDef[header] = iCol; break; } } break; } } } return colsDef; }, isMatch11(tempData) { return _.find(tempData, x => { return x.code.indexOf('-') > 0; }) }, check18MainCode(code) { const splitPos = code.indexOf('-'); if (splitPos < 0) { return !!code.match(mainReg); } else { const codeArr = code.split('-'); if (codeArr.length > 3) return false; for (const codePart of codeArr) { if (!codePart || !codePart.match(mainReg)) return false; } return true; } }, isMatch18(tempData) { const checkCode = this.check18MainCode; return _.every(tempData, x => { return !x.code || checkCode(x.code); // return !x.code || !!x.code.match(mainReg); }); } }; class ImportBaseTree { /** * 构造函数 * @param {Array} tempData - 清单模板数据 */ constructor (tempData, ctx, setting) { this.ctx = ctx; if (ctx.tender) { this.mid = ctx.tender.id; this.decimal = ctx.tender.info.decimal; this.precision = ctx.tender.info.precision; } else if (ctx.budget) { this.mid = ctx.budget.id; this.decimal = { up: 2, tp: 2}; this.precision = { other: { value: 2 }, }; } this.setting = setting; // 常量 this.splitChar = '-'; // 索引 // 以code为索引 this.codeNodes = {}; this.items = []; this.roots = []; this.pos = []; this.tempData = []; // 以id为索引 this.nodes = {}; // 缓存 this.finalNode = null; this.finalPrecision = null; this.finalXmjNode = null; this.keyNodeId = 1; this._loadTemplateTree(tempData); } /** * 加载 清单模板 * @param {Array} data - 模板数据 * @private */ _loadTemplateTree(data) { const self = this; let loadCodeNodes = true; for (const node of data) { node[this.setting.kid] = node.template_id; node[this.setting.pid] = node.pid; node.id = this.ctx.app.uuid.v4(); delete node.pid; if (node.code && loadCodeNodes) { this.codeNodes[node.code] = node; } if (node.code === '3') { loadCodeNodes = false; } this.items.push(node); if (node[this.setting.pid] === -1) { this.roots.push(node); } if (node[this.setting.kid] >= this.keyNodeId) { this.keyNodeId = node[this.setting.kid] + 1; } this.tempData.push(node); this.nodes[node[this.setting.kid]] = node; } for (const node of this.items) { node[this.setting.mid] = this.mid; node.children = this.items.filter(function (i) { return i[self.setting.pid] === node[self.setting.kid]; }); } } /** * 根据 编号、名称 查找模板节点 * @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)]; } } getPosterity(node) { let posterity = [].concat(node.children); for (const c of node.children) { posterity = posterity.concat(this.getPosterity(c)); } return posterity; } findGclParent(code, xmj) { let parent; const codePath = code.split(this.splitChar); if (codePath.length > 1) { codePath.splice(codePath.length - 1, 1); const parentCode = codePath.join(this.splitChar); const posterity = this.getPosterity(xmj); parent = posterity.find(function (x) { return x.b_code === parentCode; }) } return parent ? parent : xmj; } /** * 添加 树节点 并完善该节点的树结构 * @param {Object} node - 添加节点 * @param {Object} parent - 父项 * @returns {*} */ addNodeWithParent(node, parent) { node.id = this.ctx.app.uuid.v4(); node[this.setting.kid] = this.keyNodeId; this.keyNodeId += 1; node[this.setting.pid] = parent ? parent[this.setting.kid] : -1; node[this.setting.level] = parent ? parent[this.setting.level] + 1 : 1; node[this.setting.order] = parent ? parent.children.length + 1 : this.roots.length + 1; node[this.setting.fullPath] = parent ? parent[this.setting.fullPath] + '-' + node[this.setting.kid] : '' + node[this.setting.kid]; 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.nodes[node[this.setting.kid]] = node; this.finalNode = node; this.finalPrecision = this.ctx.helper.findPrecision(this.precision, node.unit); if (node.code) { this.finalXmjNode = node; } } _assignRelaField(temp, node) { if (!temp.quantity) temp.quantity = node.quantity; if (!temp.dgn_qty1) temp.dgn_qty1 = node.dgn_qty1; if (!temp.dgn_qty2) temp.dgn_qty2 = node.dgn_qty2; if (!temp.unit_price) temp.unit_price = node.unit_price; if (!temp.drawing_code) temp.drawing_code = node.drawing_code; if (!temp.memo) temp.memo = node.memo; if (!temp.features) temp.features = node.features; if (!temp.total_price) temp.total_price = node.total_price; } /** * 添加 项目节 * @param {Object} node - 项目节 * @returns {*} */ addXmjNode(node) { node.id = this.ctx.app.uuid.v4(); node[this.setting.mid] = this.mid; node.children = []; if (node.code.split(this.splitChar).length > 1) { const temp = this.findTempData(node); if (temp) { this.defineCacheData(temp); this._assignRelaField(temp, node); return temp; } else { const parent = this.findXmjParent(node.code); if (parent) { return this.addNodeWithParent(node, parent); } else { const newNode = this.addNodeWithParent(node, this.codeNodes['1']); newNode.error = 1; return null; } } } else { const n = this.codeNodes[node.code]; if (!n) { return this.addNodeWithParent(node, null); } else { this.defineCacheData(n); this._assignRelaField(n, node); return n; } } } /** * 添加 工程量清单 * @param {Object} node - 工程量清单 */ addGclNode(node) { node.id = this.ctx.app.uuid.v4(); node[this.setting.mid] = this.mid; node.pos = []; node.children = []; if (this.finalXmjNode) { const parent = node.b_code ? this.findGclParent(node.b_code, this.finalXmjNode) : this.finalXmjNode; return this.addNodeWithParent(node, parent); } } /** * 添加 部位明细 * @param {object} pos - 部位明细 * @returns {*} */ addPos (pos, strict = false){ if (this.finalNode && this.finalNode.pos) { if (strict) { const exist = this.finalNode.pos.find(x => { return x.name === pos.name; }); if (exist) return exist; } pos.id = this.ctx.app.uuid.v4(); pos.lid = this.finalNode.id; pos.tid = this.ctx.tender.id; pos.add_stage = 0; pos.add_stage_order = 0; pos.add_times = 0; pos.in_time = new Date(); pos.porder = this.finalNode.pos.length + 1; pos.add_user = this.ctx.session.sessionUser.accountId; this.finalNode.pos.push(pos); this.pos.push(pos); pos.sgfh_qty = this.ctx.helper.round(pos.sgfh_qty, this.finalPrecision.value); pos.quantity = this.ctx.helper.round(pos.quantity, this.finalPrecision.value); 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; } if (a.error === b.error) { 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; } else if (a.error) { return 1 } else if (b.error) { return -1; } }); for (const [i, c] of firstPart.children.entries()) { c[this.setting.order] = i + 1; } } calculateLeafWithPos () { for (const node of this.items) { if (node.children && node.children.length > 0) { node.unit_price = null; node.quantity = null; node.total_price = null; } if (!node.pos || node.pos.length === 0) { continue; } node.quantity = this.ctx.helper.sum(_.map(node.pos, 'quantity')); if (node.quantity && node.unit_price) { node.total_price = this.ctx.helper.mul(node.quantity, node.unit_price, this.decimal.tp); } else { node.total_price = null; } } } } class ImportStd18Tree extends ImportBaseTree { /** * 检查是否是父项 * @param parent * @param code * @returns {boolean} * @private */ _checkParent(parent, code) { const codeNumberPart = code.replace(gdXmjPartReg, ''); if (!parent.code) return false; const numberPart = parent.code.replace(gdXmjPartReg, ''); if (!numberPart || !codeNumberPart || numberPart.length >= codeNumberPart.length) return false; if (parent === '1060501' && code == '1060501-102') console.log(numberPart); return code.indexOf(numberPart) === 0 || code.indexOf('G' + numberPart) === 0 || code.indexOf('GD' + numberPart) === 0; } /** * 查找主表项目节父项 * @param code * @returns {*} */ findMainXmjParent(code) { const numberPart = code.replace(gdXmjPartReg, ''); if (numberPart.length <= 1) throw '首层项目节模板中未定义,不支持导入'; let parent = this.cacheMainXmjNode; while (parent) { if (this._checkParent(parent, code)) return parent; parent = this.nodes[parent[this.setting.pid]]; } return null; } /** * 查找分表项目节父项 * @param code * @returns {*} */ findSubXmjParent(code) { let parent = this.cacheSubXmjNode; while (parent && parent.is_sub && parent.code.match(subReg)) { if (this._checkParent(parent, code)) return parent; parent = this.nodes[parent[this.setting.pid]]; } return this.cacheMainXmjNode; } /** * 根据 编号 查找 父项项目节 * @param {String} code - 子项编号 * @returns {*} */ findXmjParent(code) { if (aeUtils.check18MainCode(code)) { //if (code.match(mainReg)) { if (!this.cacheMainXmjNode) throw '主表项目节找不到父项'; return this.findMainXmjParent(code); } else if (code.match(subReg)) { if (!this.cacheMainXmjNode) throw '分表项目节找不到所属主表项目节'; return this.findSubXmjParent(code); } } /** * 定义缓存节点(添加工程量清单、部位明细需使用缓存定位) * @param {Object} node - 当前添加的节点 */ defineCacheData(node) { super.defineCacheData(node); if (node.code) { if (aeUtils.check18MainCode(node.code)) { //if (node.code.match(mainReg)) { node.is_main = true; this.cacheMainXmjNode = node; this.cacheSubXmjNode = null; } else if (node.code.match(subReg)) { node.is_sub = true; this.cacheSubXmjNode = node; } if (node.code.match(specCode106.reg)) { if (this.cacheSpecMainXmj1 && this.cacheSpecMainXmj1.code.match(specCode109.reg)) { this.cacheSpecMainXmj2 = node; } else { this.cacheSpecMainXmj1 = node; } } else if (node.code.match(specCode109.reg)) { this.cacheSpecMainXmj1 = node; this.cacheSpecMainXmj2 = null; } } } /** * 添加 项目节 * @param {Object} node - 项目节 * @returns {*} */ addXmjNode(node) { //if (!node.code || (!node.code.match(mainReg) && !node.code.match(subReg))) return null; if (!node.code || (!aeUtils.check18MainCode(node.code) && !node.code.match(subReg))) return null; node.id = this.ctx.app.uuid.v4(); node.children = []; if ((specCode106.code.indexOf(node.code) >= 0)) { if (this.cacheSpecMainXmj2 && this.cacheSpecMainXmj2.code.match(specCode106.reg)) return this.addNodeWithParent(node, this.cacheSpecMainXmj2); if (this.cacheSpecMainXmj1 && this.cacheSpecMainXmj1.code.match(specCode106.reg)) return this.addNodeWithParent(node, this.cacheSpecMainXmj1); } if ((specCode109.code.indexOf(node.code) >= 0) && (this.cacheSpecMainXmj1 && this.cacheSpecMainXmj1.code.match(specCode109.reg))) { return this.addNodeWithParent(node, this.cacheSpecMainXmj1) } const temp = this.findTempData(node); if (temp) { this.defineCacheData(temp); this._assignRelaField(temp, node); return temp; } else { const parent = this.findXmjParent(node.code); return this.addNodeWithParent(node, parent); } } } class AnalysisExcelTree { /** * 构造函数 */ constructor(ctx, setting, needCols) { this.ctx = ctx; this.setting = setting; if (ctx.tender) { this.mid = ctx.tender.id; this.decimal = ctx.tender.info.decimal; this.precision = ctx.tender.info.precision; } else if (ctx.budget) { this.mid = ctx.budget.id; this.decimal = ctx.budget.decimal; this.precision = { other: { value: ctx.budget.decimal.qty }, }; } this.colsDef = null; this.colHeaderMatch = { code: {value: ['项目节编号', '预算项目节'], type: colDefineType.match}, b_code: {value: ['清单子目号', '清单编号', '子目号'], type: colDefineType.match}, pos: {value: ['计量单元'], type: colDefineType.match}, name: {value: ['名称'], type: colDefineType.match}, unit: {value: ['单位'], type: colDefineType.match}, deal_qty: {value: ['签约数量'], type: colDefineType.match}, deal_tp: {value: ['签约金额'], type: colDefineType.match}, quantity: {value: ['清单数量'], type: colDefineType.match}, dgn_qty1: {value: ['设计数量1'], type: colDefineType.match}, dgn_qty2: {value: ['设计数量2'], type: colDefineType.match}, unit_price: {value: ['单价'], type: colDefineType.match}, total_price: {value: ['金额', '合价'], type: colDefineType.match}, drawing_code: {value: ['图号', '图册号'], type: colDefineType.match}, memo: {value: ['备注'], type: colDefineType.match}, features: {value: ['项目特征'], type: colDefineType.match}, }; this.needCols = needCols || ['code', 'b_code', 'pos']; } _getNewCacheTree(tempData) { // 模板符合11编办规则,使用11编办树 if (aeUtils.isMatch18(tempData)) { return new ImportStd18Tree(tempData, this.ctx, this.setting); // 反之使用11编办(未校验模板是否符合,替换注释部分即可实现) // } else if (aeUtils.isMatch11(tempData)){ } else { return new ImportBaseTree(tempData, this.ctx, this.setting); } } /** * 读取项目节节点 * @param {Array} row - excel行数据 * @returns {*} * @private */ _loadXmjNode(row) { try { const node = {}; node.code = _.trimEnd(this.ctx.helper.replaceReturn(row[this.colsDef.code])); node.name = this.ctx.helper.replaceReturn(row[this.colsDef.name]); node.unit = this.ctx.helper.replaceReturn(row[this.colsDef.unit]); node.dgn_qty1 = aeUtils.toNumber(row[this.colsDef.dgn_qty1]); node.dgn_qty2 = aeUtils.toNumber(row[this.colsDef.dgn_qty2]); node.deal_tp = this.ctx.helper.round(aeUtils.toNumber(row[this.colsDef.deal_tp]), this.decimal.tp); node.total_price = this.ctx.helper.round(aeUtils.toNumber(row[this.colsDef.total_price]), this.decimal.tp); node.drawing_code = this.ctx.helper.replaceReturn(row[this.colsDef.drawing_code]); node.memo = this.ctx.helper.replaceReturn(row[this.colsDef.memo]); node.features = row[this.colsDef.features]; this.ctx.helper.checkDgnQtyPrecision(node); return this.cacheTree.addXmjNode(node); } catch (error) { if (error.stack) { this.ctx.logger.error(error); } else { this.ctx.getLogger('fail').info(JSON.stringify({ error, project: this.ctx.session.sessionProject, user: this.ctx.session.sessionUser, body: row, })); } return null; } } /** * 读取工程量清单数据 * @param {Array} row - excel行数据 * @returns {*} * @private */ _loadGclNode(row) { if (this.filter.filterGcl) return true; const node = {}; node.b_code = this.ctx.helper.replaceReturn(row[this.colsDef.b_code]); node.name = this.ctx.helper.replaceReturn(row[this.colsDef.name]); node.unit = this.ctx.helper.replaceReturn(row[this.colsDef.unit]); const precision = this.ctx.helper.findPrecision(this.precision, node.unit); node.deal_qty = this.ctx.helper.round(aeUtils.toNumber(row[this.colsDef.deal_qty]), precision.value); node.quantity = this.ctx.helper.round(aeUtils.toNumber(row[this.colsDef.quantity]), precision.value); node.sgfh_qty = node.quantity; node.unit_price = this.ctx.helper.round(aeUtils.toNumber(row[this.colsDef.unit_price]), this.decimal.up); node.drawing_code = this.ctx.helper.replaceReturn(row[this.colsDef.drawing_code]); node.memo = this.ctx.helper.replaceReturn(row[this.colsDef.memo]); node.features = row[this.colsDef.features]; node.deal_tp = node.deal_qty && node.unit_price ? this.ctx.helper.mul(node.deal_qty, node.unit_price, this.decimal.tp) : 0; if (node.quantity && node.unit_price) { node.total_price = this.ctx.helper.mul(node.quantity, node.unit_price, this.decimal.tp); } else { node.total_price = null; } if (this.filter.filterZeroGcl && !node.quantity && !node.total_price) return true; return this.cacheTree.addGclNode(node); } /** * 读取部位明细数据 * @param {Array} row - excel行数据 * @returns {*} * @private */ _loadPos(row) { if (this.filter.filterPos) return true; const pos = {}; pos.name = this.ctx.helper.replaceReturn(row[this.colsDef.name]); pos.quantity = aeUtils.toNumber(row[this.colsDef.quantity]); pos.sgfh_qty = pos.quantity; pos.drawing_code = this.ctx.helper.replaceReturn(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 = aeUtils.checkColHeader(row, this.colHeaderMatch); let check = true; for (const col of this.needCols) { if (!colsDef[col]) check = false; } if (check) this.colsDef = colsDef; } /** * 将excel清单 平面数据 解析为 树结构数据 * @param {object} sheet - excel清单数据 * @param {Array} tempData - 新建项目使用的清单模板 * @returns {ImportBaseTree} */ analysisData(sheet, tempData, filter) { this.filter = filter ? filter : {}; this.colsDef = null; this.cacheTree = this._getNewCacheTree(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); } } return this.cacheTree; } } class ImportGclBaseTree { /** * 构造函数 * @param {Array} tempData - 清单模板数据 */ constructor (ctx, setting, parent, maxId, defaultData) { this.ctx = ctx; this.setting = setting; if (ctx.tender) { this.mid = ctx.tender.id; this.decimal = ctx.tender.info.decimal; this.precision = ctx.tender.info.precision; } else if (ctx.budget) { this.mid = ctx.budget.id; this.decimal = { up: 2, tp: 2}; this.precision = { other: { value: 2 }, }; } this.parent = parent; this.defaultData = defaultData; // 常量 this.splitChar = '-'; // 索引 // 以code为索引 this.codeNodes = {}; this.items = []; // 缓存 this.keyNodeId = maxId ? maxId + 1 : 1; this.blankParent = null; } /** * 根据 编号 查找 父项项目节 * @param {String} code - 子项编号 * @returns {*} */ findParent(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) { parent = parent ? parent : this.parent; if (!parent.children) parent.children = []; node.id = this.ctx.app.uuid.v4(); node[this.setting.mid] = this.mid; node[this.setting.kid] = this.keyNodeId; this.keyNodeId += 1; node[this.setting.pid] = parent[this.setting.kid]; node[this.setting.level] = parent[this.setting.level] + 1; node[this.setting.order] = parent.children.length + 1; node[this.setting.fullPath] = parent[this.setting.fullPath] + '-' + node[this.setting.kid]; parent.children.push(node); node.children = []; if (this.defaultData) _.assignIn(node, this.defaultData); this.items.push(node); if (!_.isNil(node.b_code) && node.b_code !== '') { this.codeNodes[node.b_code] = node; } else { this.blankParent = node; } return node; } /** * 添加 项目节 * @param {Object} node - 项目节 * @returns {*} */ addNode(node) { if (_.isNil(node.b_code) || node.b_code === '') { return this.addNodeWithParent(node, null); } else if (node.b_code.split(this.splitChar).length > 1) { const parent = this.findParent(node.b_code); return this.addNodeWithParent(node, parent ? parent : this.blankParent); } else { return this.addNodeWithParent(node, this.blankParent); } } } class AnalysisGclExcelTree { /** * 构造函数 */ constructor(ctx, setting) { this.ctx = ctx; this.setting = setting; if (ctx.tender) { this.mid = ctx.tender.id; this.decimal = ctx.tender.info.decimal; this.precision = ctx.tender.info.precision; } else if (ctx.budget) { this.mid = ctx.budget.id; this.decimal = { up: 2, tp: 2}; this.precision = { other: { value: 2 }, }; } this.colsDef = null; this.colHeaderMatch = { b_code: {value: ['编号', '清单编号', '子目号', '子目编号', '清单号'], type: colDefineType.match}, name: {value: ['名称', '清单名称', '子目名称'], type: colDefineType.match}, unit: {value: ['单位'], type: colDefineType.pos}, quantity: {value: ['数量'], type: colDefineType.pos}, // 施工图复核数量 unit_price: {value: ['单价'], type: colDefineType.pos}, }; } /** * 读取表头并检查 * @param {Number} row - Excel数据行 */ checkColHeader(row) { const colsDef = aeUtils.checkColHeader(row, this.colHeaderMatch); if (colsDef.b_code) { this.colsDef = colsDef; } } loadRowData(row) { const node = {}; node.b_code = this.ctx.helper.replaceReturn(row[this.colsDef.b_code]); node.name = this.ctx.helper.replaceReturn(row[this.colsDef.name]); if ((_.isNil(node.b_code) || node.b_code === '') && (_.isNil(node.name) || node.name === '')) return node; node.unit = this.ctx.helper.replaceReturn(row[this.colsDef.unit]); const precision = this.ctx.helper.findPrecision(this.precision, node.unit); node.quantity = this.ctx.helper.round(aeUtils.toNumber(row[this.colsDef.quantity]), precision.value); node.unit_price = this.ctx.helper.round(aeUtils.toNumber(row[this.colsDef.unit_price]), this.decimal.up); if (node.quantity && node.unit_price) { node.total_price = this.ctx.helper.mul(node.quantity, node.unit_price, this.decimal.tp); } else { node.total_price = null; } return this.cacheTree.addNode(node); } /** * 将excel清单 平面数据 解析为 树结构数据 * @param {object} sheet - excel清单数据 * @param {Array} parentId - 导入至的节点id * @returns {ImportBaseTree} */ analysisData(sheet, parent, maxId, defaultData) { try { this.colsDef = null; this.cacheTree = new ImportGclBaseTree(this.ctx, this.setting, parent, maxId, defaultData); this.errorData = []; this.loadEnd = false; // 识别表头导入 for (const iRow in sheet.rows) { const row = sheet.rows[iRow]; if (this.colsDef && !this.loadEnd) { const result = this.loadRowData(row); // 读取失败则写入错误数据 if (!result) { this.errorData.push({ serialNo: iRow, data: row }); } } else { this.checkColHeader(row); } } // 固定列导入 // this.colsDef = { // b_code: 0, // name: 1, // unit: 2, // quantity: 3, // unit_price: 4 // }; // for (let iRow = 1, iLen = sheet.rows.length; iRow < iLen; iRow++) { // const row = sheet.rows[iRow]; // const result = this.loadRowData(row); // // 读取失败则写入错误数据 todo 返回前端告知用户? // if (!result) { // this.errorData.push({ // serialNo: iRow, // data: row, // }); // } // } return this.cacheTree; } catch(err) { this.ctx.helper.log(err); } } } class AnalysisStageExcelTree extends AnalysisExcelTree { /** * 构造函数 */ constructor(ctx, setting) { super(ctx, setting); this.mid = ctx.tender.id; this.decimal = ctx.tender.info.decimal; this.precision = ctx.tender.info.precision; this.colsDef = null; this.colHeaderMatch = { code: {value: ['项目节编号', '预算项目节'], type: colDefineType.match}, b_code: {value: ['清单子目号', '清单编号', '子目号'], type: colDefineType.match}, pos: {value: ['计量单元'], type: colDefineType.match}, name: {value: ['名称'], type: colDefineType.match}, unit: {value: ['单位'], type: colDefineType.match}, unit_price: {value: ['单价'], type: colDefineType.match}, contract_qty: {value: ['本期合同计量|数量'], type: colDefineType.match}, contract_tp: {value: ['本期合同计量|金额'], type: colDefineType.match}, deal_dgn_qty1: {value: ['合同|项目节数量1'], type: colDefineType.match}, deal_dgn_qty2: {value: ['合同|项目节数量2'], type: colDefineType.match}, c_dgn_qty1: {value: ['变更|项目节数量1'], type: colDefineType.match}, c_dgn_qty2: {value: ['变更|项目节数量2'], type: colDefineType.match}, postil: {value: ['本期批注'], type: colDefineType.match} }; this.needCols = ['code', 'b_code', 'pos', 'name', 'unit', 'unit_price', 'contract_qty', 'contract_tp']; } mergeHeaderRow(iRow, row, subRow, merge) { const result = []; for (let iCol = 0, iLen = row.length; iCol < iLen; iCol++) { const colMerge = merge.find(x => { return x.s.c === iCol && x.s.r === iRow}); if (colMerge && colMerge.s.c !== colMerge.e.c) { let iSubCol = iCol; while (iSubCol <= colMerge.e.c) { result.push(row[iCol] + '|' + subRow[iSubCol]); iSubCol++; } iCol = colMerge.e.c; } else { result.push(row[iCol]) } } return result; } /** * 读取项目节节点 * @param {Array} row - excel行数据 * @returns {*} * @private */ _loadXmjNode(row) { try { const node = {}; node.code = this.ctx.helper.replaceReturn(this.ctx.helper._.trimEnd(row[this.colsDef.code])); node.name = this.ctx.helper.replaceReturn(this.ctx.helper._.trimEnd(row[this.colsDef.name])); node.unit = this.ctx.helper.replaceReturn(this.ctx.helper._.trimEnd(row[this.colsDef.unit])); const xmj = this.cacheTree.addXmjNode(node); if (xmj) { xmj.deal_dgn_qty1 = aeUtils.toNumber(row[this.colsDef.deal_dgn_qty1]); xmj.deal_dgn_qty2 = aeUtils.toNumber(row[this.colsDef.deal_dgn_qty2]); xmj.c_dgn_qty1 = aeUtils.toNumber(row[this.colsDef.c_dgn_qty1]); xmj.c_dgn_qty2 = aeUtils.toNumber(row[this.colsDef.c_dgn_qty2]); xmj.postil = this.ctx.helper.replaceReturn(row[this.colsDef.postil]); this.ctx.helper.checkDgnQtyPrecision(xmj); return xmj; } else { return null; } } catch (error) { if (error.stack) { this.ctx.logger.error(error); } else { this.ctx.getLogger('fail').info(JSON.stringify({ error, project: this.ctx.session.sessionProject, user: this.ctx.session.sessionUser, body: row, })); } return null; } } /** * 读取工程量清单数据 * @param {Array} row - excel行数据 * @returns {*} * @private */ _loadGclNode(row) { if (this.filter.filterGcl) return true; const node = {}; node.b_code = this.ctx.helper.replaceReturn(this.ctx.helper._.trimEnd(row[this.colsDef.b_code])); node.name = this.ctx.helper.replaceReturn(this.ctx.helper._.trimEnd(row[this.colsDef.name])); node.unit = this.ctx.helper.replaceReturn(this.ctx.helper._.trimEnd(row[this.colsDef.unit])); node.unit_price = this.ctx.helper.round(aeUtils.toNumber(row[this.colsDef.unit_price]), this.decimal.up); const precision = this.ctx.helper.findPrecision(this.precision, node.unit); node.contract_qty = this.ctx.helper.round(aeUtils.toNumber(row[this.colsDef.contract_qty]), precision.value); if (node.contract_qty && node.unit_price) { node.contract_tp = this.ctx.helper.mul(node.contract_qty, node.unit_price, this.decimal.tp); } else { node.contract_tp = this.ctx.helper.round(aeUtils.toNumber(row[this.colsDef.contract_tp]), this.decimal.tp); } node.postil = this.ctx.helper.replaceReturn(row[this.colsDef.postil]); if (this.filter.filterZeroGcl && !node.contract_qty && !node.contract_tp) return true; return this.cacheTree.addGclNode(node); } /** * 读取部位明细数据 * @param {Array} row - excel行数据 * @returns {*} * @private */ _loadPos(row) { if (this.filter.filterPos) return true; let pos = {}; pos.name = this.ctx.helper.replaceReturn(row[this.colsDef.name]); pos.quantity = aeUtils.toNumber(row[this.colsDef.contract_qty]); pos = this.cacheTree.addPos(pos, true); pos.contract_qty = pos.quantity; pos.postil = this.ctx.helper.replaceReturn(row[this.colsDef.postil]); return pos; } /** * 将excel清单 平面数据 解析为 树结构数据 * @param {object} sheet - excel清单数据 * @param {Array} tempData - 新建项目使用的清单模板 * @returns {ImportBaseTree} */ analysisData(sheet, tempData, filter) { this.filter = filter ? filter : {}; this.colsDef = null; this.cacheTree = this._getNewCacheTree(tempData); this.errorData = []; this.loadEnd = false; this.loadBegin = sheet.rows.length; for (const [iRow, row] of sheet.rows.entries()) { if (this.colsDef && !this.loadEnd) { if (iRow < this.loadBegin) continue; this.loadRowData(row, iRow); } else { if (iRow === sheet.rows.length - 1) continue; const mergeRow = this.mergeHeaderRow(iRow, row, sheet.rows[iRow + 1], sheet.merge); this.checkColHeader(mergeRow); if (this.colsDef) this.loadBegin = iRow + 2; } } return this.cacheTree; } } module.exports = { AnalysisExcelTree, AnalysisGclExcelTree, AnalysisStageExcelTree };