Browse Source

多人协同功能 第一版

laiguoran 4 years ago
parent
commit
9b92307ead

+ 10 - 0
app/const/schedule.js

@@ -0,0 +1,10 @@
+'use strict';
+
+const plan_mode = {
+    tp: 1,
+    gcl: 2,
+};
+
+module.exports = {
+    plan_mode,
+};

+ 55 - 4
app/controller/schedule_controller.js

@@ -10,19 +10,27 @@
 
 const moment = require('moment');
 const measureType = require('../const/tender').measureType;
+const scheduleConst = require('../const/schedule');
 const billsPosConvert = require('../lib/bills_pos_convert');
 const _ = require('lodash');
 
 module.exports = app => {
     class ScheduleController extends app.BaseController {
+
+        async _getSelectedLedgerList(ctx) {
+            const scheduleLedgerList = await ctx.service.scheduleLedger.getAllDataByCondition({ where: { tid: ctx.tender.id } });
+            return _.map(scheduleLedgerList, 'ledger_id');
+        }
+
         async index(ctx) {
             try {
                 const renderData = {
                     tender: ctx.tender.data,
                     tenderMenu: this.menu.tenderMenu,
+                    scheduleLedgerList: await this._getSelectedLedgerList(ctx),
                     preUrl: '/tender/' + ctx.tender.id,
                 };
-                await this.layout('schedule/index.ejs', renderData);
+                await this.layout('schedule/index.ejs', renderData, 'schedule/modal.ejs');
             } catch (err) {
                 this.log(err);
                 ctx.redirect(this.menu.menu.dashboard.url);
@@ -31,15 +39,31 @@ module.exports = app => {
 
         async ledger(ctx) {
             const tender = ctx.tender;
-            const scheduleLedgerList = await ctx.service.scheduleLedger.getAllDataByCondition({ where: { tid: tender.id } });
             const renderData = {
                 tender: tender.data,
                 tenderInfo: tender.info,
                 measureType,
-                scheduleLedgerList: _.map(scheduleLedgerList, 'ledger_id'),
+                scheduleLedgerList: await this._getSelectedLedgerList(ctx),
                 jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.schedule.ledger),
             };
-            await this.layout('schedule/ledger.ejs', renderData, 'schedule/ledger_modal.ejs');
+            await this.layout('schedule/ledger.ejs', renderData);
+        }
+
+        async plan(ctx) {
+            const tender = ctx.tender;
+            const schedule = await ctx.service.schedule.getDataByCondition({ tid: tender.id });
+            const scheduleMonth = await ctx.service.scheduleMonth.getAllDataByCondition({ tid: tender.id });
+            const renderData = {
+                tender: tender.data,
+                tenderInfo: tender.info,
+                schedule,
+                scheduleMonth,
+                measureType,
+                mode: scheduleConst.plan_mode,
+                scheduleLedgerList: await this._getSelectedLedgerList(ctx),
+                jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.schedule.plan),
+            };
+            await this.layout('schedule/plan.ejs', renderData, 'schedule/plan_modal.ejs');
         }
 
         /**
@@ -79,6 +103,33 @@ module.exports = app => {
                 ctx.body = { err: 1, msg: err.toString(), data: [] };
             }
         }
+
+        /**
+         * 计划进度计算方式提交(Ajax)
+         *
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async savePlan(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const responseData = {
+                    err: 0,
+                    msg: '',
+                    data: {},
+                };
+                switch (data.type) {
+                    case 'mode':
+                        responseData.data = await ctx.service.schedule.saveMode(data.postData);
+                        break;
+                    default: throw '参数有误';
+                }
+                ctx.body = responseData;
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+        }
     }
 
     return ScheduleController;

+ 42 - 2
app/controller/tender_controller.js

@@ -16,6 +16,8 @@ const auditConst = require('../const/audit');
 const shenpiConst = require('../const/shenpi');
 const accountGroup = require('../const/account_group').group;
 const accountPermission = require('../const/account_permission');
+const measureType = require('../const/tender').measureType;
+const billsPosConvert = require('../lib/bills_pos_convert');
 
 module.exports = app => {
 
@@ -796,14 +798,22 @@ module.exports = app => {
                 });
             }
             const categoryData = await ctx.service.category.getAllCategory(ctx.session.sessionProject.id);
+            // 是否修订中
+            const lastRevise = await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id);
+            const revising = (lastRevise && lastRevise.status !== auditConst.revise.status.checked) || false;
+
             const renderData = {
                 shenpi: shenpiConst,
                 accountList,
                 accountGroup: accountGroupList,
                 tenders,
                 categoryData,
+                auditConst,
+                revising,
+                measureType,
+                jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.tender.shenpi),
             };
-            await this._list('tender/shenpi.ejs', renderData, 'tender/shenpi_modal.ejs');
+            await this.layout('tender/shenpi.ejs', renderData, 'tender/shenpi_modal.ejs');
         }
 
         async saveTenderInfoShenpi(ctx) {
@@ -828,6 +838,10 @@ module.exports = app => {
                 // } else {
                 const postData = ctx.tender.info.shenpi;
                 postData[data.code] = data.status;
+                if (data.code === shenpiConst.sp_lc[shenpiConst.sp_type.stage - 1].code) {
+                    const status = parseInt(data.status) === shenpiConst.sp_status.gdspl ? 1 : 0;
+                    await ctx.service.ledgerCooperation.changeAllStatus(status);
+                }
                 // }
                 // console.log(postData);
                 await ctx.service.tenderInfo.saveTenderInfo(ctx.tender.id, { shenpi: postData });
@@ -854,6 +868,7 @@ module.exports = app => {
                 if (ctx.session.sessionUser.is_admin === 0) {
                     throw '你没有权限修改审批流程';
                 }
+                let info = '';
                 switch (data.type) {
                     case 'add':
                         const result = await ctx.service.shenpiAudit.addAudit(data);
@@ -870,9 +885,12 @@ module.exports = app => {
                     case 'copy2os':
                         await ctx.service.shenpiAudit.copyAudit2otherShenpi(data);
                         break;
+                    case 'pwd':
+                        info = await ctx.service.ledgerCooperation.save(data);
+                        break;
                     default:break;
                 }
-                ctx.body = { err: 0, msg: '' };
+                ctx.body = { err: 0, msg: '', data: info };
             } catch (err) {
                 this.log(err);
                 ctx.body = this.ajaxErrorBody(err, '保存审批流程设置失败');
@@ -894,6 +912,28 @@ module.exports = app => {
             }
         }
 
+        /**
+         * 获取部位明细数据(Ajax)
+         *
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async loadLedgerData(ctx) {
+            try {
+                const ledgerData = await ctx.service.ledger.getData(ctx.tender.id);
+                const posData = ctx.tender.data.measure_type === measureType.tz.value
+                    ? await ctx.service.pos.getPosData({ tid: ctx.tender.id }) : [];
+                const convert = new billsPosConvert(ctx);
+                convert.loadData(ledgerData, posData, []);
+                const result = await convert.convert();
+                const ledgerCooperationList = await ctx.service.ledgerCooperation.getAllDataByCondition({ where: { tid: ctx.tender.id, status: 1 } });
+                ctx.body = { err: 0, msg: '', data: { ledgerList: result, ledgerCooperationList } };
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: [] };
+            }
+        }
+
         async billsTag(ctx) {
             try {
                 const data = JSON.parse(ctx.request.body.data);

+ 1 - 1
app/middleware/url_parse.js

@@ -22,7 +22,7 @@ module.exports = options => {
         this.urlInfo = urlInfo;
         // 防止为空
         this.request.headers.referer = this.request.headers.referer === undefined ? '/dashboard' : this.request.headers.referer;
-        // 清空原Message
+        // 清空原Message---有bug,不应该在有message未展示前被清除
         if (this.session) {
             this.session.message = null;
         }

+ 19 - 1
app/public/js/schedule_ledger.js

@@ -176,7 +176,25 @@ $(function () {
                 window.location.reload();
             },1500);
         });
-    })
+    });
+
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        key: 'menu.1.0.0',
+        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            ledgerSpread.refresh();
+            autoFlashHeight();
+        }
+    });
 
 
     // // 显示层次

+ 155 - 0
app/public/js/schedule_plan.js

@@ -0,0 +1,155 @@
+/**
+ * 进度台账相关js
+ *
+ * @author Ellisran
+ * @date 2020/11/6
+ * @version
+ */
+function getTenderId() {
+    return window.location.pathname.split('/')[2];
+}
+$(function () {
+    autoFlashHeight();
+    if(schedule && !schedule.mode) {
+        $('#mode').modal('show');
+    }
+    // 初始化台账
+    const ledgerSpread = SpreadJsObj.createNewSpread($('#ledger-spread')[0]);
+    const treeSetting = {
+        id: 'ledger_id',
+        pid: 'ledger_pid',
+        order: 'order',
+        level: 'level',
+        rootId: -1,
+        fullPath: 'full_path',
+        calcFields: ['total_price']
+        //treeCacheKey: 'ledger_bills_fold' + '_' + getTenderId(),
+        // markFoldKey: 'bills-fold',
+        // markFoldSubKey: window.location.pathname.split('/')[2],
+    };
+    treeSetting.calcFun = function (node) {
+        node.dgn_price = ZhCalc.round(ZhCalc.div(node.total_price, node.dgn_qty1), 2);
+    };
+    const ledgerTree = createNewPathTree('base', treeSetting);
+
+    const ledgerSpreadSetting = {
+        cols: [
+            {title: '编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 145, formatter: '@', readOnly: true, cellType: 'tree'},
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 185, formatter: '@', readOnly: true},
+            {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 50, formatter: '@', readOnly: true},
+            {title: '经济指标', colSpan: '1', rowSpan: '2', field: 'dgn_price', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+            {title: '总设计|工程量', colSpan: '2|1', rowSpan: '1|1', field: 'dgn_qty1', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+            {title: '|金额(万元)', colSpan: '|1', rowSpan: '|1', field: 'total_price', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        ],
+        emptyRows: 0,
+        headRows: 2,
+        headRowHeight: [25, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        // readOnly: true,
+        localCache: {
+            key: 'ledger-bills',
+            colWidth: true,
+        }
+    };
+
+    sjsSettingObj.setFxTreeStyle(ledgerSpreadSetting, sjsSettingObj.FxTreeStyle.jz);
+    if (thousandth) sjsSettingObj.setTpThousandthFormat(ledgerSpreadSetting);
+    SpreadJsObj.initSheet(ledgerSpread.getActiveSheet(), ledgerSpreadSetting);
+    SpreadJsObj.selChangedRefreshBackColor(ledgerSpread.getActiveSheet());
+
+    postData('/tender/' + getTenderId() + '/schedule/ledger/load', {}, function (data) {
+        console.log(data, selectedLedgerList);
+        const treeData = [];
+        for(const sl of selectedLedgerList) {
+            const one = _.find(data, { 'ledger_id' : sl });
+            treeData.push(one);
+        }
+        ledgerTree.loadDatas(treeData);
+        treeCalc.calculateAll(ledgerTree);
+        // console.log(ledgerTree);
+        SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), SpreadJsObj.DataType.Tree, ledgerTree);
+    }, null, true);
+
+    const ledgerSpreadObj = {
+        refreshTree: function (sheet, data) {
+            SpreadJsObj.massOperationSheet(sheet, function () {
+                const tree = sheet.zh_tree;
+                // 处理删除
+                if (data.delete) {
+                    data.delete.sort(function (x, y) {
+                        return y.deleteIndex - x.deleteIndex;
+                    });
+                    for (const d of data.delete) {
+                        sheet.deleteRows(d.deleteIndex, 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);
+                            }
+                        }
+                    }
+                }
+            });
+        },
+    };
+    // 进度计算方式选择
+    $('.mode-select').on('click', function () {
+        const _self = $(this);
+        postData(window.location.pathname + '/save', {type: 'mode', postData: $(this).data('mode')}, function (result) {
+            _self.addClass('disabled').attr('disabled', true);
+            _self.parents('.col-6').siblings('.col-6').find('button').removeClass('disabled').removeAttr('disabled');
+            $('#mode-tips').show();
+            $('#mode-cancel').show();
+            $('#mode').modal('hide');
+        })
+    });
+
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        key: 'menu.1.0.0',
+        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            ledgerSpread.refresh();
+            autoFlashHeight();
+        }
+    });
+});

+ 405 - 0
app/public/js/shenpi.js

@@ -291,6 +291,7 @@ $(document).ready(function () {
         };
         const _self = $(this);
         postData('/tender/' + cur_tenderid + '/shenpi/save', prop, function (data) {
+            $('#stage_cooperation').hide();
             if (this_status === sp_status.sqspr) {
                 _self.parents('.form-group').siblings('.lc-show').html('');
             } else if (this_status === sp_status.gdspl) {
@@ -305,6 +306,7 @@ $(document).ready(function () {
                 }
                 addhtml += '</ul>\n';
                 _self.parents('.form-group').siblings('.lc-show').html(addhtml);
+                $('#stage_cooperation').show();
             } else if (this_status === sp_status.gdzs) {
                 let addhtml = '<ul class="list-unstyled">\n' +
                     '                                        <li class="d-flex justify-content-start mb-3">\n' +
@@ -559,4 +561,407 @@ $(document).ready(function () {
             }, 1000)
         })
     });
+
+    const ledgerSpread = SpreadJsObj.createNewSpread($('#ledger-spread')[0]);
+    const treeSetting = {
+        id: 'ledger_id',
+        pid: 'ledger_pid',
+        order: 'order',
+        level: 'level',
+        rootId: -1,
+        fullPath: 'full_path',
+    };
+    const ledgerTree = createNewPathTree('base', treeSetting);
+
+    const ledgerSpreadSetting = {
+        cols: [
+            {title: '编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 185, formatter: '@', readOnly: true, cellType: 'tree'},
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 205, formatter: '@', readOnly: true},
+            {title: '密码', colSpan: '1', rowSpan: '2', field: 'pwd', hAlign: 0, width: 120, formatter: '@', getValue:'getValue.pwd', readOnly: 'readOnly.pwd'},
+        ],
+        emptyRows: 0,
+        headRows: 1,
+        headRowHeight: [25, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        // readOnly: true,
+        localCache: {
+            key: 'ledger-bills',
+            colWidth: true,
+        }
+    };
+
+    const ledgerCol = {
+        getValue: {
+            pwd: function (data) {
+                let txt = '';
+                if (data.is_leaf) {
+                    if (data.pwd && data.pwd !== '' && data.pwd !== null) {
+                        txt = data.pwd;
+                    } else {
+                        txt = '设置密码';
+                    }
+                }
+                return txt;
+            }
+        },
+        readOnly: {
+            pwd: function (data) {
+                return !data.is_leaf;
+            },
+        },
+    };
+
+    const ledgerSpreadObj = {
+        setFontColor: function(row = null) {
+            if(row) {
+                const value = ledgerSpread.getActiveSheet().getValue(row, 2);
+                if (value === '设置密码') {
+                    ledgerSpread.getActiveSheet().getCell(row, 2).foreColor('#007bff');
+                } else {
+                    ledgerSpread.getActiveSheet().getCell(row, 2).foreColor('#000');
+                }
+            } else {
+                const rowCount = ledgerSpread.getActiveSheet().getRowCount();
+                for(let i = 0; i < rowCount; i++){
+                    const value = ledgerSpread.getActiveSheet().getValue(i, 2);
+                    if (value === '设置密码') {
+                        ledgerSpread.getActiveSheet().getCell(i, 2).foreColor('#007bff');
+                    } else {
+                        ledgerSpread.getActiveSheet().getCell(i, 2).foreColor('#000');
+                    }
+                }
+            }
+        },
+        setAllRightPwd: function(uid) {
+            const selects = [];
+            for (const l of ledgerTree.datas) {
+                const coo = _.find(ledger_cooperation_list, { 'ledger_id': l.ledger_id, 'user_id': parseInt(uid) });
+                if (l.pwd && !coo) {
+                    delete l.pwd;
+                    selects.push(l);
+                } else if(coo) {
+                    l.pwd = coo.pwd;
+                    selects.push(l);
+                }
+            }
+            if(selects.length > 0) {
+                const refreshNode = ledgerTree.loadPostData({update: selects});
+                ledgerSpreadObj.refreshTree(ledgerSpread.getActiveSheet(), refreshNode);
+                ledgerSpreadObj.setFontColor();
+            }
+        },
+        refreshTree: function (sheet, data) {
+            SpreadJsObj.massOperationSheet(sheet, function () {
+                const tree = sheet.zh_tree;
+                // 处理删除
+                if (data.delete) {
+                    data.delete.sort(function (x, y) {
+                        return y.deleteIndex - x.deleteIndex;
+                    });
+                    for (const d of data.delete) {
+                        sheet.deleteRows(d.deleteIndex, 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);
+                            }
+                        }
+                    }
+                }
+            });
+        },
+        editStarting: function (e, info) {
+            if (info.sheet.zh_setting) {
+                const select = SpreadJsObj.getSelectObject(info.sheet);
+                const col = info.sheet.zh_setting.cols[info.col];
+                ledgerSpread.getActiveSheet().getCell(info.row, 2).foreColor('#000');
+                if(col.getValue(select) === '设置密码') {
+                    ledgerSpread.getActiveSheet().setValue(info.row, 2, '');
+                }
+            }
+        },
+        editEnded: function (e, info) {
+            if (info.sheet.zh_setting) {
+                const select = SpreadJsObj.getSelectObject(info.sheet);
+                const col = info.sheet.zh_setting.cols[info.col];
+                const validText = trimInvalidChar(info.editingText);
+                const user_id = parseInt($('#stage_audits').val());
+                const orgValue = select[col.field];
+                const reg = /^[0-9a-zA-Z]+$/;
+                if(validText !== '' && !reg.test(validText)) {
+                    toastr.error('不能输入非数字和字母的字符');
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    ledgerSpreadObj.setFontColor(info.row);
+                    return;
+                }
+                if((validText === '' && orgValue === undefined) || validText == orgValue) {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    ledgerSpreadObj.setFontColor(info.row);
+                    return;
+                }
+                select.pwd = validText;
+                const data = {
+                    type: 'pwd',
+                    ledger_id: select.ledger_id,
+                    user_id,
+                    pwd: validText,
+                };
+                postData('/tender/' + cur_tenderid + '/shenpi/audit/save', data, function (result) {
+                    const lcindex = _.findIndex(ledger_cooperation_list, { ledger_id: select.ledger_id, user_id });
+                    if(lcindex !== -1) {
+                        validText === '' ? ledger_cooperation_list.splice(lcindex, 1) : ledger_cooperation_list.splice(lcindex, 1, result);
+                    } else {
+                        ledger_cooperation_list.push(result);
+                    }
+                    setLeftTable(ledgerTree.datas, ledger_cooperation_list, user_id, $('#stage_audits option:selected').text());
+                    const refreshNode = ledgerTree.loadPostData({update: select});
+                    ledgerSpreadObj.refreshTree(info.sheet, refreshNode);
+                    ledgerSpreadObj.setFontColor();
+                });
+            }
+        },
+        deletePress: function (sheet) {
+            return;
+        },
+        clipboardPasted(e, info) {
+            // 禁止复制粘贴
+            SpreadJsObj.reLoadSheetHeader(ledgerSpread.getActiveSheet());
+            SpreadJsObj.reLoadSheetData(ledgerSpread.getActiveSheet());
+            return;
+        }
+    };
+
+    sjsSettingObj.setFxTreeStyle(ledgerSpreadSetting, sjsSettingObj.FxTreeStyle.jz);
+    if (thousandth) sjsSettingObj.setTpThousandthFormat(ledgerSpreadSetting);
+    SpreadJsObj.initSpreadSettingEvents(ledgerSpreadSetting, ledgerCol);
+    SpreadJsObj.initSheet(ledgerSpread.getActiveSheet(), ledgerSpreadSetting);
+    SpreadJsObj.selChangedRefreshBackColor(ledgerSpread.getActiveSheet());
+    ledgerSpread.bind(spreadNS.Events.EditStarting, ledgerSpreadObj.editStarting);
+    ledgerSpread.bind(spreadNS.Events.EditEnded, ledgerSpreadObj.editEnded);
+    SpreadJsObj.addDeleteBind(ledgerSpread, ledgerSpreadObj.deletePress);
+    ledgerSpread.bind(spreadNS.Events.ClipboardPasted, ledgerSpreadObj.clipboardPasted);
+    let ledger_data, ledger_cooperation_list = [];
+
+    // 多人协同
+    $('#cooperation').on('shown.bs.modal', function () {
+        // 执行一些动作...
+        // 更新新的多人协同表格信息
+        const newUidList = [];
+        $('.stage_div ul li').each(function (k, v) {
+            const uid = $(v).find('a').data('id');
+            if(uid) newUidList.push(uid);
+        });
+        const oldUidList = [];
+        $('#stage_audits option').each(function (k, v) {
+            const uid = parseInt($(v).val());
+            if(k !== 0) oldUidList.push(uid);
+        });
+        if (!_.isEqual(oldUidList, newUidList)) {
+            const yb = _.find(accountList, { 'id': cur_uid });
+            let newhtml = '<option value="' + yb.id + '">' + yb.name + '(原报)</option>';
+            if(newUidList.length > 0) {
+                for (const [i,id] of newUidList.entries()) {
+                    const audit = _.find(accountList, { 'id': id });
+                    newhtml += '<option value="' + audit.id + '">' + audit.name + '(' + transFormToChinese(i+1) + '审)</option>';
+                }
+            }
+            $('#stage_audits').html(newhtml);
+            if(ledger_data) {
+                setLeftTable(ledgerTree.datas, ledger_cooperation_list, cur_uid, yb.name + '(原报)');
+                ledgerSpreadObj.setAllRightPwd(cur_uid);
+            }
+        }
+        if(!ledger_data) {
+            postData('/tender/' + cur_tenderid + '/shenpi/ledger/load', {}, function (data) {
+                ledger_data = true;
+                const ledgerList = setRightData(data.ledgerList, data.ledgerCooperationList);
+                ledgerTree.loadDatas(ledgerList);
+                ledger_cooperation_list = data.ledgerCooperationList;
+                const yb = _.find(accountList, { 'id': cur_uid });
+                setLeftTable(ledgerList, ledger_cooperation_list, cur_uid, yb.name + '(原报)');
+                // treeCalc.calculateAll(ledgerTree);
+                console.log(ledgerTree);
+                SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), SpreadJsObj.DataType.Tree, ledgerTree);
+                ledgerSpreadObj.setFontColor();
+            }, null, true);
+        }
+        ledgerSpread.refresh();
+    });
+
+    $('#stage_audits').change(function () {
+        const uid = $(this).val();
+        const title = $("#stage_audits option:selected").text();
+        setLeftTable(ledgerTree.datas, ledger_cooperation_list, uid, title);
+        ledgerSpreadObj.setAllRightPwd(uid);
+    });
+
+    $('body').on('click', '.del-pwd', function () {
+        const ledger_id = parseInt($(this).data('lid'));
+        const user_id = parseInt($(this).data('uid'));
+        const data = {
+            type: 'pwd',
+            user_id,
+            ledger_id,
+            pwd: '',
+        };
+        postData('/tender/' + cur_tenderid + '/shenpi/audit/save', data, function (result) {
+            const lcindex = _.findIndex(ledger_cooperation_list, { ledger_id, user_id });
+            ledger_cooperation_list.splice(lcindex, 1);
+            setLeftTable(ledgerTree.datas, ledger_cooperation_list, user_id, $('#stage_audits option:selected').text());
+            const select = _.find(ledgerTree.datas, { ledger_id });
+            delete select.pwd;
+            const refreshNode = ledgerTree.loadPostData({update: select});
+            ledgerSpreadObj.refreshTree(ledgerSpread.getActiveSheet(), refreshNode);
+            ledgerSpreadObj.setFontColor();
+        });
+    });
+
+    $('body').on('click', '.edit-pwd', function () {
+        const pwd = $(this).data('pwd');
+        const html = `<div class="input-group input-group-sm">
+            <input type="text" class="form-control" value="${pwd}" placeholder="输入新密码" aria-describedby="button-${$(this).data('lid')}" style="width:50px">
+            <div class="input-group-append" id="button-${$(this).data('lid')}">
+                <button class="btn btn-outline-primary confirm-btn" data-pwd="${pwd}" data-lid="${$(this).data('lid')}" data-uid="${$(this).data('uid')}" type="button">确认</button>
+                <button class="btn btn-outline-secondary cancel-btn" data-pwd="${pwd}" data-lid="${$(this).data('lid')}" data-uid="${$(this).data('uid')}" type="button">取消</button>
+            </div>
+            </div>`;
+        $(this).parents('td').html(html);
+    });
+
+    $('body').on('click', '.cancel-btn', function () {
+        const html = `<p class="mb-0">${$(this).data('pwd')}</p><a href="javascript:void(0);" data-lid="${$(this).data('lid')}" data-uid="${$(this).data('uid')}" data-pwd="${$(this).data('pwd')}" class="edit-pwd">修改</a> <a href="javascript:void(0)" data-lid="${$(this).data('lid')}" data-uid="${$(this).data('uid')}" class="del-pwd text-danger">移除</a>`;
+        $(this).parents('td').html(html);
+    });
+
+    $('body').on('click', '.confirm-btn', function () {
+        const validText = $(this).parents('td').find('input').val();
+        const orgValue = $(this).data('pwd');
+        const ledger_id = parseInt($(this).data('lid'));
+        const user_id = parseInt($(this).data('uid'));
+        const reg = /^[0-9a-zA-Z]+$/;
+        if(!reg.test(validText)) {
+            toastr.error('不能输入非数字和字母的字符');
+            return;
+        }
+        if(validText == orgValue) {
+            const html = `<p class="mb-0">${$(this).data('pwd')}</p><a href="javascript:void(0);" data-lid="${$(this).data('lid')}" data-uid="${$(this).data('uid')}" data-pwd="${$(this).data('pwd')}" class="edit-pwd">修改</a> <a href="javascript:void(0)" data-lid="${$(this).data('lid')}" data-uid="${$(this).data('uid')}" class="del-pwd text-danger">移除</a>`;
+            $(this).parents('td').html(html);
+            return;
+        }
+        const data = {
+            type: 'pwd',
+            ledger_id,
+            user_id,
+            pwd: validText,
+        };
+        postData('/tender/' + cur_tenderid + '/shenpi/audit/save', data, function (result) {
+            const lcindex = _.findIndex(ledger_cooperation_list, { ledger_id, user_id });
+            ledger_cooperation_list.splice(lcindex, 1, result);
+            setLeftTable(ledgerTree.datas, ledger_cooperation_list, user_id, $('#stage_audits option:selected').text());
+            const select = _.find(ledgerTree.datas, { ledger_id });
+            select.pwd = validText;
+            const refreshNode = ledgerTree.loadPostData({update: select});
+            ledgerSpreadObj.refreshTree(ledgerSpread.getActiveSheet(), refreshNode);
+            ledgerSpreadObj.setFontColor();
+        });
+    });
+
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        key: 'menu.1.0.0',
+        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            autoFlashHeight();
+        }
+    });
 });
+
+function setRightData(datas, coolist) {
+    const newdatas = [];
+    const reg = /(^GD([a-zA-Z0-9\-]+))|(\d+)/;
+    for (const l of datas) {
+        if (reg.test(l.code) && l.level <= 4) {
+            if(l.level === 4 && l.is_leaf === false) {
+                l.is_leaf = true;
+            }
+            if(l.is_leaf) {
+                const coo = _.find(coolist, { 'ledger_id': l.ledger_id, 'user_id': cur_uid });
+                if(coo) {
+                    l.pwd = coo.pwd;
+                }
+            }
+            newdatas.push(l);
+        }
+    }
+    return newdatas;
+}
+
+function setLeftTable(ledgerList, coolist, uid, title) {
+    $('#stage_audit').text(title);
+    const showCooList = _.filter(coolist, { 'user_id': parseInt(uid) });
+    const removeList = [];
+    for (const sc of showCooList) {
+        const info = _.find(ledgerList, { 'ledger_id': sc.ledger_id });
+        if (info) {
+            sc.code = info.code;
+            sc.name = info.name;
+        } else {
+            removeList.push(sc);
+        }
+    }
+    if (removeList.length > 0) {
+        // 移除
+        _.pull(showCooList, ...removeList);
+        console.log(removeList);
+    }
+
+    let html = '';
+    for (const sc of showCooList) {
+        const pichtml = sc.sign_path ? `<img src="${sc.sign_path}" width="60"><a href="javascript:void(0);" class="d-inline-flex">更改</a>` : '<input type="file">';
+        html += `<tr>` +
+            `<td>${sc.code} ${sc.name}</td>` +
+            `<td><p class="mb-0">${sc.pwd}</p><a href="javascript:void(0);" data-lid="${sc.ledger_id}" data-uid="${sc.user_id}" data-pwd="${sc.pwd}" class="edit-pwd">修改</a> <a href="javascript:void(0)" data-lid="${sc.ledger_id}" data-uid="${sc.user_id}" class="del-pwd text-danger">移除</a></td>` +
+            `<td>${pichtml}</td>` +
+            `</tr>`;
+    }
+    $('#coo_table').html(html);
+}

+ 3 - 0
app/router.js

@@ -115,6 +115,7 @@ module.exports = app => {
     app.get('/tender/:id/shenpi', sessionAuth, tenderCheck, 'tenderController.shenpiSet');
     app.post('/tender/:id/shenpi/save', sessionAuth, tenderCheck, 'tenderController.saveTenderInfoShenpi');
     app.post('/tender/:id/shenpi/audit/save', sessionAuth, tenderCheck, 'tenderController.saveShenpiAudit');
+    app.post('/tender/:id/shenpi/ledger/load', sessionAuth, tenderCheck, uncheckTenderCheck, 'tenderController.loadLedgerData');
     app.post('/tender/:id/copy-setting', sessionAuth, tenderCheck, 'tenderController.copyTender');
 
     // 预付款
@@ -435,6 +436,8 @@ module.exports = app => {
     app.get('/tender/:id/schedule/ledger', sessionAuth, tenderCheck, uncheckTenderCheck, 'scheduleController.ledger');
     app.post('/tender/:id/schedule/ledger/load', sessionAuth, tenderCheck, uncheckTenderCheck, 'scheduleController.loadLedgerData');
     app.post('/tender/:id/schedule/ledger/save', sessionAuth, tenderCheck, uncheckTenderCheck, 'scheduleController.saveLedger');
+    app.get('/tender/:id/schedule/plan', sessionAuth, tenderCheck, uncheckTenderCheck, 'scheduleController.plan');
+    app.post('/tender/:id/schedule/plan/save', sessionAuth, tenderCheck, uncheckTenderCheck, 'scheduleController.savePlan');
 
     // 书签
     app.post('/tender/:id/ledger/tag', sessionAuth, tenderCheck, uncheckTenderCheck, 'tenderController.billsTag');

+ 54 - 0
app/service/ledger_cooperation.js

@@ -0,0 +1,54 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2018/6/1
+ * @version
+ */
+
+module.exports = app => {
+    class LedgerCooperation extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'ledger_cooperation';
+        }
+
+        async save(data) {
+            delete data.type;
+            data.tid = this.ctx.tender.id;
+            const info = await this.getDataByCondition({ tid: this.ctx.tender.id, ledger_id: data.ledger_id, user_id: data.user_id });
+            if (data.pwd === '' && info) {
+                await this.deleteById(info.id);
+            } else if (data.pwd !== '' && !info) {
+                const result = await this.db.insert(this.tableName, data);
+                data.id = result.insertId;
+            } else if (data.pwd !== '' && info) {
+                data.id = info.id;
+                await this.db.update(this.tableName, data);
+            }
+            return data;
+        }
+
+        async changeAllStatus(status) {
+            const options = {
+                where: {
+                    tid: this.ctx.tender.id,
+                },
+            };
+            const updateData = {
+                status,
+            };
+            return await this.db.update(this.ctx.service.ledgerCooperation.tableName, updateData, options);
+        }
+    }
+
+    return LedgerCooperation;
+};

+ 31 - 0
app/service/schedule.js

@@ -0,0 +1,31 @@
+'use strict';
+
+module.exports = app => {
+    class Schedule extends app.BaseService {
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'schedule';
+        }
+
+        async saveMode(data) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                const options = {
+                    where: {
+                        tid: this.ctx.tender.id,
+                    },
+                };
+                const updateData = {
+                    mode: data,
+                };
+                await transaction.update(this.tableName, updateData, options);
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+    }
+    return Schedule;
+};

+ 8 - 0
app/service/schedule_ledger.js

@@ -32,6 +32,14 @@ module.exports = app => {
                     }
                 }
                 if (insertDatas.length > 0) await transaction.insert(this.tableName, insertDatas);
+                // 判断是否已创建了形象进度表
+                const scheduleInfo = await this.ctx.service.schedule.getDataByCondition({ tid: this.ctx.tender.id });
+                if (!scheduleInfo) {
+                    const newSchedule = {
+                        tid: this.ctx.tender.id,
+                    };
+                    await transaction.insert(this.ctx.service.schedule.tableName, newSchedule);
+                }
                 await transaction.commit();
                 return true;
             } catch (err) {

+ 11 - 0
app/service/schedule_month.js

@@ -0,0 +1,11 @@
+'use strict';
+
+module.exports = app => {
+    class ScheduleMonth extends app.BaseService {
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'schedule_month';
+        }
+    }
+    return ScheduleMonth;
+};

+ 54 - 15
app/service/shenpi_audit.js

@@ -39,24 +39,63 @@ module.exports = app => {
         }
 
         async addAudit(data) {
-            const insertData = {
-                tid: this.ctx.tender.id,
-                sp_type: data.code,
-                sp_status: data.status,
-                audit_id: data.audit_id,
-            };
-            const result = await this.db.insert(this.tableName, insertData);
-            return result.effectRows === 1;
+            const transaction = await this.db.beginTransaction();
+            try {
+                if (parseInt(data.code) === shenpiConst.sp_type.stage && parseInt(data.status) === shenpiConst.sp_status.gdspl) {
+                    const options = {
+                        where: {
+                            tid: this.ctx.tender.id,
+                            user_id: data.audit_id,
+                        },
+                    };
+                    const updateData = {
+                        status: 1,
+                    };
+                    await transaction.update(this.ctx.service.ledgerCooperation.tableName, updateData, options);
+                }
+                const insertData = {
+                    tid: this.ctx.tender.id,
+                    sp_type: data.code,
+                    sp_status: data.status,
+                    audit_id: data.audit_id,
+                };
+                const result = await transaction.insert(this.tableName, insertData);
+                await transaction.commit();
+                return result.effectRows === 1;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
         }
 
         async removeAudit(data) {
-            const delData = {
-                tid: this.ctx.tender.id,
-                sp_type: data.code,
-                sp_status: data.status,
-                audit_id: data.audit_id,
-            };
-            return await this.db.delete(this.tableName, delData);
+            const transaction = await this.db.beginTransaction();
+            try {
+                const delData = {
+                    tid: this.ctx.tender.id,
+                    sp_type: data.code,
+                    sp_status: data.status,
+                    audit_id: data.audit_id,
+                };
+                await transaction.delete(this.tableName, delData);
+                if (parseInt(data.code) === shenpiConst.sp_type.stage && parseInt(data.status) === shenpiConst.sp_status.gdspl) {
+                    const options = {
+                        where: {
+                            tid: this.ctx.tender.id,
+                            user_id: data.audit_id,
+                        },
+                    };
+                    const updateData = {
+                        status: 0,
+                    };
+                    await transaction.update(this.ctx.service.ledgerCooperation.tableName, updateData, options);
+                }
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
         }
 
         async copyAudit2otherTender(data) {

+ 5 - 5
app/view/schedule/index.ejs

@@ -295,11 +295,11 @@
                 精度:3,
             }
         ];
-        const spread = new GC.Spread.Sheets.Workbook($('#option-spread1')[0], {
-            sheetCount: 1
-        });
-        spread.getActiveSheet().setDataSource(data);
-        spread.options.tabStripVisible = false;
+        // const spread = new GC.Spread.Sheets.Workbook($('#option-spread1')[0], {
+        //     sheetCount: 1
+        // });
+        // spread.getActiveSheet().setDataSource(data);
+        // spread.options.tabStripVisible = false;
     })
 </script>
 <script src="/public/js/sub_menu.js"></script>

+ 2 - 19
app/view/schedule/ledger.ejs

@@ -29,9 +29,11 @@
                     </a>
                 </div>
             </div>
+            <% if (tender.user_id === ctx.session.sessionUser.accountId) { %>
             <div class="ml-auto">
                 <button type="button" id="ledger_submit" class="btn btn-primary btn-sm pull-right">确认提交</button>
             </div>
+            <% } %>
         </div>
     </div>
     <div class="content-wrap">
@@ -44,7 +46,6 @@
         </div>
     </div>
 </div>
-<script src="/public/js/sub_menu.js"></script>
 <script>
     const tender = JSON.parse('<%- JSON.stringify(tender) %>');
     const tenderInfo = JSON.parse(unescape('<%- escape(JSON.stringify(tenderInfo)) %>'));
@@ -52,21 +53,3 @@
     const measureType = JSON.parse('<%- JSON.stringify(measureType) %>');
     const selectedLedgerList = JSON.parse('<%- JSON.stringify(scheduleLedgerList) %>');
 </script>
-<script>
-    $.subMenu({
-        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
-        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
-        key: 'menu.1.0.0',
-        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
-        callback: function (info) {
-            if (info.mini) {
-                $('.panel-title').addClass('fluid');
-                $('#sub-menu').removeClass('panel-sidebar');
-            } else {
-                $('.panel-title').removeClass('fluid');
-                $('#sub-menu').addClass('panel-sidebar');
-            }
-            autoFlashHeight();
-        }
-    });
-</script>

+ 0 - 21
app/view/schedule/ledger_modal.ejs

@@ -1,21 +0,0 @@
-<!--首次使用提示-->
-<div class="modal fade" id="first" 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">
-                <h5>首次使用形象进度需要进行进度台帐初始化设置</h5>
-            </div>
-            <div class="modal-footer">
-                <button type="button" class="btn btn-sm btn-primary" data-dismiss="modal">开始设置</button>
-            </div>
-        </div>
-    </div>
-</div>
-<script type="text/javascript">
-    $(function () {
-        // $('#first').modal('show');
-    })
-</script>

+ 35 - 0
app/view/schedule/modal.ejs

@@ -0,0 +1,35 @@
+<!--首次使用提示-->
+<div class="modal fade" id="first" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">进度台帐</h5>
+            </div>
+            <% if (tender.user_id !== ctx.session.sessionUser.accountId) { %>
+            <div class="modal-body">
+                <h5>未设置进度台账无法查看</h5>
+            </div>
+            <div class="modal-footer">
+                <a href="/tender/<%- ctx.tender.id %>" class="btn btn-sm btn-primary">回到标段概况</a>
+            </div>
+            <% } else { %>
+            <div class="modal-body">
+                <h5>首次使用形象进度需要进行进度台帐初始化设置</h5>
+            </div>
+            <div class="modal-footer">
+                <a href="/tender/<%- ctx.tender.id %>/schedule/ledger" class="btn btn-sm btn-primary">开始设置</a>
+            </div>
+            <% } %>
+        </div>
+    </div>
+</div>
+<script>
+    const selectedLedgerList = JSON.parse('<%- JSON.stringify(scheduleLedgerList) %>');
+</script>
+<script type="text/javascript">
+    $(function () {
+        if (selectedLedgerList.length === 0) {
+            $('#first').modal('show');
+        }
+    })
+</script>

+ 31 - 0
app/view/schedule/plan.ejs

@@ -0,0 +1,31 @@
+<% include ../tender/tender_sub_menu.ejs %>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main d-flex">
+            <% include ../tender/tender_sub_mini_menu.ejs %>
+            <h2>
+                计划至至 2020年12月
+            </h2>
+            <div class="ml-auto">
+                <a href="#mode" data-toggle="modal" data-target="#mode" class="btn btn-sm btn-outline-primary">计算方式</a>
+                <a href="#edit-plan" data-toggle="modal" data-target="#edit-plan" class="btn btn-sm btn-outline-primary">管理计划</a>
+                <a href="#add-plan" data-toggle="modal" data-target="#add-plan" class="btn btn-sm btn-primary">新增计划</a>
+            </div>
+        </div>
+    </div>
+    <div class="content-wrap">
+        <div class="c-body">
+            <div class="sjs-height-0" style="overflow: auto;" id="ledger-spread">
+            </div>
+        </div>
+    </div>
+</div>
+<script>
+    const tender = JSON.parse('<%- JSON.stringify(tender) %>');
+    const tenderInfo = JSON.parse(unescape('<%- escape(JSON.stringify(tenderInfo)) %>'));
+    const thousandth = <%- ctx.tender.info.display.thousandth %>;
+    const measureType = JSON.parse('<%- JSON.stringify(measureType) %>');
+    const schedule = JSON.parse('<%- JSON.stringify(schedule) %>');
+    const scheduleMonth = JSON.parse('<%- JSON.stringify(scheduleMonth) %>');
+    const mode = JSON.parse('<%- JSON.stringify(mode) %>');
+</script>

+ 89 - 0
app/view/schedule/plan_modal.ejs

@@ -0,0 +1,89 @@
+<!--新增计划-->
+<div class="modal fade" id="add-plan" 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>
+                    <input class="datepicker-here form-control" placeholder="点击选择时间" data-view="months" data-min-view="months" data-date-format="yyyy-MM" data-range="true" data-multiple-dates-separator=" ~ " data-language="zh" type="text">
+                </div>
+                <div class="alert alert-danger">2020-05 已创建</div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                <button type="button" class="btn btn-sm btn-primary" >确认新增</button>
+            </div>
+        </div>
+    </div>
+</div>
+<!--管理计划-->
+<div class="modal fade" id="edit-plan" 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">
+                <table class="table table-bordered table-hover">
+                    <tr><th>计划月</th><th width="100">删除</th></tr>
+                    <tr><td>2020-1</td><td></td></tr>
+                    <tr><td>2020-2</td><td><label><input type="checkbox"></label></td></tr>
+                    <tr><td>2020-3</td><td><label><input type="checkbox"></label></td></tr>
+                </table>
+                <div class="alert alert-danger">确认删除「2020-2」「2020-3」计划进度?删除后,数据无法恢复,请谨慎操作。</div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                <button type="button" class="btn btn-sm btn-danger" >确认删除</button>
+            </div>
+        </div>
+    </div>
+</div>
+<!--选择计算方式-->
+<div class="modal fade" id="mode" 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="row mb-2">
+                    <div class="col-6">
+                        <div class="card">
+                            <div class="card-body">
+                                <h5 class="card-title"><i class="fa fa-bookmark"></i> 金额</h5>
+                                <p class="card-text">填写计划金额,通过经济指标反算工程量</p>
+                                <% if (schedule && schedule.mode === mode.tp) { %>
+                                <button class="btn btn-primary btn-sm disabled mode-select" disabled data-mode="<%- mode.tp %>">当前</button>
+                                <% } else { %>
+                                <button class="btn btn-sm btn-primary mode-select" data-mode="<%- mode.tp %>">选择</button>
+                                <% } %>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="col-6">
+                        <div class="card">
+                            <div class="card-body">
+                                <h5 class="card-title"><i class="fa fa-bookmark"></i> 工程量</h5>
+                                <p class="card-text">填写工程量,金额=工程量×经济指标</p>
+                                <% if (schedule && schedule.mode === mode.gcl) { %>
+                                <button class="btn btn-primary btn-sm disabled mode-select" disabled data-mode="<%- mode.gcl %>">当前</button>
+                                <% } else { %>
+                                <button class="btn btn-sm btn-primary mode-select" data-mode="<%- mode.gcl %>">选择</button>
+                                <% } %>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+                <div class="alert alert-danger" id="mode-tips" style=" <% if (schedule && !schedule.mode) { %>display: none<% } %>">更改计算方式,未使用的计划月数据都将按新计算方式重新计算,请谨慎选择。</div>
+            </div>
+            <div class="modal-footer" id="mode-cancel" style=" <% if (schedule && !schedule.mode) { %>display: none <% } %>">
+                <button type="button" class="btn btn-sm btn-outline-secondary" data-dismiss="modal">关闭</button>
+            </div>
+        </div>
+    </div>
+</div>
+<% include ./modal.ejs %>

+ 5 - 23
app/view/tender/shenpi.ejs

@@ -16,6 +16,9 @@
                                 <div class="card-body <%- sp.code %>_div">
                                     <a class="pull-right set-otherTender" data-name="<%- sp.name %>" data-code="<%- sp.code %>" href="#batch" data-toggle="modal" data-target="#batch">设置其他标段</a>
                                     <a class="pull-right set-otherShenpi mr-3" data-name="<%- sp.name %>" data-code="<%- sp.code %>" href="#batch2" data-toggle="modal" data-target="#batch2">设置其他流程</a>
+                                    <% if (sp.code === 'stage' && !revising && ctx.tender.data.ledger_status === auditConst.ledger.status.checked) { %>
+                                    <a class="pull-right mr-3" id="stage_cooperation" <% if (sp.status !== shenpi.sp_status.gdspl) { %>style="display: none"<% } %> data-name="<%- sp.name %>" data-code="<%- sp.code %>" href="#cooperation" data-toggle="modal" data-target="#cooperation">多人协同</a>
+                                    <% } %>
                                     <h5 class="card-title"><%- sp.name %></h5>
                                     <div class="form-group">
                                         <div class="form-group form-check">
@@ -139,7 +142,6 @@
         </div>
     </div>
 </div>
-<script src="/public/js/sub_menu.js"></script>
 <script>
     const sp_lc = JSON.parse('<%- JSON.stringify(shenpi.sp_lc) %>');
     const sp_type = JSON.parse('<%- JSON.stringify(shenpi.sp_type) %>');
@@ -151,26 +153,6 @@
     const cur_tenderid = parseInt('<%- ctx.tender.id %>');
     const tenders = JSON.parse(unescape('<%- escape(JSON.stringify(tenders)) %>'));
     const category = JSON.parse(unescape('<%- escape(JSON.stringify(categoryData)) %>'));
-</script>
-<script src="/public/js/decimal.min.js"></script>
-<script src="/public/js/zh_calc.js"></script>
-<script src="/public/js/shenpi.js"></script>
-<script src="/public/js/tender_showhide.js"></script>
-<script>
-    $.subMenu({
-        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
-        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
-        key: 'menu.1.0.0',
-        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
-        callback: function (info) {
-            if (info.mini) {
-                $('.panel-title').addClass('fluid');
-                $('#sub-menu').removeClass('panel-sidebar');
-            } else {
-                $('.panel-title').removeClass('fluid');
-                $('#sub-menu').addClass('panel-sidebar');
-            }
-            autoFlashHeight();
-        }
-    });
+    const thousandth = <%- ctx.tender.info.display.thousandth %>;
+    const measureType = JSON.parse('<%- JSON.stringify(measureType) %>');
 </script>

+ 45 - 0
app/view/tender/shenpi_modal.ejs

@@ -79,3 +79,48 @@
         </div>
     </div>
 </div>
+<!--多人协同-->
+<div class="modal fade" id="cooperation" data-backdrop="static">
+    <div class="modal-dialog modal-xl" 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">
+                        <% const yb = ctx.helper._.find(accountList, { id: ctx.tender.data.user_id }) %>
+                        <div class="modal-height-300">
+                            <table class="table table-hover table-bordered">
+                                <thead>
+                                <tr><th colspan="3" class="text-center" id="stage_audit"><%- yb.name %>(原报)</th></tr>
+                                <tr><th>项目节编号/名称</th><th>密码</th><th>签名</th></tr>
+                                </thead>
+                                <tbody id="coo_table">
+                                </tbody>
+                            </table>
+                        </div>
+                    </div>
+                    <div class="col-6">
+                        <div class="mb-2">
+                            <select class="form-control form-control-sm" id="stage_audits">
+                                <option value="<%- ctx.tender.data.user_id %>"><%- yb.name %>(原报)</option>
+                                <% if (shenpi.sp_lc[shenpi.sp_type.stage-1].auditList) { %>
+                                <% for (const [i, audit] of shenpi.sp_lc[shenpi.sp_type.stage-1].auditList.entries()) { %>
+                                <option value="<%- audit.audit_id %>"><%- audit.name %>(<%- ctx.helper.transFormToChinese(i+1) %>审)</option>
+                                <% } %>
+                                <% } %>
+                            </select>
+                        </div>
+                        <div class="modal-height-300" style="overflow: auto;" id="ledger-spread">
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
+                <button type="button" class="btn btn-sm btn-primary">确认</button>
+            </div>
+        </div>
+    </div>
+</div>

+ 8 - 0
app/view/tender/tender_sub_menu.ejs

@@ -44,6 +44,14 @@
             </ul>
         </div>
         <div class="nav-box">
+            <h3><i class="fa fa-bar-chart "></i> 形象进度</h3>
+            <ul class="nav-list list-unstyled sub-list">
+                <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/schedule' || ctx.url === '/tender/' + ctx.tender.id + '/schedule/ledger') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/schedule"><span>进度概况</span></a></li>
+                <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/schedule/plan') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/schedule/plan"><span>计划进度</span></a></li>
+                <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/schedule/stage') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/schedule/stage"><span>计量进度</span></a></li>
+            </ul>
+        </div>
+        <div class="nav-box">
             <ul class="nav-list list-unstyled">
                 <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/report') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/report"><i class="fa fa-file-text-o"></i> <span>报表</span></a></li>
             </ul>

+ 8 - 0
app/view/tender/tender_sub_mini_menu.ejs

@@ -42,6 +42,14 @@
             </ul>
         </div>
         <div class="nav-box">
+            <h3><i class="fa fa-bar-chart "></i> 形象进度</h3>
+            <ul class="nav-list list-unstyled sub-list">
+                <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/schedule' || ctx.url === '/tender/' + ctx.tender.id + '/schedule/ledger') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/schedule"><span>进度概况</span></a></li>
+                <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/schedule/plan') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/schedule/plan"><span>计划进度</span></a></li>
+                <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/schedule/stage') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/schedule/stage"><span>计量进度</span></a></li>
+            </ul>
+        </div>
+        <div class="nav-box">
             <ul class="nav-list list-unstyled">
                 <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/report') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/report"><i class="fa fa-file-text-o"></i> <span>报表</span></a></li>
             </ul>

+ 35 - 0
config/web.js

@@ -108,6 +108,23 @@ const JsFiles = {
                 ],
                 mergeFile: 'tender',
             },
+            shenpi: {
+                files: [
+                    '/public/js/spreadjs/sheets/v11/gc.spread.sheets.all.11.2.2.min.js',
+                    '/public/js/decimal.min.js',
+                ],
+                mergeFiles: [
+                    '/public/js/sub_menu.js',
+                    '/public/js/div_resizer.js',
+                    '/public/js/spreadjs_rela/spreadjs_zh.js',
+                    '/public/js/shares/sjs_setting.js',
+                    '/public/js/zh_calc.js',
+                    '/public/js/path_tree.js',
+                    '/public/js/shenpi.js',
+                    '/public/js/tender_showhide.js',
+                ],
+                mergeFile: 'tender_shenpi',
+            },
         },
         ledger: {
             explode: {
@@ -678,6 +695,24 @@ const JsFiles = {
                 ],
                 mergeFile: 'ledger',
             },
+            plan: {
+                files: [
+                    '/public/js/spreadjs/sheets/v11/gc.spread.sheets.all.11.2.2.min.js',
+                    '/public/js/decimal.min.js',
+                ],
+                mergeFiles: [
+                    '/public/js/sub_menu.js',
+                    '/public/js/div_resizer.js',
+                    '/public/js/spreadjs_rela/spreadjs_zh.js',
+                    '/public/js/shares/sjs_setting.js',
+                    '/public/js/zh_calc.js',
+                    '/public/js/path_tree.js',
+                    '/public/js/schedule_plan.js',
+                    '/public/js/datepicker/datepicker.min.js',
+                    '/public/js/datepicker/datepicker.zh.js',
+                ],
+                mergeFile: 'ledger_plan',
+            },
         },
         change: {
             information: {