浏览代码

1. 台账修订,清单汇总
2. 导入yup,导入数据调整

MaiXinRong 6 月之前
父节点
当前提交
95a1cbd720

+ 99 - 6
app/controller/pay_controller.js

@@ -26,29 +26,122 @@ module.exports = app => {
         async index(ctx) {
             try {
                 const phasePays = await this.ctx.service.phasePay.getAllPhasePay(ctx.tender.id, 'DESC');
+                const relaStage = [];
+                for (const p of phasePays) {
+                    // todo 加载当前审批人
+                    // if (p.audit_status !== checked) await this.ctx.service.phasePay.loadUser(p);
+                    p.curAuditors = [];
+                    relaStage.push(...p.rela_stage);
+                }
+                const stages = await this.ctx.service.stage.getAllDataByCondition({ where: { tid: ctx.tender.id }, orders: [['order', 'AEC']] });
+                const validStages = stages.filter(s => {
+                    return !relaStage.find(r => { return s.id === r.id; });
+                });
                 this.ctx.service.phasePay.calculatePhasePay(phasePays);
                 const renderData = {
                     phasePays,
+                    validStages,
                     auditConst: audit.common,
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.phasePay.list)
                 };
-                await this.layout('phase_pay/index.ejs', renderData);
+                await this.layout('phase_pay/index.ejs', renderData, 'phase_pay/modal.ejs');
             } catch (err) {
                 ctx.helper.log(err);
             }
         }
 
-        async pay(ctx) {
+        async add(ctx) {
+            try {
+                if (ctx.session.sessionUser.accountId !== ctx.tender.data.user_id && ctx.tender.userAssistsId.indexOf(ctx.session.sessionUser.accountId) < 0) {
+                    throw '您无权创建计量期';
+                }
+                const date = ctx.request.body.date;
+                if (!date) throw '请选择支付年月';
+                const stage = ctx.request.body.stage;
+                if (!stage) throw '请选择计量期';
+                const memo = ctx.request.body.memo;
+
+                const pays = await ctx.service.phasePay.getAllPhasePay(ctx.tender.id, 'DESC');
+                const unCompleteCount = pays.filter(s => { return s.status !== audit.common.status.checked; }).length;
+                if (unCompleteCount.length > 0) throw `最新一起未审批通过,请审批通过后再新增`;
+                // 预留可以关联多期
+                const stages = await ctx.service.stage.getAllDataByCondition({ where: { tid: ctx.tender.id, order: stage } });
+
+                const newPhase = await ctx.service.phasePay.add(ctx.tender.id, stages, date, memo);
+                if (!newPhase) throw '新增期失败';
+
+                ctx.redirect('/tender/' + ctx.tender.id + '/pay' + newPhase.phase_order);
+            } catch (err) {
+                this.log(err);
+                ctx.postError(err, '新增期失败');
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        async del() {
+
+        }
+
+        async save() {
+
+        }
+
+        async detail(ctx) {
             try {
-                const pays = await this.ctx.service.phasePay.getAllPhasePay(ctx.tender.id, 'DESC');
+                const pays = await this.ctx.service.phasePayDetail.getDetailData(ctx.phasePay);
+                const calcBase = this.ctx.service.phasePay.getPhasePayCalcBase(ctx.phasePay, ctx.tender.info);
                 const renderData = {
                     pays,
-                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.pay.list)
+                    calcBase,
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.phasePay.detail)
                 };
-                await this.layout('pay/pay.ejs', renderData);
+                await this.layout('phase_pay/detail.ejs', renderData, 'phase_pay/detail_modal.ejs');
             } catch (err) {
                 ctx.helper.log(err);
                 ctx.postError(err, '读取合同支付数据错误');
-                ctx.redirect(this.request.headers.referer);
+                ctx.redirect(ctx.request.headers.referer);
+            }
+        }
+
+        async _detailBase(phasePay, type, data) {
+            if (isNaN(data.id) || data.id <= 0) throw '数据错误';
+            if (type !== 'add') {
+                if (isNaN(data.count) || data.count <= 0) data.count = 1;
+            }
+            switch (type) {
+                case 'add':
+                    return await this.ctx.service.phasePayDetail.addDetailNode(phasePay, data.id, data.count);
+                case 'delete':
+                    return await this.ctx.service.phasePayDetail.delete(phasePay.id, data.id, data.count);
+                case 'up-move':
+                    return await this.ctx.service.phasePayDetail.upMoveNode(phasePay.id, data.id, data.count);
+                case 'down-move':
+                    return await this.ctx.service.phasePayDetail.downMoveNode(phasePay.id, data.id, data.count);
+            }
+        }
+        async detailUpdate(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.postType || !data.postData) throw '数据错误';
+                const responseData = { err: 0, msg: '', data: {} };
+
+                switch (data.postType) {
+                    case 'add':
+                    case 'delete':
+                    case 'up-move':
+                    case 'down-move':
+                        responseData.data = await this._billsBase(ctx.detail, data.postType, data.postData);
+                        break;
+                    case 'update':
+                        responseData.data = await this.ctx.service.phasePayDetail.updateCalc(ctx.detail, data.postData);
+                        break;
+                    default:
+                        throw '未知操作';
+                }
+                ctx.body = responseData;
+            } catch (err) {
+                this.log(err);
+                ctx.body = this.ajaxErrorBody(err, '数据错误');
             }
         }
 

+ 1 - 0
app/lib/ybp_tree.js

@@ -198,6 +198,7 @@ class YbpImportTree {
             cur.quantity = isXmj ? 0 : node.quantity || 0;
             cur.total_fee = node.fees ? node.fees.marketCommon.totalFee : 0;
             cur.labour_fee = node.fees && node.fees.marketLabour ? node.fees.marketLabour.totalFee : 0;
+            cur.remark = node.remark;
         } else {
             cur.dgn_qty1 = isXmj ? node.quantity || 0 : 0;
             cur.dgn_qty2 = isXmj ? node.quantity2 || 0 : 0;

+ 1 - 1
app/middleware/phase_pay_check.js

@@ -39,7 +39,7 @@ module.exports = options => {
             if (!phasePay) throw '合同支付数据错误';
 
             // 读取原报、审核人数据
-            yield this.service.phasePay.loadUser(phasePay);
+            yield this.service.phasePay.doCheckPhase(phasePay);
             this.phasePay = phasePay;
             yield next;
         } catch (err) {

+ 96 - 0
app/public/js/phase_pay_detail.js

@@ -0,0 +1,96 @@
+'use strict';
+
+$(document).ready(() => {
+    const payUtils = {
+        tips: {
+            name: function(data) {
+                const tips = [];
+                if (data) {
+                    if (data.pause) tips.push('当前项已停用');
+                    if (!data.is_yf) tips.push('当前项不参与本期应付计算');
+                }
+                return tips.join('<br/>');
+            },
+            range_tp: function (data) {
+                if (!data || (!data.range_expr && !data.range_tp) || !data.dl_type) return '';
+
+                if (data.dl_type === 1) {
+                    return '计提期限为(当 计量期数 ≥ ' + data.dl_count + ')';
+                } else if (data.dl_type === 2) {
+                    switch (data.dl_tp_type) {
+                        case 'contract':
+                            return '计提期限为(累计合同计量 ≥ ' + data.dl_tp + ')';
+                        case 'qc':
+                            return '计提期限为(累计变更计量 ≥ ' + data.dl_tp + ')';
+                        case 'gather':
+                            return '计提期限为(累计完成计量 ≥ ' + data.dl_tp + ')';
+                    }
+                }
+            }
+        }
+    };
+    class PayObj {
+        constructor () {
+            this.spread = SpreadJsObj.createNewSpread($('#pay-spread')[0]);
+            this.sheet = this.spread.getActiveSheet();
+            this.spreadSetting = {
+                cols: [
+                    {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 230, formatter: '@', cellType: 'tree', getTip: payUtils.tips.name},
+                    {title: '本期金额(F)', colSpan: '1', rowSpan: '1', field: 'tp', hAlign: 2, width: 100, type: 'Number'},
+                    {title: '截止上期金额',  colSpan: '1', rowSpan: '1', field: 'pre_tp', hAlign: 2, width: 100, readOnly: true, type: 'Number'},
+                    {title: '截止本期金额',  colSpan: '1', rowSpan: '1', field: 'end_tp', hAlign: 2, width: 100, readOnly: true, type: 'Number'},
+                    {title: '起扣金额',  colSpan: '1', rowSpan: '1', field: 'start_tp', hAlign: 2, width: 100, type: 'Number'},
+                    {title: '付(扣)款限额',  colSpan: '1', rowSpan: '1', field: 'range_tp', hAlign: 2, width: 100, cellType: 'tip', type: 'Number', getTip: payUtils.tips.range_tp},
+                    {title: '汇总',  colSpan: '1', rowSpan: '1', field: 'is_yf', hAlign: 2, width: 60, readOnly: true, type: 'checkbox'},
+                    {title: '附件', colSpan: '1', rowSpan: '1', field: 'attachment', hAlign: 0, width: 60, readOnly: true, cellType: 'imageBtn', normalImg: '#rela-file-icon', hoverImg: '#rela-file-hover', getValue: 'getValue.attachment'},
+                    {title: '本期批注', colSpan: '1', rowSpan: '1', field: 'postil', hAlign: 0, width: 120, formatter: '@', cellType: 'autoTip'},
+                ],
+                emptyRows: 0,
+                headRows: 1,
+                headRowHeight: [32],
+                headColWidth: [30],
+                defaultRowHeight: 21,
+                headerFont: '12px 微软雅黑',
+                font: '12px 微软雅黑',
+                readOnly: readOnly,
+                localCache: {
+                    key: 'phase-pay',
+                    colWidth: true,
+                },
+                pos: SpreadJsObj.getObjPos($('#pay-spread')[0]),
+            };
+            sjsSettingObj.setFxTreeStyle(this.spreadSetting, sjsSettingObj.FxTreeStyle.phasePay);
+            SpreadJsObj.initSheet(this.sheet, this.spreadSetting);
+            this.payTree = createNewPathTree('base', {
+                id: 'tree_id', pid: 'tree_pid', order: 'tree_order',
+                level: 'tree_level', isLeaf: 'tree_is_leaf', fullPath: 'tree_full_path',
+                rootId: -1,
+            });
+        }
+        loadDatas(datas) {
+            this.payTree.loadDatas(datas);
+            SpreadJsObj.loadSheetData(this.sheet, SpreadJsObj.DataType.Tree, this.payTree);
+        }
+    }
+    const payObj = new PayObj();
+    payObj.loadDatas(details);
+
+    // todo 加载审批列表
+    $.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();
+            payObj.spread.refresh();
+        }
+    });
+});

+ 21 - 0
app/public/js/phase_pay_list.js

@@ -0,0 +1,21 @@
+'use strict';
+
+$(document).ready(() => {
+    // todo 加载审批列表
+    $.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();
+        }
+    });
+});

+ 9 - 0
app/public/js/revise.js

@@ -147,6 +147,11 @@ $(document).ready(() => {
             if (posSpread) posSpread.refresh();
         },
     });
+    const gclGather = $.cs_gclGather({
+        selector: '#gcl-gather',
+        id: 'gcl-gather',
+        relaSheet: billsSpread.getActiveSheet(),
+    });
 
     // 初始化 节点树结构
     const treeSetting = {
@@ -2738,6 +2743,7 @@ $(document).ready(() => {
             if (errorList) errorList.spread.refresh();
             if (checkList) checkList.spread.refresh();
             if (sumLoadMiss) sumLoadMiss.spread.refresh();
+            if (gclGather) gclGather.spread.refresh();
         }
     });
     $.subMenu({
@@ -2764,6 +2770,7 @@ $(document).ready(() => {
             if (errorList) errorList.spread.refresh();
             if (checkList) checkList.spread.refresh();
             if (sumLoadMiss) sumLoadMiss.spread.refresh();
+            if (gclGather) gclGather.spread.refresh();
         }
     });
 
@@ -2959,6 +2966,8 @@ $(document).ready(() => {
                 checkList.spread.refresh();
             } else if (tab.attr('content') === '#sum-load-miss') {
                 sumLoadMiss.spread.refresh();
+            } else if (tab.attr('content') === '#gcl-gather') {
+                gclGather.spread.refresh();
             }
         }
         else {// 收起工具栏

+ 18 - 0
app/public/js/shares/sjs_setting.js

@@ -1,6 +1,8 @@
 const sjsSettingObj = (function () {
     const FxTreeStyle = {
         jz: 'jianzhu',
+        contract: 'contract',
+        phasePay: 'phasePay',
     };
     const setJzFxTreeStyle = function (setting) {
         setting.selectedBackColor = '#fffacd';
@@ -56,6 +58,19 @@ const sjsSettingObj = (function () {
             }
         }
     };
+    const setPhasePayTreeStyle = function (setting) {
+        setting.selectedBackColor = '#fffacd';
+        setting.tree = {
+            getFont: function (sheet, data, row, col, defaultFont) {
+                if (sheet.zh_tree) {
+                    const levelField = sheet.zh_tree.setting.level;
+                    return data[levelField] === 1 ? 'bold ' + defaultFont : defaultFont;
+                } else {
+                    return defaultFont;
+                }
+            }
+        }
+    };
     const setFxTreeStyle = function (setting, tag) {
         switch (tag) {
             case FxTreeStyle.jz:
@@ -64,6 +79,9 @@ const sjsSettingObj = (function () {
             case FxTreeStyle.contract:
                 setContractFxTreeStyle(setting);
                 break;
+            case FxTreeStyle.phasePay:
+                setPhasePayTreeStyle(setting);
+                break;
         }
     };
 

+ 5 - 2
app/router.js

@@ -392,8 +392,11 @@ module.exports = app => {
     app.get('/tender/:id/measure/stage/:order/pay/download/file/:fid', sessionAuth, 'stageController.payDownloadFile');
     app.post('/tender/:id/measure/stage/:order/pay/delete/file', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.payDeleteFile');
 
-    app.get('/tender/:id/pay', sessionAuth, tenderCheck, uncheckTenderCheck, 'payController.index');
-    app.get('/tender/:id/pay/:order', sessionAuth, tenderCheck, uncheckTenderCheck, phasePayCheck, 'payController.pay');
+    app.get('/tender/:id/pay', sessionAuth, tenderCheck, 'payController.index');
+    app.post('/tender/:id/pay/add', sessionAuth, tenderCheck, 'payController.add');
+    app.post('/tender/:id/pay/del', sessionAuth, tenderCheck, 'payController.del');
+    app.post('/tender/:id/pay/save', sessionAuth, tenderCheck, 'payController.save');
+    app.get('/tender/:id/pay/:order/detail', sessionAuth, tenderCheck, phasePayCheck, 'payController.detail');
 
     // 变更概况
     app.get('/tender/:id/measure/stage/:order/change', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.change');

+ 5 - 1
app/service/ledger.js

@@ -712,6 +712,9 @@ module.exports = app => {
                 id: 'ledger_id', pid: 'ledger_pid', order: 'order', full_path: 'full_path', level: 'level', rootId: -1,
                 calcFields: [ 'total_price' ],
                 calc(node, helper, decimal) {
+                    const precision = helper.findPrecision(tender.info.precision, node.unit);
+                    node.quantity = helper.round(node.quantity, precision.value);
+                    node.unit_price = helper.round(node.unit_price, decimal.up);
                     if (!node.children || node.children.length === 0) {
                         node.total_price = helper.mul(node.quantity, node.unit_price, decimal.tp);
                     }
@@ -778,7 +781,8 @@ module.exports = app => {
                     total_price: isLeaf ? n.total_price || 0 : 0,
                     dgn_qty1: n.dgn_qty1 || 0,
                     dgn_qty2: n.dgn_qty2 || 0,
-                    memo: n.source.join(';'),
+                    memo: n.remark,
+                    // memo: n.source.join(';'),
                 });
                 if (!n.glj) continue;
                 for (const g of n.glj) {

+ 185 - 11
app/service/phase_pay.js

@@ -9,6 +9,27 @@
  */
 
 const audit = require('../const/audit').common;
+const calcBase = [
+    {name: '签约合同价', code: 'htj', sort: 10},
+    {name: '暂列金额', code: 'zlje', sort: 2},
+    {name: '签约合同价(不含暂列金)', code: 'htjszl', sort: 1},
+    {name: '签约开工预付款', code: 'kgyfk', sort: 2},
+    {name: '签约材料预付款', code: 'clyfk', sort: 2},
+    {name: '本期完成计量', code: 'bqwc', limit: true, sort: 10},
+    {name: '本期合同计量', code: 'bqht', limit: true, sort: 10},
+    {name: '本期变更计量', code: 'bqbg', limit: true, sort: 10},
+    {name: '本期清单完成计量', code: 'bqqdwc', limit: true, sort: 10},
+    {name: '本期清单合同计量', code: 'bqqdht', limit: true, sort: 10},
+    {name: '本期清单变更计量', code: 'bqqdbg', limit: true, sort: 10},
+    {name: '本期一般变更计量', code: 'ybbqbg', limit: true, sort: 5},
+    {name: '本期较大变更计量', code: 'jdbqbg', limit: true, sort: 5},
+    {name: '本期重大变更计量', code: 'zdbqbg', limit: true, sort: 5},
+    {name: '100章本期完成计量', code: 'ybbqwc', limit: true, sort: 1},
+    {name: '本期应付', code: 'bqyf', limit: true, ptNormalLimit: true, sort: 20},
+    {name: '奖金', code: 'bonus', limit: true, sort: 1},
+    {name: '罚金', code: 'fine', limit: true, sort: 1},
+    {name: '甲供材料', code: 'jgcl', limit: true, sort: 1},
+];
 
 module.exports = app => {
     class PhasePay extends app.BaseService {
@@ -25,6 +46,8 @@ module.exports = app => {
         }
 
         analysisPhasePay(data) {
+            if (!data) return;
+
             const datas = data instanceof Array ? data : [data];
             datas.forEach(x => {
                 x.rela_stage = x.rela_stage ? JSON.parse(x.rela_stage) : [];
@@ -42,7 +65,6 @@ module.exports = app => {
                 x.end_cut_tp = helper.add(x.cut_tp, x.pre_cut_tp);
                 x.end_sf_tp = helper.add(x.sf_tp, x.pre_sf_tp);
                 x.end_yf_tp = helper.add(x.yf_tp, x.pre_yf_tp);
-                if (thousandth)
                 for (const prop in x) {
                     if (prop.indexOf('_tp') > 0) {
                         x['display_' + prop] = formatNum(x[prop]);
@@ -77,10 +99,6 @@ module.exports = app => {
             return result;
         }
 
-        async loadUser(phasePay) {
-            // todo 加载审批人
-        }
-
         async getNewOrder(tid) {
             const sql = 'SELECT Max(`phase_order`) As max_order FROM ' + this.tableName + ' Where `tid` = ?';
             const sqlParam = [tid];
@@ -89,7 +107,7 @@ module.exports = app => {
         }
 
         async _checkRelaStageConflict(relaStage, phasePay) {
-            const pays = this.getAllPhasePay(phasePay.tid);
+            const pays = await this.getAllPhasePay(phasePay.tid);
             for (const p of pays) {
                 if (p.id === phasePay.id) continue;
                 for (const s of relaStage) {
@@ -100,14 +118,123 @@ module.exports = app => {
         }
 
         async getCalcBase(relaStage) {
-            // todo 获取计算基数
+            const result = {};
+            for (const stage of relaStage) {
+                result.contract_tp = this.ctx.helper.add(result.contract_tp, stage.contract_tp);
+                result.qc_tp = this.ctx.helper.add(result.contract_tp, stage.qc_tp);
+                result.pc_tp = this.ctx.helper.add(result.pc_tp, stage.pc_tp);
+
+                const qdSum = await this.ctx.service.stageBills.getSumTotalPriceGcl(stage);
+                result.qd_contract_tp = qdSum.contract_tp;
+                result.qd_qc_tp = qdSum.qc_tp;
+                result.qd_pc_tp = qdSum.pc_tp;
+
+                const sumGcl = await this.ctx.service.stageBills.getSumTotalPriceGcl(stage, '^[^0-9]*1[0-9]{2}(-|$)');
+                const sumPc = await this.ctx.service.stageBillsPc.getSumTotalPriceGcl(stage, '^[^0-9]*1[0-9]{2}(-|$)');
+                result.gather_100_tp = this.ctx.helper.sum([sumGcl.contract_tp, sumGcl.qc_tp, sumPc.pc_tp]);
+
+                const bg = await this.ctx.service.stage.getChangeSubtotal(stage);
+                result.common_bg_tp = bg.common;
+                result.more_bg_tp = bg.more;
+                result.great_bg_tp = bg.great;
+            }
+            result.gather_tp = this.ctx.helper.sum([result.contract_tp, result.qc_tp, result.pc_tp]);
+            result.qd_gather_tp = this.ctx.helper.sum([result.qd_contract_tp, result.qd_qc_tp, result.qd_pc_tp]);
+            const bonusSum = await this.ctx.service.stageBonus.getSumTp(relaStage);
+            result.bonus_positive_tp = bonusSum.positive_tp;
+            result.bonus_negative_tp = bonusSum.negative_tp;
+            result.bonus_tp = bonusSum.sum_tp;
+            const jgclSum = await this.ctx.service.stageJgcl.getSumTp(relaStage);
+            result.jgcl_tp = jgclSum.sum_tp;
+            const otherSum = await this.ctx.service.stageOther.getSumTp(relaStage);
+            result.other_tp = otherSum.sum_tp;
+            const safeProdSum = await this.ctx.service.stageSafeProd.getSumTp(relaStage);
+            result.safe_prod_tp = safeProdSum.sum_tp;
+            const tempLandSum = await this.ctx.service.stageTempLand.getSumTp(relaStage);
+            result.temp_land_tp = tempLandSum.sum_tp;
+            return result;
+        }
+
+        /**
+         * 获取 当期的 计算基数
+         * @return {Promise<any>}
+         */
+        getPhasePayCalcBase(phasePay, tenderInfo) {
+            const payCalcBase = JSON.parse(JSON.stringify(calcBase));
+            for (const cb of payCalcBase) {
+                switch (cb.code) {
+                    case 'htj':
+                        cb.value = tenderInfo.deal_param.contractPrice;
+                        break;
+                    case 'zlje':
+                        cb.value = tenderInfo.deal_param.zanLiePrice;
+                        break;
+                    case 'htjszl':
+                        cb.value = this.ctx.helper.sub(tenderInfo.deal_param.contractPrice, tenderInfo.deal_param.zanLiePrice);
+                        break;
+                    case 'kgyfk':
+                        cb.value = tenderInfo.deal_param.startAdvance;
+                        break;
+                    case 'clyfk':
+                        cb.value = tenderInfo.deal_param.materialAdvance;
+                        break;
+                    case 'bqwc':
+                        cb.value = phasePay.calc_base.gather_tp;
+                        break;
+                    case 'bqht':
+                        cb.value = phasePay.calc_base.contract_tp;
+                        break;
+                    case 'bqbg':
+                        cb.value = phasePay.calc_base.qc_tp;
+                        break;
+                    case 'bqqdwc':
+                        cb.value = phasePay.qd_gather_tp;
+                        break;
+                    case 'bqqdht':
+                        cb.value = phasePay.qd_contract_tp;
+                        break;
+                    case 'bqqdbg':
+                        cb.value = phasePay.qd_qc_tp;
+                        break;
+                    case 'ybbqwc':
+                        cb.value = phasePay.gather_100_tp;
+                        break;
+                    case 'ybbqbg':
+                        cb.value = phasePay.common_bg_tp;
+                        break;
+                    case 'jdbqbg':
+                        cb.value = phasePay.more_bg_tp;
+                        break;
+                    case 'zdbqbg':
+                        cb.value = phasePay.great_bg_tp;
+                        break;
+                    case 'bonus':
+                        cb.value = phasePay.bonus_positive_tp;
+                        break;
+                    case 'fine':
+                        cb.value = phasePay.bonus_negative_tp;
+                        break;
+                    case 'jgcl':
+                        cb.value = phasePay.jgcl_tp;
+                        break;
+                    case 'aqsc':
+                        cb.value = phasePay.safe_prod_tp;
+                        break;
+                    case 'lsyd':
+                        cb.value = phasePay.temp_land_tp;
+                        break;
+                    default:
+                        cb.value = 0;
+                }
+            }
+            return payCalcBase;
         }
 
         async add(tid, relaStage, phaseDate, memo) {
             if (!tid) throw '数据错误';
             const user_id = this.ctx.session.sessionUser.accountId;
 
-            const maxOrder = await this.getNewOrder(tid);
+            const maxOrder = await this.getNewOrder();
             const data = {
                 id: this.uuid.v4(), tid: tid, create_user_id: user_id, update_user_id: user_id,
                 phase_order: maxOrder + 1, phase_date: phaseDate, memo,
@@ -123,7 +250,7 @@ module.exports = app => {
                 const result = await transaction.insert(this.tableName, data);
                 if (result.affectedRows !== 1) throw '新增合同支付失败';
 
-                await this.ctx.service.phasePayDetail.initPhaseData(data);
+                await this.ctx.service.phasePayDetail.initPhaseData(transaction, data);
                 await transaction.commit();
                 return data;
             } catch(err) {
@@ -133,7 +260,7 @@ module.exports = app => {
         }
 
         async refreshCalcBase(id) {
-            const curPay = this.getPhasePay(id);
+            const curPay = await this.getPhasePay(id);
             if (!curPay) throw '合同支付不存在, 请刷新页面重试';
 
             const calcBase = await this.getCalcBase(relaStage);
@@ -146,7 +273,7 @@ module.exports = app => {
         }
 
         async resetRelaStageId(id, relaStage) {
-            const curPay = this.getPhasePay(id);
+            const curPay = await this.getPhasePay(id);
             if (!curPay) throw '合同支付不存在, 请刷新页面重试';
 
             if (await this._checkRelaStageConflict(relaStage, curPay)) throw '选择的计量期,已被调用,请刷新页面后选择计量期新增合同支付';
@@ -159,6 +286,53 @@ module.exports = app => {
             });
         }
 
+        async loadUser(phasePay) {
+            // todo 加载审批人
+            phasePay.user = await this.ctx.service.projectAccount.getAccountInfoById(phasePay.create_user_id);
+            phasePay.curAuditors = [];
+            // phasePay.curAuditors = await this.ctx.service.phasePayAudit.getAllDataByCondition({
+            //     where: { phase_id: phasePay.id, audit_times: phasePay.audit_times, audit_status: audit.status.checking }
+            // });
+            phasePay.curAuditorIds = phasePay.curAuditors.map(x => { return x.audit_id; });
+            phasePay.flowAuditors = phasePay.curAuditors.length === 0 ? []
+                : await this.ctx.service.phasePayAudit.getAllDataByCondition({
+                    where: { phase_id: phasePay.id, audit_times: phasePay.audit_times, audit_sort: phasePay.curAuditors[0].audit_sort }
+                });
+            phasePay.flowAuditorIds = phasePay.curAuditors.map(x => { return x.audit_id; });
+        }
+        async doCheckPhase(phasePay) {
+            await this.loadUser(phasePay);
+            const accountId = this.ctx.session.sessionUser.accountId;
+
+            if (phasePay.audit_status === audit.status.uncheck) {
+                phasePay.readOnly = accountId !== phasePay.create_user_id;
+                phasePay.curTimes = phasePay.audit_times;
+                phasePay.curOrder = 0;
+            } else if (phasePay.audit_status === audit.status.checkNo) {
+                phasePay.readOnly = accountId !== phasePay.create_user_id;
+                if (!phasePay.readOnly) {
+                    phasePay.curTimes = phasePay.times;
+                    phasePay.curOrder = 0;
+                } else {
+                    const checkNoAudit = await this.service.phasePayAudit.getDataByCondition({
+                        phase_id: phasePay.id, audit_times: phasePay.audit_times - 1, status: audit.status.checkNo,
+                    });
+                    phasePay.curTimes = phasePay.times - 1;
+                    phasePay.curOrder = checkNoAudit.audit_order;
+                }
+            } else if (phasePay.audit_status === audit.status.checked) {
+                phasePay.readOnly = true;
+                phasePay.curTimes = phasePay.audit_times;
+                phasePay.curOrder = phasePay.audit_max_order;
+            } else {
+                // 会签,会签人部分审批通过时,只读,但是curOrder需按原来的取值
+                phasePay.curTimes = phasePay.audit_times;
+                phasePay.curOrder = phasePay.flowAuditorIds.indexOf(accountId) < 0 ? phasePay.curAuditors[0].order : phasePay.curAuditors[0].order - 1;
+                phasePay.readOnly = !_.isEqual(stage.flowAuditorIds, stage.curAuditorIds);
+                phasePay.canCheck = phasePay.readOnly && stage.curAuditorIds.indexOf(accountId) > 0;
+            }
+        }
+
     }
 
     return PhasePay;

+ 208 - 0
app/service/phase_pay_detail.js

@@ -0,0 +1,208 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const payType = {
+    bqsf: 'bqsf', bqyf: 'bqyf', gcjl: 'gcjl', qtfk: 'qtfk', qtkk: 'qtkk', bqwc: 'bqwc'
+};
+const defaultPays = [
+    { tree_id: 1, tree_pid: -1, tree_level: 1, tree_order: 1, tree_is_leaf: 1, tree_full_path: '1', pay_type: 'bqsf', is_minus: 0, is_fixed: 1, name: '本期应付' },
+    { tree_id: 2, tree_pid: -1, tree_level: 1, tree_order: 2, tree_is_leaf: 1, tree_full_path: '1', pay_type: 'bqyf', is_minus: 0, is_fixed: 1, name: '本期应付' },
+    { tree_id: 3, tree_pid: -1, tree_level: 1, tree_order: 3, tree_is_leaf: 0, tree_full_path: '1', pay_type: 'gcjl', is_minus: 0, is_fixed: 1, name: '工程计量款' },
+    { tree_id: 4, tree_pid: -1, tree_level: 1, tree_order: 4, tree_is_leaf: 0, tree_full_path: '1', pay_type: 'qtfk', is_minus: 0, is_fixed: 1, name: '其他付款项' },
+    { tree_id: 5, tree_pid: -1, tree_level: 1, tree_order: 5, tree_is_leaf: 0, tree_full_path: '1', pay_type: 'qtkk', is_minus: 1, is_fixed: 1, name: '其他扣款项' },
+    { tree_id: 6, tree_pid: 3, tree_level: 2, tree_order: 1, tree_is_leaf: 1, tree_full_path: '3-6', pay_type: 'bqwc', is_minus: 0, is_fixed: 0, name: '本期完成计量', expr: 'bqwc' },
+    { tree_id: 7, tree_pid: 4, tree_level: 2, tree_order: 1, tree_is_leaf: 1, tree_full_path: '4-7', pay_type: '', is_minus: 0, is_fixed: 0, name: '新增付款项' },
+    { tree_id: 8, tree_pid: 5, tree_level: 2, tree_order: 1, tree_is_leaf: 1, tree_full_path: '5-8', pay_type: '', is_minus: 1, is_fixed: 0, name: '新增扣款项' },
+];
+const TreeService = require('../base/base_tree_service');
+
+class PhasePayDetail extends TreeService {
+
+    /**
+     * 构造函数
+     *
+     * @param {Object} ctx - egg全局变量
+     * @param {String} tableName - 表名
+     * @return {void}
+     */
+    constructor(ctx, setting) {
+        super(ctx, {
+            mid: 'phase_id',
+            kid: 'tree_id',
+            pid: 'tree_pid',
+            order: 'tree_order',
+            level: 'tree_level',
+            isLeaf: 'tree_is_leaf',
+            fullPath: 'tree_full_path',
+            keyPre: '',
+            uuid: true,
+        });
+        this.tableName = 'phase_pay_detail';
+    }
+
+    async initPhaseDataEmpty(conn, phasePay) {
+        const user_id = this.ctx.session.sessionUser.accountId;
+        const insertData = [];
+        for (const dp of defaultPays) {
+            insertData.push({
+                tid: phasePay.tid, phase_id: phasePay.id, create_user_id: user_id, update_user_id: user_id,
+                uuid: this.uuid.v4(), ...dp,
+            });
+        }
+        await conn.insert(this.tableName, insertData);
+    }
+
+    async initPhaseDataByPre(conn, phasePay, prePhase) {
+        const preData = await this.getAllDataByCondition({
+            where: { phase_id: prePhase.id, audit_times: prePhase.audit_times, audit_order: audit_max_order },
+        });
+        for (const pd of preData) {
+            delete pd.id;
+            pd.audit_times = 1;
+            pd.audit_order = 0;
+        }
+        await conn.insert(this.tableName, preData);
+    }
+
+    async initPhaseDataByAudit(conn, phasePay, newTimes, newOrder) {
+        const preData = await this.getAllDataByCondition({
+            where: { phase_id: prePhase.id, audit_times: phasePay.curTimes, audit_order: phasePay.curOrder },
+        });
+        for (const pd of preData) {
+            delete pd.id;
+            pd.audit_times = newTimes;
+            pd.audit_order = newOrder;
+        }
+        await conn.insert(this.tableName, preData);
+    }
+
+    async initPhaseData(conn, phasePay){
+        if (!conn) throw '内部错误';
+        const prePhase = await this.ctx.service.phasePay.getPhasePayByOrder(phasePay.tid, phasePay.phase_order - 1);
+        if (prePhase) {
+            await this.initPhaseDataByPre(conn, phasePay, prePhase);
+        } else {
+            await this.initPhaseDataEmpty(conn, phasePay);
+        }
+    }
+
+    async getDetailData(phasePay) {
+        if (phasePay.curTimes) {
+            return await this.getAllDataByCondition({ where: { phase_id: phasePay.id, audit_times: phasePay.curTimes, audit_sort: phasePay.curOrder } });
+        } else {
+            return await this.getAllDataByCondition({ where: { phase_id: phasePay.id, audit_times: phasePay.audit_times, audit_sort: phasePay.audit_max_order } });
+        }
+    }
+
+    _getDefaultData(data, phasePay, parent) {
+        data.uuid = this.uuid.v4();
+        data.tid = phasePay.tid;
+        data.phase_id = phasePay.id;
+        data.create_user_id = this.ctx.session.sessionUser.accountId;
+        data.update_user_id = this.ctx.session.sessionUser.accountId;
+        if (parent) {
+            data.is_minus = parent.is_minus;
+        }
+    }
+
+    async addChild(phasePay, select, count = 1) {
+        if (select.payType === payType.bqsf || select.payType === payType.bqyf) throw '不可新增子项';
+        if (select.tree_level >= 2) throw '不可新增子项';
+
+        const children = await this.getChildrenByParentId(phasePay.id, select[this.setting.id]);
+        const maxId = await this._getMaxLid(phasePay.id);
+        const insertData = [];
+        for (let i = 0; i < count; i++) {
+            const data = {
+                tree_id: maxId + i, tree_pid: select.tree_id, tree_order: children.length + 1 + i,
+                tree_level: select.tree_level + 1, tree_is_leaf: 1
+            };
+            data.tree_full_path = select.tree_full_path + '-' + data.tree_id;
+            this._getDefaultData(data, phasePay, select);
+            insertData.push(data);
+        }
+
+        const conn = await this.db.beginTransaction();
+        try {
+            const result = await conn.insert(this.tableName, insertData);
+            if (children.length === 0) {
+                await conn.update(this.tableName, { id: select.id, is_leaf: false });
+            }
+            await conn.commit();
+        } catch(err) {
+            conn.rollback();
+            throw err;
+        }
+        this._cacheMaxLid(phasePay.id, maxId + 1);
+
+        // 查询应返回的结果
+        const resultData = {};
+        resultData.create = await this.getNextsData(phasePay.id, data.tree_id, children.length);
+        if (children.length === 0) resultData.update = await this.getDataByKid(phasePay.id, select.tree_id);
+        return resultData;
+    }
+
+    async addNext(phasePay, select, count = 1) {
+        this.transaction = await this.db.beginTransaction();
+        try {
+            if (select) await this._updateChildrenOrder(phasePay.id, select[this.setting.pid], select[this.setting.order] + 1, count);
+            const newDatas = [];
+            const maxId = await this._getMaxLid(phasePay.id);
+            for (let i = 1; i < count + 1; i++) {
+                const newData = {};
+                newData[this.setting.kid] = maxId + i;
+                newData[this.setting.pid] = select ? select[this.setting.pid] : this.rootId;
+                newData[this.setting.level] = select ? select[this.setting.level] : 1;
+                newData[this.setting.order] = select ? select[this.setting.order] + i : i;
+                newData[this.setting.fullPath] = newData[this.setting.level] > 1
+                    ? select[this.setting.fullPath].replace('-' + select[this.setting.kid], '-' + newData[this.setting.kid])
+                    : newData[this.setting.kid] + '';
+                newData[this.setting.isLeaf] = 1;
+                this._getDefaultData(newData, phasePay);
+                newDatas.push(newData);
+            }
+            const insertResult = await this.transaction.insert(this.tableName, newDatas);
+            this._cacheMaxLid(phasePay.id, maxId + count);
+
+            if (insertResult.affectedRows !== count) throw '新增节点数据错误';
+            await this.transaction.commit();
+            this.transaction = null;
+        } catch (err) {
+            await this.transaction.rollback();
+            this.transaction = null;
+            throw err;
+        }
+
+        if (select) {
+            const createData = await this.getChildBetween(phasePay.id, select[this.setting.pid], select[this.setting.order], select[this.setting.order] + count + 1);
+            const updateData = await this.getNextsData(phasePay.id, select[this.setting.pid], select[this.setting.order] + count);
+            return {create: createData, update: updateData};
+        } else {
+            const createData = await this.getChildBetween(phasePay.id, -1, 0, count + 1);
+            return {create: createData};
+        }
+    }
+
+    async addDetailNode(phasePay, targetId, count = 1) {
+        if (!phasePay) return null;
+
+        const select = targetId ? await this.getDataByKid(detail.id, targetId) : null;
+        if (targetId && !select) throw '新增节点数据错误';
+
+        if (select[this.setting.level] === 1) {
+            return await this.addChild(phasePay, select, count);
+        } else {
+            return await this.addNext(phasePay, select, count);
+        }
+    }
+}
+
+module.exports = PhasePayDetail;
+

+ 8 - 0
app/service/stage_bonus.js

@@ -227,6 +227,14 @@ module.exports = app => {
             }
             await transaction.updateRows(this.tableName, updateDatas);
         }
+
+        async getSumTp(stage) {
+            const stages = stage instanceof Array ? stage : [stage];
+            const condition = {};
+            condition.sid = stage.length === 1 ? stage[0].id : stages.map(x => { return x.id; }).join(',');
+            const sql = `SELECT SUM(IF(tp > 0, tp, 0)) AS positive_tp, SUM(IF(tp < 0, tp, 0)) AS negative_tp, SUM(tp) AS sum_tp From ${this.tableName} ` + this.ctx.helper.whereSql(condition);
+            return await this.db.queryOne(sql);
+        }
     }
 
     return StageBonus;

+ 8 - 0
app/service/stage_jgcl.js

@@ -280,6 +280,14 @@ module.exports = app => {
             }
             await transaction.updateRows(this.tableName, updateDatas);
         }
+
+        async getSumTp(stage) {
+            const stages = stage instanceof Array ? stage : [stage];
+            const condition = {};
+            condition.sid = stage.length === 1 ? stage[0].id : stages.map(x => { return x.id; }).join(',');
+            const sql = `SELECT SUM(arrive_tp) AS arrive_tp, SUM(deduct_tp) AS deduct_tp FROM ${this.tableName} ` + this.ctx.helper.whereSql(condition);
+            return await this.db.queryOne(sql);
+        }
     }
 
     return StageJgcl;

+ 8 - 0
app/service/stage_other.js

@@ -242,6 +242,14 @@ module.exports = app => {
             }
             await transaction.updateRows(this.tableName, updateDatas);
         }
+
+        async getSumTp(stage) {
+            const stages = stage instanceof Array ? stage : [stage];
+            const condition = {};
+            condition.sid = stage.length === 1 ? stage[0].id : stages.map(x => { return x.id; }).join(',');
+            const sql = `SELECT SUM(tp) AS sum_tp FROM ${this.tableName} ` + this.ctx.helper.whereSql(condition);
+            return await this.db.queryOne(sql);
+        }
     }
 
     return StageOther;

+ 8 - 0
app/service/stage_safe_prod.js

@@ -277,6 +277,14 @@ module.exports = app => {
             }
             await transaction.updateRows(this.tableName, updateDatas);
         }
+
+        async getSumTp(stage) {
+            const stages = stage instanceof Array ? stage : [stage];
+            const condition = {};
+            condition.sid = stage.length === 1 ? stage[0].id : stages.map(x => { return x.id; }).join(',');
+            const sql = `SELECT SUM(tp) AS sum_tp FROM ${this.tableName} ` + this.ctx.helper.whereSql(condition);
+            return await this.db.queryOne(sql);
+        }
     }
 
     return StageSafeProd;

+ 8 - 0
app/service/stage_temp_land.js

@@ -246,6 +246,14 @@ module.exports = app => {
             }
             await transaction.updateRows(this.tableName, updateDatas);
         }
+
+        async getSumTp(stage) {
+            const stages = stage instanceof Array ? stage : [stage];
+            const condition = {};
+            condition.sid = stage.length === 1 ? stage[0].id : stages.map(x => { return x.id; }).join(',');
+            const sql = `SELECT SUM(tp) AS sum_tp FROM ${this.tableName} ` + this.ctx.helper.whereSql(condition);
+            return await this.db.queryOne(sql);
+        }
     }
 
     return StageTempLand;

+ 61 - 0
app/view/phase_pay/detail.ejs

@@ -0,0 +1,61 @@
+<% include ./sub_menu.ejs %>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main d-flex">
+            <% include ./sub_mini_menu.ejs %>
+            <div>
+                <div class="d-inline-block">
+                    <a href="javascript: void(0)" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="添加" name="base-opr" type="add"><i class="fa fa-plus" aria-hidden="true"></i></a>
+                    <a href="javascript: void(0)" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="删除" name="base-opr" type="del"><i class="fa fa-remove" aria-hidden="true"></i></a>
+                    <a href="javascript: void(0)" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="下移" name="base-opr" type="down-move"><i class="fa fa-arrow-down" aria-hidden="true"></i></a>
+                    <a href="javascript: void(0)" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="上移" name="base-opr" type="up-move"><i class="fa fa-arrow-up" aria-hidden="true"></i></a>
+                </div>
+                <div class="d-inline-block ">
+                    <div class="input-group input-group-sm ml-2 mt-1">
+                        <div class="input-group-prepend">
+                            <span class="input-group-text" id="expr">表达式</span>
+                        </div>
+                        <input type="text" class="form-control m-0" style="width: 270px" value="">
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+    <div class="content-wrap">
+        <div class="c-header p-0"></div>
+        <div class="row w-100 sub-content">
+            <div class="c-body col-8">
+                <div class="sjs-height-1" id="pay-spread">
+                </div>
+            </div>
+            <div class="c-body col">
+                <div class="side-bar-1"></div>
+                <div class="sjs-sh-1">
+                    <table class="table table-bordered">
+                        <tr><th></th><th>可选基数</th><th>计算代号</th><th>值</th></tr>
+                        <% for (let iBase = 0; iBase < calcBase.length; iBase++) { %>
+                        <tr>
+                            <td><%- iBase + 1 %></td>
+                            <td><%- calcBase[iBase].name %></td>
+                            <td><%- calcBase[iBase].code %></td>
+                            <% if (calcBase[iBase].code === 'bqyf') { %>
+                            <td class="text-right">--</td>
+                            <% } else {%>
+                            <td class="text-right"><%- (ctx.tender.info.display.thousandth ? ctx.helper.formatNum(calcBase[iBase].value, '#,##0.######') : calcBase[iBase].value) %></td>
+                            <% } %>
+                        </tr>
+                        <% } %>
+                    </table>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+<div style="display: none">
+    <img src="/public/images/file_clip.png" id="rela-file-icon" />
+    <img src="/public/images/file_clip_hover.png" id="rela-file-hover" />
+</div>
+<script>
+    const readOnly = <%- ctx.phasePay.readOnly %>;
+    const details = JSON.parse('<%- JSON.stringify(pays) %>');
+</script>

+ 0 - 0
app/view/phase_pay/detail_modal.ejs


+ 25 - 44
app/view/phase_pay/index.ejs

@@ -35,46 +35,46 @@
                     <% for (const pay of phasePays) { %>
                     <tr>
                         <td>
-                            <a href="<%- '/tender/' + pay.tid + '/pay/' + pay.phase_order %>" target="_blank">第 <%- pay.phase_order %> 期</a>
+                            <a href="<%- '/tender/' + pay.tid + '/pay/' + pay.phase_order + '/detail' %>" target="_blank">第 <%- pay.phase_order %> 期</a>
                             <% if (pay.audit_status !== auditConst.status.checked && pay.create_user_id === ctx.session.sessionUser.accountId) { %>
                             <a href="#edit" class="edit-pay" data-id="<%- pay.id %>" data-toggle="modal" data-target="#edit"><i class="fa fa-pencil-square-o "></i></a>
                             <% } %>
                         </td>
-                        <td class="text-center"><%- s.phase_date %></td>
+                        <td class="text-center"><%- pay.phase_date %></td>
                         <td class="text-center">
                             <% for (const s of pay.rela_stage) { %>
-                            <a href="<%- '/tender/' + pay.tid + '/measure/stage/' + s.sorder %>" target="_blank">第 <%- s.sorder %> 期</a>
+                            <a href="<%- '/tender/' + pay.tid + '/measure/stage/' + s.stage_order %>" target="_blank">第 <%- s.stage_order %> 期</a>
                             <% } %>
                         </td>
-                        <td class="text-right"><%- s.display_pay_tp %></td>
-                        <td class="text-right"><%- s.display_cut_tp %></td>
-                        <td class="text-right"><%- s.display_yf_tp %></td>
-                        <td class="text-right"><%- s.display_sf_tp %></td>
-                        <td class="text-right"><%- s.display_end_yf_tp %></td>
-                        <td class="text-right"><%- s.display_end_sf_tp %></td>
-                        <td class="<%- auditConst.auditProgressClass[s.status] %>">
-                            <% if (s.status === auditConst.status.checked && s.final_auditor_str) { %>
-                            <a href="#sp-list" data-toggle="modal" data-target="#sp-list" s-order="<%- s.order %>"><%- s.final_auditor_str %></a>
+                        <td class="text-right"><%- pay.display_pay_tp %></td>
+                        <td class="text-right"><%- pay.display_cut_tp %></td>
+                        <td class="text-right"><%- pay.display_yf_tp %></td>
+                        <td class="text-right"><%- pay.display_sf_tp %></td>
+                        <td class="text-right"><%- pay.display_end_yf_tp %></td>
+                        <td class="text-right"><%- pay.display_end_sf_tp %></td>
+                        <td class="<%- auditConst.info[pay.audit_status].class %>">
+                            <% if (pay.audit_status === auditConst.status.checked && pay.final_auditor_str) { %>
+                                <a href="#sp-list" data-toggle="modal" data-target="#sp-list" s-order="<%- pay.phase_order %>"><%- pay.final_auditor_str %></a>
                             <% } else { %>
-                            <% if (s.curAuditors.length > 0) { %>
-                            <% if (s.curAuditors[0].audit_type === auditType.key.common) { %>
-                            <a href="#sp-list" data-toggle="modal" data-target="#sp-list" s-order="<%- s.order %>"><%- s.curAuditors[0].name %><%if (s.curAuditors[0].role !== '' && s.curAuditors[0].role !== null) { %>-<%- s.curAuditors[0].role %><% } %></a>
-                            <% } else { %>
-                            <a href="#sp-list" data-toggle="modal" data-target="#sp-list" s-order="<%- s.order %>"><%- ctx.helper.transFormToChinese(s.curAuditors[0].audit_order) + '审' %></a>
-                            <% } %>
-                            <% } %>
+                                <% if (pay.curAuditors.length > 0) { %>
+                                    <% if (pay.curAuditors[0].audit_type === auditType.key.common) { %>
+                                        <a href="#sp-list" data-toggle="modal" data-target="#sp-list" s-order="<%- pay.phase_order %>"><%- pay.curAuditors[0].name %><%if (pay.curAuditors[0].role !== '' && pay.curAuditors[0].role !== null) { %>-<%- pay.curAuditors[0].role %><% } %></a>
+                                    <% } else { %>
+                                        <a href="#sp-list" data-toggle="modal" data-target="#sp-list" s-order="<%- pay.phase_order %>"><%- ctx.helper.transFormToChinese(pay.curAuditors[0].audit_order) + '审' %></a>
+                                    <% } %>
+                                <% } %>
                             <% } %>
-                            <%- auditConst.auditProgress[s.status] %>
+                            <%- auditConst.info[pay.audit_status].title %>
                         </td>
                         <td class="text-center">
-                            <% if (s.status === auditConst.status.uncheck && s.user_id === ctx.session.sessionUser.accountId) { %>
-                            <a href="<%- '/tender/' + ctx.tender.id + '/measure/stage/' + s.order %>" target="_blank" class="btn <%- auditConst.statusButtonClass[s.status] %> btn-sm"><%- auditConst.statusButton[s.status] %></a>
+                            <% if (pay.audit_status === auditConst.status.uncheck && pay.create_user_id === ctx.session.sessionUser.accountId) { %>
+                            <a href="<%- '/tender/' + pay.tid + '/pay/' + pay.phase_order %>" target="_blank" class="btn <%- auditConst.info[pay.audit_status].btnClass %> btn-sm"><%- auditConst.info[pay.audit_status].btnTitle %></a>
                             <% } else if (s.status === auditConst.status.checkNo && s.user_id === ctx.session.sessionUser.accountId) { %>
-                            <a href="<%- '/tender/' + ctx.tender.id + '/measure/stage/' + s.order %>" target="_blank" class="btn <%- auditConst.statusButtonClass[s.status] %> btn-sm"><%- auditConst.statusButton[s.status] %></a>
+                            <a href="<%- '/tender/' + pay.tid + '/pay/' + pay.phase_order %>" target="_blank" class="btn <%- auditConst.info[pay.audit_status].btnClass %> btn-sm"><%- auditConst.info[pay.audit_status].btnTitle %></a>
                             <% } else if ((s.status === auditConst.status.checking || s.status === auditConst.status.checkNoPre) && s.curAuditors && s.curAuditors.findIndex(x => { return x.aid === ctx.session.sessionUser.accountId; }) >= 0) { %>
-                            <a href="<%- '/tender/' + ctx.tender.id + '/measure/stage/' + s.order %>" target="_blank" class="btn <%- auditConst.statusButtonClass[s.status] %> btn-sm"><%- auditConst.statusButton[s.status] %></a>
+                            <a href="<%- '/tender/' + pay.tid + '/pay/' + pay.phase_order %>" target="_blank" class="btn <%- auditConst.info[pay.audit_status].btnClass %> btn-sm"><%- auditConst.info[pay.audit_status].btnTitle %></a>
                             <% } else { %>
-                            <span class="<%- auditConst.auditStringClass[s.status] %>"><%- auditConst.auditString[s.status] %></span>
+                            <span class="<%- auditConst.info[pay.audit_status].btnClass %>"><%- auditConst.info[pay.audit_status].btnTitle %></span>
                             <% } %>
                         </td>
                         <td> <%- pay.memo %></td>
@@ -86,22 +86,3 @@
         </div>
     </div>
 </div>
-<script src="/public/js/sub_menu.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();
-        }
-    });
-</script>

+ 85 - 0
app/view/phase_pay/modal.ejs

@@ -0,0 +1,85 @@
+<div class="modal" id="add-qi" data-backdrop="static" aria-modal="true" role="dialog">
+    <div class="modal-dialog" role="document">
+        <form class="modal-content" action="pay/add" method="POST" onsubmit="return checkAddValid();">
+            <div class="modal-header">
+                <h5 class="modal-title">添加新一期</h5>
+            </div>
+            <div class="modal-body">
+                <div class="form-group form-group-sm">
+                    <label>支付期</label>
+                    <input class="form-control form-control-sm" value="第 <%- (phasePays.length + 1) %> 期" type="text" readonly="">
+                    <input type="hidden" value="<%- (phasePays.length + 1) %>" name="phase_order">
+                </div>
+                <div class="form-group form-group-sm">
+                    <label>支付年月</label>
+                    <input class="datepicker-here form-control form-control-sm" name="date" placeholder="点击选择年月" data-view="months" data-min-view="months" data-date-format="yyyy-MM" data-language="zh" type="text">
+                </div>
+                <div class="form-group form-group-sm">
+                    <label>计量期</label>
+                    <select class="form-control form-control-sm" name="stage">
+                        <% for (const s of validStages) { %>
+                            <option value="<%- s.order %>" <%- (s.order === validStages[0].order ? 'selected' : '') %>>第 <%- s.order %> 期</option>
+                        <% } %>
+                    </select>
+                </div>
+                <div class="form-group form-group-sm">
+                    <label>支付期备注</label>
+                    <textarea class="form-control form-control-sm" rows="3" name="memo"></textarea>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>" />
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
+                <button type="submit" class="btn btn-sm btn-primary" id="add-qi-ok">确定</button>
+            </div>
+        </form>
+    </div>
+</div>
+<div class="modal" id="edit-qi" data-backdrop="static" aria-modal="true" role="dialog">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content" action="pay/save" method="POST" onsubmit="return checkEditValid();">
+            <div class="modal-header">
+                <h5 class="modal-title">编辑</h5>
+            </div>
+            <div class="modal-body">
+                <div class="form-group form-group-sm">
+                    <label>支付期</label>
+                    <input class="form-control form-control-sm" value="第 <%- (phasePays.length + 1) %> 期" type="text" readonly="">
+                </div>
+                <div class="form-group form-group-sm">
+                    <label>支付年月</label>
+                    <input class="datepicker-here form-control form-control-sm" name="date" placeholder="点击选择年月" data-view="months" data-min-view="months" data-date-format="yyyy-MM" data-language="zh" type="text">
+                </div>
+                <div class="form-group form-group-sm">
+                    <label>支付期备注</label>
+                    <textarea class="form-control form-control-sm" rows="3" name="memo"></textarea>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <input type="hidden" value="" name="phase_id">
+                <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>" />
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
+                <button type="button" class="btn btn-sm btn-primary" id="add-qi-ok">确定</button>
+            </div>
+        </div>
+    </div>
+</div>
+<script>
+    $('.datepicker-here').datepicker({
+        autoClose: true,
+    });
+    const checkAddValid = function() {
+        if ($('[name=date]', '#add-qi').val() == '') {
+            toastr.error('请选择计量年月');
+            return false;
+        }
+
+        if ($('[name=stage]', '#add-qi').val() == '') {
+            toastr.error('请选择计量期');
+            return false;
+        }
+    }
+    const checkEditValid = function() {
+
+    }
+</script>

+ 14 - 0
app/view/phase_pay/sub_menu.ejs

@@ -0,0 +1,14 @@
+<div class="panel-sidebar" id="sub-menu">
+    <div class="sidebar-title" data-toggle="tooltip" data-placement="right" data-original-title="<%- ctx.tender.data.name %>">
+        <%- (ctx.tender.data.name.length > 15 ? ctx.tender.data.name.substring(0,15) + '...' : ctx.tender.data.name) %>
+    </div>
+    <div class="scrollbar-auto">
+        <% include ./sub_menu_list.ejs %>
+        <div class="side-fold"><a href="javascript: void(0)" data-toggle="tooltip" data-placement="top" data-original-title="折叠侧栏" id="to-mini-menu"><i class="fa fa-upload fa-rotate-270"></i></a></div>
+    </div>
+    <script>
+        new Vue({
+            el: '.scrollbar-auto',
+        });
+    </script>
+</div>

+ 3 - 0
app/view/phase_pay/sub_menu_list.ejs

@@ -0,0 +1,3 @@
+<nav-menu title="返回" url="/tender/<%= ctx.tender.id %>/pay" tclass="text-primary" ml="1" icon="fa-chevron-left"></nav-menu>
+<nav-menu title="支付明细" url="/tender/<%= ctx.tender.id %>/pay/<%= ctx.phasePay.order + '/detail'%>" ml="3" active="<%= (ctx.url.indexOf('detail') >= 0 ? 1 : -1) %>"></nav-menu>
+<nav-menu title="输出报表" url="/tender/<%= ctx.tender.id %>/pay/<%= ctx.phasePay.order + '/report'%>" ml="3" active="<%= (ctx.url.indexOf('report') >= 0 ? 1 : -1) %>"></nav-menu>

+ 16 - 0
app/view/phase_pay/sub_mini_menu.ejs

@@ -0,0 +1,16 @@
+<!--折起的菜单-->
+<div class="min-side" id="sub-mini-menu" style="display: none;">
+    <div id="sub-mini-hint" class="side-switch" data-container="body" data-toggle="popover" data-placement="bottom" data-content="这里打开收起的菜单栏"></div>
+    <div class="side-switch">
+        <i class="fa fa-bars"></i>
+    </div>
+    <div class="side-menu" id="mini-menu-list" style="display: none">
+        <% include ./sub_menu_list.ejs %>
+        <div class="side-fold"><a href="javascript: void(0);" data-toggle="tooltip" data-placement="top" data-original-title="展开侧栏" id="to-menu"><i class="fa fa-upload fa-rotate-90"></i></a></div>
+    </div>
+</div>
+<script>
+    new Vue({
+        el: '.side-menu',
+    });
+</script>

+ 5 - 0
app/view/revise/info.ejs

@@ -193,6 +193,8 @@
                     </div>
                     <div id="sum-load-miss" class="tab-pane tab-select-show">
                     </div>
+                    <div id="gcl-gather" class="tab-pane tab-select-show">
+                    </div>
                 </div>
             </div>
         </div>
@@ -233,6 +235,9 @@
                 <li class="nav-item">
                     <a class="nav-link" content="#sum-load-miss" id="sum-load-miss-tab" href="javascript: void(0);" style="display: none;">导入信息</a>
                 </li>
+                <li class="nav-item">
+                    <a class="nav-link" content="#gcl-gather" id="gcl-gather-tab" href="javascript: void(0);">清单汇总</a>
+                </li>
             </ul>
         </div>
     </div>

+ 27 - 1
config/web.js

@@ -274,6 +274,7 @@ const JsFiles = {
                     '/public/js/spreadjs_rela/spreadjs_zh.js',
                     '/public/js/shares/sjs_setting.js',
                     '/public/js/shares/cs_tools.js',
+                    '/public/js/shares/cs_gcl_gather.js',
                     '/public/js/shares/merge_peg.js',
                     '/public/js/shares/new_tag.js',
                     '/public/js/zh_calc.js',
@@ -680,7 +681,32 @@ const JsFiles = {
             },
         },
         phasePay: {
-
+            list: {
+                files: [
+                    '/public/js/datepicker/datepicker.min.js',
+                    '/public/js/datepicker/datepicker.zh.js',
+                ],
+                mergeFiles: [
+                    '/public/js/component/menu.js',
+                    '/public/js/sub_menu.js',
+                ],
+                mergeFile: 'phase_pay_list',
+            },
+            detail: {
+                files: [
+                    '/public/js/spreadjs/sheets/v11/gc.spread.sheets.all.11.2.2.min.js',
+                    '/public/js/decimal.min.js',
+                ],
+                mergeFiles: [
+                    '/public/js/component/menu.js',
+                    '/public/js/sub_menu.js',
+                    '/public/js/spreadjs_rela/spreadjs_zh.js',
+                    '/public/js/path_tree.js',
+                    '/public/js/shares/sjs_setting.js',
+                    '/public/js/phase_pay_detail.js',
+                ],
+                mergeFile: 'phase_pay_detail',
+            }
         },
         measure: {
             compare: {