'use strict'; /** * 标段--台账 数据模型 * * @author CaiAoLin * @date 2017/12/1 * @version */ const needField = { id: 'ledger_id', pid: 'ledger_pid', order: 'order', level: 'level', fullPath: 'full_path', isLeaf: 'is_leaf', }; const keyFields = { table: ['id'], index: ['tender_id', 'ledger_id'], }; // 以下字段仅可通过树结构操作改变,不可直接通过update方式从接口提交,发现时过滤 const readOnlyFields = ['id', 'tender_id', 'ledger_id', 'ledger_pid', 'order', 'level', 'full_path', 'is_leaf']; const calcFields = ['quantity', 'unit_price', 'total_price']; const zeroRange = 0.0000000001; const rootId = -1; module.exports = app => { class Ledger extends app.BaseService { /** * 构造函数 * * @param {Object} ctx - egg全局变量 * @return {void} */ constructor(ctx) { super(ctx); this.tableName = 'ledger'; } /** * 新增数据(供内部或其他service类调用, controller不可直接使用) * @param {Array|Object} data - 新增数据 * @param {Number} tenderId - 标段id * @param {Object} transaction - 新增事务 * @returns {Promise} - {Promise<是否正确新增成功>} */ async innerAdd(data, tenderId, transaction) { const datas = data instanceof Array ? data : [data]; if (tenderId <= 0) { throw '标段id错误'; } if (datas.length <= 0) { throw '插入数据为空'; } if (!transaction) { throw '内部错误'; } // 整理数据 const insertData = []; for (const tmp of datas) { tmp.ledger_id = tmp.id; tmp.ledger_pid = tmp.pid; tmp.tender_id = tenderId; delete tmp.id; delete tmp.pid; insertData.push(tmp); } const operate = await transaction.insert(this.tableName, insertData); return operate.affectedRows === datas.length; } /** * 新增数据 * * @param {Object} data - 新增的数据(可批量) * @param {Number} tenderId - 标段id * @return {Boolean} - 返回新增的结果 */ async add(data, tenderId) { this.transaction = await this.db.beginTransaction(); let result = false; try { result = await this.innerAdd(data, tenderId, this.transaction); if (!result) { throw '新增数据错误'; } this.transaction.commit(); } catch (error) { this.transaction.rollback(); result = false; } return result; } /** * 根据层级获取数据 * * @param {Number} tenderId - 标段id * @param {Number} showLevel - 显示层数 * @return {Array} - 返回数据 */ async getDataByTenderId(tenderId, showLevel = 4) { if (tenderId <= 0) { return []; } this.initSqlBuilder(); this.sqlBuilder.setAndWhere('tender_id', { value: tenderId, operate: '=', }); if (showLevel > 0) { this.sqlBuilder.setAndWhere('level', { value: showLevel, operate: '<=', }); } const [sql, sqlParam] = this.sqlBuilder.build(this.tableName); const data = await this.db.query(sql, sqlParam); return data; } /** * 根据节点Id获取数据 * * @param {Number} tenderId - 标段id * @param {Number} nodeId - 项目节/工程量清单节点id * @return {Object} - 返回查询到的节点数据 */ async getDataByNodeId(tenderId, nodeId) { if ((nodeId <= 0) || (tenderId <= 0)) { return undefined; } this.initSqlBuilder(); this.sqlBuilder.setAndWhere('tender_id', { value: tenderId, operate: '=', }); this.sqlBuilder.setAndWhere('ledger_id', { value: nodeId, operate: '=', }); const [sql, sqlParam] = this.sqlBuilder.build(this.tableName); const data = await this.db.queryOne(sql, sqlParam); return data; } /** * 根据节点Id获取数据 * @param {Number} tenderId - 标段Id * @param {Array} nodesIds - 节点Id * @return {Array} */ async getDataByNodeIds(tenderId, nodesIds) { if (tenderId <= 0) { return []; } this.initSqlBuilder(); this.sqlBuilder.setAndWhere('tender_id', { value: tenderId, operate: '=', }); this.sqlBuilder.setAndWhere('ledger_id', { value: nodesIds, operate: 'in', }); const [sql, sqlParam] = this.sqlBuilder.build(this.tableName); const data = await this.db.query(sql, sqlParam); return data; } /** * 根据主键id获取数据 * @param {Array|Number} id - 主键id * @returns {Promise<*>} */ async getDataByIds(id) { const ids = id instanceof Array ? id : [id]; this.initSqlBuilder(); this.sqlBuilder.setAndWhere('id', { value: ids, operate: 'in', }); const [sql, sqlParam] = this.sqlBuilder.build(this.tableName); const data = await this.db.query(sql, sqlParam); return data; } /** * 根据标准清单源检索 * @param tenderId * @param source * @returns {Promise<*>} */ async getDataBySource(tenderId, source) { this.initSqlBuilder(); this.sqlBuilder.setAndWhere('tender_id', { value: tenderId, operate: '=', }); this.sqlBuilder.setAndWhere('source', { value: source, operate: '=', }); const [sql, sqlParam] = this.sqlBuilder.build(this.tableName); const data = await this.db.query(sql, sqlParam); return data; } /** * 获取最末的子节点 * @param {Number} tenderId - 标段id * @param {Number} pid - 父节点id * @return {Object} */ async getLastChildData(tenderId, pid) { this.initSqlBuilder(); this.sqlBuilder.setAndWhere('tender_id', { value: tenderId, operate: '=', }); this.sqlBuilder.setAndWhere('ledger_pid', { value: pid, operate: '=', }); this.sqlBuilder.orderBy = [['order', 'DESC']]; const [sql, sqlParam] = this.sqlBuilder.build(this.tableName); const resultData = this.db.queryOne(sql, sqlParam); return resultData; } /** * 根据 父节点id 和 节点排序order 获取数据 * * @param {Number} tenderId - 标段id * @param {Number} pid - 父节点id * @param {Number|Array} order - 排序 * @return {Object|Array} - 查询结果 */ async getDataByParentAndOrder(tenderId, pid, order) { if ((tenderId <= 0) || (pid <= 0) || (order <= 0)) { return undefined; } this.initSqlBuilder(); this.sqlBuilder.setAndWhere('tender_id', { value: tenderId, operate: '=', }); this.sqlBuilder.setAndWhere('ledger_pid', { value: pid, operate: '=', }); if (order instanceof Array) { this.sqlBuilder.setAndWhere('order', { value: order, operate: 'in', }); } else { this.sqlBuilder.setAndWhere('order', { value: order, operate: '=', }); } const [sql, sqlParam] = this.sqlBuilder.build(this.tableName); let data; if (order instanceof Array) { data = await this.db.query(sql, sqlParam); } else { data = await this.db.queryOne(sql, sqlParam); } return data; } /** * 根据 父节点id 获取子节点 * @param tenderId * @param nodeId * @return {Promise<*>} */ async getChildrenByParentId(tenderId, nodeId) { if (tenderId <= 0 || !nodeId) { return undefined; } const nodeIds = nodeId instanceof Array ? nodeId : [nodeId]; if (nodeIds.length === 0) { return []; } this.initSqlBuilder(); this.sqlBuilder.setAndWhere('tender_id', { value: tenderId, operate: '=', }); this.sqlBuilder.setAndWhere('ledger_pid', { value: nodeIds, operate: 'in', }); this.sqlBuilder.orderBy = [['order', 'ASC']]; const [sql, sqlParam] = this.sqlBuilder.build(this.tableName); const data = await this.db.query(sql, sqlParam); return data; } /** * 根据 父节点ID 和 节点排序order 获取全部后节点数据 * @param {Number} tenderId - 标段id * @param {Number} pid - 父节点id * @param {Number} order - 排序 * @return {Array} */ async getNextsData(tenderId, pid, order) { if ((tenderId <= 0) || (order < 0)) { return undefined; } this.initSqlBuilder(); this.sqlBuilder.setAndWhere('tender_id', { value: tenderId, operate: '=', }); this.sqlBuilder.setAndWhere('ledger_pid', { value: pid, operate: '=', }); this.sqlBuilder.setAndWhere('order', { value: order, operate: '>', }); const [sql, sqlParam] = this.sqlBuilder.build(this.tableName); const data = await this.db.query(sql, sqlParam); return data; } /** * 根据full_path获取数据 full_path Like ‘1.2.3%’(传参full_path = '1.2.3%') * @param {Number} tenderId - 标段id * @param {String} full_path - 路径 * @return {Promise} */ async getDataByFullPath(tenderId, full_path) { this.initSqlBuilder(); this.sqlBuilder.setAndWhere('tender_id', { value: tenderId, operate: '=', }); this.sqlBuilder.setAndWhere('full_path', { value: this.db.escape(full_path), operate: 'Like', }); const [sql, sqlParam] = this.sqlBuilder.build(this.tableName); const resultData = await this.db.query(sql, sqlParam); return resultData; } /** * 根据full_path检索自己及所有父项 * @param {Number} tenderId - 标段id * @param {Array|String} fullPath - 节点完整路径 * @returns {Promise<*>} * @private */ async getFullLevelDataByFullPath(tenderId, fullPath) { const explodePath = this.ctx.helper.explodePath(fullPath); this.initSqlBuilder(); this.sqlBuilder.setAndWhere('tender_id', { value: tenderId, operate: '=', }); this.sqlBuilder.setAndWhere('full_path', { value: explodePath, operate: 'in' }); const [sql, sqlParam] = this.sqlBuilder.build(this.tableName); const data = await this.db.query(sql, sqlParam); return data; }; /** * 统计子节点total_price * @param {Number} tenderId - 标段id * @param {Number} pid - 父节点id * @param {Number} order - order取值 * @param {String} orderOperate - order比较操作符 * @returns {Promise} */ async addUpChildren(tenderId, pid, order, orderOperate) { this.initSqlBuilder(); const sql = ['SELECT SUM(??) As value FROM ?? ', ' WHERE '] const sqlParam = ['total_price', this.tableName]; sql.push(' ?? = ' + tenderId); sqlParam.push('tender_id'); sql.push(' And ?? = ' + pid); sqlParam.push('ledger_pid'); sql.push(' And ?? ' + orderOperate + ' ' + order); sqlParam.push('order'); const result = await this.db.queryOne(sql.join(''), sqlParam); return result.value; } /** * 更新order * @param {Number} tenderId - 标段id * @param {Number} parentId - 父节点id * @param {Number} order - 自增起始order(含) * @param {Number} incre - 自增量 * @returns {Promise<*>} * @private */ async _updateChildrenOrderAfter(tenderId, parentId, order, incre = 1) { this.initSqlBuilder(); this.sqlBuilder.setAndWhere('tender_id', { value: tenderId, operate: '=' }); this.sqlBuilder.setAndWhere('order', { value: order, operate: '>=', }); this.sqlBuilder.setAndWhere('ledger_pid', { value: parentId, operate: '=', }); this.sqlBuilder.setUpdateData('order', { value: Math.abs(incre), selfOperate: incre > 0 ? '+' : '-', }); const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'update'); const data = await this.transaction.query(sql, sqlParam); return data; } /** * select的全部后兄弟节点,Order自增 * * @param {Object} select - 选中的节点 * @param {Number} incre - 自增值 * @return {Array} - 自增后的数据 * @private */ async _updateSelectNextsOrder(select, incre = 1) { return await this._updateChildrenOrderAfter(select.tender_id, select.ledger_pid, select.order + 1, incre); } /** * 从数据库获取标段的最大节点id * * @param {Number} tenderId - 标段id * @return {Number} * @private */ async _getMaxNodeId(tenderId) { const sql = 'SELECT Max(??) As max_id FROM ?? Where tender_id = ' + tenderId; const sqlParam = ['ledger_id', this.tableName]; const queryResult = await this.db.queryOne(sql, sqlParam); return queryResult.max_id; } /** * 根据selectData, data 新增数据(新增为selectData的后项,该方法不可单独使用) * * @param {Number} tenderId - 标段id * @param {Object} selectData - 选中节点的数据 * @param {Object} data - 新增节点的初始数据 * @return {Object} - 新增结果 * @private */ async _addNodeData(tenderId, selectData, data) { if (tenderId <= 0) { return undefined; } if (!data) { data = {}; } const cacheKey = 'tender_node_maxId:' + tenderId; let maxId = parseInt(await this.cache.get(cacheKey)); if (!maxId) { maxId = await this._getMaxNodeId(tenderId); this.cache.set(cacheKey, maxId, 'EX', this.ctx.app.config.cacheTime); } data.tender_id = tenderId; data.ledger_id = maxId + 1; data.ledger_pid = selectData.ledger_pid; data.level = selectData.level; data.order = selectData.order + 1; data.full_path = selectData.full_path.replace('.' + selectData.ledger_id, '.' + data.ledger_id); data.is_leaf = true; const result = await this.transaction.insert(this.tableName, data); this.cache.set(cacheKey, maxId + 1, 'EX', this.ctx.app.config.cacheTime); return result; } /** * 根据parentData, data新增数据(新增为parentData的最后一个子项) * @param {Number} tenderId - 标段id * @param {Object} parentData - 父项数据 * @param {Object} data - 新增节点,初始数据 * @returns {Promise<*>} - 新增结果 * @private */ async _addChildNodeData(tenderId, parentData, data) { if (tenderId <= 0) { return undefined; } if (!data) { data = {}; } const pid = parentData ? parentData.ledger_id : rootId; const cacheKey = 'tender_node_maxId: ' + tenderId; let maxId = parseInt(await this.cache.get(cacheKey)); if (!maxId) { maxId = await this._getMaxNodeId(tenderId); this.cache.set(cacheKey, maxId, 'EX', this.ctx.app.config.cacheTime); } data.tender_id = tenderId; data.ledger_id = maxId + 1; data.ledger_pid = pid; if (data.order === undefined) { data.order = 1; } data.level = parentData ? parentData.level + 1 : 1; data.full_path = parentData ? parentData.full_path + '.' + data.ledger_id : '' + data.ledger_id; if (data.is_leaf === undefined) { data.is_leaf = true; } const result = await this.transaction.insert(this.tableName, data); this.cache.set(cacheKey, maxId + 1, 'EX', this.ctx.app.config.cacheTime); return [result, data]; } /** * 根据parentData, data新增数据(自动排序) * @param tenderId * @param parentData * @param data * @returns {Promise} * @private */ async _addChildAutoOrder(tenderId, parentData, data) { const self = this; const findPreData = function (list, a) { if (!list || list.length === 0) { return null; } for (let i = 0, iLen = list.length; i < iLen; i++) { if (self.ctx.helper.compareCode(list[i].code, a.code) > 0) { return i > 0 ? list[i-1] : null; } } return list[list.length -1]; } const pid = parentData ? parentData.ledger_id : rootId; const children = await this.getChildrenByParentId(tenderId, pid); const preData = findPreData(children, data); let parent = null; if (!preData || children.indexOf(preData) < children.length - 1) { await this._updateChildrenOrderAfter(tenderId, pid, preData ? preData.order + 1 : 1); } data.order = preData ? preData.order + 1 : 1; const [addResult, node] = await this._addChildNodeData(tenderId, parentData, data); return [addResult, node]; } /** * tenderId标段中, 在selectId后新增一个节点 * * @param {Number} tenderId - 标段id * @param {Number} selectId - 选中节点id * @param {Object} data - 新增节点初始化数据 * @return {Array} 新增后的数据,其他被修改的数据 */ async addNode(tenderId, selectId, data) { if ((tenderId <= 0) || (selectId <= 0)) { return []; } const selectData = await this.getDataByNodeId(tenderId, selectId); if (!selectData) { throw '新增节点数据错误'; } if (!this.transaction) { this.transaction = await this.db.beginTransaction(); } try { // 选中节点的所有后兄弟节点,order+1 await this._updateSelectNextsOrder(selectData); // 数据库创建新增节点数据 const newNode = await this._addNodeData(tenderId, selectData, data); if (!newNode) { throw '新增节点数据错误'; } await this.transaction.commit(); } catch (err) { await this.transaction.rollback(); this.transaction = null; throw err; } this.transaction = null; // 查询应返回的结果 const createData = await this.getDataByParentAndOrder(selectData.tender_id, selectData.ledger_pid, [selectData.order + 1]); const updateData = await this.getNextsData(selectData.tender_id, selectData.ledger_pid, selectData.order + 1); return { create: createData, update: updateData }; } /** * 从标准数据中提取有效数据 * @param {Object} stdData - 从标准库中查询所得 * @returns {name, unit, source, code, b_code} * @private */ _filterStdData(stdData) { const result = { name: stdData.name, unit: stdData.unit, source: stdData.source } result.code = stdData.code ? stdData.code : ''; result.b_code = stdData.b_code ? stdData.b_code : ''; return result; } /** * 添加节点(来自标准清单) * @param {Number} tenderId * @param {Number} selectId * @param {Object} stdData * @returns {Promise<*>} */ async addStdNode(tenderId, selectId, stdData) { const newData = this.filterStdData(stdData); const result = await this.addNode(tenderId, selectId, newData); return result; } /** * 添加节点,并同步添加父节点 * @param {Number} tenderId - 标段id * @param {Number} selectId - 选中节点id * @param {Object} stdData - 节点数据 * @param {StandardLib} stdLib - 标准库 * @returns {Promise} */ async addStdNodeWithParent(tenderId, stdData, stdLib) { const fullLevel = await stdLib.getFullLevelDataByFullPath(stdData.list_id, stdData.full_path); fullLevel.sort(function (x, y) { return x.level - y.level }); let isNew = false, node, firstNew, updateParent, addResult; const expandIds = []; this.transaction = await this.db.beginTransaction(); try { for (let i = 0, len = fullLevel.length; i < len; i++) { const stdNode = fullLevel[i]; if (isNew) { const newData = this._filterStdData(stdNode); newData.is_leaf = (i === len - 1); [addResult, node] = await this._addChildNodeData(tenderId, node, newData); } else { const parent = node; node = await this.getDataByCondition({ tender_id: tenderId, ledger_pid: parent ? parent.ledger_id : rootId, code: stdNode.code, name: stdNode.name }); if (!node) { isNew = true; const newData = this._filterStdData(stdNode); newData.is_leaf = (i === len - 1); [addResult, node] = await this._addChildAutoOrder(tenderId, parent, newData); if (parent && parent.is_leaf) { await this.transaction.update(this.tableName, {id: parent.id, is_leaf: false} ); updateParent = parent; } firstNew = node; } else { expandIds.push(node.ledger_id); } } } this.transaction.commit(); } catch (err) { await this.transaction.rollback(); throw err; } // 查询应返回的结果 let createData = [], updateData = []; if (firstNew) { createData = await this.getDataByFullPath(tenderId, firstNew.full_path + '%'); updateData = await this.getNextsData(tenderId, firstNew.ledger_pid, firstNew.order); if (updateParent) { updateData.push(await this.getDataByCondition({id: updateParent.id})); } } const expandData = await this.getChildrenByParentId(tenderId, expandIds); return { create: createData, update: updateData, expand: expandData }; } /** * 删除节点 * @param {Number} tenderId - 标段id * @param {Object} deleteData - 删除节点数据 * @returns {Promise<*>} * @private */ async _deleteNodeData(tenderId, deleteData) { this.initSqlBuilder(); this.sqlBuilder.setAndWhere('tender_id', { value: tenderId, operate: '=', }); this.sqlBuilder.setAndWhere('full_path', { value: this.db.escape(deleteData.full_path + '%'), operate: 'Like', }); const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'delete'); const result = await this.transaction.query(sql, sqlParam); return result; } /** * tenderId标段中, 删除选中节点及其子节点 * * @param {Number} tenderId - 标段id * @param {Number} selectId - 选中节点id * @return {Array} - 被删除的数据 */ async deleteNode(tenderId, selectId) { if ((tenderId <= 0) || (selectId <= 0)) { return []; } const selectData = await this.getDataByNodeId(tenderId, selectId); if (!selectData) { throw '删除节点数据错误'; } const parentData = await this.getDataByNodeId(tenderId, selectData.ledger_pid); this.transaction = await this.db.beginTransaction(); let deleteData = []; try { // 获取将要被删除的数据 deleteData = await this.getDataByFullPath(tenderId, selectData.full_path + '%'); // 删除 const operate = await this._deleteNodeData(tenderId, selectData); // 选中节点--父节点 只有一个子节点时,应升级is_leaf if (parentData) { const count = this.db.count(this.tableName, { ledger_pid: selectData.ledger_pid }); if (count === 1) { await this.transaction.update(this.tableName, { id: parentData.id, is_leaf: true }); } } // 选中节点--全部后节点 order-- await this._updateSelectNextsOrder(selectData, -1); // 更新父项金额 if (this.ctx.helper.checkZero(selectData.total_price)) { const parentFullPath = selectData.full_path.replace('.' + selectData.ledger_id, ''); const updateMap = {}; updateMap[parentFullPath] = -selectData.total_price; await this._increCalcParent(tenderId, updateMap); } await this.transaction.commit(); } catch (err) { deleteData = []; await this.transaction.rollback(); throw err; } // 查询结果 let updateData = []; if (deleteData.length > 0) { updateData = await this.getNextsData(tenderId, selectData.ledger_pid, selectData.order - 1); updateData = updateData ? updateData : []; const updateData1 = await this.getDataByNodeId(tenderId, selectData.ledger_pid); if (this.ctx.helper.checkZero(selectData.total_price)) { const updateData2 = await this.getFullLevelDataByFullPath(tenderId, updateData1.full_path); updateData = updateData.concat(updateData2); } else if (updateData1.is_leaf !== parentData.is_leaf) { updateData.push(updateData1); } } return { delete: deleteData, update: updateData }; } /** * tenderId标段中, 选中节点selectId上移 * * @param {Number} tenderId - 标段id * @param {Number} selectId - 选中节点id * @return {Array} - 发生改变的数据 */ async upMoveNode(tenderId, selectId) { if ((tenderId <= 0) || (selectId <= 0)) { return []; } const selectData = await this.getDataByNodeId(tenderId, selectId); if (!selectData) { throw '上移节点数据错误'; } const preData = await this.getDataByParentAndOrder(tenderId, selectData.ledger_pid, selectData.order - 1); if (!preData) { throw '节点不可上移'; } this.transaction = await this.db.beginTransaction(); try { const sData = await this.transaction.update(this.tableName, { id: selectData.id, order: selectData.order - 1 }); const pData = await this.transaction.update(this.tableName, { id: preData.id, order: preData.order + 1 }); this.transaction.commit(); } catch (err) { await this.transaction.rollback(); throw err; } const resultData = await this.getDataByParentAndOrder(tenderId, selectData.ledger_pid, [selectData.order, preData.order]); return { update: resultData }; } /** * tenderId标段中, 选中节点selectId下移 * * @param {Number} tenderId - 标段id * @param {Number} selectId - 选中节点id * @return {Array} - 发生改变的数据 */ async downMoveNode(tenderId, selectId) { if ((tenderId <= 0) || (selectId <= 0)) { return []; } const selectData = await this.getDataByNodeId(tenderId, selectId); if (!selectData) { throw '下移节点数据错误'; } const nextData = await this.getDataByParentAndOrder(tenderId, selectData.ledger_pid, selectData.order + 1); if (!nextData) { throw '节点不可下移'; } this.transaction = await this.db.beginTransaction(); try { const sData = await this.transaction.update(this.tableName, { id: selectData.id, order: selectData.order + 1 }); const pData = await this.transaction.update(this.tableName, { id: nextData.id, order: nextData.order - 1 }); this.transaction.commit(); } catch (err) { await this.transaction.rollback(); throw err; } const resultData = await this.getDataByParentAndOrder(tenderId, selectData.ledger_pid, [selectData.order, nextData.order]); return { update: resultData }; } /** * 升级selectData, 同步修改所有子节点 * @param {Object} selectData - 升级操作,选中节点 * @return {Object} * @private */ async _syncUplevelChildren(selectData) { this.initSqlBuilder(); this.sqlBuilder.setAndWhere('tender_id', { value: selectData.tender_id, operate: '=', }); this.sqlBuilder.setAndWhere('full_path', { value: this.db.escape(selectData.full_path + '.%'), operate: 'like', }); this.sqlBuilder.setUpdateData('level', { value: 1, selfOperate: '-', }); this.sqlBuilder.setUpdateData('full_path', { value: ['`full_path`', this.db.escape(selectData.ledger_pid + '.'), this.db.escape('')], literal: 'Replace', }); const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'update'); const data = this.transaction.query(sql, sqlParam); return data; } /** * 选中节点的后兄弟节点,全部变为当前节点的子节点 * @param {Object} selectData - 选中节点 * @return {Object} * @private */ async _syncUpLevelNexts(selectData) { // 查询selectData的lastChild const lastChildData = await this.getLastChildData(selectData.tender_id, selectData.ledger_id); const nextsData = await this.getNextsData(selectData.tender_id, selectData.ledger_pid, selectData.order); if (nextsData && nextsData.length > 0) { // 修改nextsData pid, 排序 this.initSqlBuilder(); this.sqlBuilder.setUpdateData('ledger_pid', { value: selectData.ledger_id, }); const orderInc = lastChildData ? lastChildData.order - selectData.order : -selectData.order; this.sqlBuilder.setUpdateData('order', { value: Math.abs(orderInc), selfOperate: orderInc > 0 ? '+' : '-', }); this.sqlBuilder.setAndWhere('ledger_pid', { value: selectData.ledger_pid, operate: '=', }); this.sqlBuilder.setAndWhere('order', { value: selectData.order, operate: '>', }); const [sql1, sqlParam1] = this.sqlBuilder.build(this.tableName, 'update'); await this.transaction.query(sql1, sqlParam1); // 选中节点 is_leaf应为false if (selectData.is_leaf) { const updateData = { id: selectData.id, is_leaf: false, }; await this.transaction.update(this.tableName, updateData); } // 修改nextsData及其子节点的full_path const oldSubStr = this.db.escape(selectData.ledger_pid + '.'); const newSubStr = this.db.escape(selectData.ledger_id + '.'); const sqlArr = []; sqlArr.push('Update ?? SET `full_path` = Replace(`full_path`,' + oldSubStr + ',' + newSubStr + ') Where'); sqlArr.push('(`tender_id` = ' + selectData.tender_id + ')'); sqlArr.push(' And ('); for (const data of nextsData) { sqlArr.push('`full_path` Like ' + this.db.escape(data.full_path + '%')); if (nextsData.indexOf(data) < nextsData.length - 1) { sqlArr.push(' Or '); } } sqlArr.push(')'); const sql = sqlArr.join(''); const resultData = await this.transaction.query(sql, [this.tableName]); return resultData; } } /** * 升级节点 * * @param {Number} tenderId - 标段id * @param {Number} selectId - 选中节点id * @return {Array} - 发生改变的数据 */ async upLevelNode(tenderId, selectId) { if ((tenderId <= 0) || (selectId <= 0)) { return []; } const selectData = await this.getDataByNodeId(tenderId, selectId); if (!selectData) { throw '升级节点数据错误'; } const parentData = await this.getDataByNodeId(tenderId, selectData.ledger_pid); if (!parentData) { throw '升级节点数据错误'; } this.transaction = await this.db.beginTransaction(); const newFullPath = selectData.full_path.replace(selectData.ledger_pid + '.', ''); try { // 选中节点--父节点 选中节点为firstChild时,修改is_leaf if (selectData.order === 1) { this.transaction.update(this.tableName, { id: parentData.id, is_leaf: true, total_price: 0 }); } else { this.transaction.update(this.tableName, { id: parentData.id, total_price: await this.addUpChildren(tenderId, selectData.ledger_pid, selectData.order, '<') }); } // 选中节点--父节点--全部后兄弟节点 order+1 await this._updateSelectNextsOrder(parentData); // 选中节点 修改pid, order, full_path let totalPrice = selectData.total_price ? selectData.total_price : 0; const plus = await this.addUpChildren(tenderId, selectData.ledger_pid, selectData.order, '>'); totalPrice = plus ? totalPrice + plus : totalPrice; const updateData = { id: selectData.id, ledger_pid: parentData.ledger_pid, order: parentData.order + 1, level: selectData.level - 1, full_path: newFullPath, total_price: totalPrice }; await this.transaction.update(this.tableName, updateData); // 选中节点--全部子节点(含孙) level-1, full_path变更 await this._syncUplevelChildren(selectData); // 选中节点--全部后兄弟节点 收编为子节点 修改pid, order, full_path await this._syncUpLevelNexts(selectData); this.transaction.commit(); } catch (err) { await this.transaction.rollback(); throw err; } // 查询修改的数据 const resultData1 = await this.getDataByFullPath(tenderId, newFullPath + '%'); const resultData2 = await this.getNextsData(tenderId, parentData.ledger_pid, parentData.order + 1); // 默认原Parent被刷新过,不核对total_price修改 const preParent = await this.getDataByNodeId(tenderId, parentData.ledger_id); resultData2.push(preParent); return { update: resultData1.concat(resultData2) }; } /** * 降级selectData, 同步修改所有子节点 * @param {Object} selectData - 选中节点 * @param {Object} preData - 选中节点的前一节点(降级后为父节点) * @return {Promise<*>} * @private */ async _syncDownlevelChildren(selectData, preData) { this.initSqlBuilder(); this.sqlBuilder.setAndWhere('tender_id', { value: selectData.tender_id, operate: '=', }); this.sqlBuilder.setAndWhere('full_path', { value: this.db.escape(selectData.full_path + '.%'), operate: 'like', }); this.sqlBuilder.setUpdateData('level', { value: 1, selfOperate: '+', }); this.sqlBuilder.setUpdateData('full_path', { value: ['`full_path`', this.db.escape('.' + selectData.ledger_id), this.db.escape('.' + preData.ledger_id + '.' + selectData.ledger_id)], literal: 'Replace', }); const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'update'); const data = this.transaction.query(sql, sqlParam); return data; } /** * 降级节点 * * @param {Number} tenderId - 标段id * @param {Number} selectId - 选中节点id * @return {Array} - 发生改变的数据 */ async downLevelNode(tenderId, selectId) { if ((tenderId <= 0) || (selectId <= 0)) { return []; } const selectData = await this.getDataByNodeId(tenderId, selectId); if (!selectData) { throw '降级节点数据错误'; } const preData = await this.getDataByParentAndOrder(tenderId, selectData.ledger_pid, selectData.order - 1); if (!preData) { throw '节点不可降级'; } const preLastChildData = await this.getLastChildData(tenderId, preData.ledger_id); this.transaction = await this.db.beginTransaction(); const orgLastPath = selectData.level === 1 ? selectData.ledger_id : '.' + selectData.ledger_id; const newLastPath = selectData.level === 1 ? preData.ledger_id + '.' + selectData.ledger_id : '.' + preData.ledger_id + '.' + selectData.ledger_id; const newFullPath = selectData.full_path.replace(orgLastPath, newLastPath); try { // 选中节点--全部后节点 order-- await this._updateSelectNextsOrder(selectData, -1); // 选中节点 修改pid, level, order, full_path const updateData = { id: selectData.id, ledger_pid: preData.ledger_id, order: preLastChildData ? preLastChildData.order + 1 : 1, level: selectData.level + 1, full_path: newFullPath, }; await this.transaction.update(this.tableName, updateData); // 选中节点--全部子节点(含孙) level++, full_path await this._syncDownlevelChildren(selectData, preData); // 选中节点--前兄弟节点 is_leaf应为false if (preData.is_leaf || this.ctx.helper.checkZero(selectData.total_price)) { const updateData2 = { id: preData.id, is_leaf: false }; if (this.ctx.helper.checkZero(selectData.total_price)) { updateData2['total_price'] = preData.total_price ? preData.total_price + selectData.total_price : selectData.total_price; } await this.transaction.update(this.tableName, updateData2); } this.transaction.commit(); } catch (err) { this.transaction.rollback(); throw err; } // 查询修改的数据 // 选中节点及子节点 const resultData1 = await this.getDataByFullPath(tenderId, newFullPath + '%'); // 选中节点--原前兄弟节点&全部后兄弟节点 const queryOrder = (preData.is_leaf || this.ctx.helper.checkZero(selectData.total_price)) ? preData.order - 1 : preData.order; const resultData2 = await this.getNextsData(tenderId, preData.ledger_pid, queryOrder); return { update: resultData1.concat(resultData2) }; } /** * 过滤data中update方式不可提交的字段 * @param {Number} id - 主键key * @param {Object} data * @return {Object<{id: *}>} * @private */ _filterUpdateInvalidField(id, data) { const result = { id, }; for (const prop in data) { if (readOnlyFields.indexOf(prop) === -1) { result[prop] = data[prop]; } } return result; } /** * newData中,以orgData为基准,过滤掉orgData中未定义或值相等的部分 * @param {Object} orgData * @param {Object} newData * @private */ _filterChangedField(orgData, newData) { const result= {}; let bChanged = false; for (const prop in orgData) { if (newData[prop] && newData[prop] !== orgData[prop]) { result[prop] = newData[prop]; bChanged = true; } } return bChanged ? result : undefined; } /** * 检查data中是否含有计算字段 * @param {Object} data * @returns {boolean} * @private */ _checkCalcField(data) { for (const prop in data) { if (calcFields.indexOf(prop) >= 0) { return true; } } return false; } /** * 提交数据 - 不影响计算等未提交项 * @param {Number} tenderId - 标段id * @param {Object} data - 提交数据 * @return {Object} - 提交后的数据 */ async updateInfo(tenderId, data) { // 简单校验数据 if (tenderId <= 0) { throw '标段不存在'; } if (tenderId !== data.tender_id) { throw '提交数据错误'; } try { // 过滤不可提交字段 const updateNode = await this.getDataById(data.id); if (!updateNode || tenderId !== updateNode.tender_id || data.ledger_id !== updateNode.ledger_id) { throw '提交数据错误'; } const updateData = this._filterUpdateInvalidField(updateNode.id, data); await this.db.update(this.tableName, updateData); } catch (err) { throw err; } const result = await this.getDataByNodeId(tenderId, data.ledger_id); return result; } /** * 提交多条数据 - 不影响计算等未提交项 * @param {Number} tenderId - 标段id * @param {Array} datas - 提交数据 * @return {Array} - 提交后的数据 */ async updateInfos(tenderId, datas) { if (tenderId <= 0) { throw '标段不存在'; } for (const data of datas) { if (tenderId !== data.tender_id) { throw '提交数据错误'; } } this.transaction = await this.db.beginTransaction(); try { for (const data of datas) { const updateNode = await this.getDataById(data.id); if (!updateNode || tenderId !== updateNode.tender_id || data.ledger_id !== updateNode.ledger_id) { throw '提交数据错误'; } const updateData = this._filterUpdateInvalidField(updateNode.id, data); await this.transaction.update(this.tableName, updateData); } this.transaction.commit(); } catch (err) { this.transaction.rollback(); throw err; } const filter = []; for (const data of datas) { filter.push(data.id); } this.initSqlBuilder(); this.sqlBuilder.setAndWhere('id', { value: filter, operate: 'in', }); const [sql, sqlParam] = this.sqlBuilder.build(this.tableName); const resultData = await this.db.query(sql, sqlParam); return resultData; } /** * 复制粘贴整块 * @param {Number} tenderId - 标段Id * @param {Number} selectId - 选中几点Id * @param {Array} block - 复制节点Id * @return {Object} - 提价后的数据(其中新增粘贴数据,只返回第一层) */ async pasteBlock(tenderId, selectId, block) { if ((tenderId <= 0) || (selectId <= 0)) { return []; } const selectData = await this.getDataByNodeId(tenderId, selectId); if (!selectData) { throw '位置数据错误'; } const newParentPath = selectData.full_path.replace(selectData.ledger_id, ''); const copyNodes = await this.getDataByNodeIds(tenderId, block); if (!copyNodes || copyNodes.length <= 0) { throw '复制数据错误'; } let bSameParent = true; for (const node of copyNodes) { if (node.ledger_pid !== copyNodes[0].ledger_pid) { bSameParent = false; break; } } if (!bSameParent) { throw '复制数据错误:仅可操作同层节点'; } const orgParentPath = copyNodes[0].full_path.replace(copyNodes[0].ledger_id, ''); let incre = 0; this.transaction = await this.db.beginTransaction(); try { // 选中节点的所有后兄弟节点,order+粘贴节点个数 await this._updateSelectNextsOrder(selectData, copyNodes.length); // 数据库创建新增节点数据 for (const node of copyNodes) { incre += node.total_price ? node.total_price : 0; const datas = await this.getDataByFullPath(tenderId, node.full_path + '%'); const cacheKey = 'tender_node_maxId:' + tenderId; let maxId = parseInt(await this.cache.get(cacheKey)); if (!maxId) { maxId = await this._getMaxNodeId(tenderId); } this.cache.set(cacheKey, maxId + datas.length, 'EX', this.ctx.app.config.cacheTime); // 计算粘贴数据中需更新部分 for (let index = 0; index < datas.length; index++) { const data = datas[index]; const newId = maxId + index + 1; delete data.id; if (!data.is_leaf) { for (const children of datas) { children.full_path = children.full_path.replace('.' + data.ledger_id, '.' + newId); if (children.ledger_pid === data.ledger_id) { children.ledger_pid = newId; } } } else { data.full_path = data.full_path.replace('.' + data.ledger_id, '.' + newId); } data.ledger_id = newId; data.full_path = data.full_path.replace(orgParentPath, newParentPath); if (data.ledger_pid === copyNodes[0].ledger_pid) { data.ledger_pid = selectData.ledger_pid; data.order = selectData.order + index + 1; } data.level = data.level + selectData.level - copyNodes[0].level; } // 插入粘贴数据 await this.transaction.insert(this.tableName, datas); } // 更新父节点金额 if (this.ctx.helper.checkZero(incre)) { const updateMap = {}; updateMap[newParentPath] = incre; await this._increCalcParent(tenderId, updateMap); } await this.transaction.commit(); } catch (err) { await this.transaction.rollback(); throw err; } // 查询应返回的结果 const order = []; for (let i = 1; i <= copyNodes.length; i++) { order.push(selectData.order + i); } const createData = await this.getDataByParentAndOrder(selectData.tender_id, selectData.ledger_pid, order); const updateData = await this.getNextsData(selectData.tender_id, selectData.ledger_pid, selectData.order + copyNodes.length); if (this.ctx.helper.checkZero(incre)) { const updateData1 = await this.getFullLevelDataByFullPath(selectData.tender_id, newParentPath); return { create: createData, update: updateData.concat(updateData1) }; } else { return { create: createData, update: updateData }; } } /** * 增量更新父项金额 * @param {Number} tenderId - 标段id * @param {Object} updateMap - 增量更新数,使用更新父项的full_path为索引 * e.g: {'1.2.6.8': 30, '1.2.6.10': 40}表示'1.2.6.8'增量30,'1.2.6.10'增量40(此处同步更新'1.2.6', '1.2', '1') * @returns {Promise} * @private */ async _increCalcParent(tenderId, updateMap) { for (const prop in updateMap) { this.initSqlBuilder(); this.sqlBuilder.setAndWhere('tender_id', { value: tenderId, operate: '=' }); const fullPath = this.ctx.helper.explodePath(prop); this.sqlBuilder.setAndWhere('full_path', { value: this.ctx.helper.explodePath(prop), operate: 'in' }); this.sqlBuilder.setUpdateData('total_price', { value: updateMap[prop] > 0 ? updateMap[prop] : -updateMap[prop], selfOperate: updateMap[prop] > 0 ? '+' : '-' }); const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'update'); await this.transaction.query(sql, sqlParam); } } /** * 提交数据 - 响应计算(增量方式计算) * @param {Number} tenderId * @param {Object} data * @returns {Promise<*>} */ async updateCalc(tenderId, data) { const findData = function (id, datas) { for (const d of datas) { if (d.id === id) { return d; } } return undefined; } // 简单验证数据 if (tenderId <= 0) { throw '标段不存在'; } if (!data) { throw '提交数据错误1'; } const datas = data instanceof Array ? data : [data]; const ids = []; for (const row of datas) { if (tenderId !== row.tender_id) { throw '提交数据错误2'; } ids.push(row.id); } const updateMap = {}, updateFullPath = []; this.transaction = await this.db.beginTransaction(); try { for (const row of datas) { const updateNode = await this.getDataById(row.id); if (!updateNode || tenderId !== updateNode.tender_id || row.ledger_id !== updateNode.ledger_id) { throw '提交数据错误3'; } let updateData; if (this._checkCalcField(row)) { const calcData = this.ctx.helper.updateObj(updateNode, row); if (updateNode.is_leaf) { calcData.total_price = calcData.quantity * calcData.unit_price; } if (updateNode.total_price === undefined || this.ctx.helper.checkZero(calcData.total_price - updateNode.total_price)) { const pfp = updateNode.full_path.replace('.' + updateNode.ledger_id, ''); if (updateMap[pfp]) { updateMap[pfp] = updateMap[pfp] + calcData.total_price - updateNode.total_price; } else { updateMap[pfp] = calcData.total_price - updateNode.total_price; updateFullPath.push(pfp); } } const data1 = this._filterChangedField(updateNode, calcData); updateData = this._filterUpdateInvalidField(updateNode.id, data1); } else { updateData = this._filterUpdateInvalidField(updateNode.id, row); } await this.transaction.update(this.tableName, updateData); } await this._increCalcParent(tenderId, updateMap); this.transaction.commit(); } catch (err) { this.transaction.rollback(); throw err; } const result1 = await this.getDataByIds(ids); if (updateFullPath.length > 0) { const result2 = await this.getFullLevelDataByFullPath(tenderId, updateFullPath); return result1.concat(result2); } else { return result1; } } } return Ledger; };