Browse Source

1. 台账审批流程
2. 压缩代码相关

MaiXinRong 7 years ago
parent
commit
28a38a6eb0

+ 15 - 1
app.js

@@ -7,7 +7,8 @@
  * @date 2017/8/29
  * @version
  */
-
+const Uglyfy = require('uglify-es');
+const fs = require('fs');
 const BaseService = require('./app/base/base_service');
 const BaseController = require('./app/base/base_controller');
 module.exports = app => {
@@ -48,4 +49,17 @@ module.exports = app => {
             return error;
         }
     });
+
+    //压缩前端js
+    if (app.config.min) {
+        app.minify = (file) => {
+            const files = file instanceof Array ? file : [file];
+            for (const f of files) {
+                const fileName = app.baseDir + '/app/public/js/' + f;
+                const code = fs.readFileSync(fileName, 'utf8');
+                fs.writeFileSync(fileName.replace('.js', '.min.js'), Uglyfy.minify(code, { mangle: true }).code);
+            }
+        };
+        app.minify(['spreadjs_rela/spreadjs_zh.js', 'path_tree.js']);
+    }
 };

+ 17 - 11
app/base/base_controller.js

@@ -55,17 +55,23 @@ class BaseController extends Controller {
         // 取出后删除
         this.ctx.session.message = null;
 
-        const viewString = await this.ctx.renderView(view, data);
-        const modalString = modal === '' ? '' : await this.ctx.renderView(modal, data);
-        const renderData = {
-            content: viewString,
-            message: JSON.stringify(message),
-            modal: modalString,
-            dropDownMenu: data.dropDownMenu === undefined ? [] : data.dropDownMenu,
-            breadCrumb: data.breadCrumb === undefined ? '' : data.breadCrumb,
-            tenderList: data.tenderList === undefined ? [] : data.tenderList,
-        };
-        await this.ctx.render('layout/layout.ejs', renderData);
+        try {
+            data.min = this.app.config.min ? '.min' : '';
+            const viewString = await this.ctx.renderView(view, data);
+            const modalString = modal === '' ? '' : await this.ctx.renderView(modal, data);
+            const renderData = {
+                min: this.app.config.min ? '.min' : '',
+                content: viewString,
+                message: JSON.stringify(message),
+                modal: modalString,
+                dropDownMenu: data.dropDownMenu === undefined ? [] : data.dropDownMenu,
+                breadCrumb: data.breadCrumb === undefined ? '' : data.breadCrumb,
+                tenderList: data.tenderList === undefined ? [] : data.tenderList,
+            };
+            await this.ctx.render('layout/layout.ejs', renderData);
+        } catch(err) {
+            console.log(err);
+        }
     }
 
     /**

+ 34 - 0
app/const/audit.js

@@ -0,0 +1,34 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const status = {
+    uncheck: 1,
+    checking: 2,
+    checked: 3,
+    checkNo: 4,
+};
+
+const statusString = [];
+statusString[status.uncheck] = '未开始审批';
+statusString[status.checking] = '审批中';
+statusString[status.checked] = '审批完成';
+statusString[status.checkNo] = '退回';
+
+const statusClass = [];
+statusClass[status.uncheck] = '';
+statusClass[status.checking] = 'text-warning';
+statusClass[status.checked] = 'text-success';
+statusClass[status.checkNo] = 'text-warning';
+
+module.exports = {
+    status,
+    statusString,
+    statusClass,
+};

+ 32 - 0
app/const/spread.js

@@ -0,0 +1,32 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2018/5/31
+ * @version
+ */
+
+const ledgerSpread = {
+    cols: [
+        {title: '项目节编号', field: 'code', width: 150, cellType: 'tree'},
+        {title: '清单编号', field: 'b_code', width: 80},
+        {title: '名称', field: 'name', width: 230},
+        {title: '单位', field: 'unit', width: 50},
+        {title: '单价', field: 'unit_price', width: 60, type: 'Number'},
+        {title: '数量', field: 'quantity', width: 60, type: 'Number'},
+        {title: '金额', field: 'total_price', width: 60, type: 'Number'},
+        {title: '施工图原设计', field: 'design', width: 60},
+        {title: '图(册)号', field: 'drawingCode', width: 80},
+        {title: '备注', field: 'memo', width: 100}
+    ],
+    emptyRows: 3,
+    headRows: 2,
+    headRowHeight: [40, 40],
+    defaultRowHeight: 21,
+};
+
+module.exports = {
+    ledgerSpread,
+}

+ 2 - 1
app/controller/dashboard_controller.js

@@ -19,8 +19,9 @@ module.exports = app => {
          * @return {void}
          */
         async index(ctx) {
+            const auditTenders = await ctx.service.ledgerAudit.getAuditTender(ctx.session.sessionUser.accountId);
             const renderData = {
-
+                auditTenders,
             };
             await this.layout('dashboard/index.ejs', renderData);
         }

+ 213 - 0
app/controller/ledger_audit_controller.js

@@ -0,0 +1,213 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2018/5/28
+ * @version
+ */
+const auditConst = require('../const/audit');
+const spreadConst = require('../const/spread');
+
+module.exports = app => {
+    class LedgerAuditController extends app.BaseController {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局context
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            ctx.showProject = true;
+        }
+
+        /**
+         * 台账审批页面(get)
+         *
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async index(ctx) {
+            try {
+                if (!ctx.request.query.tenderId) {
+                    throw '标段信息错误';
+                }
+
+                const tenderId = ctx.request.query.tenderId;
+                const tender = await ctx.service.tender.getDataById(tenderId);
+                ctx.session.sessionUser.tenderId = tender.id;
+                ctx.session.sessionUser.tenderName = tender.name;
+
+                const curAuditor = await ctx.service.ledgerAudit.getCurAuditor(tenderId, tender.times);
+                if (curAuditor.audit_id !== ctx.session.sessionUser.accountId) {
+                    throw '审核信息错误';
+                }
+
+                const auditors = await ctx.service.ledgerAudit.getAuditors(tenderId, tender.times);
+                const ledgerData = await ctx.service.ledger.getDataByTenderId(tenderId);
+                const renderData = {
+                    auditConst,
+                    tender,
+                    curAuditor,
+                    auditors,
+                    ledger: JSON.stringify(ledgerData),
+                    ledgerSpreadSetting: JSON.stringify(spreadConst.ledgerSpread),
+                    readOnly: true,
+                };
+                await this.layout('ledger/audit.ejs', renderData, 'ledger/audit_modal.ejs');
+            } catch(err) {
+                console.log(err);
+                ctx.redirect('/dashboard');
+            }
+        }
+
+        /**
+         * 新增审批人(Ajax)
+         *
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async add(ctx) {
+            const responseData = {
+                err: 0,
+                msg: '',
+                data: [],
+            };
+            try {
+                const tenderId = ctx.session.sessionUser.tenderId;
+                if (!tenderId) {
+                    throw '当前未打开标段';
+                }
+                const data = JSON.parse(ctx.request.body.data);
+                const id = data.auditorId;
+                if (isNaN(id) || id <= 0) {
+                    throw '参数错误';
+                }
+
+                const result = await ctx.service.ledgerAudit.addAuditor(tenderId, id);
+                if (!result) {
+                    throw '添加审核人失败';
+                }
+
+                responseData.data = await ctx.service.ledgerAudit.getAuditor(tenderId, id);
+            } catch (err) {
+                responseData.err = 1;
+                responseData.msg = err.toString();
+            }
+
+            ctx.body = responseData;
+        }
+
+        /**
+         * 移除审批人(Ajax)
+         *
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async remove(ctx) {
+            const responseData = {
+                err: 0,
+                msg: '',
+                data: [],
+            };
+            try {
+                const tenderId = ctx.session.sessionUser.tenderId;
+                if (!tenderId) {
+                    throw '当前未打开标段';
+                }
+                const data = JSON.parse(ctx.request.body.data);
+                const id = data.auditorId;
+                if (!(id instanceof Number) || isNaN(id) || id <= 0) {
+                    throw '参数错误';
+                }
+
+                const result = await ctx.service.ledgerAudit.deleteAuditor(tenderId, id);
+                if (!result) {
+                    throw '移除审核人失败';
+                }
+
+                responseData.data = await ctx.service.ledgerAudit.getAuditors(tenderId);
+            } catch (err) {
+                responseData.err = 1;
+                responseData.msg = err.toString();
+            }
+
+            ctx.body = responseData;
+        }
+
+        /**
+         * 上报(post)
+         *
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async start(ctx) {
+            try {
+                const tenderId = ctx.session.sessionUser.tenderId;
+                if (!tenderId) {
+                    throw '未打开标段';
+                }
+                const tenderData = await ctx.service.tender.getDataById(tenderId);
+                if (!tenderData || tenderData.ledger_status === auditConst.status.checking || tenderData.ledger_status === auditConst.status.checked) {
+                    throw '标段数据有误';
+                }
+                if (tenderData.user_id !== ctx.session.sessionUser.accountId) {
+                    throw '上报失败';
+                }
+                if (!tenderData.times) {
+                    tenderData.times = 1;
+                }
+
+                await ctx.service.ledgerAudit.start(tenderId, tenderData.times);
+
+                ctx.redirect('/ledger/explode');
+            } catch (err) {
+                console.log(err);
+                ctx.session.postError = err.toString();
+                ctx.redirect('/ledger/explode');
+            }
+        }
+
+        /**
+         * 审批(post)
+         *
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async check(ctx) {
+            try {
+                const tenderId = ctx.session.sessionUser.tenderId;
+                if (!tenderId) {
+                    throw '当前未打开标段';
+                }
+                const tenderData = await ctx.service.tender.getDataById(tenderId);
+                if (!tenderData || tenderData.ledger_status !== auditConst.status.checking ) {
+                    throw '当前标段数据有误';
+                }
+                if (!tenderData.times) {
+                    tenderData.times = 1;
+                }
+                const curAudit = await ctx.service.ledgerAudit.getCurAuditor(tenderId, tenderData.times);
+                if (curAudit.audit_id !== ctx.session.sessionUser.accountId) {
+                    throw '审批失败';
+                }
+                const checkType = parseInt(ctx.request.body.checkType);
+                if (!checkType || isNaN(checkType)) {
+                    throw '提交数据错误';
+                }
+
+                await ctx.service.ledgerAudit.check(tenderId, checkType, ctx.request.body.opinion, tenderData.times);
+
+                ctx.redirect('/ledger/explode');
+            } catch (err) {
+                console.log(err);
+                ctx.session.postError = err.toString();
+                ctx.redirect(ctx.request.headers.referer);
+            }
+        }
+    }
+
+    return LedgerAuditController;
+};

+ 48 - 0
app/controller/ledger_controller.js

@@ -12,6 +12,8 @@ const stdDataAddType = {
     self: 1,
     withParent: 2
 }
+const auditConst = require('../const/audit');
+const spreadConst = require('../const/spread');
 
 module.exports = app => {
 
@@ -62,9 +64,21 @@ module.exports = app => {
                     }
                 }
 
+                const tenderData = await ctx.service.tender.getDataById(tenderId);
+                if (!tenderData.ledger_status) {
+                    tenderData.ledger_status = auditConst.status.uncheck;
+                }
+                const curAuditor = await ctx.service.ledgerAudit.getCurAuditor(tenderId, tenderData.times);
+                const auditors = await ctx.service.ledgerAudit.getAuditors(tenderId, tenderData.times);
+
                 const ledgerData = await ctx.service.ledger.getDataByTenderId(tenderId);
                 const renderData = {
+                    tenderData,
+                    auditConst,
+                    auditors,
+                    curAuditor,
                     ledger: JSON.stringify(ledgerData),
+                    ledgerSpreadSetting: JSON.stringify(spreadConst.ledgerSpread),
                     tenderList,
                 };
                 await this.layout('ledger/explode.ejs', renderData, 'ledger/explode_modal.ejs');
@@ -72,6 +86,16 @@ module.exports = app => {
         }
 
         /**
+         * 检查标段是否只读(审核中,审核完成)
+         * @param {Object} tenderData
+         * @returns {boolean}
+         * @private
+         */
+        _ledgerReadOnly (tenderData) {
+            return tenderData.ledger_status === auditConst.status.checking || tenderData.ledger_status === auditConst.status.checked;
+        }
+
+        /**
          * 获取子节点
          * @param ctx
          * @return {Promise<void>}
@@ -118,6 +142,10 @@ module.exports = app => {
                 if (!tenderId) {
                     throw '当前未打开标段';
                 }
+                const tenderData = await ctx.service.tender.getDataById(tenderId);
+                if (!tenderData || tenderData.user_id !== ctx.session.sessionUser.accountId || this._ledgerReadOnly(tenderData)) {
+                    throw '标段数据错误';
+                }
                 const data = JSON.parse(ctx.request.body.data);
                 if ((isNaN(data.id) || data.id <= 0) || (!data.postType)) {
                     throw '参数错误';
@@ -170,6 +198,10 @@ module.exports = app => {
                 if (!tenderId) {
                     throw '当前未打开标段';
                 }
+                const tenderData = await ctx.service.tender.getDataById(tenderId);
+                if (!tenderData || tenderData.user_id !== ctx.session.sessionUser.accountId || this._ledgerReadOnly(tenderData)) {
+                    throw '标段数据错误';
+                }
                 const data = JSON.parse(ctx.request.body.data);
                 if (data instanceof Array) {
                     responseData.data = await ctx.service.ledger.updateInfos(tenderId, data);
@@ -195,6 +227,10 @@ module.exports = app => {
                 if (!tenderId) {
                     throw '当前未打开标段';
                 }
+                const tenderData = await ctx.service.tender.getDataById(tenderId);
+                if (!tenderData || tenderData.user_id !== ctx.session.sessionUser.accountId || this._ledgerReadOnly(tenderData)) {
+                    throw '标段数据错误';
+                }
                 const data = JSON.parse(ctx.request.body.data);
                 responseData.data = await ctx.service.ledger.updateCalc(tenderId, data);
             } catch (err) {
@@ -221,6 +257,10 @@ module.exports = app => {
                 if (!tenderId) {
                     throw '当前未打开标段';
                 }
+                const tenderData = await ctx.service.tender.getDataById(tenderId);
+                if (!tenderData || tenderData.user_id !== ctx.session.sessionUser.accountId || this._ledgerReadOnly(tenderData)) {
+                    throw '标段数据错误';
+                }
                 const data = JSON.parse(ctx.request.body.data);
                 if ((isNaN(data.id) || data.id <= 0) || (!data.block || data.block.length <= 0)) {
                     throw '参数错误';
@@ -251,6 +291,10 @@ module.exports = app => {
                 if (!tenderId) {
                     throw '当前未打开标段';
                 }
+                const tenderData = await ctx.service.tender.getDataById(tenderId);
+                if (!tenderData || tenderData.user_id !== ctx.session.sessionUser.accountId || this._ledgerReadOnly(tenderData)) {
+                    throw '标段数据错误';
+                }
                 const data = JSON.parse(ctx.request.body.data);
                 if ((isNaN(data.id) || data.id <= 0) || !data.stdType || !data.stdNode) {
                     throw '参数错误';
@@ -310,6 +354,10 @@ module.exports = app => {
                 if (!tenderId) {
                     throw '当前未打开标段';
                 }
+                const tenderData = await ctx.service.tender.getDataById(tenderId);
+                if (!tenderData || tenderData.user_id !== ctx.session.sessionUser.accountId || this._ledgerReadOnly(tenderData)) {
+                    throw '标段数据错误';
+                }
                 const data = JSON.parse(ctx.request.body.data);
                 if ((isNaN(data.id) || data.id <= 0) || !data.batchType) {
                     throw '参数错误';

+ 32 - 0
app/controller/project_controller.js

@@ -115,6 +115,38 @@ module.exports = app => {
             }
             ctx.redirect(ctx.request.headers.referer);
         }
+
+        /**
+         * 查询项目的参与人
+         *
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async searchAccount(ctx) {
+            const projectId = ctx.session.sessionProject.id;
+            const responseData = {
+                err: 0,
+                msg: '',
+                data: [],
+            };
+            try {
+                if (!projectId) {
+                    throw '当前未打开项目';
+                }
+                const data = JSON.parse(ctx.request.body.data);
+                const name = data.keyword;
+                if (!name || name === '') {
+                    throw '请输入姓名进行检索';
+                }
+
+                responseData.data = await ctx.service.projectAccount.getAccountInfoByName(projectId, name);
+            } catch (err) {
+                responseData.err = 1;
+                responseData.msg = err.toString();
+            }
+
+            ctx.body = responseData;
+        }
     }
 
     return ProjectController;

+ 203 - 170
app/public/js/ledger.js

@@ -15,28 +15,11 @@ $(document).ready(function() {
         order: 'order',
         level: 'level',
         rootId: -1,
-        keys: ['id', 'tender_id', 'ledger_id']
+        keys: ['id', 'tender_id', 'ledger_id'],
+        preUrl: '/ledger'
     });
     ledgerTree.loadDatas(ledger);
-    SpreadJsObj.initSheet(ledgerSpread.getActiveSheet(), {
-        cols: [
-            {title: '项目节编号', field: 'code', width: 150, cellType: 'tree'},
-            {title: '清单编号', field: 'b_code', width: 80},
-            {title: '名称', field: 'name', width: 230},
-            {title: '单位', field: 'unit', width: 50},
-            {title: '单价', field: 'unit_price', width: 60, type: 'Number'},
-            {title: '数量', field: 'quantity', width: 60, type: 'Number'},
-            {title: '金额', field: 'total_price', width: 60, type: 'Number'},
-            {title: '施工图原设计', field: 'design', width: 60},
-            {title: '图(册)号', field: 'drawingCode', width: 80},
-            {title: '备注', field: 'memo', width: 100}
-        ],
-        treeCol: 0,
-        emptyRows: 3,
-        headRows: 2,
-        headRowHeight: [40, 40],
-        defaultRowHeight: 21,
-    });
+    SpreadJsObj.initSheet(ledgerSpread.getActiveSheet(), ledgerSpreadSetting);
     SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), 'tree', ledgerTree);
 
     const treeOperationObj = {
@@ -67,11 +50,11 @@ $(document).ready(function() {
             if (!tree) { return; }
             const node = sheet.zh_tree.nodes[row];
 
-            setObjEnable($('#delete'), node);
-            setObjEnable($('#up-move'), node && node.order > 1);
-            setObjEnable($('#down-move'), node && !tree.isLastSibling(node));
-            setObjEnable($('#up-level'), node && tree.getParent(node));
-            setObjEnable($('#down-level'), node && node.order > 1);
+            setObjEnable($('#delete'), !sheet.zh_setting.readOnly && node);
+            setObjEnable($('#up-move'), !sheet.zh_setting.readOnly && node && node.order > 1);
+            setObjEnable($('#down-move'), !sheet.zh_setting.readOnly && node && !tree.isLastSibling(node));
+            setObjEnable($('#up-level'), !sheet.zh_setting.readOnly && node && tree.getParent(node));
+            setObjEnable($('#down-level'), !sheet.zh_setting.readOnly && node && node.order > 1);
         },
         refreshTree: function (sheet, data) {
             SpreadJsObj.massOperationSheet(sheet, function () {
@@ -383,161 +366,164 @@ $(document).ready(function() {
         }
     };
 
-    ledgerSpread.bind(GC.Spread.Sheets.Events.SelectionChanged, function (e, info) {
-        treeOperationObj.refreshOperationValid(info.sheet, info.newSelections);
-    });
-    ledgerSpread.bind(GC.Spread.Sheets.Events.EditEnded, treeOperationObj.editEnded);
-    ledgerSpread.bind(GC.Spread.Sheets.Events.ClipboardPasted, treeOperationObj.clipboardPasted);
-    SpreadJsObj.addDeleteBind(ledgerSpread, treeOperationObj.deletePress);
-    ledgerSpread.bind(GC.Spread.Sheets.Events.ClipboardChanging, function (e, info) {
-        const copyText = SpreadJsObj.getFilterCopyText(info.sheet);
-    });
-    ledgerSpread.bind(GC.Spread.Sheets.Events.ClipboardChanged, function (e, info) {
-        console.log(info.copyData.text);
-    });
-    ledgerSpread.bind(GC.Spread.Sheets.Events.ClipboardPasting, function (e, info) {
-    });
-
-    // 绑定 删除等 顶部按钮
-    $('#delete').click(function () {
-        treeOperationObj.deleteNode(ledgerSpread);
-    });
-    $('#up-move').click(function () {
-        treeOperationObj.upMove(ledgerSpread);
-    });
-    $('#down-move').click(function () {
-        treeOperationObj.downMove(ledgerSpread);
-    });
-    $('#up-level').click(function () {
-        treeOperationObj.upLevel(ledgerSpread);
-    });
-    $('#down-level').click(function () {
-        treeOperationObj.downLevel(ledgerSpread);
-    });
-    treeOperationObj.refreshOperationValid(ledgerSpread.getActiveSheet(), ledgerSpread.getActiveSheet().getSelections());
-
-    let batchInsertObj;
-    // 右键菜单
-    $.contextMenu({
-        selector: '#ledger-spread',
-        build: function ($trigger, e) {
-            const target = SpreadJsObj.safeRightClickSelection($trigger, e, ledgerSpread);
-            return target.hitTestType === GC.Spread.Sheets.SheetArea.viewport || target.hitTestType === GC.Spread.Sheets.SheetArea.rowHeader;
-        },
-        items: {
-            'create': {
-                name: '新增',
-                icon: 'fa-sign-in',
-                callback: function (key, opt) {
-                    treeOperationObj.addNode(ledgerSpread);
-                },
-                visible: function(key, opt){
-                    const sheet = ledgerSpread.getActiveSheet();
-                    const selection = sheet.getSelections();
-                    const row = selection[0].row;
-                    const select = ledgerTree.nodes[row];
-                    return select;
-                }
+    if (!ledgerSpreadSetting.readOnly) {
+        ledgerSpread.bind(GC.Spread.Sheets.Events.SelectionChanged, function (e, info) {
+            treeOperationObj.refreshOperationValid(info.sheet, info.newSelections);
+        });
+        ledgerSpread.bind(GC.Spread.Sheets.Events.EditEnded, treeOperationObj.editEnded);
+        ledgerSpread.bind(GC.Spread.Sheets.Events.ClipboardPasted, treeOperationObj.clipboardPasted);
+        SpreadJsObj.addDeleteBind(ledgerSpread, treeOperationObj.deletePress);
+        ledgerSpread.bind(GC.Spread.Sheets.Events.ClipboardChanging, function (e, info) {
+            const copyText = SpreadJsObj.getFilterCopyText(info.sheet);
+        });
+        ledgerSpread.bind(GC.Spread.Sheets.Events.ClipboardChanged, function (e, info) {
+            console.log(info.copyData.text);
+        });
+        ledgerSpread.bind(GC.Spread.Sheets.Events.ClipboardPasting, function (e, info) {
+        });
+
+        // 绑定 删除等 顶部按钮
+        $('#delete').click(function () {
+            treeOperationObj.deleteNode(ledgerSpread);
+        });
+        $('#up-move').click(function () {
+            treeOperationObj.upMove(ledgerSpread);
+        });
+        $('#down-move').click(function () {
+            treeOperationObj.downMove(ledgerSpread);
+        });
+        $('#up-level').click(function () {
+            treeOperationObj.upLevel(ledgerSpread);
+        });
+        $('#down-level').click(function () {
+            treeOperationObj.downLevel(ledgerSpread);
+        });
+
+        let batchInsertObj;
+        // 右键菜单
+        $.contextMenu({
+            selector: '#ledger-spread',
+            build: function ($trigger, e) {
+                const target = SpreadJsObj.safeRightClickSelection($trigger, e, ledgerSpread);
+                return target.hitTestType === GC.Spread.Sheets.SheetArea.viewport || target.hitTestType === GC.Spread.Sheets.SheetArea.rowHeader;
             },
-            'delete': {
-                name: '删除',
-                icon: 'fa-remove',
-                callback: function (key, opt) {
-                    treeOperationObj.deleteNode(ledgerSpread);
-                },
-                visible: function (key, opt) {
-                    const sheet = ledgerSpread.getActiveSheet();
-                    const selection = sheet.getSelections();
-                    const row = selection[0].row;
-                    const select = ledgerTree.nodes[row];
-                    return select;
-                }
-            },
-            'copyBlock': {
-                name: '复制整块',
-                icon: 'fa-files-o',
-                callback: function (key, opt) {
-                    /*ledgerSpread.commandManager().execute({
-                        cmd:"copy",
-                        sheetName:ledgerSpread.getActiveSheet().name()
-                    });*/
-                    treeOperationObj.block = [];
-                    const copyBlockList = [];
-                    const sheet = ledgerSpread.getActiveSheet();
-                    const sel = sheet.getSelections()[0];
-                    let iRow = sel.row;
-                    const pid = sheet.zh_tree.nodes[iRow].ledger_pid;
-                    while (iRow < sel.row + sel.rowCount) {
-                        const node = sheet.zh_tree.nodes[iRow];
-                        if (node.ledger_pid !== pid) {
-                            toast('error: 仅可同时选中同层节点', 'error', 'exclamation-circle');
-                            return;
-                        }
-                        copyBlockList.push(node.ledger_id);
-                        iRow += sheet.zh_tree.getPosterity(node).length + 1;
+            items: {
+                'create': {
+                    name: '新增',
+                    icon: 'fa-sign-in',
+                    callback: function (key, opt) {
+                        treeOperationObj.addNode(ledgerSpread);
+                    },
+                    visible: function(key, opt){
+                        const sheet = ledgerSpread.getActiveSheet();
+                        const selection = sheet.getSelections();
+                        const row = selection[0].row;
+                        const select = ledgerTree.nodes[row];
+                        return select;
                     }
-                    treeOperationObj.block = copyBlockList;
                 },
-                visible: function (key, opt) {
-                    const sheet = ledgerSpread.getActiveSheet();
-                    const selection = sheet.getSelections();
-                    const row = selection[0].row;
-                    const select = ledgerTree.nodes[row];
-                    return select;
-                }
-            },
-            'pasteBlock': {
-                name: '粘贴',
-                icon: 'fa-clipboard',
-                disabled: function (key, opt) {
-                    const block = treeOperationObj.block || [];
-                    return block.length <= 0 && false;
+                'delete': {
+                    name: '删除',
+                    icon: 'fa-remove',
+                    callback: function (key, opt) {
+                        treeOperationObj.deleteNode(ledgerSpread);
+                    },
+                    visible: function (key, opt) {
+                        const sheet = ledgerSpread.getActiveSheet();
+                        const selection = sheet.getSelections();
+                        const row = selection[0].row;
+                        const select = ledgerTree.nodes[row];
+                        return select;
+                    }
                 },
-                callback: function (key, opt) {
-                    const block = treeOperationObj.block || [];
-                    if (block.length > 0) {
-                        treeOperationObj.pasteBlock(ledgerSpread, block);
-                    } else {
-                        document.execCommand('paste');
+                'copyBlock': {
+                    name: '复制整块',
+                    icon: 'fa-files-o',
+                    callback: function (key, opt) {
+                        /*ledgerSpread.commandManager().execute({
+                            cmd:"copy",
+                            sheetName:ledgerSpread.getActiveSheet().name()
+                        });*/
+                        treeOperationObj.block = [];
+                        const copyBlockList = [];
+                        const sheet = ledgerSpread.getActiveSheet();
+                        const sel = sheet.getSelections()[0];
+                        let iRow = sel.row;
+                        const pid = sheet.zh_tree.nodes[iRow].ledger_pid;
+                        while (iRow < sel.row + sel.rowCount) {
+                            const node = sheet.zh_tree.nodes[iRow];
+                            if (node.ledger_pid !== pid) {
+                                toast('error: 仅可同时选中同层节点', 'error', 'exclamation-circle');
+                                return;
+                            }
+                            copyBlockList.push(node.ledger_id);
+                            iRow += sheet.zh_tree.getPosterity(node).length + 1;
+                        }
+                        treeOperationObj.block = copyBlockList;
+                    },
+                    visible: function (key, opt) {
+                        const sheet = ledgerSpread.getActiveSheet();
+                        const selection = sheet.getSelections();
+                        const row = selection[0].row;
+                        const select = ledgerTree.nodes[row];
+                        return select;
                     }
-                }
-            },
-            'batchInsertChild': {
-                name: '批量插入子项',
-                icon: 'fa-sign-in',
-                disabled: function (key, opt) {
-                    return false;
                 },
-                callback: function(key, opt) {
-                    $('h5', $('#batch')).text('批量插入子项');
-                    if (!batchInsertObj) {
-                        batchInsertObj = new BatchInsertObj($('#batch'), key);
-                    } else {
-                        batchInsertObj.batchType = key;
-                        batchInsertObj.initView();
+                'pasteBlock': {
+                    name: '粘贴',
+                    icon: 'fa-clipboard',
+                    disabled: function (key, opt) {
+                        const block = treeOperationObj.block || [];
+                        return block.length <= 0 && false;
+                    },
+                    callback: function (key, opt) {
+                        const block = treeOperationObj.block || [];
+                        if (block.length > 0) {
+                            treeOperationObj.pasteBlock(ledgerSpread, block);
+                        } else {
+                            document.execCommand('paste');
+                        }
                     }
-                    $('#batch').modal('show');
                 },
-            },
-            'batchInsertNext': {
-                name: '批量插入后项',
-                icon: 'fa-sign-in',
-                disabled: function (key, opt) {
-                    return false;
+                'batchInsertChild': {
+                    name: '批量插入子项',
+                    icon: 'fa-sign-in',
+                    disabled: function (key, opt) {
+                        return false;
+                    },
+                    callback: function(key, opt) {
+                        $('h5', $('#batch')).text('批量插入子项');
+                        if (!batchInsertObj) {
+                            batchInsertObj = new BatchInsertObj($('#batch'), key);
+                        } else {
+                            batchInsertObj.batchType = key;
+                            batchInsertObj.initView();
+                        }
+                        $('#batch').modal('show');
+                    },
                 },
-                callback: function (key, opt) {
-                    $('h5', $('#batch')).text('批量插入后项');
-                    if (!batchInsertObj) {
-                        batchInsertObj = new BatchInsertObj($('#batch'), key);
-                    } else {
-                        batchInsertObj.batchType = key;
-                        batchInsertObj.initView();
-                    }
-                    $('#batch').modal('show');
+                'batchInsertNext': {
+                    name: '批量插入后项',
+                    icon: 'fa-sign-in',
+                    disabled: function (key, opt) {
+                        return false;
+                    },
+                    callback: function (key, opt) {
+                        $('h5', $('#batch')).text('批量插入后项');
+                        if (!batchInsertObj) {
+                            batchInsertObj = new BatchInsertObj($('#batch'), key);
+                        } else {
+                            batchInsertObj.batchType = key;
+                            batchInsertObj.initView();
+                        }
+                        $('#batch').modal('show');
+                    },
                 },
-            },
-        }
-    });
+            }
+        });
+    }
+
+    treeOperationObj.refreshOperationValid(ledgerSpread.getActiveSheet(), ledgerSpread.getActiveSheet().getSelections());
 
     let stdChapter, stdBills, dealBills;
     // 展开收起标准清单
@@ -811,5 +797,52 @@ $(document).ready(function() {
             return result;
         }
     }
+
+    $('#searchAccount').click(() => {
+        const data = {
+            keyword: $('#searchName').val(),
+        }
+        postData('/search/user', data, (data) => {
+            const resultDiv = $('#searchResult');
+            $('h5>span', resultDiv).text(data.name);
+            $('#addAuditor').attr('auditorId', data.id);
+            $('h6', resultDiv).text(data.role);
+            $('p', resultDiv).text(data.company);
+            resultDiv.show();
+        }, () => {
+            $('#searchResult').hide();
+        });
+    });
+    $('#addAuditor').click(() => {
+        const data = {
+            auditorId: $('#addAuditor').attr('auditorId'),
+        };
+        console.log(data);
+        postData('/ledger/audit/add', data, (data) => {
+            const html = [];
+            html.push('<li class="list-group-item" auditorId="'+ data.audit_id +'"><a href="javascript: void(0)" class="text-danger pull-right">移除</a>');
+            html.push('<span>');
+            html.push(data.audit_order + ' ');
+            html.push(data.name + ' ');
+            html.push('</span>');
+            html.push('<small class="text-muted">');
+            html.push(data.role);
+            html.push('</small></li>');
+            $('#auditors').append(html.join(''));
+        });
+    });
+    $('li>a', '#auditors').bind('click', function () {
+        const li = $(this).parent();
+        const data = {
+            auditorId: parseInt(li.attr('auditorId')),
+        }
+        postData('/ledger/audit/delete', data, (data) => {
+            li.remove();
+            for (const a of data) {
+                const aLi = $('li[auditorId=' + a.audit_id + ']');
+                $('span', aLi).text(a.audit_order + ' ' + a.name + ' ');
+            }
+        });
+    });
 });
 

+ 81 - 0
app/public/js/ledger_audit.js

@@ -0,0 +1,81 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2018/5/30
+ * @version
+ */
+
+$(document).ready(() => {
+    autoFlashHeight();
+    const ledgerSpread = SpreadJsObj.createNewSpread($('#ledger-spread')[0]);
+    const ledgerTree = createNewPathTree({
+        id: 'ledger_id',
+        pid: 'ledger_pid',
+        order: 'order',
+        level: 'level',
+        rootId: -1,
+        keys: ['id', 'tender_id', 'ledger_id']
+    });
+    ledgerTree.loadDatas(ledger);
+    SpreadJsObj.initSheet(ledgerSpread.getActiveSheet(), ledgerSpreadSetting);
+    SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), 'tree', ledgerTree);
+
+
+    let dealBills;
+    // 展开收起标准清单
+    $('a', '.side-menu').bind('click', function () {
+        const tab = $(this), tabPanel = $(tab.attr('content'));
+        const showSideTools = function (show) {
+            if (show) {
+                $('.c-body.col-12').removeClass('col-12').addClass('col-8');
+                $('.c-body.col-0').removeClass('col-0').addClass('col-4').show();
+            } else {
+                $('.c-body.col-8').removeClass('col-8').addClass('col-12');
+                $('.c-body.col-4').removeClass('col-4').addClass('col-0').hide();
+            }
+        }
+        if (!tab.hasClass('active')) {
+            $('a', '#side-menu').removeClass('active');
+            tab.addClass('active');
+            showSideTools(tab.hasClass('active'));
+            $('.tab-content .tab-pane').hide();
+            tabPanel.show();
+            if (tab.attr('content') === '#deal-bills' && !dealBills) {
+                dealBills = new DealBills($('#deal-bills-spread')[0], {
+                    cols: [
+                        {title: '清单编号', field: 'code', width: 120, readOnly: true},
+                        {title: '名称', field: 'name', width: 230, readOnly: true},
+                        {title: '单位', field: 'unit', width: 50, readOnly: true},
+                        {title: '单价', field: 'unit_price', width: 50, readOnly: true},
+                        {title: '数量', field: 'quantity', width: 50, readOnly: true},
+                    ],
+                    emptyRows: 0,
+                });
+                dealBills.loadData();
+            }
+        } else {
+            tab.removeClass('active');
+            showSideTools(tab.hasClass('active'));
+            tabPanel.hide();
+        }
+        ledgerSpread.refresh();
+    });
+    class DealBills {
+        constructor (obj, spreadSetting) {
+            this.obj = obj;
+            this.url = '/deal';
+            this.spreadSetting = spreadSetting;
+            this.spread = SpreadJsObj.createNewSpread(this.obj);
+            SpreadJsObj.initSheet(this.spread.getActiveSheet(), this.spreadSetting);
+        }
+        loadData () {
+            const self = this;
+            postData(this.url+'/get-data', {}, function (data) {
+                SpreadJsObj.loadSheetData(self.spread.getActiveSheet(), 'data', data);
+            });
+        }
+    }
+});

+ 10 - 6
app/public/js/spreadjs_rela/spreadjs_zh.js

@@ -178,6 +178,7 @@ const SpreadJsObj = {
         this.initSheetHeader(sheet);
         sheet.setRowCount(sheet.zh_setting.emptyRows);
         sheet.extendCellType = {};
+        sheet.getRange(0, 0, sheet.getRowCount(), sheet.getColumnCount()).locked(setting.readOnly);
     },
     /**
      * 整个sheet重新加载数据
@@ -199,9 +200,9 @@ const SpreadJsObj = {
                 sheet.zh_setting.cols.forEach(function (col, j) {
                     const cell = sheet.getCell(i, j);
                     if (col.field !== '' && data[col.field]) {
-                        cell.value(data[col.field]).locked(col.readOnly || false);
+                        cell.value(data[col.field]).locked(col.readOnly || sheet.zh_setting.readOnly || false);
                     } else {
-                        cell.locked(col.readOnly || false);
+                        cell.locked(col.readOnly || sheet.zh_setting.readOnly || false);
                     }
                 });
             });
@@ -221,6 +222,7 @@ const SpreadJsObj = {
                     sheet.getRange(-1, j, -1, 1).cellType(sheet.extendCellType.tip);
                 }
             });
+            this.endMassOperation(sheet);
         } catch (err) {
             this.endMassOperation(sheet);
         }
@@ -245,9 +247,9 @@ const SpreadJsObj = {
                 sheet.zh_setting.cols.forEach(function (col, j) {
                     const cell = sheet.getCell(i, j);
                     if (col.field !== '' && data[col.field]) {
-                        cell.value(data[col.field]).locked(col.readOnly || false);
+                        cell.value(data[col.field]).locked(col.readOnly || sheet.zh_setting.readOnly || false);
                     } else {
-                        cell.locked(col.readOnly || false);
+                        cell.locked(col.readOnly || sheet.zh_setting.readOnly || false);
                     }
                 });
             };
@@ -267,6 +269,7 @@ const SpreadJsObj = {
                     sheet.getRange(row, j, count, 1).cellType(sheet.extendCellType.tip);
                 }
             });
+            this.endMassOperation(sheet);
         } catch (err) {
             this.endMassOperation(sheet);
         }
@@ -291,9 +294,9 @@ const SpreadJsObj = {
                     // 设置值
                     const cell = sheet.getCell(row, j);
                     if (col.field !== '' && data[col.field]) {
-                        cell.value(data[col.field]).locked(col.readOnly || false);
+                        cell.value(data[col.field]).locked(col.readOnly || sheet.zh_setting.readOnly || false);
                     } else {
-                        cell.locked(col.readOnly || false);
+                        cell.locked(col.readOnly || sheet.zh_setting.readOnly || false);
                     }
                     // 设置单元格格式
                     if (col.cellType) {
@@ -311,6 +314,7 @@ const SpreadJsObj = {
                     }
                 });
             };
+            this.endMassOperation(sheet);
         } catch (err) {
             this.endMassOperation(sheet);
         }

+ 10 - 0
app/router.js

@@ -44,6 +44,13 @@ module.exports = app => {
     app.get('/ledger/change', sessionAuth, 'ledgerController.change');
     app.get('/ledger/index', sessionAuth, 'ledgerController.index');
 
+    // 台账审批相关
+    app.get('/ledger/audit', sessionAuth, 'ledgerAuditController.index');
+    app.post('/ledger/audit/add', sessionAuth, 'ledgerAuditController.add');
+    app.post('/ledger/audit/delete', sessionAuth, 'ledgerAuditController.remove');
+    app.post('/ledger/audit/start', sessionAuth, 'ledgerAuditController.start');
+    app.post('/ledger/audit/check', sessionAuth, 'ledgerAuditController.check');
+
     // 签约清单
     app.post('/deal/get-data', sessionAuth, 'dealBillsController.getData');
     app.post('/deal/upload-excel', sessionAuth, 'dealBillsController.loadExcel');
@@ -72,4 +79,7 @@ module.exports = app => {
     app.post('/std/bills/get-children', sessionAuth, 'stdBillsController.getChildren');
     app.post('/std/chapter/get-data', sessionAuth, 'stdChapterController.getData');
     app.post('/std/chapter/get-children', sessionAuth, 'stdChapterController.getChildren');
+
+    // 查询
+    app.post('/search/user', sessionAuth, 'projectController.searchAccount');
 };

+ 264 - 0
app/service/ledger_audit.js

@@ -0,0 +1,264 @@
+'use strict';
+
+/**
+ * 台账审批流程表
+ *
+ * @author Mai
+ * @date 2018/5/25
+ * @version
+ */
+
+const auditConst = require('../const/audit');
+
+module.exports = app => {
+    class LedgerAudit extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'ledger_audit';
+        }
+
+        /**
+         * 获取标段审核人信息
+         *
+         * @param {Number} tenderId - 标段id
+         * @param {Number} auditorId - 审核人id
+         * @param {Number} times - 第几次审批
+         * @returns {Promise<*>}
+         */
+        async getAuditor(tenderId, auditorId, times = 1) {
+            const sql = 'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`audit_order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
+                'FROM ?? AS la, ?? AS pa ' +
+                'WHERE la.`tender_id` = ? and la.`audit_id` = ? and la.`times` = ?' +
+                '    and la.`audit_id` = pa.`id`';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, tenderId, auditorId, times];
+            return await this.db.queryOne(sql, sqlParam);
+        }
+
+        /**
+         * 获取标段审核列表信息
+         *
+         * @param {Number} tenderId - 标段id
+         * @param {Number} times - 第几次审批
+         * @returns {Promise<*>}
+         */
+        async getAuditors(tenderId, times = 1) {
+            const sql = 'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`audit_order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
+                'FROM ?? AS la, ?? AS pa ' +
+                'WHERE la.`tender_id` = ? and la.`times` = ?' +
+                '    and la.`audit_id` = pa.`id`';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, tenderId, times];
+            return await this.db.query(sql, sqlParam);
+        }
+
+        /**
+         * 获取标段当前审核人
+         *
+         * @param {Number} tenderId - 标段id
+         * @param {Number} times - 第几次审批
+         * @returns {Promise<*>}
+         */
+        async getCurAuditor(tenderId, times = 1) {
+            const sql = 'SELECT la.`audit_id`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`audit_order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
+                'FROM ?? AS la, ?? AS pa ' +
+                'WHERE la.`tender_id` = ? and la.`status` = ? and la.`times` = ?' +
+                '    and la.`audit_id` = pa.`id`';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, tenderId, auditConst.status.checking, times];
+            return await this.db.queryOne(sql, sqlParam);
+        }
+
+        /**
+         * 获取最新审核顺序
+         *
+         * @param {Number} tenderId - 标段id
+         * @param {Number} times - 第几次审批
+         * @returns {Promise<number>}
+         */
+        async getNewOrder(tenderId, times = 1) {
+            const sql = 'SELECT Max(??) As max_order FROM ?? Where `tender_id` = ? and `times` = ?';
+            const sqlParam = ['audit_order', this.tableName, tenderId, times];
+            const result = await this.db.queryOne(sql, sqlParam);
+            return result && result.max_order ? result.max_order + 1 : 1;
+        }
+
+        /**
+         * 新增审核人
+         *
+         * @param {Number} tenderId - 标段id
+         * @param {Number} auditorId - 审核人id
+         * @param {Number} times - 第几次审批
+         * @returns {Promise<number>}
+         */
+        async addAuditor(tenderId, auditorId, times = 1) {
+            const newOrder = await this.getNewOrder(tenderId, times);
+            const data = {
+                tender_id: tenderId,
+                audit_id: auditorId,
+                times: times,
+                audit_order: newOrder,
+                status: auditConst.status.uncheck,
+            };
+            const result = await this.db.insert(this.tableName, data);
+            return result.effectRows = 1;
+        }
+
+        /**
+         * 移除审核人时,同步其后审核人order
+         * @param transaction - 事务
+         * @param {Number} tenderId - 标段id
+         * @param {Number} auditorId - 审核人id
+         * @param {Number} times - 第几次审批
+         * @returns {Promise<*>}
+         * @private
+         */
+        async _syncOrderByDelete(transaction, tenderId, order, times) {
+            this.initSqlBuilder();
+            this.sqlBuilder.setAndWhere('tender_id', {
+                value: tenderId,
+                operate: '='
+            });
+            this.sqlBuilder.setAndWhere('audit_order', {
+                value: order,
+                operate: '>=',
+            });
+            this.sqlBuilder.setAndWhere('times', {
+                value: times,
+                operate: '=',
+            });
+            this.sqlBuilder.setUpdateData('audit_order', {
+                value: 1,
+                selfOperate: '-',
+            });
+            const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'update');
+            const data = await transaction.query(sql, sqlParam);
+
+            return data;
+        }
+
+        /**
+         * 移除审核人
+         *
+         * @param {Number} tenderId - 标段id
+         * @param {Number} auditorId - 审核人id
+         * @param {Number} times - 第几次审批
+         * @returns {Promise<boolean>}
+         */
+        async deleteAuditor(tenderId, auditorId, times = 1) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                const condition = {tender_id: tenderId, audit_id: auditorId, times: times};
+                const auditor = await this.getDataByCondition(condition);
+                if (!auditor) {
+                    throw '该审核人不存在';
+                }
+                await this._syncOrderByDelete(transaction, tenderId, auditor.audit_order, times);
+                await transaction.delete(this.tableName, condition);
+                await transaction.commit();
+            } catch(err) {
+                await transaction.rollback();
+                throw err;
+            }
+            return true;
+        }
+
+        /**
+         * 开始审批
+         *
+         * @param {Number} tenderId - 标段id
+         * @param {Number} times - 第几次审批
+         * @returns {Promise<boolean>}
+         */
+        async start(tenderId, times = 1) {
+            const audit = await this.getDataByCondition({tender_id: tenderId, times: times, audit_order: 1});
+            if (!audit) {
+                throw '审核人信息错误';
+            }
+
+            const transaction = await this.db.beginTransaction();
+            try {
+                await transaction.update(this.tableName, {id: audit.id, status: auditConst.status.checking, begin_time: new Date()});
+                await transaction.update(this.ctx.service.tender.tableName, {id: tenderId, ledger_status: auditConst.status.checking});
+                await transaction.commit();
+            } catch(err) {
+                await transaction.rollback();
+                throw err;
+            }
+            return true;
+        }
+
+        /**
+         * 审批
+         * @param {Number} tenderId - 标段id
+         * @param {auditConst.status.checked|auditConst.status.checkNo} checkType - 审批结果
+         * @param {Number} times - 第几次审批
+         * @returns {Promise<void>}
+         */
+        async check(tenderId, checkType, opinion, times = 1) {
+            if (checkType !== auditConst.status.checked && checkType !== auditConst.status.checkNo) {
+                throw '提交数据错误';
+            }
+
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 整理当前流程审核人状态更新
+                const time = new Date();
+                const audit = await this.getDataByCondition({tender_id: tenderId, times: times, status: auditConst.status.checking});
+                if (!audit) {
+                    throw '审核数据错误';
+                }
+                // 更新当前审核流程
+                await transaction.update(this.tableName, {id: audit.id, status: checkType, opinion: opinion, end_time: time});
+                if (checkType === auditConst.status.checked) {
+                    const nextAudit = await this.getDataByCondition({tender_id: tenderId, times: times, audit_order: audit.audit_order + 1});
+                    // 无下一审核人表示,审核结束
+                    if (nextAudit) {
+                        await transaction.update(this.tableName, {id: nextAudit.id, status: auditConst.status.checking, begin_time: time});
+                    } else {
+                        // 同步标段信息
+                        await transaction.update(this.ctx.service.tender.tableName, {id: tenderId, ledger_status: checkType});
+                    }
+                } else {
+                    // 同步标段信息
+                    await transaction.update(this.ctx.service.tender.tableName, {id: tenderId, ledger_times: times+1, ledger_status: checkType});
+                    // 拷贝新一次审核流程列表
+                    const auditors = await this.getAllDataByCondition({
+                        where: {tender_id: tenderId, times: times},
+                        columns: ['tender_id', 'audit_order', 'audit_id']
+                    });
+                    for (const a of auditors) {
+                        a.times = times + 1;
+                        a.status = auditConst.status.uncheck;
+                    }
+                    await transaction.insert(this.tableName, auditors);
+                }
+
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        /**
+         * 获取审核人需要审核的标段列表
+         *
+         * @param auditorId
+         * @returns {Promise<*>}
+         */
+        async getAuditTender(auditorId) {
+            const sql = 'SELECT la.`audit_id`, la.`times`, la.`audit_order`, la.`begin_time`, t.`id`, t.`name`, t.`project_id`, t.`type`, t.`user_id` ' +
+                'FROM ?? AS la, ?? AS t ' +
+                'WHERE la.`audit_id` = ? and la.`status` = ?' +
+                '    and la.`tender_id` = t.`id`';
+            const sqlParam = [this.tableName, this.ctx.service.tender.tableName, auditorId, auditConst.status.checking];
+            return await this.db.query(sql, sqlParam);
+        }
+    }
+
+    return LedgerAudit;
+};

+ 24 - 0
app/service/project_account.js

@@ -234,6 +234,30 @@ module.exports = app => {
         }
 
         /**
+         * 根据项目Id,用户名查找用户数据
+         * @param projectId
+         * @param name
+         * @returns {Promise<*>}
+         */
+        async getAccountInfoByName(projectId, name) {
+            this.initSqlBuilder();
+            this.sqlBuilder.columns = ['id', 'name', 'company', 'role'];
+            this.sqlBuilder.setAndWhere('project_id', {
+                operate: '=',
+                value: projectId,
+            });
+            this.sqlBuilder.setAndWhere('name', {
+                operate: 'like',
+                value: this.db.escape('%' + name + '%'),
+            });
+
+            const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'select');
+            const info = await this.db.queryOne(sql, sqlParam);
+
+            return info;
+        }
+
+        /**
          * 修改用户数据
          *
          * @param {Object} data - post过来的数据

+ 12 - 2
app/view/dashboard/index.ejs

@@ -7,13 +7,23 @@
             <h4>需要你处理</h4>
         </div>
         <div class="c-body">
-            内容
+            <ul class="list-unstyled m-0">
+                <% for (const t of auditTenders) { %>
+                <li class="media">
+                    <img class="mr-3" src="public/images/avatar.png">
+                    <div class="media-body">
+                        <span class="pull-right text-muted"><%- t.begin_time.toLocaleString() %></span>
+                        <h5 class="mt-0"><%- ctx.session.sessionUser.name %><small class="ml-3 text-muted">监理</small></h5>
+                        <p><a href="/ledger/explode?tenderId=<%- t.id %>"><%- t.name %></a> 台帐 需要您 <a href="/ledger/audit?tenderId=<%- t.id %>">审批</a>。</p>
+                    </div>
+                </li>
+                <% } %>
+            </ul>
         </div>
         <div class="c-header mt-4">
             <h4>需要你关注</h4>
         </div>
         <div class="c-body">
-            内容
         </div>
     </div>
 </div>

+ 105 - 0
app/view/ledger/audit.ejs

@@ -0,0 +1,105 @@
+<div class="panel-content">
+    <div class="panel-title fluid">
+        <div class="title-main d-flex justify-content-between">
+            <div></div>
+            <div>
+                <a href="#sp-done" data-toggle="modal" data-target="#sp-done" class="btn btn-success btn-sm pull-right">审批通过</a>
+                <a href="#sp-back" data-toggle="modal" data-target="#sp-back" class="btn btn-warning btn-sm pull-right">审批退回</a>
+            </div>
+        </div>
+    </div>
+    <div class="content-wrap row pr-46">
+        <div class="c-header p-0 col-12">
+        </div>
+        <!--核心内容(两栏)-->
+        <div class="row w-100 sub-content">
+            <!--左栏-->
+            <div class="c-body col-12">
+                <div class="sjs-height-1" id="ledger-spread">
+                </div>
+            </div>
+            <!--右栏-->
+            <div class="c-body col-0" style="display: none;">
+                <div class="tab-content">
+                    <div id="deal-bills" class="tab-pane">
+                        <div class="sjs-height-1" id="deal-bills-spread">
+                        </div>
+                    </div>
+                    <div id="opinion" class="tab-pane">
+                        <div class="side-bar">
+                            <select class="form-control form-control-sm"><option>王五</option><option>张三</option></select>
+                        </div>
+                        <div class="sjs-height-2" style="overflow:auto">
+                            <div class="card my-3">
+                                <div class="card-header">添加清单意见</div>
+                                <div class="card-body">
+                                    <!--无清单 提示添加清单-->
+                                    <div class="alert alert-primary  px-2 py-1" role="alert">
+                                        台帐清单右键,添加至"清单意见"
+                                    </div>
+                                    <!--已添加清单-->
+                                    <table class="table table-sm table-bordered mb-2">
+                                        <tr><td>行2</td><td>203-1-1</td><td>挖土方</td><td><a href="#">移除</a></td></tr>
+                                    </table>
+                                    <textarea class="form-control" placeholder="审批内容"></textarea>
+                                    <div class="alert alert-warning px-2 py-1 mt-2 mb-1" role="alert">
+                                        请填写审批内容。
+                                    </div>
+                                    <div class="alert alert-warning px-2 py-1 mt-2 mb-1" role="alert">
+                                        至少需要一条清单。
+                                    </div>
+                                    <button class="btn btn-sm btn-primary mt-1">添加</button>
+                                </div>
+                            </div>
+                            <legend>张三 清单意见</legend>
+                            <div class="card my-3">
+                                <div class="card-body">
+                                    <table class="table table-sm table-bordered mb-2">
+                                        <tr><td>行2</td><td>203-1-1</td><td>挖土方</td></tr>
+                                    </table>
+                                    <p>审批意见</p>
+                                    <span class="text-muted">2018-05-31 21:18:20</span>
+                                </div>
+                            </div>
+                            <div class="card my-3">
+                                <div class="card-body">
+                                    <table class="table table-sm table-bordered mb-2">
+                                        <tr><td>行3</td><td>203-1-2</td><td>挖土方</td></tr>
+                                    </table>
+                                    <p>审批意见</p>
+                                    <span class="text-muted">2018-05-23 13:11:10</span>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <!--右侧菜单-->
+        <div class="side-menu">
+            <!--右侧菜单-->
+            <ul class="nav flex-column right-nav">
+                <li class="nav-item">
+                    <a class="nav-link" href="javascript: void(0);" role="tab" content="#deal-bills">签约清单</a>
+                </li>
+                <li class="nav-item">
+                    <a class="nav-link" href="javascript: void(0);" role="tab" content="#opinion">清单意见</a>
+                </li>
+            </ul>
+        </div>
+    </div>
+</div>
+<script type="text/javascript">
+    let ledger = '<%- ledger %>';
+    ledger = JSON.parse(ledger);
+    let ledgerSpreadSetting = '<%- ledgerSpreadSetting %>';
+    ledgerSpreadSetting = JSON.parse(ledgerSpreadSetting);
+    ledgerSpreadSetting.readOnly = <%- readOnly %>;
+</script>
+<script src="/public/js/spreadjs/sheets/gc.spread.sheets.all.10.0.1.min.js"></script>
+<script>
+    GC.Spread.Sheets.LicenseKey = "559432293813965#A0y3iTOzEDOzkjMyMDN9UTNiojIklkI1pjIEJCLi4TPB9mM5AFNTd4cvZ7SaJUVy3CWKtWYXx4VVhjMpp7dYNGdx2ia9sEVlZGOTh7NRlTUwkWR9wEV4gmbjBDZ4ElR8N7cGdHVvEWVBtCOwIGW0ZmeYVWVr3mI0IyUiwCMzETN8kzNzYTM0IicfJye&Qf35VfiEzRwEkI0IyQiwiIwEjL6ByUKBCZhVmcwNlI0IiTis7W0ICZyBlIsIyNyMzM5ADI5ADNwcTMwIjI0ICdyNkIsIibj9SbvNmL4N7bjRnch56ciojIz5GRiwiI8+Y9sWY9QmZ0Jyp96uL9v6L0wap9biY9qiq95q197Wr9g+89iojIh94Wiqi";
+</script>
+<script src="/public/js/spreadjs_rela/spreadjs_zh<%= min %>.js"></script>
+<script src="/public/js/path_tree<%= min %>.js"></script>
+<script src="/public/js/ledger_audit.js"></script>

+ 76 - 0
app/view/ledger/audit_modal.ejs

@@ -0,0 +1,76 @@
+<!--审批通过-->
+<div class="modal fade" id="sp-done" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <form class="modal-content" action="/ledger/audit/check" method="post">
+            <div class="modal-header">
+                <h5 class="modal-title">审批通过</h5>
+            </div>
+            <div class="modal-body">
+                <div class="card mt-3">
+                    <ul class="list-group list-group-flush">
+                        <% for (let i = 0, iLen = auditors.length; i < iLen; i++) { %>
+                        <li class="list-group-item" auditorId="<%- auditors[i].audit_id %>">
+                            <% if (auditors[i].status !== auditConst.status.uncheck) { %>
+                            <span class="<%- auditConst.statusClass[auditors[i].status] %> pull-right"><%- auditConst.statusString[auditors[i].status] %></span>
+                            <% } %>
+                            <h5 class="card-title"><%- auditors[i].audit_order %> <%- auditors[i].name %> <small class="text-muted"><%- auditors[i].role %></small></h5>
+                            <% if (auditors[i].status === auditConst.status.checked) { %>
+                            <p class="card-text"><%- auditors[i].opinion %> <%- auditors[i].end_time ? auditors[i].end_time.toLocaleString() : '' %></p>
+                            <% } else if (auditors[i].status === auditConst.status.checking) { %>
+                            <div class="form-group">
+                                <label>审批意见<b class="text-danger">*</b></label>
+                                <textarea class="form-control" name="opinion"></textarea>
+                            </div>
+                            <% } %>
+                        </li>
+                        <% } %>
+                    </ul>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
+                <input type="hidden" name="_csrf" value="<%= ctx.csrf %>" />
+                <input type="hidden" name="checkType" value="<%= auditConst.status.checked %>" />
+                <button type="submit" class="btn btn-success" >确认通过</button>
+            </div>
+        </form>
+    </div>
+</div>
+<!--审批退回-->
+<div class="modal fade" id="sp-back" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <form class="modal-content" action="/ledger/audit/check" method="post">
+            <div class="modal-header">
+                <h5 class="modal-title">审批退回</h5>
+            </div>
+            <div class="modal-body">
+                <div class="card mt-3">
+                    <ul class="list-group list-group-flush">
+                        <% for (let i = 0, iLen = auditors.length; i < iLen; i++) { %>
+                        <li class="list-group-item" auditorId="<%- auditors[i].audit_id %>">
+                            <% if (auditors[i].status !== auditConst.status.uncheck) { %>
+                            <span class="<%- auditConst.statusClass[auditors[i].status] %> pull-right"><%- auditConst.statusString[auditors[i].status] %></span>
+                            <% } %>
+                            <h5 class="card-title"><%- auditors[i].audit_order %> <%- auditors[i].name %> <small class="text-muted"><%- auditors[i].role %></small></h5>
+                            <% if (auditors[i].status === auditConst.status.checked) { %>
+                            <p class="card-text"><%- auditors[i].opinion %> <%- auditors[i].end_time ? auditors[i].end_time.toLocaleString() : '' %></p>
+                            <% } else if (auditors[i].status === auditConst.status.checking) { %>
+                            <div class="form-group">
+                                <label>审批意见<b class="text-danger">*</b></label>
+                                <textarea class="form-control" name="opinion"></textarea>
+                            </div>
+                            <% } %>
+                        </li>
+                        <% } %>
+                    </ul>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
+                <input type="hidden" name="_csrf" value="<%= ctx.csrf %>" />
+                <input type="hidden" name="checkType" value="<%= auditConst.status.checkNo %>" />
+                <button type="submit" class="btn btn-warning" >确认退回</button>
+            </div>
+        </form>
+    </div>
+</div>

+ 48 - 4
app/view/ledger/explode.ejs

@@ -42,8 +42,20 @@
                 </div>
             </div>
             <div>
-                <a href="#" class="btn btn-primary btn-sm pull-right">上报审批</a>
-                <a href="#" class="btn btn-outline-secondary btn-sm pull-right disabled">报审完成</a>
+                <% if (tenderData.ledger_status === auditConst.status.checkNo) { %>
+                    <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-warning btn-sm pull-right text-dark">报审退回</a>
+                <% } else if (tenderData.ledger_status === auditConst.status.checking) { %>
+                    <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-warning btn-sm pull-right text-dark">审批中</a>
+                <% } else if (tenderData.ledger_status === auditConst.status.checked) { %>
+                    <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-secondary btn-sm pull-right">审批完成</a>
+                <% } %>
+                <% if (ctx.session.sessionUser.accountId === tenderData.user_id) { %>
+                    <% if (tenderData.ledger_status === auditConst.status.uncheck) { %>
+                        <a href="#sub-sp" data-toggle="modal" data-target="#sub-sp" class="btn btn-primary btn-sm pull-right">上报审批</a>
+                    <% } else if (tenderData.ledger_status === auditConst.status.checkNo) { %>
+                        <a href="#sub-sp" data-toggle="modal" data-target="#sub-sp2" class="btn btn-primary btn-sm pull-right">重新上报</a>
+                    <% } %>
+                <% } %>
             </div>
         </div>
     </div>
@@ -73,6 +85,32 @@
                         <div id="deal-bills-spread" class="sjs-height-2">
                         </div>
                     </div>
+                    <div id="opinion" class="tab-pane">
+                        <div class="side-bar">
+                            <select class="form-control form-control-sm"><option>王五</option><option>张三</option></select>
+                        </div>
+                        <div class="sjs-height-2" style="overflow:auto">
+                            <legend>张三 清单意见</legend>
+                            <div class="card my-3">
+                                <div class="card-body">
+                                    <table class="table table-sm table-bordered mb-2">
+                                        <tr><td>行2</td><td>203-1-1</td><td>挖土方</td></tr>
+                                    </table>
+                                    <p>审批意见</p>
+                                    <span class="text-muted">2018-05-31 21:18:20</span>
+                                </div>
+                            </div>
+                            <div class="card my-3">
+                                <div class="card-body">
+                                    <table class="table table-sm table-bordered mb-2">
+                                        <tr><td>行3</td><td>203-1-2</td><td>挖土方</td></tr>
+                                    </table>
+                                    <p>审批意见</p>
+                                    <span class="text-muted">2018-05-23 13:11:10</span>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
                 </div>
             </div>
         </div>
@@ -88,6 +126,9 @@
                 <li class="nav-item">
                     <a class="nav-link" content="#deal-bills" href="javascript: void(0);">签约清单</a>
                 </li>
+                <li class="nav-item">
+                    <a class="nav-link" content="#opinion" href="javascript: void(0);">清单意见</a>
+                </li>
             </ul>
         </div>
     </div>
@@ -95,12 +136,15 @@
 <script type="text/javascript">
     let ledger = '<%- ledger %>';
     ledger = JSON.parse(ledger);
+    let ledgerSpreadSetting = '<%- ledgerSpreadSetting %>';
+    ledgerSpreadSetting = JSON.parse(ledgerSpreadSetting);
+    ledgerSpreadSetting.readOnly = <%- tenderData.user_id !== ctx.session.sessionUser.accountId || tenderData.ledger_status === auditConst.status.checking || tenderData.ledger_status === auditConst.status.checked %>;
 </script>
 <script src="/public/js/spreadjs/sheets/gc.spread.sheets.all.10.0.1.min.js"></script>
 <script>
     GC.Spread.Sheets.LicenseKey = "559432293813965#A0y3iTOzEDOzkjMyMDN9UTNiojIklkI1pjIEJCLi4TPB9mM5AFNTd4cvZ7SaJUVy3CWKtWYXx4VVhjMpp7dYNGdx2ia9sEVlZGOTh7NRlTUwkWR9wEV4gmbjBDZ4ElR8N7cGdHVvEWVBtCOwIGW0ZmeYVWVr3mI0IyUiwCMzETN8kzNzYTM0IicfJye&Qf35VfiEzRwEkI0IyQiwiIwEjL6ByUKBCZhVmcwNlI0IiTis7W0ICZyBlIsIyNyMzM5ADI5ADNwcTMwIjI0ICdyNkIsIibj9SbvNmL4N7bjRnch56ciojIz5GRiwiI8+Y9sWY9QmZ0Jyp96uL9v6L0wap9biY9qiq95q197Wr9g+89iojIh94Wiqi";
 </script>
-<script src="/public/js/spreadjs_rela/spreadjs_zh.js"></script>
+<script src="/public/js/spreadjs_rela/spreadjs_zh<%= min %>.js"></script>
 <script src="/public/js/spreadjs_rela/extend_celltype.js"></script>
-<script src="/public/js/path_tree.js"></script>
+<script src="/public/js/path_tree<%= min %>.js"></script>
 <script src="/public/js/ledger.js"></script>

+ 120 - 0
app/view/ledger/explode_modal.ejs

@@ -59,3 +59,123 @@
         </div>
     </div>
 </div>
+<% if (tenderData.ledger_status === auditConst.status.uncheck) { %>
+<!--上报审批-->
+<div class="modal fade" id="sub-sp" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">上报审批</h5>
+            </div>
+            <div class="modal-body">
+                <div class="form-group">
+                    <label>搜索审批人</label>
+                    <div class="input-group">
+                        <input class="form-control" placeholder="请输入姓名进行检索" type="text" id="searchName">
+                        <div class="input-group-append">
+                            <button class="btn btn-outline-secondary" type="button" id="searchAccount"><i class="fa fa-search"></i></button>
+                        </div>
+                    </div>
+                </div>
+                <div class="card border-primary" id="searchResult" style="display: none">
+                    <div class="card-body">
+                        <h5 class="card-title">
+                            <a href="javascript: void(0)" class="btn btn-primary btn-sm pull-right" id="addAuditor">添加</a>
+                            <span>张三</span>
+                        </h5>
+                        <h6 class="card-subtitle mb-2 text-muted">监理</h6>
+                        <p class="card-text">XXXXX公司</p>
+                    </div>
+                </div>
+                <div class="card mt-3">
+                    <div class="card-header">
+                        审批流程
+                    </div>
+                    <ul class="list-group list-group-flush" id="auditors">
+                        <% for (let i = 0, iLen = auditors.length; i < iLen; i++) { %>
+                        <li class="list-group-item" auditorId="<%- auditors[i].audit_id %>">
+                            <a href="javascript: void(0)" class="text-danger pull-right">移除</a>
+                            <%- auditors[i].audit_order %> <%- auditors[i].name %>
+                            <small class="text-muted"><%- auditors[i].role %></small>
+                        </li>
+                        <% } %>
+                    </ul>
+                </div>
+            </div>
+            <form class="modal-footer" method="post" action="/ledger/audit/start">
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
+                <input type="hidden" name="_csrf" value="<%= ctx.csrf %>">
+                <button class="btn btn-primary" type="submit">确认上报</button>
+            </form>
+        </div>
+    </div>
+</div>
+<% } %>
+<% if (tenderData.ledger_status === auditConst.status.checkNo) { %>
+<!--重新审批-->
+<div class="modal fade" id="sub-sp2" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">重新上报</h5>
+            </div>
+            <div class="modal-body">
+                <div class="alert alert-primary" role="alert">
+                    重新上报后,将由 <%- auditors[0].name %> 继续审批
+                </div>
+                <div class="card mt-3">
+                    <div class="card-header">
+                        审批流程
+                    </div>
+                    <ul class="list-group list-group-flush">
+                        <% for (let i = 0, iLen = auditors.length; i < iLen; i++) { %>
+                        <li class="list-group-item" auditorId="<%- auditors[i].audit_id %>">
+                            <% if (auditors[i].status !== auditConst.status.uncheck) { %>
+                            <span class="<%- auditConst.statusClass[auditors[i].status] %> pull-right"><%- auditConst.statusString[auditors[i].status] %></span>
+                            <% } %>
+                            <h5 class="card-title"><%- auditors[i].audit_order %> <%- auditors[i].name %> <small class="text-muted"><%- auditors[i].role %></small></h5>
+                            <p class="card-text"><%- auditors[i].opinion %> <%- auditors[i].end_time ? auditors[i].end_time.toLocaleString() : '' %></p>
+                        </li>
+                        <% } %>
+                    </ul>
+                </div>
+            </div>
+            <form class="modal-footer" action="/ledger/audit/start" method="post">
+                <button type="submit" class="btn btn-primary">确认上报</button>
+                <input type="hidden" name="_csrf" value="<%= ctx.csrf %>">
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
+            </form>
+        </div>
+    </div>
+</div>
+<% } %>
+<% if (tenderData.status !== auditConst.status.unCheck) { %>
+<!--审批流程/结果-->
+<div class="modal fade" id="sp-list" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">审批流程</h5>
+            </div>
+            <div class="modal-body">
+                <div class="card mt-3">
+                    <ul class="list-group list-group-flush">
+                        <% for (let i = 0, iLen = auditors.length; i < iLen; i++) { %>
+                        <li class="list-group-item" auditorId="<%- auditors[i].audit_id %>">
+                            <% if (auditors[i].status !== auditConst.status.uncheck) { %>
+                            <span class="<%- auditConst.statusClass[auditors[i].status] %> pull-right"><%- auditConst.statusString[auditors[i].status] %></span>
+                            <% } %>
+                            <h5 class="card-title"><%- auditors[i].audit_order %> <%- auditors[i].name %> <small class="text-muted"><%- auditors[i].role %></small></h5>
+                            <p class="card-text"><%- auditors[i].opinion %> <%- auditors[i].end_time ? auditors[i].end_time.toLocaleString() : '' %></p>
+                        </li>
+                        <% } %>
+                    </ul>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
+            </div>
+        </div>
+    </div>
+</div>
+<% } %>

+ 3 - 0
config/config.default.js

@@ -66,5 +66,8 @@ module.exports = appInfo => {
         fileSize: '10mb',
     };
 
+    // 是否压缩替换前端js
+    config.min = true;
+
     return config;
 };

+ 3 - 0
config/config.local.js

@@ -60,5 +60,8 @@ module.exports = appInfo => {
         fileSize: '10mb',
     };
 
+    // 是否压缩替换前端js
+    config.min = true;
+
     return config;
 };

+ 4 - 0
config/config.qa.js

@@ -40,6 +40,7 @@ module.exports = appInfo => {
             db: '0',
         },
         agent: true,
+        version: '1.0.0',
     };
 
     // geetest验证码key
@@ -60,5 +61,8 @@ module.exports = appInfo => {
         fileSize: '10mb',
     };
 
+    // 是否压缩替换前端js
+    config.min = true;
+
     return config;
 };

+ 2 - 0
package.json

@@ -18,6 +18,8 @@
     "node-xlsx": "^0.12.0",
     "stream-to-array": "^2.3.0",
     "ueditor": "^1.2.3",
+    "uglify-es": "^3.3.9",
+    "uglify-js": "^3.3.27",
     "xmlreader": "^0.2.3"
   },
   "devDependencies": {

+ 0 - 2
test/app/service/ledger.test.js

@@ -993,8 +993,6 @@ describe('test/app/service/ledger.test.js', () => {
         ]
         // 选中1-1-3(id=14)
         const result = yield ctx.service.ledger.batchInsertChild(testTenderId, 14, batchData);
-
-        console.log(result.create);
         assert(result.create.length === 6);
 
         assert(result.update.length === 3);

+ 51 - 0
test/app/service/ledger_audit.test.js

@@ -0,0 +1,51 @@
+'use strict';
+
+/**
+ * 台账审批流程单元测试
+ *
+ * @author Mai
+ * @date 2018/5/25
+ * @version
+ */
+
+const { app, assert } = require('egg-mock/bootstrap');
+const testTenderId = 3, testProjectId = 17;
+
+describe('test/app/service/ledger_audit.test.js', () => {
+    let times = 1;
+    // 清理旧测试数据
+    it('clear history test data', function* () {
+        const ctx = app.mockContext();
+        const result = yield ctx.service.ledgerAudit.db.delete(ctx.service.ledgerAudit.tableName, { tender_id: testTenderId });
+        assert(result.affectedRows >= 0);
+    });
+    // 新增审核人
+    it('test addAuditor', function* () {
+        const ctx = app.mockContext();
+        const result = yield ctx.service.ledgerAudit.addAuditor(testTenderId, 11, times);
+        assert(result);
+    });
+    // 查询审核人
+    it('test getAuditor', function* () {
+        const ctx = app.mockContext();
+        const result = yield ctx.service.ledgerAudit.getAuditor(testTenderId, 11, times);
+        assert(result);
+        assert(result.audit_order === 1);
+        assert(result.name === 'olymcai');
+    });
+    // 获取台账审核人列表
+    it('test getAuditors', function* () {
+        const ctx = app.mockContext();
+        const result = yield ctx.service.ledgerAudit.getAuditors(testTenderId, times);
+        assert(result);
+        assert(result.length === 1);
+        assert(result[0].audit_order === 1);
+        assert(result[0].name === 'olymcai');
+    });
+    // 获取New Order
+    it('test getNewOrder', function* () {
+        const ctx = app.mockContext();
+        const result = yield ctx.service.ledgerAudit.getNewOrder(testTenderId, times);
+        assert(result === 2);
+    });
+});