فهرست منبع

baseTreeService 1.0,待使用后台测试后再提交

MaiXinRong 5 سال پیش
والد
کامیت
0a51048715

+ 2 - 0
app.js

@@ -16,6 +16,7 @@ const crypto = require('crypto');
 //const calc = require('number-precision');
 
 const BaseService = require('./app/base/base_service');
+const BaseTreeService = require('./app/base/base_tree_service');
 const BaseController = require('./app/base/base_controller');
 
 const menu = require('./config/menu');
@@ -29,6 +30,7 @@ module.exports = app => {
     app.menu = menu;
     // 数据模型基类
     app.BaseService = BaseService;
+    app.BaseTreeService = BaseTreeService;
 
     // 控制器基类
     app.BaseController = BaseController;

+ 820 - 0
app/base/base_tree_service.js

@@ -0,0 +1,820 @@
+'use strict';
+
+/**
+ * 提供基础操作:
+ * 1. 增删改查
+ * 2. 粘贴整块
+ * 3. 简易导入
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const Service = require('./base_service');
+// sql拼装器
+const SqlBuilder = require('../lib/sql_builder');
+
+class TreeService extends Service {
+    /**
+     * 构造函数
+     *
+     * @param {Object} ctx - egg全局context
+     * @param {Object} setting - 树结构设置
+     * e.g.: {
+     *     mid: 'tender_id', 分块id(例如tender_id, rid, list_id)
+     *     kid: 'ledger_id', 分块内的树结构id
+     *     pid: 'ledger_pid', 父节点id
+     *     order: 'order',
+     *     level: 'level',
+     *     fullPath: 'full_path',
+     *     isLeaf: 'is_leaf',
+     *     keyPre: 'revise_bills_maxLid:'
+     * }
+     * @return {void}
+     */
+    constructor(ctx, setting) {
+        super(ctx);
+        this.tableName = setting.tableName;
+        this.setting = setting;
+        // 以下字段仅可通过树结构操作改变,不可直接通过update方式从接口提交,发现时过滤
+        this.readOnlyFields = ['id'];
+        this.readOnlyFields.push(this.setting.mid);
+        this.readOnlyFields.push(this.setting.kid);
+        this.readOnlyFields.push(this.setting.pid);
+        this.readOnlyFields.push(this.setting.order);
+        this.readOnlyFields.push(this.setting.level);
+        this.readOnlyFields.push(this.setting.fullPath);
+        this.readOnlyFields.push(this.setting.isLeaf);
+    }
+
+    getCondition (condition) {
+        const result = {};
+        if (condition.mid) result[this.setting.mid] = condition.mid;
+        if (condition.kid) result[this.setting.kid] = condition.kid;
+        if (condition.pid) result[this.setting.pid] = condition.pid;
+        if (condition[this.setting.order]) result[this.setting.order] = condition[this.setting.order];
+        if (condition.level) result[this.setting.level] = condition.level;
+        if (condition.fullPath) result[this.setting.fullPath] = condition.fullPath;
+        if (condition.isLeaf) result[this.setting.isLeaf] = condition.isLeaf;
+        return result;
+    }
+
+    /**
+     * 获取 修订 清单数据
+     * @param {Number} mid - masterId
+     * @returns {Promise<void>}
+     */
+    async getData(mid) {
+        return await this.db.select(this.tableName, {
+            where: this.getCondition({mid: mid})
+        });
+    }
+
+    /**
+     * 获取节点数据
+     * @param {Number} mid - masterId
+     * @param {Number} id
+     * @returns {Promise<void>}
+     */
+    async getDataByLid (mid, kid) {
+        return await this.db.get(this.tableName, this.getCondition({
+            mid: mid, kid: kid,
+        }));
+    }
+
+    /**
+     * 获取节点数据
+     * @param id
+     * @returns {Promise<Array>}
+     */
+    async getDataById(id) {
+        if (id instanceof Array) {
+            return await this.db.select(this.tableName, { where: {id: id} });
+        } else {
+            return await this.db.get(this.tableName, { id: id });
+        }
+    }
+
+    /**
+     * 获取最末的子节点
+     * @param {Number} mid - masterId
+     * @param {Number} pid - 父节点id
+     * @return {Object}
+     */
+    async getLastChildData(mid, pid) {
+        this.initSqlBuilder();
+        this.sqlBuilder.setAndWhere(this.setting.mid, {
+            value: mid,
+            operate: '=',
+        });
+        this.sqlBuilder.setAndWhere(this.setting.pid, {
+            value: pid,
+            operate: '=',
+        });
+        this.sqlBuilder.orderBy = [['order', 'DESC']];
+        const [sql, sqlParam] = this.sqlBuilder.build(this.tableName);
+        const resultData = await this.db.queryOne(sql, sqlParam);
+
+        return resultData;
+    }
+
+    /**
+     * 根据 父节点id 和 节点排序order 获取数据
+     *
+     * @param {Number} mid - master id
+     * @param {Number} pid - 父节点id
+     * @param {Number|Array} order - 排序
+     * @return {Object|Array} - 查询结果
+     */
+    async getDataByParentAndOrder(mid, pid, order) {
+        const result = await this.db.select(this.tableName, {
+            where: this.getCondition({mid: mid, pid: pid, order: order})
+        });
+        return order instanceof Array ? result : (result.length > 0 ? result[0] : null);
+    }
+
+    /**
+     * 根据 父节点ID 和 节点排序order 获取全部后节点数据
+     * @param {Number} mid - master id
+     * @param {Number} pid - 父节点id
+     * @param {Number} order - 排序
+     * @return {Array}
+     */
+    async getNextsData(mid, pid, order) {
+        this.initSqlBuilder();
+        this.sqlBuilder.setAndWhere(this.setting.mid, {
+            value: mid,
+            operate: '=',
+        });
+        this.sqlBuilder.setAndWhere(this.setting.pid, {
+            value: pid,
+            operate: '=',
+        });
+        this.sqlBuilder.setAndWhere(this.setting.order, {
+            value: order,
+            operate: '>',
+        });
+
+        const [sql, sqlParam] = this.sqlBuilder.build(this.tableName);
+        const data = await this.db.query(sql, sqlParam);
+
+        return data;
+    }
+
+    /**
+     * 获取最大节点id
+     *
+     * @param {Number} mid - master id
+     * @return {Number}
+     * @private
+     */
+    async _getMaxLid(mid) {
+        const cacheKey = this.setting.keyPre + mid;
+        let maxId = parseInt(await this.cache.get(cacheKey));
+        if (!maxId) {
+            const sql = 'SELECT Max(??) As max_id FROM ?? Where ' + this.setting.mid + ' = ?';
+            const sqlParam = ['ledger_id', this.tableName, mid];
+            const queryResult = await this.db.queryOne(sql, sqlParam);
+            maxId = queryResult.max_id || 0;
+            this.cache.set(cacheKey, maxId, 'EX', this.ctx.app.config.cacheTime);
+        }
+        return maxId;
+    }
+
+    /**
+     * 缓存最大节点id
+     *
+     * @param {Number} mid - master id
+     * @param {Number} maxId - 当前最大节点id
+     * @returns {Promise<void>}
+     * @private
+     */
+    _cacheMaxLid(mid, maxId) {
+        this.cache.set(this.setting.keyPre + mid , maxId, 'EX', this.ctx.app.config.cacheTime);
+    }
+
+    /**
+     * 更新order
+     * @param {Number} mid - master id
+     * @param {Number} pid - 父节点id
+     * @param {Number} order - 开始更新的order
+     * @param {Number} incre - 更新的增量
+     * @returns {Promise<*>}
+     * @private
+     */
+    async _updateChildrenOrder(mid, pid, order, incre = 1) {
+        this.initSqlBuilder();
+        this.sqlBuilder.setAndWhere(this.setting.mid, {
+            value: mid,
+            operate: '=',
+        });
+        this.sqlBuilder.setAndWhere(this.setting.order, {
+            value: order,
+            operate: '>=',
+        });
+        this.sqlBuilder.setAndWhere(this.setting.pid, {
+            value: pid,
+            operate: '=',
+        });
+        this.sqlBuilder.setUpdateData(this.setting.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;
+    }
+
+    /**
+     * 新增数据(新增为selectData的后项,该方法不可单独使用)
+     *
+     * @param {Number} mid - 台账id
+     * @param {Object} select - 选中节点的数据
+     * @param {Object} data - 新增节点的初始数据
+     * @return {Object} - 新增结果
+     * @private
+     */
+    async _addNodeData(mid, select, data) {
+        if (!data) {
+            data = {};
+        }
+        const maxId = await this._getMaxLid(mid);
+
+        data.id = this.uuid.v4();
+        data[this.setting.kid] = maxId + 1;
+        data[this.setting.pid] = select[this.setting.pid];
+        data[this.setting.mid] = mid;
+        data[this.setting.level] = select[this.setting.level];
+        data[this.setting.order] = select[this.setting.order] + 1;
+        data[this.setting.fullPath] = select[this.setting.fullPath].replace('.' + select[this.setting.kid], '.' + data[this.setting.kid]);
+        data[this.setting.isLeaf] = true;
+        const result = await this.transaction.insert(this.tableName, data);
+
+        this._cacheMaxLid(mid, maxId + 1);
+
+        return result;
+    }
+
+    /**
+     * 新增节点
+     * @param {Number} mid - 台账id
+     * @param {Number} kid - 清单节点id
+     * @returns {Promise<void>}
+     */
+    async addNode(mid, kid, data) {
+        if (!mid || !kid) return null;
+        const select = await this.getDataByLid(mid, kid);
+        if (!select) {
+            throw '新增节点数据错误';
+        }
+
+        this.transaction = await this.db.beginTransaction();
+        try {
+            await this._updateChildrenOrder(mid, select[this.setting.pid], select[this.setting.order]+1);
+            const newNode = await this._addNodeData(mid, select, data);
+            if (newNode.affectedRows !== 1) {
+                throw '新增节点数据额错误';
+            }
+            await this.transaction.commit();
+            this.transaction = null;
+        } catch (err) {
+            await this.transaction.rollback();
+            this.transaction = null;
+            throw err;
+        }
+
+        const createData = await this.getDataByParentAndOrder(mid, select[this.setting.pid], [select[this.setting.order] + 1]);
+        const updateData = await this.getNextsData(mid, select[this.setting.pid], select[this.setting.order] + 1);
+        return {create: createData, update: updateData};
+    }
+
+    /**
+     * 删除节点
+     * @param {Number} tenderId - 标段id
+     * @param {Object} deleteData - 删除节点数据
+     * @return {Promise<*>}
+     * @private
+     */
+    async _deleteNodeData(mid, deleteNode) {
+        this.initSqlBuilder();
+        this.sqlBuilder.setAndWhere(this.setting.mid, {
+            value: mid,
+            operate: '=',
+        });
+        this.sqlBuilder.setAndWhere(this.setting.fullPath, {
+            value: this.db.escape(deleteNode[this.setting.fullPath] + '%'),
+            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(mid, kid) {
+        if ((mid <= 0) || (kid <= 0)) return [];
+        const select = await this.getDataByNodeId(mid, kid);
+        if (!select) throw '删除节点数据错误';
+        const parent = await this.getDataByNodeId(mid, select[this.setting.pid]);
+        // 获取将要被删除的数据
+        const deleteData = await this.getDataByFullPath(mid, select[this.setting.fullPath] + '%');
+        if (deleteData.length === 0) throw '删除节点数据错误';
+
+        this.transaction = await this.db.beginTransaction();
+        try {
+            // 删除
+            const operate = await this._deleteNodeData(mid, select);
+            // 选中节点--父节点 只有一个子节点时,应升级is_leaf
+            if (parent) {
+                const count = await this.db.count(this.tableName, this.getCondition({mid: mid, pid: select[this.setting.pid]}));
+                if (count === 1) {
+                    const updateParent = {id: parent.id };
+                    updateParent[this.setting.isLeaf] = true;
+                    await this.transaction.update(this.tableName, updateParent);
+                }
+            }
+            // 选中节点--全部后节点 order--
+            await this._updateSelectNextsOrder(select, -1);
+            // 删除部位明细
+            //await this.ctx.service.pos.deletePosData(this.transaction, tenderId, this._.map(deleteData, 'id'));
+            await this.transaction.commit();
+        } catch (err) {
+            await this.transaction.rollback();
+            throw err;
+        }
+        // 查询结果
+        const updateData = await this.getNextsData(mid, select[this.setting.pid], select[this.setting.order] - 1);
+        if (parent) {
+            const updateData1 = await this.getDataByNodeId(mid, select[this.setting.pid]);
+            if (updateData1[this.setting.isLeaf]) {
+                updateData.push(updateData1);
+            }
+        }
+        return { delete: deleteData, update: updateData };
+    }
+
+    /**
+     * 上移节点
+     *
+     * @param {Number} mid - master id
+     * @param {Number} kid - 选中节点id
+     * @return {Array} - 发生改变的数据
+     */
+    async upMoveNode(mid, kid) {
+        if (!mid || !kid) return null;
+        const select = await this.getDataByLid(mid, kid);
+        if (!select) {
+            throw '上移节点数据错误';
+        }
+        const pre = await this.getDataByParentAndOrder(mid, select[this.setting.pid], select[this.setting.order] - 1);
+        if (!pre) {
+            throw '节点不可上移';
+        }
+
+        this.transaction = await this.db.beginTransaction();
+        try {
+            const sData = await this.transaction.update(this.tableName, { id: select.id, order: select[this.setting.order] - 1 });
+            const pData = await this.transaction.update(this.tableName, { id: pre.id, order: pre[this.setting.order] + 1 });
+            await this.transaction.commit();
+        } catch (err) {
+            await this.transaction.rollback();
+            throw err;
+        }
+
+        const resultData = await this.getDataByParentAndOrder(mid, select[this.setting.pid], [select[this.setting.order], pre[this.setting.order]]);
+        return { update: resultData };
+    }
+
+    /**
+     * 下移节点
+     *
+     * @param {Number} mid - master id
+     * @param {Number} kid - 选中节点id
+     * @return {Array} - 发生改变的数据
+     */
+    async downMoveNode(mid, kid) {
+        if (!mid || !kid) return null;
+        const select = await this.getDataByLid(mid, kid);
+        if (!select) {
+            throw '下移节点数据错误';
+        }
+        const next = await this.getDataByParentAndOrder(mid, select[this.setting.pid], select[this.setting.order] + 1);
+        if (!next) {
+            throw '节点不可下移';
+        }
+
+        this.transaction = await this.db.beginTransaction();
+        try {
+            const sData = await this.transaction.update(this.tableName, { id: select.id, order: select[this.setting.order] + 1 });
+            const pData = await this.transaction.update(this.tableName, { id: next.id, order: next[this.setting.order] - 1 });
+            await this.transaction.commit();
+        } catch (err) {
+            await this.transaction.rollback();
+            throw err;
+        }
+
+        const resultData = await this.getDataByParentAndOrder(mid, select[this.setting.pid], [select[this.setting.order], next[this.setting.order]]);
+        return { update: resultData };
+    }
+
+    /**
+     * 升级selectData, 同步修改所有子节点
+     * @param {Object} selectData - 升级操作,选中节点
+     * @return {Object}
+     * @private
+     */
+    async _syncUplevelChildren(select) {
+        this.initSqlBuilder();
+        this.sqlBuilder.setAndWhere(this.setting.mid, {
+            value: selectData.tender_id,
+            operate: '=',
+        });
+        this.sqlBuilder.setAndWhere(this.setting.fullPath, {
+            value: this.db.escape(select[this.setting.fullPath] + '.%'),
+            operate: 'like',
+        });
+        this.sqlBuilder.setUpdateData(this.setting.level, {
+            value: 1,
+            selfOperate: '-',
+        });
+        this.sqlBuilder.setUpdateData(this.setting.fullPath, {
+            value: [this.setting.fullPath, this.db.escape(select[this.setting.pid] + '.'), this.db.escape('')],
+            literal: 'Replace',
+        });
+        const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'update');
+        const data = await this.transaction.query(sql, sqlParam);
+
+        return data;
+    }
+
+    /**
+     * 选中节点的后兄弟节点,全部变为当前节点的子节点
+     * @param {Object} selectData - 选中节点
+     * @return {Object}
+     * @private
+     */
+    async _syncUpLevelNexts(select) {
+        // 查询selectData的lastChild
+        const lastChild = await this.getLastChildData(select[this.setting.mid], select[this.setting.kid]);
+        const nexts = await this.getNextsData(select[this.setting.mid], select[this.setting.pid], select[this.setting.order]);
+        if (nexts && nexts.length > 0) {
+            // 修改nextsData pid, 排序
+            this.initSqlBuilder();
+            this.sqlBuilder.setUpdateData(this.setting.pid, {
+                value: select[this.setting.kid],
+            });
+            const orderInc = lastChild ? lastChild[this.setting.order] - select[this.setting.order] : - select[this.setting.order];
+            this.sqlBuilder.setUpdateData(this.setting.order, {
+                value: Math.abs(orderInc),
+                selfOperate: orderInc > 0 ? '+' : '-',
+            });
+            this.sqlBuilder.setAndWhere(this.setting.mid, {
+                value: select[this.setting.mid],
+                operate: '=',
+            });
+            this.sqlBuilder.setAndWhere(this.setting.pid, {
+                value: select[this.setting.pid],
+                operate: '=',
+            });
+            this.sqlBuilder.setAndWhere(this.setting.order, {
+                value: select[this.setting.order],
+                operate: '>',
+            });
+            const [sql1, sqlParam1] = this.sqlBuilder.build(this.tableName, 'update');
+            await this.transaction.query(sql1, sqlParam1);
+
+            // 选中节点 is_leaf应为false
+            if (select.is_leaf) {
+                const updateData = { id: select.id, is_leaf: false };
+                await this.transaction.update(this.tableName, updateData);
+            }
+
+            // 修改nextsData及其子节点的full_path
+            const oldSubStr = this.db.escape(select[this.setting.pid] + '.');
+            const newSubStr = this.db.escape(select[this.setting.kid] + '.');
+            const sqlArr = [];
+            sqlArr.push('Update ?? SET `full_path` = Replace(`full_path`,' + oldSubStr + ',' + newSubStr + ') Where');
+            sqlArr.push('(`' + this.setting.mid + '` = ' + select[this.setting.mid] + ')');
+            sqlArr.push(' And (');
+            for (const data of nexts) {
+                sqlArr.push('`' + this.setting.fullPath + '` Like ' + this.db.escape(data[this.setting.fullPath] + '%'));
+                if (nexts.indexOf(data) < nexts.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(mid, kid) {
+        if ((mid <= 0) || (kid <= 0)) return [];
+
+        const select = await this.getDataByNodeId(mid, kid);
+        if (!select) throw '升级节点数据错误';
+        const parent = await this.getDataByNodeId(mid, select[this.setting.pid]);
+        if (!parent) throw '升级节点数据错误';
+
+        this.transaction = await this.db.beginTransaction();
+        const newFullPath = select[this.setting.fullPath].replace(select[this.setting.pid] + '.', '');
+        try {
+            // 选中节点--父节点 选中节点为firstChild时,修改is_leaf
+            if (select[this.setting.order] === 1) {
+                await this.transaction.update(this.tableName, {
+                    id: parent.id,
+                    is_leaf: true,
+                });
+            }
+            // 选中节点--父节点--全部后兄弟节点 order+1
+            await this._updateSelectNextsOrder(parent);
+            // 选中节点 修改pid, order, full_path, level, is_leaf, 清空计算项
+            const updateData = { id: select.id };
+            updateData[this.setting.pid] = parent[this.setting.pid];
+            updateData[this.setting.order] = parent[this.setting.order] + 1;
+            updateData[this.setting.level] = select[this.setting.level] + 1;
+            updateData[this.setting.fullPath] = newFullPath;
+
+            const nexts = await this.getNextsData(mid, parent[this.setting.kid], select[this.setting.order]);
+            if (nexts.length > 0) {
+                updateData.is_leaf = true;
+                // updateData.unit_price = null;
+                // updateData.quantity = null;
+                // updateData.total_price = null;
+                // updateData.deal_qty = null;
+                // updateData.deal_tp = null;
+            }
+            await this.transaction.update(this.tableName, updateData);
+            // 选中节点--全部子节点(含孙) level-1, full_path变更
+            await this._syncUplevelChildren(select);
+            // 选中节点--全部后兄弟节点 收编为子节点 修改pid, order, full_path
+            await this._syncUpLevelNexts(select);
+            await this.transaction.commit();
+        } catch (err) {
+            await this.transaction.rollback();
+            throw err;
+        }
+
+        // 查询修改的数据
+        const resultData1 = await this.getDataByFullPath(mid, newFullPath + '%');
+        const resultData2 = await this.getNextsData(mid, parent[this.setting.pid], parent[this.setting.order] + 1);
+        return { update: resultData1.concat(resultData2) };
+    }
+
+    /**
+     * 降级selectData, 同步修改所有子节点
+     * @param {Object} selectData - 选中节点
+     * @param {Object} preData - 选中节点的前一节点(降级后为父节点)
+     * @return {Promise<*>}
+     * @private
+     */
+    async _syncDownlevelChildren(select, pre) {
+        this.initSqlBuilder();
+        this.sqlBuilder.setAndWhere(this.setting.mid, {
+            value: select[this.setting.mid],
+            operate: '=',
+        });
+        this.sqlBuilder.setAndWhere(this.setting.fullPath, {
+            value: this.db.escape(select[this.setting.fullPath] + '.%'),
+            operate: 'like',
+        });
+        this.sqlBuilder.setUpdateData(this.setting.level, {
+            value: 1,
+            selfOperate: '+',
+        });
+        this.sqlBuilder.setUpdateData(this.setting.fullPath, {
+            value: [this.setting.fullPath, this.db.escape('.' + select[this.setting.kid]), this.db.escape('.' + pre[this.setting.kid] + '.' + select[this.setting.kid])],
+            literal: 'Replace',
+        });
+        const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'update');
+        const data = await this.transaction.query(sql, sqlParam);
+
+        return data;
+    }
+
+    /**
+     * 降级节点
+     *
+     * @param {Number} tenderId - 标段id
+     * @param {Number} selectId - 选中节点id
+     * @return {Array} - 发生改变的数据
+     */
+    async downLevelNode(mid, kid) {
+        if ((mid <= 0) || (kid <= 0)) return [];
+        const select = await this.getDataByNodeId(mid, kid);
+        if (!select) throw '降级节点数据错误';
+        const pre = await this.getDataByParentAndOrder(mid, select[this.setting.pid], select[this.setting.order] - 1);
+        if (!pre) throw '节点不可降级';
+        const preLastChild = await this.getLastChildData(mid, pre[this.setting.kid]);
+
+        this.transaction = await this.db.beginTransaction();
+        const orgLastPath = select[this.setting.level] === 1 ? select[this.setting.kid] : '.' + select[this.setting.kid];
+        const newLastPath = select[this.setting.level] === 1 ? pre[this.setting.kid] + '.' + select[this.setting.kid] : '.' + pre[this.setting.kid] + '.' + select[this.setting.kid];
+        const newFullPath = select.full_path.replace(orgLastPath, newLastPath);
+        try {
+            // 选中节点--全部后节点 order--
+            await this._updateSelectNextsOrder(select, -1);
+            // 选中节点 修改pid, level, order, full_path
+            const updateData = { id: select.id };
+            updateData[this.setting.kid] = pre[this.setting.kid];
+            updateData[this.setting.order] = preLastChild ? preLastChild[this.setting.order] + 1 : 1;
+            updateData[this.setting.level] = select[this.setting.level] + 1;
+            updateData[this.setting.fullPath] = newFullPath;
+            await this.transaction.update(this.tableName, updateData);
+            // 选中节点--全部子节点(含孙) level++, full_path
+            await this._syncDownlevelChildren(select, pre);
+            // 选中节点--前兄弟节点 is_leaf应为false, 清空计算相关字段
+            const updateData2 = { id: pre.id };
+            updateData2[this.setting.isLeaf] = false;
+            // updateData2.unit_price = null;
+            // updateData2.quantity = null;
+            // updateData2.total_price = null;
+            // updateData2.deal_qty = null;
+            // updateData2.deal_tp = null;
+            await this.transaction.update(this.tableName, updateData2);
+            await this.transaction.commit();
+        } catch (err) {
+            await this.transaction.rollback();
+            throw err;
+        }
+
+        // 查询修改的数据
+        // 选中节点及子节点
+        const resultData1 = await this.getDataByFullPath(mid, newFullPath + '%');
+        // 选中节点--原前兄弟节点&全部后兄弟节点
+        const resultData2 = await this.getNextsData(mid, pre[this.setting.pid], pre[this.setting.order]);
+        return { update: resultData1.concat(resultData2) };
+    }
+
+    /**
+     * 过滤data中update方式不可提交的字段
+     * @param {Number} id - 主键key
+     * @param {Object} data
+     * @return {Object<{id: *}>}
+     * @private
+     */
+    _filterUpdateInvalidField(id, data) {
+        const result = {id: id};
+        for (const prop in data) {
+            if (this.readOnlyFields.indexOf(prop) === -1) {
+                result[prop] = data[prop];
+            }
+        }
+        return result;
+    }
+
+    /**
+     * 提交多条数据 - 不影响计算等未提交项
+     * @param {Number} tenderId - 标段id
+     * @param {Array} datas - 提交数据
+     * @return {Array} - 提交后的数据
+     */
+    async updateInfos(mid, datas) {
+        if (mid <= 0) throw '数据错误';
+        for (const data of datas) {
+            if (mid !== data[this.setting.mid]) throw '提交数据错误';
+        }
+
+        this.transaction = await this.db.beginTransaction();
+        try {
+            for (const data of datas) {
+                const updateNode = await this.getDataById(data.id);
+                if (!updateNode || mid !== updateNode[this.setting.mid] || data.kid !== updateNode[this.setting.kid]) {
+                    throw '提交数据错误';
+                }
+                const updateData = this._filterUpdateInvalidField(updateNode.id, data);
+                await this.transaction.update(this.tableName, updateData);
+            }
+            await this.transaction.commit();
+        } catch (err) {
+            await this.transaction.rollback();
+            throw err;
+        }
+
+        const resultData = await this.getDataById(this._.map(datas, 'id'));
+        return resultData;
+    }
+
+    async pasteBlockRelaData(relaData) {
+        if (!this.transaction) throw '更新数据错误';
+        // for (const id of relaData) {
+        //     await this.ctx.service.pos.copyBillsPosData(id.org, id.new, this.transaction);
+        // }
+    }
+
+    async getPasteBlockResult(select, copyNodes) {
+        const createData = await this.getDataByIds(newIds);
+        const updateData = await this.getNextsData(selectData[this.setting.mid], selectData[this.setting.pid], selectData[this.setting.order] + copyNodes.length);
+        //const posData = await this.ctx.service.pos.getPosData({ lid: newIds });
+        return {
+            ledger: { create: createData, update: updateData },
+            //pos: posData,
+        };
+    }
+
+    /**
+     * 复制粘贴整块
+     * @param {Number} tenderId - 标段Id
+     * @param {Number} selectId - 选中几点Id
+     * @param {Array} block - 复制节点Id
+     * @return {Object} - 提价后的数据(其中新增粘贴数据,只返回第一层)
+     */
+    async pasteBlock(mid, kid, block) {
+        if ((mid <= 0) || (kid <= 0)) return [];
+
+        const selectData = await this.getDataByNodeId(mid, kid);
+        if (!selectData) throw '数据错误';
+
+        const copyNodes = await this.getDataByNodeIds(mid, block);
+        if (!copyNodes || copyNodes.length <= 0)  throw '复制数据错误';
+        for (const node of copyNodes) {
+            if (node[this.setting.pid] !== copyNodes[0][this.setting.pid]) throw '复制数据错误:仅可操作同层节点';
+        }
+
+        const newParentPath = selectData[this.setting.fullPath].replace(selectData[this.setting.kid], '');
+        const orgParentPath = copyNodes[0][this.setting.fullPath].replace(copyNodes[0][this.setting.kid], '');
+        const newIds = [];
+        this.transaction = await this.db.beginTransaction();
+        try {
+            // 选中节点的所有后兄弟节点,order+粘贴节点个数
+            await this._updateSelectNextsOrder(selectData, copyNodes.length);
+            for (let iNode = 0; iNode < copyNodes.length; iNode++) {
+                const node = copyNodes[iNode];
+                let datas = await this.getDataByFullPath(mid, node[this.setting.fullPath] + '%');
+                datas = this._.sortBy(datas, 'level');
+
+                const maxId = await this._getMaxLid(mid);
+                this._cacheMaxLid(mid, maxId + datas.length);
+
+                const billsId = [];
+                // 计算粘贴数据中需更新部分
+                for (let index = 0; index < datas.length; index++) {
+                    const data = datas[index];
+                    const newId = maxId + index + 1;
+                    const idChange = {
+                        org: data.id,
+                    };
+                    data.id = this.uuid.v4();
+                    idChange.new = data.id;
+                    data[this.setting.mid] = mid;
+                    if (!data[this.setting.isLeaf]) {
+                        for (const children of datas) {
+                            children[this.setting.fullPath] = children[this.setting.fullPath].replace('.' + data[this.setting.kid], '.' + newId);
+                            if (children[this.setting.pid] === data[this.setting.kid]) {
+                                children[this.setting.pid] = newId;
+                            }
+                        }
+                    } else {
+                        data[this.setting.fullPath] = data[this.setting.fullPath].replace('.' + data[this.setting.kid], '.' + newId);
+                    }
+                    data[this.setting.kid] = newId;
+                    data[this.setting.fullPath] = data[this.setting.fullPath].replace(orgParentPath, newParentPath);
+                    if (data[this.setting.pid] === node[this.setting.pid]) {
+                        data[this.setting.pid] = selectData[this.setting.pid];
+                        data[this.setting.order] = selectData[this.setting.order] + iNode + 1;
+                    }
+                    data[this.setting.level] = data[this.setting.level] + selectData[this.setting.level] - copyNodes[0][this.setting.level];
+                    idChange.isLeaf = data[this.setting.isLeaf];
+                    billsId.push(idChange);
+                    newIds.push(data.id);
+                }
+                const newData = await this.transaction.insert(this.tableName, datas);
+                await this.pasteBlockRelaData(billsId);
+            }
+            // 数据库创建新增节点数据
+            await this.transaction.commit();
+        } catch (err) {
+            await this.transaction.rollback();
+            throw err;
+        }
+
+        // 查询应返回的结果
+        const createData = await this.getDataByIds(newIds);
+        const updateData = await this.getNextsData(selectData[this.setting.mid], selectData[this.setting.pid], selectData[this.setting.order] + copyNodes.length);
+        //const posData = await this.ctx.service.pos.getPosData({ lid: newIds });
+        return {
+            ledger: { create: createData, update: updateData },
+            //pos: posData,
+        };
+    }
+
+}
+
+module.exports = TreeService;

+ 2 - 1
app/controller/revise_controller.js

@@ -286,7 +286,7 @@ module.exports = app => {
 
                 switch (data.postType) {
                     case 'add':
-                        result = await ctx.service.reviseBills.addNode(ctx.tender.id, ctx.revise.id, data.id);
+                        result = await ctx.service.reviseBills.addReviseNode(ctx.tender.id, ctx.revise.id, data.id);
                         break;
                     // case 'delete':
                     //     result = await ctx.service.reviseBills.deleteNode(ctx.revise.id, data.id);
@@ -306,6 +306,7 @@ module.exports = app => {
                     default:
                         throw '未知操作';
                 }
+                console.log(result);
                 ctx.body = {err: 0, msg: '', data: result};
             } catch (err) {
                 this.log(err);

+ 1 - 1
app/controller/standard_lib_controller.js

@@ -38,7 +38,7 @@ class StandardLibController extends BaseController {
             if (isNaN(data.list_id) || data.list_id <= 0) {
                 throw '参数错误';
             }
-            const libData = await this.model.getData(data.list_id, -1);
+            const libData = await this.model.getData(data.list_id);
 
             responseData.data = libData;
         } catch (error) {

+ 14 - 292
app/service/revise_bills.js

@@ -8,14 +8,8 @@
  * @version
  */
 
-const keyPre = 'revise_bills_maxLid:';
-const tidField = 'tender_id';
-const lidField = 'ledger_id';
-const pidField = 'ledger_pid';
-const pathField = 'full_path';
-
 module.exports = app => {
-    class ReviseBills extends app.BaseService {
+    class ReviseBills extends app.BaseTreeService {
 
         /**
          * 构造函数
@@ -24,203 +18,17 @@ module.exports = app => {
          * @return {void}
          */
         constructor(ctx) {
-            super(ctx);
-            this.tableName = 'revise_bills';
-        }
-
-        /**
-         * 获取 修订 清单数据
-         * @param {Number}tid - 标段id
-         * @returns {Promise<void>}
-         */
-        async getData(tid) {
-            return await this.db.select(this.tableName, {
-                where: {tender_id: tid}
-            });
-        }
-
-        /**
-         * 获取节点数据
-         * @param {Number} tid - 标段id
-         * @param {Number} lid - 标段内,台账id
-         * @returns {Promise<void>}
-         */
-        async getDataByLid (tid, lid) {
-            return await this.db.get(this.tableName, {tender_id: tid, ledger_id: lid});
-        }
-
-        /**
-         * 获取节点数据
-         * @param id
-         * @returns {Promise<Array>}
-         */
-        async getDataById(id) {
-            if (id instanceof Array) {
-                return await this.db.select(this.tableName, { where: {id: id} });
-            } else {
-                return await this.db.get(this.tableName, { id: id });
-            }
-        }
-
-        /**
-         * 获取最末的子节点
-         * @param {Number} tenderId - 标段id
-         * @param {Number} pid - 父节点id
-         * @return {Object}
-         */
-        async getLastChildData(tid, pid) {
-            this.initSqlBuilder();
-            this.sqlBuilder.setAndWhere('tender_id', {
-                value: tid,
-                operate: '=',
-            });
-            this.sqlBuilder.setAndWhere('ledger_pid', {
-                value: pid,
-                operate: '=',
-            });
-            this.sqlBuilder.orderBy = [['order', 'DESC']];
-            const [sql, sqlParam] = this.sqlBuilder.build(this.tableName);
-            const resultData = await this.db.queryOne(sql, sqlParam);
-
-            return resultData;
-        }
-        /**
-         * 根据 父节点id 和 节点排序order 获取数据
-         *
-         * @param {Number} tid - 标段id
-         * @param {Number} pid - 父节点id
-         * @param {Number|Array} order - 排序
-         * @return {Object|Array} - 查询结果
-         */
-        async getDataByParentAndOrder(tid, pid, order) {
-            const result = await this.db.select(this.tableName, {where: {
-                tender_id: tid, ledger_pid: pid, order: order
-            }});
-            return order instanceof Array ? result : (result.length > 0 ? result[0] : null);
-        }
-        /**
-         * 根据 父节点ID 和 节点排序order 获取全部后节点数据
-         * @param {Number} tid - 标段id
-         * @param {Number} pid - 父节点id(ledger_pid)
-         * @param {Number} order - 排序
-         * @return {Array}
-         */
-        async getNextsData(tid, pid, order) {
-            this.initSqlBuilder();
-            this.sqlBuilder.setAndWhere('tender_id', {
-                value: tid,
-                operate: '=',
+            super(ctx, {
+                mid: 'tender_id',
+                kid: 'ledger_id',
+                pid: 'ledger_pid',
+                order: 'order',
+                level: 'level',
+                isLeaf: 'is_leaf',
+                fullPath: 'full_path',
+                keyPre: 'revise_bills_maxLid:'
             });
-            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;
-        }
-
-        /**
-         * 获取最大节点id
-         *
-         * @param {Number} tid - 台账id
-         * @return {Number}
-         * @private
-         */
-        async _getMaxLid(tid) {
-            const cacheKey = keyPre + tid;
-            let maxId = parseInt(await this.cache.get(cacheKey));
-            if (!maxId) {
-                const sql = 'SELECT Max(??) As max_id FROM ?? Where tender_id = ?';
-                const sqlParam = ['ledger_id', this.tableName, tid];
-                const queryResult = await this.db.queryOne(sql, sqlParam);
-                maxId = queryResult.max_id || 0;
-                this.cache.set(cacheKey, maxId, 'EX', this.ctx.app.config.cacheTime);
-            }
-            return maxId;
-        }
-
-        /**
-         * 缓存最大节点id
-         *
-         * @param {Number} tid - 台账id
-         * @param {Number} maxId - 当前最大节点id
-         * @returns {Promise<void>}
-         * @private
-         */
-        async _cacheMaxLid(tid, maxId) {
-            this.cache.set(keyPre + tid , maxId, 'EX', this.ctx.app.config.cacheTime);
-        }
-
-        /**
-         * 更新order
-         * @param {Number} tid - 台账id
-         * @param {Number} pid - 父节点id(ledger_id)
-         * @param {Number} order - 开始更新的order
-         * @param {Number} incre - 更新的增量
-         * @returns {Promise<*>}
-         * @private
-         */
-        async _updateChildrenOrder(tid, pid, order, incre = 1) {
-            this.initSqlBuilder();
-            this.sqlBuilder.setAndWhere('tender_id', {
-                value: tid,
-                operate: '=',
-            });
-            this.sqlBuilder.setAndWhere('order', {
-                value: order,
-                operate: '>=',
-            });
-            this.sqlBuilder.setAndWhere('ledger_pid', {
-                value: pid,
-                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;
-        }
-
-        /**
-         * 新增数据(新增为selectData的后项,该方法不可单独使用)
-         *
-         * @param {Number} tid - 台账id
-         * @param {uuid} rid - 修订id
-         * @param {Object} select - 选中节点的数据
-         * @param {Object} data - 新增节点的初始数据
-         * @return {Object} - 新增结果
-         * @private
-         */
-        async _addNodeData(tid, rid, select, data) {
-            if (!data) {
-                data = {};
-            }
-            const maxId = await this._getMaxLid(tid);
-
-            data.id = this.uuid.v4();
-            data.crid = rid;
-            data.ledger_id = maxId + 1;
-            data.ledger_pid = select.ledger_pid;
-            data.tender_id = tid;
-            data.level = select.level;
-            data.order = select.order + 1;
-            data.full_path = select.full_path.replace('.' + select.ledger_id, '.' + data.ledger_id);
-            data.is_leaf = true;
-            const result = await this.transaction.insert(this.tableName, data);
-
-            this._cacheMaxLid(tid, maxId + 1);
-
-            return result;
+            this.tableName = 'revise_bills';
         }
 
         /**
@@ -230,95 +38,9 @@ module.exports = app => {
          * @param {Number} lid - 清单节点id
          * @returns {Promise<void>}
          */
-        async addNode(tid, rid, lid) {
-            if (!tid || !rid || !lid) return null;
-            const select = await this.getDataByLid(tid, lid);
-            if (!select) {
-                throw '新增节点数据错误';
-            }
-
-            this.transaction = await this.db.beginTransaction();
-            try {
-                await this._updateChildrenOrder(tid, select.ledger_pid, select.order+1);
-                const newNode = await this._addNodeData(tid, rid, select);
-                if (newNode.affectedRows !== 1) {
-                    throw '新增节点数据额错误';
-                }
-                await this.transaction.commit();
-                this.transaction = null;
-            } catch (err) {
-                await this.transaction.rollback();
-                this.transaction = null;
-                throw err;
-            }
-
-            const createData = await this.getDataByParentAndOrder(tid, select.ledger_pid, [select.order + 1]);
-            const updateData = await this.getNextsData(tid, select.ledger_pid, select.order + 1);
-            return {create: createData, update: updateData};
-        }
-
-        /**
-         * 上移节点
-         *
-         * @param {Number} tid - 台账id
-         * @param {Number} lid - 选中节点id
-         * @return {Array} - 发生改变的数据
-         */
-        async upMoveNode(tid, lid) {
-            if (!tid || !lid) return null;
-            const select = await this.getDataByLid(tid, lid);
-            if (!select) {
-                throw '上移节点数据错误';
-            }
-            const pre = await this.getDataByParentAndOrder(tid, select.ledger_pid, select.order - 1);
-            if (!pre) {
-                throw '节点不可上移';
-            }
-
-            this.transaction = await this.db.beginTransaction();
-            try {
-                const sData = await this.transaction.update(this.tableName, { id: select.id, order: select.order - 1 });
-                const pData = await this.transaction.update(this.tableName, { id: pre.id, order: pre.order + 1 });
-                await this.transaction.commit();
-            } catch (err) {
-                await this.transaction.rollback();
-                throw err;
-            }
-
-            const resultData = await this.getDataByParentAndOrder(tid, select.ledger_pid, [select.order, pre.order]);
-            return { update: resultData };
-        }
-
-        /**
-         * 下移节点
-         *
-         * @param {Number} tid - 台账id
-         * @param {Number} lid - 选中节点id
-         * @return {Array} - 发生改变的数据
-         */
-        async downMoveNode(tid, lid) {
-            if (!tid || !lid) return null;
-            const select = await this.getDataByLid(tid, lid);
-            if (!select) {
-                throw '下移节点数据错误';
-            }
-            const next = await this.getDataByParentAndOrder(tid, select.ledger_pid, select.order + 1);
-            if (!next) {
-                throw '节点不可下移';
-            }
-
-            this.transaction = await this.db.beginTransaction();
-            try {
-                const sData = await this.transaction.update(this.tableName, { id: select.id, order: select.order + 1 });
-                const pData = await this.transaction.update(this.tableName, { id: next.id, order: next.order - 1 });
-                await this.transaction.commit();
-            } catch (err) {
-                await this.transaction.rollback();
-                throw err;
-            }
-
-            const resultData = await this.getDataByParentAndOrder(tid, select.ledger_pid, [select.order, next.order]);
-            return { update: resultData };
+        async addReviseNode(tid, rid, lid) {
+            if (!rid) return null;
+            return await this.addNode(tid, lid, {crid: rid});
         }
 
         /**

+ 11 - 38
app/service/standard_lib.js

@@ -7,9 +7,9 @@
  * @version
  */
 
-const BaseService = require('../base/base_service');
+const BaseTreeService = require('../base/base_tree_service');
 
-class StandardLib extends BaseService {
+class StandardLib extends BaseTreeService {
 
     /**
      * 构造函数
@@ -18,10 +18,9 @@ class StandardLib extends BaseService {
      * @param {String} tableName - 表名
      * @return {void}
      */
-    constructor(ctx, tableName) {
-        super(ctx);
+    constructor(ctx, setting, tableName) {
+        super(ctx, setting);
         this.tableName = tableName;
-        this.dataId = '';
         this.stdType = '';
     }
 
@@ -31,32 +30,6 @@ class StandardLib extends BaseService {
 
 
     /**
-     * 获取数据
-     *
-     * @param {Number} listId - 项目节列表id
-     * @param {Number} level - 小于此层级的全部不显示
-     * @return {Array} - 返回对应数据
-     */
-    async getData(listId, level = 2) {
-        this.initSqlBuilder();
-        this.sqlBuilder.setAndWhere('list_id', {
-            operate: '=',
-            value: listId,
-        });
-        if (level >= 0) {
-            this.sqlBuilder.setAndWhere('level', {
-                operate: '<=',
-                value: level,
-            });
-        }
-        const [sql, sqlParam] = this.sqlBuilder.build(this.tableName);
-        const list = await this.db.query(sql, sqlParam);
-
-        return list;
-    }
-
-
-    /**
      * 实例中具体的dataId使用字段不相同,统一赋值到source下
      * @param {Object|Array} data
      */
@@ -65,7 +38,7 @@ class StandardLib extends BaseService {
         const datas = data instanceof Array ? data : [data];
         for (const d of datas) {
             if (d) {
-                d.source = this.stdType + '-' + d.list_id + '-' + d[this.dataId];
+                d.source = this.stdType + '-' + d[this.setting.mid] + '-' + d[this.setting.kid];
             }
         }
     }
@@ -77,11 +50,11 @@ class StandardLib extends BaseService {
      */
     async getDataByDataId(listId, dataId) {
         this.initSqlBuilder();
-        this.sqlBuilder.setAndWhere('list_id', {
+        this.sqlBuilder.setAndWhere(this.setting.mid, {
             value: listId,
             operate: '='
         });
-        this.sqlBuilder.setAndWhere(this.dataId, {
+        this.sqlBuilder.setAndWhere(this.setting.kid, {
             value: dataId,
             operate: '='
         });
@@ -100,11 +73,11 @@ class StandardLib extends BaseService {
      */
     async getDataByFullPath(listId, full_path) {
         this.initSqlBuilder();
-        this.sqlBuilder.setAndWhere('list_id', {
+        this.sqlBuilder.setAndWhere(this.setting.mid, {
             value: listId,
             operate: '=',
         });
-        this.sqlBuilder.setAndWhere('full_path', {
+        this.sqlBuilder.setAndWhere(this.setting.fullPath, {
             value: this.db.escape(full_path),
             operate: 'Like',
         });
@@ -125,11 +98,11 @@ class StandardLib extends BaseService {
         const explodePath = this.ctx.helper.explodePath(fullPath);
 
         this.initSqlBuilder();
-        this.sqlBuilder.setAndWhere('list_id', {
+        this.sqlBuilder.setAndWhere(this.setting.mid, {
             value: listId,
             operate: '=',
         });
-        this.sqlBuilder.setAndWhere('full_path', {
+        this.sqlBuilder.setAndWhere(this.setting.fullPath, {
             value: explodePath,
             operate: 'in'
         });

+ 10 - 2
app/service/std_bills.js

@@ -20,8 +20,16 @@ module.exports = app => {
          * @return {void}
          */
         constructor(ctx) {
-            super(ctx, 'bill');
-            this.dataId = 'bill_id';
+            super(ctx, {
+                mid: 'list_id',
+                kid: 'bill_id',
+                pid: 'pid',
+                order: 'order',
+                level: 'level',
+                isLeaf: 'is_leaf',
+                fullPath: 'full_path',
+                keyPre: 'revise_bills_maxLid:'
+            }, 'bill');
             this.stdType = 'bill';
         }
 

+ 10 - 2
app/service/std_chapter.js

@@ -20,8 +20,16 @@ module.exports = app => {
          * @return {void}
          */
         constructor(ctx) {
-            super(ctx, 'project_chapter');
-            this.dataId = 'chapter_id';
+            super(ctx, {
+                mid: 'list_id',
+                kid: 'chapter_id',
+                pid: 'pid',
+                order: 'order',
+                level: 'level',
+                isLeaf: 'is_leaf',
+                fullPath: 'full_path',
+                keyPre: 'revise_bills_maxLid:'
+            }, 'project_chapter');
             this.stdType = 'chapter';
         }