12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043 |
- 'use strict';
- /**
- *
- *
- * @author Mai
- * @date
- * @version
- */
- const _ = require('lodash');
- const colDefineType = {
- match: 1,
- pos: 2,
- };
- const mainReg = /^(GD*)?G?(\d\d)*\d$/i;
- 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;
- })
- },
- isMatch18(tempData) {
- return _.every(tempData, x => {
- 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.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;
- 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 (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 (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;
- 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},
- };
- 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 = 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]);
- 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.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 };
|