Browse Source

台账修订第一次提交
1. 新增台账修订
2. 修订页面初始化等
3. 新增节点、上下移节点、批量插入部位明细
4. 保存修订详情

MaiXinRong 6 years ago
parent
commit
1368eb4f56

+ 1 - 1
app/base/base_controller.js

@@ -143,7 +143,7 @@ class BaseController extends Controller {
         if (error.stack) {
             return {err: 2, msg: msg, data: null};
         } else {
-            return {err: 1, msg: err.toString(), data: null};
+            return {err: 1, msg: error.toString(), data: null};
         }
     }
 }

+ 0 - 177
app/controller/ledger_controller.js

@@ -656,183 +656,6 @@ module.exports = app => {
             }
         }
 
-        async _getAddReviseValid(ctx) {
-            const stage  = await ctx.service.stage.getLastestStage(ctx.tender.id, true);
-            const revise = await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id);
-            return (ctx.tender.data.user_id === ctx.session.sessionUser.accountId) &&
-                (ctx.tender.data.ledger_status === auditConst.status.checked) &&
-                (!stage || stage.status === audit.stage.status.checked) &&
-                (!revise || !revise.valid || revise.status === audit.revise.status.checked);
-        }
-
-        /**
-         * 台账修订页面 (Get)
-         *
-         * @param {object} ctx - egg全局变量
-         * @return {void}
-         */
-        async revise(ctx) {
-            try {
-                // 分页相关
-                const count = await ctx.service.ledgerRevise.count({tid: ctx.tender.id});
-                const ledgerRevise = await ctx.service.ledgerRevise.getReviseList(ctx.tender.id);
-                const addValid = await this._getAddReviseValid(ctx);
-                const renderData = {
-                    tender: ctx.tender.data,
-                    tenderMenu: this.menu.tenderMenu,
-                    preUrl: '/tender/' + ctx.tender.id,
-                    pageInfo: {
-                        page: ctx.page,
-                        total: Math.ceil(count/app.config.pageSize),
-                        queryData: JSON.stringify(ctx.urlInfo.query),
-                    },
-                    ledgerRevise,
-                    addValid,
-                    auditConst: audit.revise,
-                };
-                await this.layout('ledger/revise.ejs', renderData, 'ledger/revise_modal.ejs');
-            } catch (err) {
-                this.log(err);
-                ctx.redirect(ctx.request.header.referer);
-            }
-        }
-
-        /**
-         * 新增 修订 (Post)
-         * @param ctx
-         * @returns {Promise<void>}
-         */
-        async addRevise(ctx) {
-            try {
-                const addValid = await this._getAddReviseValid(ctx);
-                if (!addValid) {
-                    throw '无法新增修订';
-                }
-                const revise = await ctx.service.ledgerRevise.add(ctx.tender.id, ctx.session.sessionUser.accountId);
-
-                ctx.redirect('/tender/' + ctx.tender.id + '/ledger/revise/info');
-            } catch(err) {
-                this.log(err);
-                ctx.redirect(ctx.request.header.referer);
-            }
-        }
-
-        /**
-         * 作废 修订 (Post)
-         * @param ctx
-         * @returns {Promise<void>}
-         */
-        async cancelRevise(ctx) {
-            try {
-                const revise = await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id);
-                if (revise.uid !== ctx.session.sessionUser.accountId) {
-                    throw '您无权作废该修订';
-                }
-                if (revise.status === audit.revise.checking) {
-                    throw '修订审批中,不可作废';
-                } else if (revise.status === audit.revise.checked) {
-                    throw '修订已审批通过,不可作废';
-                }
-                if (revise.valid) {
-                    const result = await ctx.service.ledgerRevise.cancelRevise(revise.id);
-                }
-
-                ctx.redirect('/tender/' + ctx.tender.id + '/ledger/revise/');
-            } catch(err) {
-                this.log(err);
-                ctx.redirect(ctx.request.header.referer);
-            }
-        }
-
-        async _getDefaultReviseInfoData(ctx, revise) {
-            const reviseBills = await ctx.service.reviseBills.getData(ctx.tender.id, revise.id);
-            const revisePos = await ctx.service.revisePos.getData(ctx.tender.id, revise.id);
-            const [ledgerSpread, posSpread] = this._getSpreadSetting();
-            return {
-                revise: revise, tender: ctx.tender.data,
-                reviseBills, revisePos, ledgerSpread, posSpread, tenderMenu, measureType,
-                preUrl: '/tender/' + ctx.tender.id,
-                audit: audit.revise,
-                jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.ledger.revise),
-            };
-        }
-
-        async _getHistoryReviseInfo(ctx, revise) {
-            const renderData = await this._getDefaultReviseInfoData(ctx, revise);
-            renderData.ledgerSpread.readOnly = true;
-            renderData.posSpread.readOnly = true;
-            renderData.readOnly = true;
-            renderData.history = true;
-            renderData.historyRevise = await ctx.service.ledgerRevise.getReviseList(ctx.tender.id);
-            await this.layout('ledger/revise_info.ejs', renderData, 'ledger/revise_info_modal.ejs');
-        }
-
-        async _getReviseInfo(ctx, revise) {
-            const renderData = await this._getDefaultReviseInfoData(ctx, revise);
-            const readOnly = revise.status === audit.revise.status.checking || revise.status === audit.revise.status.checked || revise.status.uid !== ctx.session.sessionUser.accountId;
-            if (readOnly) {
-                renderData.ledgerSpread.readOnly = true;
-                renderData.posSpread.readOnly = true;
-            }
-            renderData.readOnly = readOnly;
-            renderData.history = false;
-            renderData.historyRevise = [];
-            await this.layout('ledger/revise_info.ejs', renderData, 'ledger/revise_info_modal.ejs');
-        }
-
-        /**
-         * 修订 详细页面(Get)
-         * @param ctx
-         * @returns {Promise<void>}
-         */
-        async reviseInfo(ctx) {
-            try {
-                const revise = await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id);
-                if (revise.status === audit.revise.status.checked) {
-                    await this._getHistoryReviseInfo(ctx);
-                } else {
-                    await this._getReviseInfo(ctx, revise);
-                }
-            } catch (err) {
-                this.log(err);
-                ctx.redirect(ctx.request.header.referer);
-            }
-        }
-
-        /**
-         * 保存 修订详情 (Ajax-post)
-         * @param ctx
-         * @returns {Promise<void>}
-         */
-        async saveRevise(ctx) {
-            try {
-                const revise = await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id);
-                if (!revise.valid) {
-                    throw '该修订已作废';
-                } else if (revise.status === audit.revise.status.uncheck || revise.status === audit.revise.status.checkNo) {
-                    if (ctx.session.sessionUser.accountId !== revise.uid) {
-                        throw '您无权修改';
-                    }
-                } else if (revise.status === audit.revise.status.checking) {
-                    throw '修订在审批中,不可修改修订数据';
-                } else if (revise.staut === audit.revise.status.checked) {
-                    throw '修订已经审批通过,不可修改修订数据,请新增下一修订';
-                }
-                const data = JSON.parse(ctx.request.body.data);
-                if (data.content === undefined) {
-                    throw '保存数据有误'
-                }
-                const result = await ctx.service.ledgerRevise.defaultUpdate({id: revise.id, content: data.content});
-                if (result.affectedRows !== 1) {
-                    throw '保存数据失败,请重试';
-                }
-                ctx.body = {err: 0, msg: '', data: null};
-            } catch(err) {
-                this.log(err);
-                ctx.body = this.ajaxErrorBody(err, '保存数据失败,请重试');
-            }
-        }
-
         /**
          * 计量台账页面 (Get)
          *

+ 355 - 0
app/controller/revise_controller.js

@@ -0,0 +1,355 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const stdDataAddType = {
+    withParent: 1,
+    child: 2,
+    next: 3,
+};
+const audit = require('../const/audit');
+const tenderMenu = require('../../config/menu').tenderMenu;
+const measureType = require('../const/tender').measureType;
+const spreadConst = require('../const/spread');
+const fs = require('fs');
+const LzString = require('lz-string');
+const accountGroup = require('../const/account_group').group;
+
+module.exports = app => {
+
+    class ReviseController extends app.BaseController {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+        }
+
+        /**
+         * 是否可以新增修订
+         * @param ctx
+         * @returns {Promise<boolean>}
+         * @private
+         */
+        async _getAddReviseValid(ctx) {
+            const stage  = await ctx.service.stage.getLastestStage(ctx.tender.id, true);
+            const revise = await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id);
+            return (ctx.tender.data.user_id === ctx.session.sessionUser.accountId) &&
+                (ctx.tender.data.ledger_status === audit.ledger.status.checked) &&
+                (!stage || stage.status === audit.stage.status.checked) &&
+                (!revise || !revise.valid || revise.status === audit.revise.status.checked);
+        }
+
+        /**
+         * 台账修订页面 (Get)
+         *
+         * @param {object} ctx - egg全局变量
+         * @return {void}
+         */
+        async index(ctx) {
+            try {
+                // 分页相关
+                const count = await ctx.service.ledgerRevise.count({tid: ctx.tender.id});
+                const ledgerRevise = await ctx.service.ledgerRevise.getReviseList(ctx.tender.id);
+                const addValid = await this._getAddReviseValid(ctx);
+                const renderData = {
+                    tender: ctx.tender.data,
+                    tenderMenu: this.menu.tenderMenu,
+                    preUrl: '/tender/' + ctx.tender.id,
+                    pageInfo: {
+                        page: ctx.page,
+                        total: Math.ceil(count/app.config.pageSize),
+                        queryData: JSON.stringify(ctx.urlInfo.query),
+                    },
+                    ledgerRevise,
+                    addValid,
+                    auditConst: audit.revise,
+                };
+                await this.layout('revise/index.ejs', renderData, 'revise/modal.ejs');
+            } catch (err) {
+                this.log(err);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 新增 修订 (Post)
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async add(ctx) {
+            try {
+                const addValid = await this._getAddReviseValid(ctx);
+                if (!addValid) {
+                    throw '无法新增修订';
+                }
+                const revise = await ctx.service.ledgerRevise.add(ctx.tender.id, ctx.session.sessionUser.accountId);
+
+                ctx.redirect('/tender/' + ctx.tender.id + '/revise/info');
+            } catch(err) {
+                this.log(err);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 作废 修订 (Post)
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async cancel(ctx) {
+            try {
+                const revise = await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id);
+                if (revise.uid !== ctx.session.sessionUser.accountId) {
+                    throw '您无权作废该修订';
+                }
+                if (revise.status === audit.revise.checking) {
+                    throw '修订审批中,不可作废';
+                } else if (revise.status === audit.revise.checked) {
+                    throw '修订已审批通过,不可作废';
+                }
+                if (revise.valid) {
+                    const result = await ctx.service.ledgerRevise.cancelRevise(revise.id);
+                }
+
+                ctx.redirect('/tender/' + ctx.tender.id + '/revise/');
+            } catch(err) {
+                this.log(err);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 保存 修订详情 (Ajax-post)
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async save(ctx) {
+            try {
+                const revise = await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id);
+                if (!revise.valid) {
+                    throw '该修订已作废';
+                } else if (revise.status === audit.revise.status.uncheck || revise.status === audit.revise.status.checkNo) {
+                    if (ctx.session.sessionUser.accountId !== revise.uid) {
+                        throw '您无权修改';
+                    }
+                } else if (revise.status === audit.revise.status.checking) {
+                    throw '修订在审批中,不可修改修订数据';
+                } else if (revise.staut === audit.revise.status.checked) {
+                    throw '修订已经审批通过,不可修改修订数据,请新增下一修订';
+                }
+                const data = JSON.parse(ctx.request.body.data);
+                if (data.content === undefined) {
+                    throw '保存数据有误'
+                }
+                const result = await ctx.service.ledgerRevise.defaultUpdate({id: revise.id, content: data.content});
+                if (result.affectedRows !== 1) {
+                    throw '保存数据失败,请重试';
+                }
+                ctx.body = {err: 0, msg: '', data: null};
+            } catch(err) {
+                this.log(err);
+                ctx.body = this.ajaxErrorBody(err, '保存数据失败,请重试');
+            }
+        }
+
+        /**
+         * 获取SpreadSetting
+         * @private
+         */
+        _getSpreadSetting(revise) {
+            const _ = this.app._;
+            function removeFieldCols(setting, cols) {
+                _.remove(setting.cols, function(c) {
+                    return cols.indexOf(c.field) > -1;
+                });
+            }
+            const tender = this.ctx.tender;
+            const setting = tender.info.display.ledger.clQty ? spreadConst.withCl : spreadConst.withoutCl;
+            const ledger = JSON.parse(JSON.stringify(setting.ledger));
+            const pos = JSON.parse(JSON.stringify(setting.pos));
+
+            if (revise.status === audit.revise.status.checking || revise.status === audit.revise.status.checked) {
+                ledger.readOnly = true;
+                pos.readOnly = true;
+            }
+            if (tender.data.measure_type === measureType.tz.value) {
+                removeFieldCols(ledger, spreadConst.filterCols.tzWithoutCols);
+            }
+            if (!tender.info.display.ledger.dgnQty) {
+                removeFieldCols(ledger, spreadConst.filterCols.dgnCols);
+            }
+            return [ledger, pos];
+        }
+
+        async _getDefaultReviseInfoData(ctx, revise) {
+            const reviseBills = await ctx.service.reviseBills.getData(ctx.tender.id, revise.id);
+            const revisePos = await ctx.service.revisePos.getData(ctx.tender.id, revise.id);
+            const [ledgerSpread, posSpread] = this._getSpreadSetting(revise);
+            return {
+                revise: revise, tender: ctx.tender.data,
+                reviseBills, revisePos, ledgerSpread, posSpread, tenderMenu, measureType,
+                preUrl: '/tender/' + ctx.tender.id,
+                audit: audit.revise,
+                jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.ledger.revise),
+            };
+        }
+
+        /**
+         * 历史修订页面(修订完成后,用于查看)
+         * @param ctx
+         * @param revise
+         * @returns {Promise<void>}
+         * @private
+         */
+        async _getHistoryReviseInfo(ctx, revise) {
+            const renderData = await this._getDefaultReviseInfoData(ctx, revise);
+            renderData.ledgerSpread.readOnly = true;
+            renderData.posSpread.readOnly = true;
+            renderData.readOnly = true;
+            renderData.history = true;
+            renderData.historyRevise = await ctx.service.ledgerRevise.getReviseList(ctx.tender.id);
+            await this.layout('revise/info.ejs', renderData, 'revise/info_modal.ejs');
+        }
+
+        /**
+         * 修订页面(草稿,审批中)
+         * @param ctx
+         * @param revise
+         * @returns {Promise<void>}
+         * @private
+         */
+        async _getReviseInfo(ctx, revise) {
+            const renderData = await this._getDefaultReviseInfoData(ctx, revise);
+            const readOnly = (revise.status === audit.revise.status.checking || revise.status === audit.revise.status.checked || revise.uid !== ctx.session.sessionUser.accountId);
+            if (readOnly) {
+                renderData.ledgerSpread.readOnly = true;
+                renderData.posSpread.readOnly = true;
+            }
+            renderData.readOnly = readOnly;
+            renderData.history = false;
+            renderData.historyRevise = [];
+            await this.layout('revise/info.ejs', renderData, 'revise/info_modal.ejs');
+        }
+
+        /**
+         * 修订 详细页面(Get)
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async info(ctx) {
+            try {
+                const revise = await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id);
+                if (revise.status === audit.revise.status.checked) {
+                    await this._getHistoryReviseInfo(ctx);
+                } else {
+                    await this._getReviseInfo(ctx, revise);
+                }
+            } catch (err) {
+                this.log(err);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        async _checkRevise() {
+            const revise = await this.ctx.service.ledgerRevise.getLastestRevise(this.ctx.tender.id);
+            if (revise.uid !== this.ctx.session.sessionUser.accountId) {
+                throw '修订数据错误';
+            } else if (revise.status === audit.revise.status.checking) {
+                throw '修订审批中,不可修改';
+            } else if (revise.status === audit.revise.status.checked) {
+                throw '修订已完成,如需修订台账,请新增修订';
+            }
+            this.ctx.revise = revise;
+        }
+
+        /**
+         * 增、删、上下移、升降级
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async baseOpr(ctx) {
+            try {
+                await this._checkRevise();
+                const data = JSON.parse(ctx.request.body.data);
+                let result;
+
+                switch (data.postType) {
+                    case 'add':
+                        result = await ctx.service.reviseBills.addNode(ctx.tender.id, ctx.revise.id, data.id);
+                        break;
+                    // case 'delete':
+                    //     result = await ctx.service.reviseBills.deleteNode(ctx.revise.id, data.id);
+                    //     break;
+                    case 'up-move':
+                        result = await ctx.service.reviseBills.upMoveNode(ctx.tender.id, data.id);
+                        break;
+                    case 'down-move':
+                        result = await ctx.service.reviseBills.downMoveNode(ctx.tender.id, data.id);
+                        break;
+                    // case 'up-level':
+                    //     result = await ctx.service.reviseBills.upLevelNode(ctx.revise.id, data.id);
+                    //     break;
+                    // case 'down-level':
+                    //     result = await ctx.service.reviseBills.downLevelNode(ctx.revise.id, data.id);
+                    //     break;
+                    default:
+                        throw '未知操作';
+                }
+                ctx.body = {err: 0, msg: '', data: result};
+            } catch (err) {
+                this.log(err);
+                ctx.body = this.ajaxErrorBody(err, '数据错误');
+            }
+        }
+
+        /**
+         * 批量插入数据 (Ajax)
+         *
+         * data = {id, batchData, batchType}
+         * data.batchType = 'batchInsertChild'/'batchInsertNext'
+         * data.batchData = [{name, children}] -- 项目节列表
+         * data.batchData.children = [{code, name, unit, unit_price, quantity}] -- 工程量清单列表
+         *
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async batchInsert(ctx) {
+            try {
+                await this._checkRevise();
+                const data = JSON.parse(ctx.request.body.data);
+                if ((isNaN(data.id) || data.id <= 0) || !data.batchType) {
+                    throw '参数错误';
+                }
+
+                let result;
+                switch (data.batchType) {
+                    case 'child':
+                        result = await ctx.service.reviseBills.batchInsertChild(ctx.tender.id, ctx.revise.id, data.id, data.batchData);
+                        break;
+                    case 'next':
+                        result = await ctx.service.reviseBills.batchInsertNext(ctx.tender.id, ctx.revise.id, data.id, data.batchData);
+                        break;
+                    default:
+                        throw '参数错误';
+                }
+                ctx.body = {err: 0, msg: '', data: result};
+            } catch (err) {
+                this.log(err);
+                ctx.body = this.ajaxErrorBody(err, '数据错误');
+            }
+        }
+    }
+
+    return ReviseController;
+};

+ 1 - 0
app/public/js/ledger.js

@@ -183,6 +183,7 @@ $(document).ready(function() {
 
             const count = ledgerTree.getPosterity(node).length;
             tree.baseOperation('/tender/' + getTenderId() + '/ledger/base-operation', node, 'delete', function (result) {
+                //treeOperationObj.refreshTree(sheet, result);
                 sheet.deleteRows(row, count + 1);
                 for (const data of result.update) {
                     SpreadJsObj.reLoadRowData(sheet, tree.nodes.indexOf(data), tree.getPosterity(data).length + 1);

+ 511 - 41
app/public/js/revise.js

@@ -1,7 +1,7 @@
 'use strict';
 
 /**
- *
+ * 台账修订页面js
  *
  * @author Mai
  * @date
@@ -42,6 +42,241 @@ $(document).ready(() => {
     const pos = new PosData({ id: 'id', ledgerId: 'lid' });
     pos.loadDatas(posData);
 
+    const billsTreeSpreadObj = {
+        /**
+         * 刷新顶部按钮是否可用
+         * @param sheet
+         * @param selections
+         */
+        refreshOperationValid: function (sheet) {
+            const setObjEnable = function (obj, enable) {
+                if (enable) {
+                    obj.removeClass('disabled');
+                } else {
+                    obj.addClass('disabled');
+                }
+            };
+            const tree = sheet.zh_tree;
+            const node = SpreadJsObj.getSelectObject(sheet);
+            const preNode = tree.getPreSiblingNode(node);
+            const valid = !sheet.zh_setting.readOnly;
+
+            setObjEnable($('a[type="add"]'), valid);
+            setObjEnable($('a[type="delete"]'), valid && node);
+            setObjEnable($('a[type="up-move"]'), valid && node && preNode);
+            setObjEnable($('a[type="down-move"]'), valid && node && !tree.isLastSibling(node));
+            if (isTz) {
+                const posRange = node ? pos.getLedgerPos(node.id) : [];
+                setObjEnable($('a[type="up-level"]'), valid && node && tree.getParent(node) && node.level > 2 && (!posRange || posRange.length === 0));
+                const preNodePosRange = preNode ? pos.getLedgerPos(preNode.id) : [];
+                setObjEnable($('a[type="down-level"]'), valid && node && preNode && (!preNodePosRange || preNodePosRange.length === 0));
+            } else {
+                setObjEnable($('#up-level'), valid && node && tree.getParent(node));
+                setObjEnable($('#down-level'), valid && node && preNode);
+            }
+            setObjEnable($('#cut'), valid);
+            setObjEnable($('#paste'), valid);
+        },
+        /**
+         *
+         * @param sheet
+         * @param data
+         */
+        refreshTree: function (sheet, data) {
+            SpreadJsObj.massOperationSheet(sheet, function () {
+                const tree = sheet.zh_tree;
+                // 处理删除
+                if (data.delete) {
+                    for (const d of data.delete) {
+                        sheet.deleteRows(tree.nodes.indexOf(d), 1);
+                    }
+                }
+                // 处理新增
+                if (data.create) {
+                    const newNodes = data.create;
+                    if (newNodes) {
+                        newNodes.sort(function (a, b) {
+                            return a.index - b.index;
+                        });
+
+                        for (const node of newNodes) {
+                            sheet.addRows(node.index, 1);
+                            SpreadJsObj.reLoadRowData(sheet, tree.nodes.indexOf(node), 1);
+                        }
+                    }
+                }
+                // 处理更新
+                if (data.update) {
+                    const rows = [];
+                    for (const u of data.update) {
+                        rows.push(tree.nodes.indexOf(u));
+                    }
+                    SpreadJsObj.reLoadRowsData(sheet, rows);
+                }
+                // 处理展开
+                if (data.expand) {
+                    const expanded = [];
+                    for (const e of data.expand) {
+                        if (expanded.indexOf(e) === -1) {
+                            const posterity = tree.getPosterity(e);
+                            for (const p of posterity) {
+                                sheet.setRowVisible(tree.nodes.indexOf(p), p.visible);
+                                expanded.push(p);
+                            }
+                        }
+                    }
+                }
+            });
+        },
+        selectionChanged: function (e, info) {
+            if (info.newSelections[0].row !== info.oldSelections[0].row) {
+                billsTreeSpreadObj.refreshOperationValid(info.sheet);
+                posSpreadObj.loadCurPosData();
+                //posSearch.search($('#pos-keyword').val());
+            }
+        },
+        /**
+         * 新增节点
+         * @param spread
+         */
+        baseOpr: function (sheet, type) {
+            const self = this;
+            const tree = sheet.zh_tree;
+            const node = SpreadJsObj.getSelectObject(sheet);
+            if (!tree || !node) return;
+
+            postData(window.location.pathname + '/base-opr', {id: node.ledger_id, postType: type}, function (result) {
+                const refreshData = tree.loadPostData(result);
+                self.refreshTree(sheet, refreshData);
+                if (['up-move', 'down-move', 'up-level', 'down-level'].indexOf(type) > -1) {
+                    const sel = sheet.getSelections()[0];
+                    if (sel) {
+                        sheet.setSelection(tree.nodes.indexOf(node), sel.col, sel.rowCount, sel.colCount);
+                    }
+                }
+                self.refreshOperationValid(sheet);
+            });
+        },
+        /**
+         * 编辑单元格响应事件
+         * @param {Object} e
+         * @param {Object} info
+         */
+        editEnded: function (e, info) {
+            if (info.sheet.zh_setting) {
+                const col = info.sheet.zh_setting.cols[info.col];
+                const sortData = info.sheet.zh_dataType === 'tree' ? info.sheet.zh_tree.nodes : info.sheet.zh_data;
+                const node = sortData[info.row];
+                const data = {
+                    id: node.id,
+                    tender_id: node.tender_id,
+                    ledger_id: node.ledger_id
+                };
+                // 未改变值则不提交
+                const orgValue = node[col.field];
+                if (orgValue == info.editingText || ((!orgValue || orgValue === '') && (info.editingText === ''))) {
+                    return;
+                }
+                // 台账模式,检查部位明细相关
+                if (checkTzMeasureType()) {
+                    if (col.field === 'sgfh_qty' || col.field === 'sgfh_tp' ||
+                        col.field === 'sjcl_qty' || col.field === 'sjcl_tp' ||
+                        col.field === 'qtcl_qty' || col.field === 'qtcl_tp') {
+                        if (!node.children || node.children.length ===0) {
+                            const lPos = pos.getLedgerPos(node.id);
+                            if (lPos && lPos.length > 0) {
+                                toast('清单含有部位明细,不可修改施工图复核数量', 'error');
+                                SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                                return;
+                            }
+                        }
+                    }
+                    if (col.field === 'b_code' && (info.editingText === '' || !info.editingText)) {
+                        const lPos = pos.getLedgerPos(node.id);
+                        if (lPos && lPos.length > 0) {
+                            toast('清单含有部位明细,请先删除部位明细,再删除清单编号', 'error');
+                            SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                            return;
+                        }
+                    }
+                }
+                // 获取更新数据
+                if (info.editingText) {
+                    data[col.field] = col.type === 'Number' ? parseFloat(info.editingText) : info.editingText.replace('\n', '');
+                } else {
+                    data[col.field] = null;
+                }
+                // 更新至服务器
+                info.sheet.zh_tree.update('/tender/' + getTenderId() + '/ledger/update', data, function (result) {
+                    treeOperationObj.refreshTree(info.sheet, result);
+                });
+            }
+        },
+    };
+    billsTreeSpreadObj.refreshOperationValid(billsSheet);
+    if (!readOnly) {
+        // 增删上下移升降级
+        $('a[name="base-opr"]').click(function () {
+            billsTreeSpreadObj.baseOpr(billsSheet, this.getAttribute('type'));
+        });
+        billsSpread.bind(spreadNS.Events.SelectionChanged, billsTreeSpreadObj.selectionChanged);
+        let batchInsertObj;
+        // 右键菜单
+        $.contextMenu({
+            selector: '#bills-spread',
+            build: function ($trigger, e) {
+                const target = SpreadJsObj.safeRightClickSelection($trigger, e, billsSpread);
+                return target.hitTestType === spreadNS.SheetArea.viewport || target.hitTestType === spreadNS.SheetArea.rowHeader;
+            },
+            items: {
+                'batchInsertBillsPos': {
+                    name: '批量插入清单-部位',
+                    icon: 'fa-sign-in',
+                    disabled: function (key, opt) {
+                        const select = SpreadJsObj.getSelectObject(billsSheet);
+                        if (select) {
+                            if (select.code && select.code !== '') {
+                                return !billsTree.isLeafXmj(select);
+                            } else {
+                                const parent = billsTree.getParent(select);
+                                return !(parent && billsTree.isLeafXmj(parent));
+                            }
+                        } else {
+                            return false;
+                        }
+                    },
+                    callback: function (key, opt) {
+                        if (!batchInsertObj) {
+                            batchInsertObj = new BatchInsertBillsPosObj($('#batch'));
+                        } else {
+                            batchInsertObj.initView();
+                        }
+                        $('#batch').modal('show');
+                    }
+                },
+            }
+        });
+    }
+
+    const posSpreadObj = {
+        /**
+         * 加载部位明细 根据当前台账选择节点
+         */
+        loadCurPosData: function () {
+            const node = SpreadJsObj.getSelectObject(billsSheet);
+            if (node) {
+                const posData = pos.getLedgerPos(node.id) || [];
+                SpreadJsObj.loadSheetData(posSpread.getActiveSheet(), 'data', posData);
+            } else {
+                SpreadJsObj.loadSheetData(posSpread.getActiveSheet(), 'data', []);
+            }
+        },
+        editStarting: function (e, info) {
+            posSpreadObj.ledgerTreeNode = SpreadJsObj.getSelectObject(billsSheet);
+        },
+    };
+    posSpreadObj.loadCurPosData();
+
     $.divResizer({
         select: '#revise-resize',
         callback: function () {
@@ -52,7 +287,279 @@ $(document).ready(() => {
         }
     });
 
+    class stdLib {
+        constructor(obj, stdType, treeSetting, spreadSetting) {
+            this.obj = obj;
+            this.url = '/std/' + stdType;
+            this.treeSetting = treeSetting;
+            treeSetting.preUrl = this.url;
+            this.spreadSetting = spreadSetting;
+            this.spread = SpreadJsObj.createNewSpread(this.obj);
+            SpreadJsObj.initSheet(this.spread.getActiveSheet(), this.spreadSetting);
+            this.spread.getActiveSheet().bind(GC.Spread.Sheets.Events.CellDoubleClick, function (e, info) {
+                const stdSheet = info.sheet;
+                const mainSheet = ledgerSpread.getActiveSheet();
+                if (!stdSheet.zh_setting || !stdSheet.zh_tree || !mainSheet.zh_tree) { return; }
+
+                const stdTree = stdSheet.zh_tree;
+                const stdNode = stdTree.nodes[info.row];
+                const mainTree = mainSheet.zh_tree;
+                const sel = mainSheet.getSelections()[0];
+                const mainNode = mainTree.nodes[sel.row];
+                if (!stdNode) { return; }
+
+                mainTree.postData('/tender/' + getTenderId() + '/ledger/add-by-std', mainNode, {
+                    tender_id: mainNode.tender_id,
+                    stdType: stdType,
+                    stdLibId: stdNode.list_id,
+                    stdNode: stdTree.getNodeKey(stdNode)
+                }, function (result) {
+                    treeOperationObj.refreshTree(mainSheet, result);
+                    treeOperationObj.refreshOperationValid(mainSheet);
+                });
+            });
+            this.pathTree = createNewPathTree('base', this.treeSetting);
+        }
+        loadLib (listId) {
+            const self = this;
+            postData(this.url+'/get-data', {list_id: listId}, function (data) {
+                self.pathTree.loadDatas(data);
+                SpreadJsObj.loadSheetData(self.spread.getActiveSheet(), 'tree', self.pathTree);
+            });
+        }
+    }
+    class DealBills {
+        constructor (selector, spreadSetting) {
+            const self = this;
+            this.loaded = false;
+            this.obj = $(selector)[0];
+            this.url = '/tender/' + window.location.pathname.split('/')[2] + '/deal';
+            this.spreadSetting = spreadSetting;
+            this.spread = SpreadJsObj.createNewSpread(this.obj);
+            SpreadJsObj.initSheet(this.spread.getActiveSheet(), this.spreadSetting);
+            this.spread.bind(spreadNS.Events.CellDoubleClick, function (e, info) {
+                const dealSheet = info.sheet;
+                const mainSheet = ledgerSpread.getActiveSheet();
+
+                const dealBills = SpreadJsObj.getSelectObject(dealSheet);
+                if (!dealBills) { return; }
+                const mainTree = mainSheet.zh_tree;
+                const mainNode = SpreadJsObj.getSelectObject(mainSheet);
+                if (!mainNode || !mainTree) { return; }
+
+                if (mainNode.code && mainNode.code !== '' && !mainTree.isLeafXmj(mainNode)) {
+                    toast('非最底层项目下,不应添加清单', 'error');
+                    return;
+                }
+
+                postData('/tender/' + getTenderId() + '/ledger/add-by-deal', {
+                    id: mainNode.ledger_id,
+                    type: mainNode.code ? 'child' : 'next',
+                    dealBills: {
+                        b_code: dealBills.code, name: dealBills.name, unit: dealBills.unit,
+                        unit_price: dealBills.unit_price,
+                    },
+                }, function (result) {
+                    const refreshData = mainTree.loadPostData(result);
+                    treeOperationObj.refreshTree(mainSheet, refreshData);
+                    treeOperationObj.refreshOperationValid(mainSheet);
+                });
+            });
+            SpreadJsObj.forbiddenSpreadContextMenu(selector, this.spread);
+        }
+        loadData () {
+            if (this.loaded) return;
+            const self = this;
+            postData(this.url+'/get-data', {}, function (data) {
+                self.data = data;
+                //self.calculateData();
+                SpreadJsObj.loadSheetData(self.spread.getActiveSheet(), 'data', data);
+                self.loaded = true;
+            });
+        }
+        calculateData () {
+            if (this.data) {
+                for (const d of this.data) {
+                    d.total_price = _.multiply(d.quantity, d.unit_price);
+                }
+            }
+        }
+    }
+    class BatchInsertBillsPosObj {
+        constructor (obj) {
+            const self = this;
+            this.obj = obj;
+            this.billsCount = 6;
+            this.posCount = 1000;
+            // 初始化 清单编号窗口 参数
+            this.qdSpreadSetting = {
+                cols: [
+                    {title: '编号', field: 'code', hAlign: 0, width: 80, formatter: '@'},
+                    {title: '名称', field: 'name', hAlign: 0, width: 120, formatter: '@'},
+                    {title: '单位', field: 'unit', hAlign: 1, width: 50, formatter: '@'},
+                    {title: '单价', field: 'unit_price', hAlign: 2, width: 50},
+                    {title: '图册号', field: 'name', hAlign: 0, width: 60, formatter: '@'},
+                ],
+                emptyRows: this.billsCount,
+                headRows: 1,
+                headRowHeight: [40],
+            };
+            this.qdSpread = SpreadJsObj.createNewSpread($('.batch-l-t', this.obj)[0]);
+            // 初始化 部位数量复核表 参数
+            this.posSpreadSetting = {
+                cols: [
+                    {title: '部位', field: 'bw', hAlign: 0, width: 80, formatter: '@'},
+                    {title: '图册号', field: 'drawingCode', hAlign: 0, formatter: '@', width: 60},
+                ],
+                emptyRows: this.posCount,
+                headRows: 1,
+                headRowHeight: [40],
+            };
+            for (let iNum = 1; iNum <= this.billsCount; iNum++) {
+                this.posSpreadSetting.cols.push(
+                    {title: '清单' + iNum, field: 'bills' + iNum, hAlign: 2, width: 50}
+                )
+            }
+            this.posSpread = SpreadJsObj.createNewSpread($('.batch-l-b', this.obj)[0]);
+            // 初始化 签约清单 参数
+            this.dealSpreadSetting = {
+                cols: [
+                    {title: '清单编号', field: 'code', width: 80, formatter: '@', readOnly: true},
+                    {title: '名称', field: 'name', width: 120, formatter: '@', readOnly: true},
+                    {title: '单位', field: 'unit', width: 50, formatter: '@', readOnly: true},
+                    {title: '单价', field: 'unit_price', width: 50, readOnly: true},
+                ],
+                emptyRows: 0,
+                headRows: 1,
+                headRowHeight: [40],
+            };
+            this.dealSpread = SpreadJsObj.createNewSpread($('.batch-r', this.obj)[0]);
+            // 初始化 清单编号、部位数量复核表 表格
+            this.initView();
+            SpreadJsObj.initSheet(this.dealSpread.getActiveSheet(), this.dealSpreadSetting);
+            SpreadJsObj.refreshColumnAlign(this.dealSpread.getActiveSheet());
+            // 拉取签约清单数据
+            dealBills.loadData();
+            SpreadJsObj.loadSheetData(this.dealSpread.getActiveSheet(), 'data', dealBills.data);
+            // 双击签约清单,自动添加到清单编号窗口
+            this.dealSpread.bind(GC.Spread.Sheets.Events.CellDoubleClick, function (e, info) {
+                const deal = info.sheet.zh_data[info.row];
+                const qdSheet = self.qdSpread.getActiveSheet(), posSheet = self.posSpread.getActiveSheet();
+                const sel = qdSheet.getSelections()[0];
+                qdSheet.getCell(sel.row, 0).value(deal.code);
+                qdSheet.getCell(sel.row, 1).value(deal.name);
+                qdSheet.getCell(sel.row, 2).value(deal.unit);
+                qdSheet.getCell(sel.row, 3).value(deal.unit_price);
+                if (sel.row + 1 === qdSheet.getRowCount()) {
+                    const count = sel.row + 2;
+                    qdSheet.setRowCount(count);
+                    qdSheet.getCell(sel.row + 1, 0, GC.Spread.Sheets.SheetArea.rowHeader).text('清单' + count);
+
+                    const colCount = posSheet.getColumnCount() + 1
+                    posSheet.setColumnCount(colCount);
+                    posSheet.getCell(0, colCount - 1, GC.Spread.Sheets.SheetArea.colHeader).text('数量' + count);
+                }
+                qdSheet.setSelection(sel.row + 1, sel.col, 1, 1);
+            });
+
+            this.obj.bind('shown.bs.modal', function () {
+                self.qdSpread.refresh();
+                self.posSpread.refresh();
+                self.dealSpread.refresh();
+            });
+
+            $('#batch-ok').click(function () {
+                const selection = billsSheet.getSelections();
+                const row = selection[0].row;
+                const select = billsTree.nodes[row];
+                if (select) {
+                    const insertData = {};
+                    insertData.batchType = (select.code && select.code !== '') ? 'child' : 'next';
+                    insertData.id = select[billsTree.setting.id];
+                    insertData.batchData = self.getBatchData();
+                    postData(window.location.pathname + '/batch-insert', insertData, function (data) {
+                        pos.updateDatas(data.pos);
+                        const result = billsTree.loadPostData(data.ledger);
+                        billsTreeSpreadObj.refreshTree(billsSheet, result);
+                        billsTreeSpreadObj.refreshOperationValid(billsSheet, selection);
+                        self.obj.modal('hide');
+                    });
+                }
+            });
+        }
+        // 初始化左侧表格
+        initView () {
+            // 初始化 清单编号
+            const qdSheet = this.qdSpread.getActiveSheet();
+            SpreadJsObj.initSheet(qdSheet, this.qdSpreadSetting);
+            SpreadJsObj.refreshColumnAlign(qdSheet);
+            // 清理原有数据
+            SpreadJsObj.beginMassOperation(qdSheet);
+            qdSheet.clear(0, 0, qdSheet.getRowCount(), qdSheet.getColumnCount(), GC.Spread.Sheets.SheetArea.viewport, GC.Spread.Sheets.StorageType.data);
+            for (let iRow = 1; iRow <= this.billsCount; iRow++) {
+                qdSheet.getCell(iRow - 1, 0, GC.Spread.Sheets.SheetArea.rowHeader).text('清单' + iRow);
+            }
+            qdSheet.setSelection(0, 0, 1 ,1);
+            SpreadJsObj.endMassOperation(qdSheet);
+            // 初始化 部位数量复核表
+            const posSheet = this.posSpread.getActiveSheet();
+            SpreadJsObj.initSheet(posSheet, this.posSpreadSetting);
+            SpreadJsObj.refreshColumnAlign(posSheet);
+            // 清理原有数据
+            SpreadJsObj.beginMassOperation(posSheet);
+            posSheet.setColumnWidth(0, 45, GC.Spread.Sheets.SheetArea.rowHeader);
+            posSheet.clear(0, 0, posSheet.getRowCount(), posSheet.getColumnCount(), GC.Spread.Sheets.SheetArea.viewport, GC.Spread.Sheets.StorageType.data);
+            posSheet.setSelection(0, 0, 1 ,1);
+            SpreadJsObj.endMassOperation(posSheet);
+            // 检查签约清单数据,以工具栏数据为准
+            if (dealBills) {
+                SpreadJsObj.loadSheetData(this.dealSpread.getActiveSheet(), 'data', dealBills.data);
+            }
+            this.dealSpread.getActiveSheet().setSelection(0, 0, 1, 1);
+        }
+        // 获取界面数据
+        getBatchData () {
+            const result = [];
+            const qdSheet = this.qdSpread.getActiveSheet(), posSheet = this.posSpread.getActiveSheet();
+            for (let iRow = 0; iRow < qdSheet.getRowCount(); iRow++) {
+                if (qdSheet.getText(iRow, 0) === '') { continue; }
+                const qd = {
+                    b_code: qdSheet.getText(iRow, 0),
+                    name: qdSheet.getText(iRow, 1),
+                    unit: qdSheet.getText(iRow, 2),
+                    price: _.toNumber(qdSheet.getText(iRow, 3)),
+                    pos: [],
+                };
+                result.push(qd);
+                for (let iPosRow = 0; iPosRow < posSheet.getRowCount(); iPosRow++) {
+                    const value = _.toNumber(posSheet.getText(iPosRow, iRow + 2));
+                    if (value !== 0 && !isNaN(value)) {
+                        qd.pos.push({
+                            name: posSheet.getText(iPosRow, 0),
+                            drawing_code: posSheet.getText(iPosRow, 1),
+                            quantity: value,
+                        });
+                    }
+                }
+            }
+            return result;
+        }
+    }
     let stdChapter, stdBills;
+    const dealBills = new DealBills('#deal-bills-spread', {
+        cols: [
+            {title: '清单编号', field: 'code', hAlign: 0, width: 120, formatter: '@', readOnly: true},
+            {title: '名称', field: 'name', hAlign: 0, width: 230, formatter: '@', readOnly: true},
+            {title: '单位', field: 'unit', hAlign: 1, width: 50, formatter: '@', readOnly: true},
+            {title: '单价', field: 'unit_price', hAlign: 2, width: 50, readOnly: true},
+            {title: '数量', field: 'quantity', hAlign: 2, width: 50, readOnly: true},
+            {title: '金额', field: 'total_price', hAlign: 2, width: 50, readOnly: true},
+        ],
+        emptyRows: 0,
+        headRows: 1,
+        headRowHeight: [40],
+        defaultRowHeight: 21,
+    });
     $.divResizer({
         select: '#revise-right-spr',
         callback: function () {
@@ -151,6 +658,9 @@ $(document).ready(() => {
                     stdBills.loadLib(1);
                 }
                 stdBills.spread.refresh();
+            } else if (tab.attr('content') === '#deal-bills') {
+                dealBills.loadData();
+                dealBills.spread.refresh();
             }
         }
         billsSpread.refresh();
@@ -158,47 +668,7 @@ $(document).ready(() => {
             posSpread.refresh();
         }
     });
-    class stdLib {
-        constructor(obj, stdType, treeSetting, spreadSetting) {
-            this.obj = obj;
-            this.url = '/std/' + stdType;
-            this.treeSetting = treeSetting;
-            treeSetting.preUrl = this.url;
-            this.spreadSetting = spreadSetting;
-            this.spread = SpreadJsObj.createNewSpread(this.obj);
-            SpreadJsObj.initSheet(this.spread.getActiveSheet(), this.spreadSetting);
-            this.spread.getActiveSheet().bind(GC.Spread.Sheets.Events.CellDoubleClick, function (e, info) {
-                const stdSheet = info.sheet;
-                const mainSheet = ledgerSpread.getActiveSheet();
-                if (!stdSheet.zh_setting || !stdSheet.zh_tree || !mainSheet.zh_tree) { return; }
-
-                const stdTree = stdSheet.zh_tree;
-                const stdNode = stdTree.nodes[info.row];
-                const mainTree = mainSheet.zh_tree;
-                const sel = mainSheet.getSelections()[0];
-                const mainNode = mainTree.nodes[sel.row];
-                if (!stdNode) { return; }
 
-                mainTree.postData('/tender/' + getTenderId() + '/ledger/add-by-std', mainNode, {
-                    tender_id: mainNode.tender_id,
-                    stdType: stdType,
-                    stdLibId: stdNode.list_id,
-                    stdNode: stdTree.getNodeKey(stdNode)
-                }, function (result) {
-                    treeOperationObj.refreshTree(mainSheet, result);
-                    treeOperationObj.refreshOperationValid(mainSheet);
-                });
-            });
-            this.pathTree = createNewPathTree('base', this.treeSetting);
-        }
-        loadLib (listId) {
-            const self = this;
-            postData(this.url+'/get-data', {list_id: listId}, function (data) {
-                self.pathTree.loadDatas(data);
-                SpreadJsObj.loadSheetData(self.spread.getActiveSheet(), 'tree', self.pathTree);
-            });
-        }
-    }
     // 修订详情 保存
     $('#save').click(function () {
         const content = $('textarea').val();

+ 7 - 5
app/router.js

@@ -98,11 +98,13 @@ module.exports = app => {
     app.post('/tender/:id/pos/update', sessionAuth, tenderCheck, 'ledgerController.posUpdate');
     app.post('/tender/:id/pos/paste', sessionAuth, tenderCheck, 'ledgerController.posPaste');
     // 台账修订
-    app.get('/tender/:id/ledger/revise', sessionAuth, tenderCheck, 'ledgerController.revise');
-    app.post('/tender/:id/ledger/revise/add', sessionAuth, tenderCheck, 'ledgerController.addRevise');
-    app.post('/tender/:id/ledger/revise/cancel', sessionAuth, tenderCheck, 'ledgerController.cancelRevise');
-    app.post('/tender/:id/ledger/revise/save', sessionAuth, tenderCheck, 'ledgerController.saveRevise');
-    app.get('/tender/:id/ledger/revise/info', sessionAuth, tenderCheck, 'ledgerController.reviseInfo');
+    app.get('/tender/:id/revise', sessionAuth, tenderCheck, 'reviseController.index');
+    app.post('/tender/:id/revise/add', sessionAuth, tenderCheck, 'reviseController.add');
+    app.post('/tender/:id/revise/cancel', sessionAuth, tenderCheck, 'reviseController.cancel');
+    app.post('/tender/:id/revise/save', sessionAuth, tenderCheck, 'reviseController.save');
+    app.get('/tender/:id/revise/info', sessionAuth, tenderCheck, 'reviseController.info');
+    app.post('/tender/:id/revise/info/base-opr', sessionAuth, tenderCheck, 'reviseController.baseOpr');
+    app.post('/tender/:id/revise/info/batch-insert', sessionAuth, tenderCheck, 'reviseController.batchInsert')
     //app.post('/tender/:id/ledger/revise/audit/', sessionAuth, tenderCheck, 'ledgerController.reviseStatus');
     //app.get('/tender/:id/ledger/index', sessionAuth, 'ledgerController.index');
 

+ 4 - 0
app/service/ledger.js

@@ -1491,6 +1491,7 @@ module.exports = app => {
          * @return {Object} - 提价后的数据(其中新增粘贴数据,只返回第一层)
          */
         async pasteBlock(tenderId, selectId, block) {
+            console.log(1);
             if ((tenderId <= 0) || (selectId <= 0)) {
                 return [];
             }
@@ -1515,6 +1516,7 @@ module.exports = app => {
                 throw '复制数据错误:仅可操作同层节点';
             }
             const orgParentPath = copyNodes[0].full_path.replace(copyNodes[0].ledger_id, '');
+            console.log(2);
 
             const newIds = [];
             this.transaction = await this.db.beginTransaction();
@@ -1577,6 +1579,7 @@ module.exports = app => {
                 await this.transaction.rollback();
                 throw err;
             }
+            console.log(3);
 
             // 查询应返回的结果
             const order = [];
@@ -1590,6 +1593,7 @@ module.exports = app => {
                 ledger: { create: createData, update: updateData },
                 pos: posData,
             };
+            console.log(4);
         }
 
         /**

+ 8 - 7
app/service/ledger_revise.js

@@ -76,19 +76,19 @@ module.exports = app => {
             // }
         }
 
-        async _initReviseBills(transaction, tid, rid) {
+        async _initReviseBills(transaction, tid) {
             const sql = 'Insert Into ' + this.ctx.service.reviseBills.tableName +
-                '  Select ? As rid, ' + this.ctx.service.ledger.tableName + '.* From ' + this.ctx.service.ledger.tableName +
+                '  Select * From ' + this.ctx.service.ledger.tableName +
                 '  Where `tender_id` = ?';
-            const sqlParam = [rid, tid];
+            const sqlParam = [tid];
             await transaction.query(sql, sqlParam);
         }
 
-        async _initRevisePos(transaction, tid, rid) {
+        async _initRevisePos(transaction, tid) {
             const sql = 'Insert Into ' + this.ctx.service.revisePos.tableName +
-                '  Select ? As rid, ' + this.ctx.service.pos.tableName + '.* From ' + this.ctx.service.pos.tableName +
+                '  Select * From ' + this.ctx.service.pos.tableName +
                 '  Where `tid` = ?';
-            const sqlParam = [rid, tid];
+            const sqlParam = [tid];
             await transaction.query(sql, sqlParam);
         }
 
@@ -114,7 +114,8 @@ module.exports = app => {
                 if (result.affectedRows !== 1) {
                     throw '新增台账修订失败';
                 }
-                // todo 拷贝数据(清单、部位明细)
+                await transaction.delete(this.ctx.service.reviseBills.tableName, {tender_id: tid});
+                await transaction.delete(this.ctx.service.revisePos.tableName, {tid: tid});
                 await this._initReviseBills(transaction, tid, data.id);
                 await this._initRevisePos(transaction, tid, data.id);
                 await transaction.commit();

+ 471 - 3
app/service/revise_bills.js

@@ -8,6 +8,12 @@
  * @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 {
 
@@ -25,13 +31,475 @@ module.exports = app => {
         /**
          * 获取 修订 清单数据
          * @param {Number}tid - 标段id
-         * @param {uuid}rid - 修订id
          * @returns {Promise<void>}
          */
-        async getData(tid, rid) {
+        async getData(tid) {
             return await this.db.select(this.tableName, {
-                where: {tender_id: tid, rid: rid}
+                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: '=',
+            });
+            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;
+        }
+
+        /**
+         * 新增节点
+         * @param {Number} tid - 台账id
+         * @param {uuid} rid - 修订id
+         * @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 };
+        }
+
+        /**
+         * 批量插入子项
+         * @param {uuid} rid - 修订id
+         * @param {Number} lid - 节点id
+         * @param {Object} data - 批量插入数据
+         * @return {Promise<void>}
+         */
+        async batchInsertChild(tid, rid, lid, data) {
+            const result = { ledger: {}, pos: null };
+            if (!tid || !rid || !lid) return result;
+
+            const select = await this.getDataByLid(tid, lid);
+            if (!select) {
+                throw '位置数据错误';
+            }
+
+            // 计算id和order
+            const maxId = await this._getMaxLid(tid);
+            const lastChild = await this.getLastChildData(tid, lid);
+            const order = lastChild ? lastChild.order : 0;
+            // 整理数据
+            const bills = [], pos = [], newIds = [];
+            for (let i = 0, iLen = data.length; i < iLen; i++) {
+                // 合并新增数据
+                const qd = {
+                    crid: rid,
+                    id: this.uuid.v4(),
+                    tender_id: tid,
+                    ledger_id: maxId + i + 1,
+                    ledger_pid: select.ledger_id,
+                    is_leaf: true,
+                    order: order + i + 1,
+                    level: select.level + 1,
+                    b_code: data[i].b_code,
+                    name: data[i].name,
+                    unit: data[i].unit,
+                    unit_price: data[i].price,
+                };
+
+                const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, bills.unit);
+                qd.sgfh_qty = 0;
+                for (const p of data[i].pos) {
+                    const inP = {
+                        id: this.uuid.v4(), tid: tid, lid: qd.id, crid: rid,
+                        add_stage: 0, add_times: 0, add_user: this.ctx.session.sessionUser.accountId,
+                        name: p.name, drawing_code: p.drawing_code,
+                    };
+                    if (p.quantity) {
+                        inP.sgfh_qty = this.round(p.quantity, precision.value);
+                        inP.quantity = inP.sgfh_qty;
+                    }
+                    qd.sgfh_qty = this.ctx.helper.add(qd.sgfh_qty, inP.sgfh_qty);
+                    pos.push(inP);
+                }
+                qd.full_path = select.full_path + '.' + qd.ledger_id;
+                qd.sgfh_tp = this.ctx.helper.mul(qd.sgfh_qty, qd.unit_price, this.ctx.tender.info.decimal.tp);
+                qd.quantity = qd.sgfh_qty;
+                qd.total_price = qd.sgfh_tp;
+                bills.push(qd);
+                newIds.push(qd.id);
+            }
+
+            this.transaction = await this.db.beginTransaction();
+            try {
+                // 更新父项isLeaf
+                if (!lastChild) {
+                    await this.transaction.update(this.tableName, {
+                        is_leaf: false,
+                        unit_price: null,
+                        quantity: null,
+                        total_price: null,
+                        deal_qty: null,
+                        deal_tp: null,
+                    }, {tender_id: rid, id: select.id});
+                }
+                // 数据库创建新增节点数据
+                await this.transaction.insert(this.tableName, bills);
+                await this.transaction.insert(this.ctx.service.revisePos.tableName, pos);
+                this._cacheMaxLid(tid, maxId + data.length);
+                await this.transaction.commit();
+            } catch (err) {
+                await this.transaction.rollback();
+                throw err;
+            }
+
+            // 查询应返回的结果
+            result.ledger.create = await this.getDataById(newIds);
+            if (!lastChild) {
+                result.ledger.update = await this.getDataByLid(select.id);
+            }
+            result.pos = await this.ctx.service.revisePos.getDataByLid(tid, newIds);
+            return result;
+        }
+
+        /**
+         * 批量插入后项
+         * @param {Number} tid - 台账id
+         * @param {uuid} rid - 修订id
+         * @param {Number} lid - 节点id
+         * @param {Object} data - 批量插入数据
+         * @return {Promise<void>}
+         */
+        async batchInsertNext(tid, rid, lid, data) {
+            const result = { ledger: {}, pos: null };
+            if (!tid || !rid || !lid) return result;
+
+            const select = await this.getDataByLid(tid, lid);
+            if (!select) {
+                throw '位置数据错误';
+            }
+            const parentData = await this.getDataByLid(tid, select.ledger_pid);
+            if (!parentData) {
+                throw '位置数据错误';
+            }
+
+            // 计算id和order
+            const maxId = await this._getMaxLid(tid);
+            const order = select.order;
+            // 整理数据
+            const bills = [], pos = [], newIds = [];
+            for (let i = 0, iLen = data.length; i < iLen; i++) {
+                // 合并新增数据
+                const qd = {
+                    crid: rid,
+                    id: this.uuid.v4(),
+                    tender_id: tid,
+                    ledger_id: maxId + i + 1,
+                    ledger_pid: parentData.ledger_id,
+                    is_leaf: true,
+                    order: order + i + 1,
+                    level: parentData.level + 1,
+                    b_code: data[i].b_code,
+                    name: data[i].name,
+                    unit: data[i].unit,
+                    unit_price: data[i].price,
+                };
+
+                const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, bills.unit);
+                qd.sgfh_qty = 0;
+                for (const p of data[i].pos) {
+                    const inP = {
+                        id: this.uuid.v4(), tid: tid, lid: qd.id, crid: rid,
+                        add_stage: 0, add_times: 0, add_user: this.ctx.session.sessionUser.accountId,
+                        name: p.name, drawing_code: p.drawing_code,
+                    };
+                    if (p.quantity) {
+                        inP.sgfh_qty = this.round(p.quantity, precision.value);
+                        inP.quantity = inP.sgfh_qty;
+                    }
+                    qd.sgfh_qty = this.ctx.helper.add(qd.sgfh_qty, inP.sgfh_qty);
+                    pos.push(inP);
+                }
+                qd.full_path = parentData.full_path + '.' + qd.ledger_id;
+                qd.sgfh_tp = this.ctx.helper.mul(qd.sgfh_qty, qd.unit_price, this.ctx.tender.info.decimal.tp);
+                qd.quantity = qd.sgfh_qty;
+                qd.total_price = qd.sgfh_tp;
+                bills.push(qd);
+                newIds.push(qd.id);
+            }
+
+            this.transaction = await this.db.beginTransaction();
+            try {
+                // 选中节点的所有后兄弟节点,order+粘贴节点个数
+                await this._updateChildrenOrder(tid, select.ledger_pid, select.order+1);
+                // 数据库创建新增节点数据
+                await this.transaction.insert(this.tableName, bills);
+                await this.transaction.insert(this.ctx.service.revisePos.tableName, pos);
+                this._cacheMaxLid(tid, maxId + data.length);
+                await this.transaction.commit();
+            } catch (err) {
+                await this.transaction.rollback();
+                throw err;
+            }
+
+            // 查询应返回的结果
+            result.ledger.create = await this.getDataById(newIds);
+            result.ledger.update = await this.getNextsData(select.tender_id, select.ledger_pid, select.order + data.length);
+            result.pos = await this.ctx.service.revisePos.getDataByLid(tid, newIds);
+            return result;
         }
     }
 

+ 26 - 2
app/service/revise_pos.js

@@ -28,11 +28,35 @@ module.exports = app => {
          * @param {uuid}rid - 修订id
          * @returns {Promise<void>}
          */
-        async getData(tid, rid) {
+        async getData(tid) {
             return await this.db.select(this.tableName, {
-                where: {tid: tid, rid: rid}
+                where: {tid: tid}
             });
         }
+
+        async getDataByLid(tid, lid) {
+            return await this.db.select(this.tableName, {
+                where: {tid: tid, lid: lid}
+            });
+        }
+
+        async insertLedgerPosData(transaction, tid, rid, bills, data) {
+            const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, bills.unit);
+            const insertDatas = [];
+            for (const d of data) {
+                const inD = {
+                    id: this.uuid.v4(), tid: tid, lid: bills.id, crid: rid,
+                    add_stage: 0, add_times: 0, add_user: this.ctx.session.sessionUser.accountId,
+                    name: d.name, drawing_code: d.drawing_code,
+                };
+                if (d.quantity) {
+                    inD.sgfh_qty = this.round(d.quantity, precision.value);
+                    inD.quantity = inD.sgfh_qty;
+                }
+                insertDatas.push(inD);
+            }
+            await transaction.insert(this.tableName, insertDatas);
+        }
     }
 
     return RevisePos;

+ 1 - 0
app/service/stage_change.js

@@ -152,6 +152,7 @@ module.exports = app => {
          * @returns {Promise<{}>}
          */
         async posChange(pos, changes) {
+            console.log(pos);
             const self = this;
             function getNewChange(cid, cbid, times, order, qty) {
                 return {

+ 4 - 4
app/view/ledger/revise.ejs

@@ -51,15 +51,15 @@
                     <td>
                         <% if (lr.valid) { %>
                         <% if (lr.status === auditConst.status.uncheck) { %>
-                        <a href="<%- preUrl + '/ledger/revise/info' %>" class="btn btn-primary btn-sm">修订</a>
+                        <a href="<%- preUrl + '/revise/info' %>" class="btn btn-primary btn-sm">修订</a>
                         <a href="#remove" data-toggle="modal" data-target="#remove" class="btn btn-secondary btn-sm">作废</a>
                         <% } else if (lr.status === auditConst.status.checking) { %>
-                        <a href="<%- preUrl + '/ledger/revise/info' %>" class="btn btn-success btn-sm">审批</a><
+                        <a href="<%- preUrl + '/revise/info' %>" class="btn btn-success btn-sm">审批</a><
                         <% } else if (lr.status === auditConst.status.checkNo) { %>
-                        <a href="<%- preUrl + '/ledger/revise/info' %>" class="btn btn-primary btn-sm">重新上报</a>
+                        <a href="<%- preUrl + '/revise/info' %>" class="btn btn-primary btn-sm">重新上报</a>
                         <a href="#remove" data-toggle="modal" data-target="#remove" class="btn btn-secondary btn-sm">作废</a>
                         <% } else if (ledgerRevise.indexOf(lr) === ledgerRevise.length - 1) { %>
-                        <a href="<%- preUrl + '/ledger/revise/info' %>">查看修订内容</a>
+                        <a href="<%- preUrl + '/revise/info' %>">查看修订内容</a>
                         <% } %>
                         <% } %>
                     </td>

+ 17 - 9
app/view/ledger/revise_info.ejs

@@ -23,15 +23,15 @@
                 </div>
                 <!--编制人工具-->
                 <div class="d-inline-block">
-                    <a href="" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="插入"><i class="fa fa-plus" aria-hidden="true"></i></a>
-                    <a href="" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="删除"><i class="fa fa-remove" aria-hidden="true"></i></a>
-                    <a href="" class="btn btn-sm disabled" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="升级"><i class="fa fa-arrow-left" aria-hidden="true"></i></a>
-                    <a href="" class="btn btn-sm disabled" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="降级"><i class="fa fa-arrow-right" aria-hidden="true"></i></a>
-                    <a href="" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="下移"><i class="fa fa-arrow-down" aria-hidden="true"></i></a>
-                    <a href="" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="上移"><i class="fa fa-arrow-up" aria-hidden="true"></i></a>
-                    <a href="" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="复制"><i class="fa fa-files-o" aria-hidden="true"></i></a>
-                    <a href="" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="剪切"><i class="fa fa-scissors" aria-hidden="true"></i></a>
-                    <a href="" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="粘贴"><i class="fa fa-clipboard" aria-hidden="true"></i></a>
+                    <a href="javascript: void(0);" name="base-opr" type="add" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="插入"><i class="fa fa-plus" aria-hidden="true"></i></a>
+                    <a href="javascript: void(0);" name="base-opr" type="delete" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="删除"><i class="fa fa-remove" aria-hidden="true"></i></a>
+                    <a href="javascript: void(0);" name="base-opr" type="up-level" class="btn btn-sm disabled" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="升级"><i class="fa fa-arrow-left" aria-hidden="true"></i></a>
+                    <a href="javascript: void(0);" name="base-opr" type="down-level" class="btn btn-sm disabled" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="降级"><i class="fa fa-arrow-right" aria-hidden="true"></i></a>
+                    <a href="javascript: void(0);" name="base-opr" type="down-move" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="下移"><i class="fa fa-arrow-down" aria-hidden="true"></i></a>
+                    <a href="javascript: void(0);" name="base-opr" type="up-move" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="上移"><i class="fa fa-arrow-up" aria-hidden="true"></i></a>
+                    <a href="javascript: void(0);" id="copy" class="btn btn-sm" data-toggle="tooltip disabled" data-placement="bottom" title="" data-original-title="复制"><i class="fa fa-files-o" aria-hidden="true"></i></a>
+                    <a href="javascript: void(0);" id="cut" class="btn btn-sm" data-toggle="tooltip disabled" data-placement="bottom" title="" data-original-title="剪切"><i class="fa fa-scissors" aria-hidden="true"></i></a>
+                    <a href="javascript: void(0);" id="paste" class="btn btn-sm" data-toggle="tooltip disabled" data-placement="bottom" title="" data-original-title="粘贴"><i class="fa fa-clipboard" aria-hidden="true"></i></a>
                 </div>
                 <div class="d-inline-block">
                     <div class="input-group input-group-sm ml-2 mt-1">
@@ -130,6 +130,11 @@
                             <select class="form-control form-control-sm"><option>0号计量台帐部位参考(项目节)</option></select>
                         </div>
                         <div id="std-bills-spread" class="sjs-sh-3">
+                        </div>
+                    </div>
+                    <div id="deal-bills" class="tab-pane">
+                        <div id="deal-bills-spread" class="sjs-sh-4">
+                        </div>
                     </div>
                 </div>
             </div>
@@ -147,6 +152,9 @@
                 <li class="nav-item">
                     <a class="nav-link" content="#std-bills" href="javascript: void(0);">工程量清单</a>
                 </li>
+                <li class="nav-item">
+                    <a class="nav-link" content="#deal-bills" href="javascript: void(0);">签约清单</a>
+                </li>
             </ul>
         </div>
     </div>

+ 32 - 0
app/view/ledger/revise_info_modal.ejs

@@ -1,3 +1,35 @@
+
+<!--批量添加清单部位-->
+<div class="modal fade" id="batch" data-backdrop="static">
+    <div class="modal-dialog modal-lg" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">批量插入清单部位</h5>
+            </div>
+            <div class="modal-body">
+                <div class="row">
+                    <div class="col-6">
+                        <h6>清单信息</h6>
+                        <div class="batch-l-t">
+                        </div>
+                        <h6>部位数量复核表</h6>
+                        <div class="batch-l-b">
+                        </div>
+                    </div>
+                    <div class="col-6">
+                        <h6>签约清单</h6>
+                        <div class="batch-r">
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
+                <button type="button" class="btn btn-primary" id="batch-ok">确定</button>
+            </div>
+        </div>
+    </div>
+</div>
 <% if (revise.status === audit.status.uncheck) { %>
 <!--上报审批-->
 <div class="modal fade" id="sub-sp" data-backdrop="static">

+ 46 - 0
app/view/revise/modal.ejs

@@ -0,0 +1,46 @@
+<!--弹出新建台帐修订-->
+<div class="modal fade" id="add-bg" data-backdrop="static">
+    <form class="modal-dialog" role="document" action="<%- preUrl + '/revise/add' %>" method="post">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">新建修订</h5>
+            </div>
+            <div class="modal-body">
+                <h5>创建修订后,本期计量将暂时锁定,锁定后本期计量无法计量和上报,需修订完成后,才能解锁。</h5>
+            </div>
+            <div class="modal-footer">
+                <input type="hidden" name="_csrf" value="<%= ctx.csrf %>" />
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
+                <button type="submit" class="btn btn-primary">确定新建</button>
+            </div>
+        </div>
+    </form>
+</div>
+
+<% if (ledgerRevise.length > 0 && (ledgerRevise[0].status === auditConst.status.uncheck || ledgerRevise[0].status === auditConst.status.checkNo) && ctx.session.sessionUser.accountId === ledgerRevise[0].uid) { %>
+<!--弹出作废-->
+<div class="modal fade" id="remove" data-backdrop="static">
+    <form class="modal-dialog" role="document" action="<%- preUrl + '/revise/cancel' %>" method="post" onsubmit="return $('#cancel-confirm').val() === '修订作废';">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">作废修订</h5>
+            </div>
+            <div class="modal-body">
+                <div class="alert alert-danger">
+                    修订 已有数据,请谨慎操作;
+                    <br>作废后,数据无法恢复,请谨慎操作;
+                    <br>确认作废,请在以下输入框输入"&nbsp;&nbsp;&nbsp;<b>修订作废</b>&nbsp;&nbsp;&nbsp;",再点击“确认作废”按钮。
+                </div>
+                <div class="form-group">
+                    <input class="form-control" placeholder="" type="text" id="cancel-confirm">
+                </div>
+            </div>
+            <div class="modal-footer">
+                <input type="hidden" name="_csrf" value="<%= ctx.csrf %>" />
+                <button type="button" class="btn btn-secondary" data-dismiss="modal" >取消</button>
+                <button type="submit" class="btn btn-danger">确认作废</button>
+            </div>
+        </div>
+    </form>
+</div>
+<% } %>

+ 1 - 1
config/menu.js

@@ -64,7 +64,7 @@ const tenderMenu = {
             }, {
                 name: '台帐修订',
                 display: true,
-                url: '/ledger/revise',
+                url: '/revise',
             },
         ],
     },