瀏覽代碼

1. 合同支付1.0
2. 标段属性
3. 部分单元测试

MaiXinRong 6 年之前
父節點
當前提交
9ed4807214

+ 44 - 6
app/const/deal_pay.js

@@ -15,9 +15,47 @@ const payType = {
     wc: 4
 };
 const payTemplate = [
-    {order: 1, name: '本期应付', ptype: payType.yf},
-    {order: 2, name: '本期实付', ptype: payType.sf},
-    {order: 3, name: '本期计量完成', ptype: payType.wc},
-    {order: 4, name: '质量保证金', ptype: payType.normal},
-    {order: 5, name: '扣回开工预付款', ptype: payType.normal},
-];
+    {order: 1, name: '本期应付', ptype: payType.yf, minus: false},
+    {order: 2, name: '本期实付', ptype: payType.sf, minus: false},
+    {order: 3, name: '本期计量完成', ptype: payType.wc, minus: false, expr: 'bqwc'},
+    {order: 4, name: '质量保证金', ptype: payType.normal, minus: true},
+    {order: 5, name: '扣回开工预付款', ptype: payType.normal, minus: true, expr: '(bqwc/htj)*2*kgyfk', sexpr: 'htj*30%', rexpr: 'kgyfk'},
+];
+
+const calcBase = [
+    {name: '签约合同价', code: 'htj'},
+    {name: '签约合同价(不含暂列金)', code: 'htjszl'},
+    {name: '签约开工预付款', code: 'kgyfk'},
+    {name: '签约材料预付款', code: 'clyfk'},
+    {name: '本期完成计量', code: 'bqwc'},
+    {name: '100章本期完成计量', code: 'ybbqwc'},
+];
+
+const chapterDetailType = {
+    chapter: 1,
+    withoutChapter: 21,
+    gclSum: 11,
+    xmjSum: 31,
+    sum: 41
+};
+
+const chapterDetail = [
+    {name: '清单 第100章 总则', cType: 1, serialNo: 1, filter: '^1[0-9]{2}-'},
+    {name: '清单 第200章 路基', cType: 1, serialNo: 2, filter: '^2[0-9]{2}-'},
+    {name: '清单 第300章 路面', cType: 1, serialNo: 3, filter: '^3[0-9]{2}-'},
+    {name: '清单 第400章 桥梁、涵洞', cType: 1, serialNo: 4, filter: '^4[0-9]{2}-'},
+    {name: '清单 第500章 隧道', cType: 1, serialNo: 5, filter: '^5[0-9]{2}-'},
+    {name: '清单 第600章 安全设施及预埋管线', cType: 1, serialNo: 6, filter: '^6[0-9]{2}-'},
+    {name: '清单 第700章 绿化及环境保护', cType: 1, serialNo: 7, filter: '^7[0-9]{2}-'},
+    {name: '未计入章节清单合计', cType: 21, serialNo: 8},
+    {name: '清单小计(A)', cType: 11, serialNo: 9},
+    {name: '非清单项费用(B)', cType: 31, serialNo: 10},
+    {name: '合计(C=A+B)', cType: 41, serialNo: 11},
+];
+
+module.exports = {
+    payType,
+    payTemplate,
+    calcBase,
+    chapterDetail,
+};

+ 0 - 2
app/const/tender_info.js

@@ -59,8 +59,6 @@ const defaultInfo = {
     decimal: {
         qty: 3,
         tp: 2,
-        deal: false,
-        dealTp: 2,
         pay: false,
         payTp: 0,
     },

+ 129 - 4
app/controller/stage_controller.js

@@ -383,6 +383,12 @@ module.exports = app => {
             }
         }
 
+        /**
+         * 获取计算参数
+         * @param ctx
+         * @returns {Promise<*[]>}
+         * @private
+         */
         async _getPayCalcBase(ctx) {
             //const calcBase = JSON.parse(JSON.stringify(payConst.calcBase));
             const calcBase = [
@@ -400,7 +406,7 @@ module.exports = app => {
                         cb.value = param.contractPrice;
                         break;
                     case 'htjszl':
-                        cb.value = this._.subtract(param.contractPrice, param.zanLiePrice);
+                        cb.value = this.app._.subtract(param.contractPrice, param.zanLiePrice);
                         break;
                     case 'kgyfk':
                         cb.value = param.startAdvance;
@@ -409,15 +415,18 @@ module.exports = app => {
                         cb.value = param.materialAdvance;
                         break;
                     case 'bqwc':
-                        cb.value = await ctx.stage.stageBills.getSumTotalPrice(ctx.stage);
+                        const sum = await ctx.service.stageBills.getSumTotalPrice(ctx.stage);
+                        cb.value = this.app._.add(sum.contract_tp, sum.qc_tp);
                         break;
                     case 'ybbqwc':
-                        cb.value = await ctx.sevice.stageBills.getSumTotalPrice(ctx.stage, '^1\d{2}-');
+                        const sumGcl = await ctx.service.stageBills.getSumTotalPriceGcl(ctx.stage, '^1[0-9]{2}-');
+                        cb.value = this.app._.add(sumGcl.contract_tp, sumGcl.qc_tp);
                         break;
                     default:
                         cb.value = 0;
                 }
             }
+            return calcBase;
         }
 
         /**
@@ -429,7 +438,18 @@ module.exports = app => {
             try {
                 await this._getStage(ctx);
                 const renderData = this._getDefaultRenderData(ctx);
-                renderData.pay = await ctx.service.stagePos.get
+                const dealPay = await ctx.service.pay.getAllDataByCondition({
+                    where: { tid: ctx.tender.id },
+                    orders: [['order', 'acs']],
+                });
+                if (dealPay && dealPay.length > 0) {
+                    renderData.dealPay = dealPay;
+                } else {
+                    await ctx.service.pay.addDefaultPayData();
+                    renderData.dealPay = await ctx.service.pay.getAllDataByCondition({where: { tid: ctx.tender.id } });
+                }
+                renderData.calcBase = await this._getPayCalcBase(ctx);
+                renderData.jsFiles = this.app.jsFiles.common.concat(this.app.jsFiles.stage.pay);
                 await this.layout('stage/pay.ejs', renderData, 'stage/pay_modal.ejs');
             } catch (err) {
                 this.log(err);
@@ -437,6 +457,111 @@ module.exports = app => {
             }
         }
 
+        async savePayData(ctx) {
+            try {
+                await this._getStage(ctx);
+                // 检查登录用户,是否可操作
+                if (ctx.session.sessionUser.accountId === ctx.stage.user_id) {
+                    if (ctx.stage.status === auditConst.flow.status.checking || ctx.stage.status === auditConst.flow.status.checked) {
+                        throw '该计量期当前您无权操作';
+                    }
+                } else {
+                    if (!ctx.stage.curAuditor || ctx.stage.curAuditor.id !== ctx.session.sessionUser.accountId) {
+                        throw '该计量期当前您无权操作';
+                    }
+                }
+
+                const data = JSON.parse(ctx.request.body.data);
+                const responseData = { err: 0, msg: '', data: {}, };
+                switch (data.type) {
+                    case 'add':
+                        responseData.data = await ctx.service.pay.add();
+                        break;
+                    case 'del':
+                        responseData.data = await ctx.service.pay.del(data.id);
+                        break;
+                    case 'changeOrder':
+                        responseData.data = await ctx.service.pay.changeOrder(data.id1, data.id2);
+                        break;
+                    case 'info':
+                        responseData.data = await ctx.service.pay.save(data.updateData);
+                        break;
+                    case 'stage':
+                        responseData.data = await ctx.service.stagePay.saveData(data.updateData);
+                        break;
+                }
+                ctx.body = responseData;
+            } catch(err) {
+                this.log(err);
+                ctx.body = {err: 1, msg: err.toString(), data: null};
+            }
+        }
+
+        /**
+         * 合同支付 - 章节明细
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async chapterDetail(ctx) {
+            const _ = this.app._;
+            const fields = ['contract_tp', 'qc_tp', 'gather_tp', 'end_gather_tp'];
+            function assignStageData(chapter, curStage, preStage) {
+                chapter.contract_tp = curStage.contract_tp;
+                chapter.qc_tp = curStage.qc_tp;
+                chapter.gather_tp = _.add(curStage.contract_tp, curStage.qc_tp);
+                if (preStage) {
+                    chapter.pre_contract_tp = preStage.contract_tp;
+                    chapter.pre_qc_tp = preStage.qc_tp;
+                    chapter.pre_gather_tp = _.add(preStage.contract_tp, preStage.qc_tp);
+                    chapter.end_contract_tp = _.add(curStage.contract_tp, preStage.contract_tp);
+                    chapter.end_qc_tp = _.add(curStage.qc_tp, preStage.qc_tp);
+                    chapter.end_gather_tp = _.add(chapter.gather_tp, chapter.pre_gather_tp);
+                } else {
+                    chapter.end_contract_tp = curStage.contract_tp;
+                    chapter.end_qc_tp = curStage.qc_tp;
+                    chapter.end_gather_tp = chapter.gather_tp;
+                }
+            }
+
+            try {
+                await this._getStage(ctx);
+                const chapterDetail = JSON.parse(JSON.stringify(payConst.chapterDetail));
+                const calcDetail = _.sortBy(chapterDetail, ['cType']);
+                for (const cd of calcDetail) {
+                    switch (cd.cType) {
+                        case 1:
+                            const tp = await ctx.service.stageBills.getSumTotalPriceGcl(ctx.stage, cd.filter);
+                            assignStageData(cd, tp);
+                            break;
+                        case 11:
+                            assignStageData(cd, await ctx.service.stageBills.getSumTotalPriceGcl(ctx.stage));
+                            break;
+                        case 21:
+                            const sum = _.find(calcDetail, {cType: 11});
+                            const chapters = _.filter(calcDetail, {cType: 1});
+                            for (const f of fields) {
+                                cd[f] = _.subtract(sum[f], _.sumBy(chapters, f));
+                            }
+                            break;
+                        case 31:
+                            assignStageData(cd, await ctx.service.stageBills.getSumTotalPriceNotGcl(ctx.stage));
+                            break;
+                        case 41:
+                            const sum1 = _.find(calcDetail, {cType: 11});
+                            const sum2 = _.find(calcDetail, {cType: 31});
+                            for (const f of fields) {
+                                cd[f] = _.add(sum1.value, sum2.value);
+                            }
+                            break;
+                    }
+                }
+                ctx.body = {err: 0, msg: '', data: {detail: chapterDetail, decimal: ctx.tender.info.decimal}};
+            } catch (err) {
+                this.log(err);
+                ctx.body = {err: 1, msg: err.toString(), data: null};
+            }
+        }
+
         /**
          * 变更令
          * @param ctx

+ 21 - 0
app/extend/helper.js

@@ -537,6 +537,27 @@ module.exports = {
     },
 
     /**
+     * 过滤无效数据
+     *
+     * @param obj
+     * @param fields - 有效数据的数组
+     */
+    filterValidFields(data, fields) {
+
+        if (data) {
+            const result = {};
+            for (const prop in data) {
+                if (fields.indexOf(prop) !== -1) {
+                    result[prop] = data[prop];
+                }
+            }
+            return result;
+        } else {
+            return data;
+        }
+    },
+
+    /**
      * 四舍五入(统一,方便以后万一需要置换)
      * @param {Number} value - 舍入的数字
      * @param {Number} decimal - 要保留的小数位数

+ 61 - 0
app/middleware/stage_check.js

@@ -0,0 +1,61 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const auditConst = require('../const/audit').flow;
+
+module.exports = options => {
+    /**
+     * 期校验 中间件
+     * 1. 读取期数据
+     * 2. 检验用户是否参与期(不校验具体权限)
+     *
+     * @param {function} next - 中间件继续执行的方法
+     * @return {void}
+     */
+    return function* stageCheck(next) {
+        try {
+            // 读取标段数据
+            const stageOrder = parseInt(this.params.id);
+            if (stageOrder <= 0) {
+                throw '您访问的期不存在';
+            }
+            console.log(this.tender);
+            console.log(stageOrder);
+            const stage = yield this.service.stage.getDataByCondition({
+                tid: this.tender.id,
+                order: stageOrder,
+            });
+            console.log(stage);
+            if (!stage) {
+                throw '期数据错误'
+            }
+            stage.auditors = yield this.service.stageAudit.getAuditors(stage.id, stage.times);
+            stage.curAuditor = yield this.service.stageAudit.getCurAuditor(stage.id, stage.times);
+            // todo 校验权限 (标段参与人、分享)
+            this.stage = stage;
+            yield next;
+        } catch (err) {
+            console.log(err);
+            // 输出错误到日志
+            if (err.stack) {
+                this.logger.error(err);
+            } else {
+                this.getLogger('fail').info(JSON.stringify({
+                    error: err,
+                    project: this.session.sessionProject,
+                    user: this.session.sessionUser,
+                    body: this.session.body,
+                }));
+            }
+            // 重定向值标段管理
+            this.redirect(this.request.headers.referer);
+        }
+    };
+};

+ 452 - 23
app/public/js/spreadjs_rela/spreadjs_zh.js

@@ -30,6 +30,34 @@ proto.dottedLine = function (x1, y1, x2, y2, interval = 4) {
 const spreadNS = GC.Spread.Sheets;
 // SpreadJs常用方法
 const SpreadJsObj = {
+    initSpreadSettingEvents: function (setting, events) {
+        const getEvent = function (eventName) {
+            const names = eventName.split('.');
+            let event = events;
+            for (let name of names) {
+                if (event[name]) {
+                    event = event[name];
+                } else {
+                    return null;
+                }
+            }
+            if (event && Object.prototype.toString.apply(event) !== "[object Function]") {
+                return null;
+            } else {
+                return event;
+            }
+        };
+
+        for (const col of setting.cols) {
+            if (col.readOnly && Object.prototype.toString.apply(col.readOnly) === "[object String]") {
+                col.readOnly = getEvent(col.readOnly);
+            }
+            if (col.getValue && Object.prototype.toString.apply(col.getValue) === "[object String]") {
+                col.getValue = getEvent(col.getValue);
+            }
+        }
+    },
+
     DataType: {
         Data: 'data',
         Tree: 'tree',
@@ -164,6 +192,25 @@ const SpreadJsObj = {
         return target;
     },
     /**
+     * 获取写入sheet的数据序列
+     * data:sheet.zh_data, tree: sheet.zh_tree.nodes
+     * @param sheet
+     * @returns {*}
+     */
+    getSortData: function (sheet) {
+        if (sheet.zh_dataType) {
+            if (sheet.zh_dataType === this.DataType.Data) {
+                return sheet.zh_data;
+            } else if (sheet.zh_dataType === this.DataType.Tree) {
+                return sheet.zh_tree.nodes;
+            } else {
+                return null;
+            }
+        } else {
+            return null;
+        }
+    },
+    /**
      * sheet中 使用delete键,触发EndEdited事件
      * @param {GC.Spreads.Sheets.Workbook} spread
      * @param {function} fun
@@ -241,9 +288,17 @@ const SpreadJsObj = {
         if (!data) { return }
         sheet.zh_setting.cols.forEach(function (col, j) {
             const cell = sheet.getCell(row, j);
-            if (col.field !== '' && data[col.field]) {
+            if (col.getValue && Object.prototype.toString.apply(col.getValue) === "[object Function]") {
+                cell.value(col.getValue(data));
+            } else if (col.field !== '' && data[col.field]) {
                 cell.value(data[col.field]);
             }
+            if (col.font) {
+                cell.font(col.font);
+            }
+            if (col.foreColor) {
+                cell.foreColor(col.foreColor);
+            }
             if (col.readOnly && Object.prototype.toString.apply(col.readOnly) === "[object Function]") {
                 cell.locked(col.readOnly(data) || sheet.zh_setting.readOnly || false).vAlign(1).hAlign(col.hAlign);
             } else {
@@ -252,8 +307,58 @@ const SpreadJsObj = {
             if (col.formatter) {
                 cell.formatter(col.formatter);
             }
+            if (sheet.zh_setting.getColor && Object.prototype.toString.apply(sheet.zh_setting.getColor) === "[object Function]") {
+                cell.backColor(sheet.zh_setting.getColor(data, col, sheet.getDefaultStyle().backColor));
+            }
         });
     },
+    _defineColCellType: function (sheet, col, colSetting) {
+        if(colSetting.cellType === 'ellipsis') {
+            if (!sheet.extendCellType.ellipsis) {
+                sheet.extendCellType.ellipsis = this.CellType.getEllipsisTextCellType();
+            }
+            sheet.getRange(-1, col, -1, 1).cellType(sheet.extendCellType.ellipsis);
+        }
+        if(colSetting.cellType === 'html') {
+            if (!sheet.extendCellType.html) {
+                sheet.extendCellType.html = this.CellType.getHtmlCellType();
+            }
+            sheet.getRange(-1, col, -1, 1).cellType(sheet.extendCellType.html);
+        }
+        if (colSetting.cellType === 'image') {
+            if (!sheet.extendCellType.image) {
+                sheet.extendCellType.image = this.CellType.getImageCellType();
+            }
+            sheet.getRange(-1, col, -1, 1).cellType(sheet.extendCellType.image);
+        }
+        if (colSetting.cellType === 'imageBtn') {
+            if (!sheet.extendCellType.image) {
+                sheet.extendCellType.imageBtn = this.CellType.getImageButtonCellType();
+            }
+            sheet.getRange(-1, col, -1, 1).cellType(sheet.extendCellType.imageBtn);
+        }
+        if (colSetting.cellType === 'tree') {
+            if (!sheet.extendCellType.tree) {
+                sheet.extendCellType.tree = this.CellType.getTreeNodeCellType();
+            }
+            sheet.getRange(-1, col, -1, 1).cellType(sheet.extendCellType.tree);
+        }
+        if (colSetting.cellType === 'tip') {
+            if (!sheet.extendCellType.tip) {
+                sheet.extendCellType.tip = this.CellType.getTipCellType();
+            }
+            sheet.getRange(-1, col, -1, 1).cellType(sheet.extendCellType.tip);
+        }
+        if (colSetting.cellType === 'checkbox') {
+            if (!sheet.extendCellType.checkbox) {
+                sheet.extendCellType.checkbox = new spreadNS.CellTypes.CheckBox();
+            }
+            sheet.getRange(-1, col, -1, 1).cellType(sheet.extendCellType.checkbox);
+        }
+        if (colSetting.formatter) {
+            sheet.getRange(-1, col, -1, 1).formatter(colSetting.formatter);
+        }
+    },
     /**
      * 整个sheet重新加载数据
      * @param {GC.Spread.Sheets.Worksheet} sheet
@@ -278,26 +383,7 @@ const SpreadJsObj = {
             // 设置列单元格格式
             sheet.zh_setting.cols.forEach(function (col, j) {
                 //if (!col.cellType) { return; }
-
-                if (col.cellType === 'tree') {
-                    if (!sheet.extendCellType.tree) {
-                        sheet.extendCellType.tree = self.CellType.getTreeNodeCellType();
-                    }
-                    sheet.getRange(-1, j, -1, 1).cellType(sheet.extendCellType.tree);
-                } else if (col.cellType === 'tip') {
-                    if (!sheet.extendCellType.tip) {
-                        sheet.extendCellType.tip = self.CellType.getTipCellType();
-                    }
-                    sheet.getRange(-1, j, -1, 1).cellType(sheet.extendCellType.tip);
-                } else if (col.cellType === 'checkbox') {
-                    if (!sheet.extendCellType.checkbox) {
-                        sheet.extendCellType.checkbox = new spreadNS.CellTypes.CheckBox();
-                    }
-                    sheet.getRange(-1, j, -1, 1).cellType(sheet.extendCellType.checkbox);
-                }
-                if (col.formatter) {
-                    sheet.getRange(-1, j, -1, 1).formatter(col.formatter);
-                }
+                self._defineColCellType(sheet, j, col);
             });
             this.endMassOperation(sheet);
         } catch (err) {
@@ -803,7 +889,7 @@ const SpreadJsObj = {
             return new TreeNodeCellType();
         },
         /**
-         * 获取带悬浮提示CellType
+         * 获取 带悬浮提示CellType
          * @returns {TipCellType}
          */
         getTipCellType: function () {
@@ -873,6 +959,349 @@ const SpreadJsObj = {
             };
 
             return new TipCellType();
-        }
+        },
+        /**
+         * 获取 带图片的cellType(图片需在document中定义好img,并写入col的img属性)
+         *
+         * img:
+         * 1. 整列固定,则传入img的select
+         *    e.g. {title: '附件', field: 'attachment', cellType: 'image', img = '#attachment-img'}
+         *
+         * 2. 各单元格自定义,则
+         *    e.g. {title: '附件', field: 'attachment', cellType: 'image', img = getAttachmentImage}
+         *    function getAttachmentImage (data) {
+         *      $('#attachment-img').url = data.attachmentImageUrl;
+         *      return $('#attachment-img')[0];
+         *    }
+         *
+         * @returns {ImageCellType}
+         */
+        getImageCellType: function () {
+            const indent = 10;
+            const ImageCellType = function (){};
+            ImageCellType.prototype = new spreadNS.CellTypes.Text();
+            const proto = ImageCellType.prototype;
+            proto.getImage = function (sheet, iRow, iCol) {
+                const col = sheet.zh_setting.cols[iCol];
+                let imgSource = col.img;
+                if (imgSource && Object.prototype.toString.apply(imgSource) === "[object Function]") {
+                    const sortData = SpreadJsObj.getSortData(sheet);
+                    const data = sortData ? sortData[iRow] : null;
+                    return data ? imgSource(data) : null;
+                } else {
+                    return $(imgSource)[0] ? $(imgSource)[0] : null;
+                }
+
+            };
+            proto.paint = function (canvas, value, x, y, w, h, style, options) {
+                const img = this.getImage(options.sheet, options.row, options.col);
+                if (img) {
+                    if (style.backColor) {
+                        canvas.save();
+                        canvas.fillStyle = style.backColor;
+                        canvas.fillRect(x, y, indent + img.width, h);
+                        canvas.restore();
+                    }
+                    canvas.drawImage(img, x + 10, y + (h - img.height) / 2);
+                    if (style.hAlign !== spreadNS.HorizontalAlign.left) {
+                        style.hAlign = spreadNS.HorizontalAlign.left;
+                    }
+                    x = x + indent + img.width;
+                    w = w - indent - img.width;
+                }
+                // Drawing Text
+                spreadNS.CellTypes.Text.prototype.paint.apply(this, [canvas, value, x, y, w, h, style, options]);
+            };
+            /**
+             * 获取点击信息
+             * @param {Number} x
+             * @param {Number} y
+             * @param {Object} cellStyle
+             * @param {Object} cellRect
+             * @param {Object} context
+             * @returns {{x: *, y: *, row: *, col: *|boolean|*[]|number|{}|UE.dom.dtd.col, cellStyle: *, cellRect: *, sheet: *|StyleSheet, sheetArea: *}}
+             */
+            proto.getHitInfo = function (x, y, cellStyle, cellRect, context) {
+                return {
+                    x: x,
+                    y: y,
+                    row: context.row,
+                    col: context.col,
+                    cellStyle: cellStyle,
+                    cellRect: cellRect,
+                    sheet: context.sheet,
+                    sheetArea: context.sheetArea
+                };
+            };
+            /**
+             * 鼠标点击
+             * @param {Object} hitinfo - 见getHitInfo
+             */
+            proto.processMouseDown = function (hitinfo) {
+                const img = this.getImage(hitinfo.sheet, hitinfo.row, hitinfo.col);
+                if (img) {
+                    const halfX = img.width / 2, halfY = img.height / 2;
+                    const centerX = hitinfo.cellRect.x + indent + halfX;
+                    const centerY = hitinfo.cellRect.y + hitinfo.cellRect.height / 2;
+
+                    // 点击展开节点时,如果已加载子项,则展开,反之这加载子项,展开
+                    if (Math.abs(hitinfo.x - centerX) < halfX && Math.abs(hitinfo.y - centerY) < halfY) {
+                        const imageClick = hitinfo.sheet.zh_setting ? hitinfo.sheet.zh_setting.imageClick : null;
+                        if (imageClick && Object.prototype.toString.apply(imageClick) === "[object Function]") {
+                            const sortData = SpreadJsObj.getSortData(hitinfo.sheet);
+                            const data = sortData ? sortData[hitinfo.row] : null;
+                            imageClick(data);
+                        }
+                    }
+                }
+            };
+            return new ImageCellType();
+        },
+        /**
+         *
+         * 获取 带normal-hover-active按钮的cellType(需定义三张图片,须在document中定义好img,并写入col的normalImg, hoverImg, activeImg属性)
+         * 其中:normalImg必需,向下套用(不存在activeImg则使用hoverImg,不存在hoverImg则使用normalImg)
+         * 三个img均可像getImageCellType一样动态获取,参见getImageCellType注释
+         *
+         * @returns {ImageCellType}
+         */
+        getImageButtonCellType: function () {
+            let hover = 1, active = 2;
+            const indent = 10;
+            const ImageCellType = function (){};
+            ImageCellType.prototype = new spreadNS.CellTypes.Text();
+            const proto = ImageCellType.prototype;
+            proto.getImage = function (sheet, iRow, iCol) {
+                const col = sheet.zh_setting.cols[iCol];
+                let imgSource = col.normalImg;
+                const cell = sheet.getCell(iRow, iCol), tag = cell.tag();
+                if (tag === active) {
+                    imgSource = col.activeImg ? col.activeImg : (col.hoverImg ? col.hoverImg : col.normalImg);
+                } else if (tag === hover) {
+                    imgSource = col.hoverImg ? col.hoverImg : col.normalImg;
+                }
+                if (imgSource && Object.prototype.toString.apply(imgSource) === "[object Function]") {
+                    const sortData = SpreadJsObj.getSortData(sheet);
+                    const data = sortData ? sortData[iRow] : null;
+                    return data ? imgSource(data) : null;
+                } else {
+                    return $(imgSource)[0] ? $(imgSource)[0] : null;
+                }
+
+            };
+            proto.paint = function (canvas, value, x, y, w, h, style, options) {
+                const img = this.getImage(options.sheet, options.row, options.col);
+                if (img) {
+                    if (style.backColor) {
+                        canvas.save();
+                        canvas.fillStyle = style.backColor;
+                        canvas.fillRect(x, y, indent + img.width, h);
+                        canvas.restore();
+                    }
+                    canvas.drawImage(img, x + 10, y + (h - img.height) / 2);
+                    if (style.hAlign !== spreadNS.HorizontalAlign.left) {
+                        style.hAlign = spreadNS.HorizontalAlign.left;
+                    }
+                    x = x + indent + img.width;
+                    w = w - indent - img.width;
+                }
+                // Drawing Text
+                spreadNS.CellTypes.Text.prototype.paint.apply(this, [canvas, value, x, y, w, h, style, options]);
+            };
+            /**
+             * 获取点击信息
+             * @param {Number} x
+             * @param {Number} y
+             * @param {Object} cellStyle
+             * @param {Object} cellRect
+             * @param {Object} context
+             * @returns {{x: *, y: *, row: *, col: *|boolean|*[]|number|{}|UE.dom.dtd.col, cellStyle: *, cellRect: *, sheet: *|StyleSheet, sheetArea: *}}
+             */
+            proto.getHitInfo = function (x, y, cellStyle, cellRect, context) {
+                return {
+                    x: x,
+                    y: y,
+                    row: context.row,
+                    col: context.col,
+                    cellStyle: cellStyle,
+                    cellRect: cellRect,
+                    sheet: context.sheet,
+                    sheetArea: context.sheetArea
+                };
+            };
+            /**
+             * 鼠标点击
+             * @param {Object} hitinfo - 见getHitInfo
+             */
+            proto.processMouseEnter = function (hitinfo) {
+                const col = hitinfo.sheet.zh_setting.cols[hitinfo.col];
+                // Drawing Image
+                if (col.hoverImg) {
+                    const cell = hitinfo.sheet.getCell(hitinfo.row, hitinfo.col);
+                    cell.tag(hover);
+                    hitinfo.sheet.repaint(hitinfo.cellRect);
+                }
+            };
+            proto.processMouseLeave = function (hitinfo) {
+                const col = hitinfo.sheet.zh_setting.cols[hitinfo.col];
+                // Drawing Image
+                if (col.hoverImg) {
+                    const cell = hitinfo.sheet.getCell(hitinfo.row, hitinfo.col);
+                    cell.tag(null);
+                    hitinfo.sheet.repaint(hitinfo.cellRect);
+                }
+            };
+            proto.processMouseDown = function (hitinfo) {
+                const col = hitinfo.sheet.zh_setting.cols[hitinfo.col];
+                if (col.activeImg) {
+                    const cell = hitinfo.sheet.getCell(hitinfo.row, hitinfo.col);
+                    cell.tag(active);
+                    hitinfo.sheet.repaint(hitinfo.cellRect);
+                }
+            };
+            proto.processMouseUp = function (hitinfo) {
+                const imageClick = hitinfo.sheet.zh_setting ? hitinfo.sheet.zh_setting.imageClick : null;
+                if (imageClick && Object.prototype.toString.apply(imageClick) === "[object Function]") {
+                    const sortData = SpreadJsObj.getSortData(hitinfo.sheet);
+                    const data = sortData ? sortData[hitinfo.row] : null;
+                    imageClick(data);
+                    const cell = hitinfo.sheet.getCell(hitinfo.row, hitinfo.col);
+                    cell.tag(null);
+                    hitinfo.sheet.repaint(hitinfo.cellRect);
+                }
+            };
+            /*
+             注释部分以进入鼠标进入图片,点击图片为基准更新图片,鼠标快速移动时,可能失效
+              */
+            // proto.processMouseDown = function (hitinfo) {
+            //     const img = this.getImage(hitinfo.sheet, hitinfo.row, hitinfo.col);
+            //     const halfX = img.width / 2, halfY = img.height / 2;
+            //     const centerX = hitinfo.cellRect.x + indent + halfX;
+            //     const centerY = hitinfo.cellRect.y + hitinfo.cellRect.height / 2;
+            //
+            //     if (Math.abs(hitinfo.x - centerX) < halfX && Math.abs(hitinfo.y - centerY) < halfY) {
+            //         const cell = hitinfo.sheet.getCell(hitinfo.row, hitinfo.col);
+            //         cell.tag(down);
+            //         hitinfo.sheet.repaint(hitinfo.cellRect);
+            //     }
+            // };
+            // proto.processMouseUp = function (hitinfo) {
+            //     const img = this.getImage(hitinfo.sheet, hitinfo.row, hitinfo.col);
+            //     const halfX = img.width / 2, halfY = img.height / 2;
+            //     const centerX = hitinfo.cellRect.x + indent + halfX;
+            //     const centerY = hitinfo.cellRect.y + hitinfo.cellRect.height / 2;
+            //
+            //     // 点击展开节点时,如果已加载子项,则展开,反之这加载子项,展开
+            //     if (Math.abs(hitinfo.x - centerX) < halfX && Math.abs(hitinfo.y - centerY) < halfY) {
+            //         const imageClick = hitinfo.sheet.zh_setting ? hitinfo.sheet.zh_setting.imageClick : null;
+            //         if (imageClick && Object.prototype.toString.apply(imageClick) === "[object Function]") {
+            //             const sortData = SpreadJsObj.getSortData(hitinfo.sheet);
+            //             const data = sortData ? sortData[hitinfo.row] : null;
+            //             imageClick(data);
+            //             const cell = hitinfo.sheet.getCell(hitinfo.row, hitinfo.col);
+            //             cell.tag(null);
+            //             hitinfo.sheet.repaint(hitinfo.cellRect);
+            //         }
+            //     }
+            // };
+            // proto.processMouseMove = function (hitinfo) {
+            //     const img = this.getImage(hitinfo.sheet, hitinfo.row, hitinfo.col);
+            //     const halfX = img.width / 2, halfY = img.height / 2;
+            //     const centerX = hitinfo.cellRect.x + indent + halfX;
+            //     const centerY = hitinfo.cellRect.y + hitinfo.cellRect.height / 2;
+            //     const cell = hitinfo.sheet.getCell(hitinfo.row, hitinfo.col);
+            //     if (Math.abs(hitinfo.x - centerX) < halfX && Math.abs(hitinfo.y - centerY) < halfY) {
+            //         if (cell.tag() !== hover) {
+            //             cell.tag(hover);
+            //             hitinfo.sheet.repaint(hitinfo.cellRect);
+            //         }
+            //     } else {
+            //         if (cell.tag() === hover) {
+            //             cell.tag(null);
+            //             hitinfo.sheet.repaint(hitinfo.cellRect);
+            //         }
+            //     }
+            // };
+            return new ImageCellType();
+        },
+        /**
+         * 获取 嵌入Html的cellType
+         * @returns {HTMLCellType}
+         */
+        getHtmlCellType: function () {
+            const HTMLCellType = function (){};
+            HTMLCellType.prototype = new spreadNS.CellTypes.Text;
+            const proto = ImageCellType.prototype;
+            proto.paint = function (ctx, value, x, y, w, h, style, context) {
+                let DOMURL = window.URL || window.webkitURL || window;
+                let cell = context.sheet.getCell(context.row, context.col);
+                let img = cell.tag();
+                if (img) {
+                    try {
+                        ctx.save();
+                        ctx.rect(x, y, w, h);
+                        ctx.clip();
+                        ctx.drawImage(img, x + 2, y + 2)
+                        ctx.restore();
+                        cell.tag(null);
+                        return;
+                    }
+                    catch (err) {
+                        GC.Spread.Sheets.CustomCellType.prototype.paint.apply(this, [ctx, "#HTMLError", x, y, w, h, style, context])
+                        cell.tag(null);
+                        return;
+                    }
+                }
+                let svgPattern = '<svg xmlns="http://www.w3.org/2000/svg" width="{0}" height="{1}">' +
+                    '<foreignObject width="100%" height="100%"><div xmlns="http://www.w3.org/1999/xhtml" style="font:{2}">{3}</div></foreignObject></svg>';
+
+                let data = svgPattern.replace("{0}", w).replace("{1}", h).replace("{2}", style.font).replace("{3}", value);
+                let doc = document.implementation.createHTMLDocument("");
+                doc.write(data);
+                // Get well-formed markup
+                data = (new XMLSerializer()).serializeToString(doc.body.children[0]);
+
+                img = new Image();
+                //var svg = new Blob([data], {type: 'image/svg+xml;charset=utf-8'});
+                //var url = DOMURL.createObjectURL(svg);
+                //img.src = url;
+                img.src = 'data:image/svg+xml;base64,' + window.btoa(data);
+                cell.tag(img);
+                img.onload = function () {
+                    context.sheet.repaint(new GC.Spread.Sheets.Rect(x, y, w, h));
+                }
+            };
+            return new HTMLCellType();
+        },
+        /**
+         * 获取 字符超长缩略的cellType
+         * @returns {EllipsisTextCellType}
+         */
+        getEllipsisTextCellType: function () {
+            const EllipsisTextCellType = function (){};
+            EllipsisTextCellType.prototype = new spreadNS.CellTypes.Text;
+            const proto = EllipsisTextCellType.prototype;
+            const getEllipsisText = function(c, str, maxWidth) {
+                var width = c.measureText(str).width;
+                var ellipsis = '…';
+                var ellipsisWidth = c.measureText(ellipsis).width;
+                if (width <= maxWidth || width <= ellipsisWidth) {
+                    return str;
+                } else {
+                    var len = str.length;
+                    while (width >= maxWidth - ellipsisWidth && len-- > 0) {
+                        str = str.substring(0, len);
+                        width = c.measureText(str).width;
+                    }
+                    return str + ellipsis;
+                }
+            };
+            proto.paint = function (ctx, value, x, y, w, h, style, context) {
+                ctx.font = style.font;
+                value = getEllipsisText(ctx, value, w - 2);
+                spreadNS.CellTypes.Text.prototype.paint.apply(this, [ctx, value, x, y, w, h, style, context]);
+            };
+            return new EllipsisTextCellType();
+        },
     }
 };

+ 526 - 0
app/public/js/stage_pay.js

@@ -0,0 +1,526 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2019/3/20
+ * @version
+ */
+
+function getTenderId() {
+    return window.location.pathname.split('/')[2];
+}
+
+function getStageId() {
+    return window.location.pathname.split('/')[5];
+}
+
+class PayCalculate {
+    constructor (bases) {
+        this.bases = _.sortBy(bases, function (a, b) {
+            if (a && b) {
+                return b.code.indexOf(a.code) === -1 ? 1 : -1;
+            } else {
+                return 0;
+            }
+        });
+        for (const b of this.bases) {
+            b.reg = new RegExp(b.code, 'igm');
+        }
+        this.percentReg = /[0-9]+%/g;
+    }
+
+    calculateExpr(expr) {
+        let formula = expr;
+        for (const b of this.bases) {
+            formula = formula.replace(b.reg, b.value);
+        }
+        const percent = formula.match(this.percentReg);
+        if (percent) {
+            for (const p of percent) {
+                const v = math.eval(p.replace('%', '/100'));
+                formula = formula.replace(p, v);
+            }
+        }
+        return math.eval(formula);
+    }
+
+    calculateStartRangePrice (pays) {
+        for (const p of pays) {
+            if (!p.sprice && p.sexpr && p.sexpr !== '') {
+                p.sprice = this.calculateExpr(p.sexpr);
+            }
+            if (!p.rprice && p.rexpr && p.rexpr !== '') {
+                p.rprice = this.calculateExpr(p.rexpr);
+            }
+        }
+    }
+
+    calculate(pays) {
+        for (const p of pays) {
+            if (p.ptype === 1 || p.ptype === 4) {
+                if (p.expr && p.expr !== '') {
+                    p.tp = this.calculateExpr(p.expr);
+                }
+                console.log(p);
+            }
+            p.end_tp = _.add(p.tp, p.pre_tp);
+        }
+    }
+}
+
+$(document).ready(() => {
+    autoFlashHeight();
+    const calcualtor = new PayCalculate(calcBase);
+    calcualtor.calculateStartRangePrice(dealPay);
+    calcualtor.calculate(dealPay);
+
+
+    const paySpread = SpreadJsObj.createNewSpread($('#pay-spread')[0]);
+    const paySpreadSetting = {
+        cols: [
+            {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 150, formatter: '@', readOnly: 'readOnly.name'},
+            {title: '扣款', colSpan: '1', rowSpan: '1', field: 'minus', hAlign: 1, width: 50, cellType: 'checkbox', readOnly: 'readOnly.minus'},
+            {title: '本期金额(表达式)', colSpan: '1', rowSpan: '1', field: 'tp', hAlign: 2, width: 100, readOnly: 'readOnly.tp'},
+            {title: '累计金额',  colSpan: '1', rowSpan: '1', field: 'end_tp', hAlign: 2, width: 100, readOnly: true},
+            {title: '起扣金额',  colSpan: '1', rowSpan: '1', field: 'sprice', hAlign: 2, width: 100, readOnly: 'readOnly.sprice'},
+            {title: '付(扣)款限额',  colSpan: '1', rowSpan: '1', field: 'rprice', hAlign: 2, width: 100, readOnly: 'readOnly.rprice'},
+            {
+                title: '附件', colSpan: '1', rowSpan: '1', field: 'attachment', hAlign: 2, width: 60, readOnly: true, cellType: 'imageBtn',
+                normalImg: '#rela-file-icon', hoverImg: '#rela-file-hover', activeImg: '#rela-file-down', getValue: 'getValue.attachment'
+            },
+            {title: '状态', colSpan: '1', rowSpan: '1', field: '', hAlign: 0, width: 120, readOnly: true, getValue: 'getValue.state', foreColor: 'red', font: '8px 宋体'},
+        ],
+        emptyRows: 0,
+        headRows: 1,
+        headRowHeight: [50],
+        defaultRowHeight: 30,
+    };
+    paySpreadSetting.imageClick = function (data) {
+        $('#file').modal('show');
+    };
+    paySpreadSetting.getColor = function (data, col, defaultColor) {
+        if (data) {
+            if (data.pause) {
+                return '#666666';
+            } else if (data.csorder == getStageId() && data.uid > 0) {
+                return '#FFFFE1';
+            } else if (!data.is_yf) {
+                return '#d6d8db';
+            } else {
+                return defaultColor;
+            }
+        } else {
+            return defaultColor;
+        }
+    };
+    const payCol = {
+        getValue: {
+            attachment: function (data) {
+                return data.attchement ? data.attachement.length : 0;
+            },
+            state: function (data) {
+                const value = [];
+                if (data.pause) {
+                    value.push('停用');
+                }
+                if (!data.is_yf) {
+                    value.push('不参与本期应付');
+                }
+                return value.join('\n');
+            },
+        },
+        readOnly: {
+            isSpecial: function (data) {
+                return data.ptype !== 1;
+            },
+            isOld: function (data) {
+                return data.csorder < getStageId();
+            },
+            name: function (data) {
+                return payCol.readOnly.isSpecial(data) || payCol.readOnly.isOld(data);
+            },
+            minus: function (data) {
+                return payCol.readOnly.isSpecial(data) || payCol.readOnly.isOld(data);
+            },
+            tp: function (data) {
+                return data.ptype === 2 || data.ptype === 4 || payCol.readOnly.isOld(data);
+            },
+            sprice: function (data) {
+                return payCol.readOnly.isSpecial(data) || payCol.readOnly.isOld(data);
+            },
+            rprice: function (data) {
+                return payCol.readOnly.isSpecial(data) || payCol.readOnly.isOld(data);
+            },
+        }
+    };
+    SpreadJsObj.initSpreadSettingEvents(paySpreadSetting, payCol);
+    SpreadJsObj.initSheet(paySpread.getActiveSheet(), paySpreadSetting);
+    SpreadJsObj.loadSheetData(paySpread.getActiveSheet(), SpreadJsObj.DataType.Data, dealPay);
+
+    const paySpreadObj = {
+        refreshActn: function () {
+            const setObjEnable = function (obj, enable) {
+                if (enable) {
+                    obj.removeClass('disabled');
+                } else {
+                    obj.addClass('disabled');
+                }
+            };
+            const sheet = paySpread.getActiveSheet();
+            const select = SpreadJsObj.getSelectObject(sheet);
+            setObjEnable($('#del'), select);
+            setObjEnable($('#up-move'), select && dealPay.indexOf(select) > 0);
+            setObjEnable($('#down-move'), select && dealPay.indexOf(select) < dealPay.length - 1);
+        },
+        add: function () {
+            const sheet = paySpread.getActiveSheet();
+            postData(window.location.pathname + '/save', {type: 'add'}, function (result) {
+                if (result) {
+                    dealPay.push(result);
+                    sheet.addRows(dealPay.length - 1, 1);
+                    SpreadJsObj.reLoadRowData(sheet, dealPay.length - 1);
+                    sheet.setSelection(dealPay.length - 1, 0, 1, 1);
+                    paySpreadObj.refreshActn();
+                }
+            });
+        },
+        del: function () {
+            const sheet = paySpread.getActiveSheet();
+            const select = SpreadJsObj.getSelectObject(sheet);
+            postData(window.location.pathname + '/save', {type: 'del', id: select.id}, function () {
+                const index = dealPay.indexOf(select);
+                dealPay.splice(index, 1);
+                sheet.deleteRows(index, 1);
+                const sel = sheet.getSelections();
+                sheet.setSelection(index > 0 ? index - 1 : 0, sel.length > 0 ? sel[0].col : 0, 1, 1);
+                paySpreadObj.refreshActn();
+            });
+        },
+        upMove: function () {
+            const sheet = paySpread.getActiveSheet();
+            const cur = SpreadJsObj.getSelectObject(sheet);
+            const up = dealPay[dealPay.indexOf(cur) - 1];
+            postData(window.location.pathname + '/save', {type: 'changeOrder', id1: cur.id, id2: up.id}, function () {
+                const order = cur.order;
+                cur.order = up.order;
+                up.order = order;
+                dealPay.sort(function (a, b) {return a.order - b.order});
+                SpreadJsObj.reLoadSheetData(sheet);
+                const sel = sheet.getSelections();
+                const index = dealPay.indexOf(cur);
+                sheet.setSelection(index, sel.length > 0 ? sel[0].col : 0, 1, 1);
+                paySpreadObj.refreshActn();
+            });
+        },
+        downMove: function () {
+            const sheet = paySpread.getActiveSheet();
+            const cur = SpreadJsObj.getSelectObject(sheet);
+            const down = dealPay[dealPay.indexOf(cur) + 1];
+            postData(window.location.pathname + '/save', {type: 'changeOrder', id1: cur.id, id2: down.id}, function () {
+                const order = cur.order;
+                cur.order = down.order;
+                down.order = order;
+                dealPay.sort(function (a, b) {return a.order - b.order});
+                SpreadJsObj.reLoadSheetData(sheet);
+                const sel = sheet.getSelections();
+                const index = dealPay.indexOf(cur);
+                sheet.setSelection(index, sel.length > 0 ? sel[0].col : 0, 1, 1);
+                paySpreadObj.refreshActn();
+            });
+        },
+        selectionChanged: function (e, info) {
+            paySpreadObj.refreshActn();
+        },
+        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 orgValue = select[col.field];
+                if (orgValue == info.editingText || ((!orgValue || orgValue === '') && (info.editingText === ''))) {
+                    return;
+                }
+                // 获取更新信息
+                const data = {
+                    type: col.field === 'tp' ? 'stage' : 'info',
+                    updateData: {}
+                };
+                // 获取更新数据
+                if (col.field === 'tp') {
+
+                } else {
+                    data.updateData.id = select.id;
+                    if (info.editingText) {
+                        data.updateData[col.field] = col.type === 'Number' ? parseFloat(info.editingText) : info.editingText.replace('\n', '');
+                    } else {
+                        data.updateData[col.field] = null;
+                    }
+                }
+                // 更新至服务器
+                postData(window.location.pathname + '/save', data, function (result) {
+                    _.assign(select, result);
+                    SpreadJsObj.reLoadRowData(paySpread.getActiveSheet(), info.row);
+                });
+            }
+        },
+        buttonClicked: function (e, info) {
+            if (info.sheet.zh_setting) {
+                const select = SpreadJsObj.getSelectObject(info.sheet);
+                const col = info.sheet.zh_setting.cols[info.col];
+                if (col.field === 'minus') {
+                    // 获取更新数据
+                    // 获取更新信息
+                    const data = {
+                        type: 'info',
+                        updateData: { id: select.id },
+                    };
+                    data.updateData.minus = info.sheet.getValue(info.row, info.col);
+                    // 更新至服务器
+                    postData(window.location.pathname + '/save', data, function (result) {
+                        _.assign(select, result);
+                        SpreadJsObj.reLoadRowData(paySpread.getActiveSheet(), info.row);
+                    });
+                }
+            }
+        },
+    };
+    paySpreadObj.refreshActn();
+    paySpread.bind(spreadNS.Events.EditEnded, paySpreadObj.editEnded);
+    paySpread.bind(spreadNS.Events.SelectionChanged, paySpreadObj.selectionChanged);
+    paySpread.bind(spreadNS.Events.ButtonClicked, paySpreadObj.buttonClicked);
+    $('#add').click(paySpreadObj.add);
+    $('#del').click(paySpreadObj.del);
+    $('#up-move').click(paySpreadObj.upMove);
+    $('#down-move').click(paySpreadObj.downMove);
+
+    const deadlineObj = {
+        initView(pay) {
+            // 模式
+            $('[name=dl-type][value=' + pay.dl_type +']')[0].checked = true;
+            // 计量期数
+            $('#stage-count').val(pay.dl_count ? pay.dl_count : getStageId()).attr('min', getStageId());
+            // 计量金额类型
+            const tpType = pay.dl_tp_type ? pay.dl_tp_type : 'gather';
+            $('[value=' + tpType + ']')[0].checked = true;
+            // 计量金额
+            $('#tp').val(pay.dl_tp ? pay.dl_tp : 0);
+            if (pay.dl_type === 1) {
+                $('[dl-type=1]').show();
+                $('[dl-type=2]').hide();
+                deadlineObj.getStageCountHint();
+                $('#dl-hint').show();
+            } else if (pay.dl_type === 2) {
+                $('[dl-type=1]').hide();
+                $('[dl-type=2]').show();
+                deadlineObj.getTotalPriceHint();
+                $('#dl-hint').show();
+            } else {
+                $('[dl-type=1]').hide();
+                $('[dl-type=2]').hide();
+                $('#dl-hint').hide();
+            }
+        },
+        getStageCountHint() {
+            $('#range-hint').text('当 计量期数 >= ' + $('#stage-count').val() + ' 时');
+        },
+        getTotalPriceHint() {
+            const tpType = $('[name=tp-type]:checked').attr('id');
+            const tpName = $('[for='+ tpType +']').text();
+            const tpRange = $('#tp').val();
+            $('#range-hint').text('当 ' + tpName + ' >= ' + (tpRange !== '' ? tpRange : 0) + ' 时');
+        },
+        changeDeadlineType() {
+            const type = $(this).val();
+            if (type == 1) {
+                $('[dl-type=1]').show();
+                deadlineObj.getStageCountHint();
+            } else {
+                $('[dl-type=1]').hide();
+            }
+            if (type == 2) {
+                $('[dl-type=2]').show();
+                deadlineObj.getTotalPriceHint();
+            } else {
+                $('[dl-type=2]').hide();
+            }
+            if (type == 0) {
+                $('#dl-hint').hide();
+            } else {
+                $('#dl-hint').show();
+            }
+
+        }
+    };
+    // 右键菜单
+    $.contextMenu({
+        selector: '#pay-spread',
+        build: function ($trigger, e) {
+            const target = SpreadJsObj.safeRightClickSelection($trigger, e, paySpread);
+            return target.hitTestType === GC.Spread.Sheets.SheetArea.viewport || target.hitTestType === GC.Spread.Sheets.SheetArea.rowHeader;
+        },
+        items: {
+            'detail': {
+                name: '明细',
+                icon: 'fa-list',
+                callback: function (key, opt) {
+                    if ($('#detail-list').children().length === 0) {
+                        postData(window.location.pathname + '/detail', null, function (result) {
+                            const html = [], decimal = result.decimal.tp;
+                            for (const cd of result.detail) {
+                                html.push('<tr>');
+                                html.push('<td>', cd.name, '</td>');
+                                html.push('<td class="text-right">', _.round(cd.contract_tp, decimal), '</td>');
+                                html.push('<td class="text-right">', _.round(cd.qc_tp, decimal), '</td>');
+                                html.push('<td class="text-right">', _.round(cd.gather_tp, decimal), '</td>');
+                                html.push('<td class="text-right">', _.round(cd.end_gather_tp, decimal), '</td>');
+                                html.push('</tr>');
+                            }
+                            $('#detail-list').html(html.join(''));
+                            $('#detail').modal('show');
+                        });
+                    } else {
+                        $('#detail').modal('show');
+                    }
+                },
+                visible: function(key, opt){
+                    const select = SpreadJsObj.getSelectObject(paySpread.getActiveSheet());
+                    return select.ptype === 4;
+                }
+            },
+            'start': {
+                name: '启用',
+                icon: 'fa-play',
+                callback: function (key, opt) {
+                    const select = SpreadJsObj.getSelectObject(paySpread.getActiveSheet());
+                    const data = {
+                        type: 'stage',
+                        updateData: {
+                            id: select.id,
+                            pause: false
+                        }
+                    };
+                    // 更新至服务器
+                    postData(window.location.pathname + '/save', data, function (result) {
+                        _.assign(select, result);
+                        SpreadJsObj.reLoadRowData(paySpread.getActiveSheet(), dealPay.indexOf(select));
+                    });
+                },
+                visible: function (key, opt) {
+                    const select = SpreadJsObj.getSelectObject(paySpread.getActiveSheet());
+                    return select.ptype === 1 && select.pause;
+                }
+            },
+            'stop': {
+                name: '停用',
+                icon: 'fa-pause',
+                callback: function (key, opt) {
+                    const select = SpreadJsObj.getSelectObject(paySpread.getActiveSheet());
+                    const data = {
+                        type: 'stage',
+                        updateData: {
+                            id: select.id,
+                            pause: true
+                        }
+                    };
+                    // 更新至服务器
+                    postData(window.location.pathname + '/save', data, function (result) {
+                        _.assign(select, result);
+                        SpreadJsObj.reLoadRowData(paySpread.getActiveSheet(), dealPay.indexOf(select));
+                    });
+                },
+                visible: function (key, opt) {
+                    const select = SpreadJsObj.getSelectObject(paySpread.getActiveSheet());
+                    return select.ptype === 1 && !select.pause;
+                }
+            },
+            'setDeadline': {
+                name: '设置计提期限',
+                icon: 'fa-clipboard',
+                visible: function (key, opt) {
+                    const select = SpreadJsObj.getSelectObject(paySpread.getActiveSheet());
+                    return select.ptype === 1 && select.csorder == getStageId();
+                },
+                callback: function (key, opt) {
+                    const select = SpreadJsObj.getSelectObject(paySpread.getActiveSheet());
+                    deadlineObj.initView(select);
+                    $('#deadline').modal('show');
+                    // if (select.rprice) {
+                    //     $('#deadline').modal('show');
+                    // } else {
+                    //     toast('计提期限用于达到条件时,即刻计量至付(扣)款限额,应先设置付(扣)款限额', 'warning');
+                    // }
+                }
+            },
+            'dropYF': {
+                name: '不参与本期应付计算',
+                icon: 'fa-chain-broken',
+                visible: function (key, opt) {
+                    const select = SpreadJsObj.getSelectObject(paySpread.getActiveSheet());
+                    return select.ptype === 1 && select.is_yf && select.csorder == getStageId();
+                },
+                callback: function (key, opt) {
+                    const select = SpreadJsObj.getSelectObject(paySpread.getActiveSheet());
+                    const data = {
+                        type: 'info',
+                        updateData: {
+                            id: select.id,
+                            is_yf: false
+                        }
+                    };
+                    // 更新至服务器
+                    postData(window.location.pathname + '/save', data, function (result) {
+                        _.assign(select, result);
+                        SpreadJsObj.reLoadRowData(paySpread.getActiveSheet(), dealPay.indexOf(select));
+                    });
+                }
+            },
+            'belongYF': {
+                name: '参与本期应付计算',
+                icon: 'fa-chain',
+                visible: function (key, opt) {
+                    const select = SpreadJsObj.getSelectObject(paySpread.getActiveSheet());
+                    return select.ptype === 1 && !select.is_yf && select.csorder == getStageId();
+                },
+                callback: function (key, opt) {
+                    const select = SpreadJsObj.getSelectObject(paySpread.getActiveSheet());
+                    const data = {
+                        type: 'info',
+                        updateData: {
+                            id: select.id,
+                            is_yf: true
+                        }
+                    };
+                    // 更新至服务器
+                    postData(window.location.pathname + '/save', data, function (result) {
+                        _.assign(select, result);
+                        SpreadJsObj.reLoadRowData(paySpread.getActiveSheet(), dealPay.indexOf(select));
+                    });
+                }
+            }
+        }
+    });
+    $('[name=dl-type]').click(deadlineObj.changeDeadlineType);
+    $('[name=tp-type]').click(deadlineObj.getTotalPriceHint);
+    $('#stage-count').change(deadlineObj.getStageCountHint);
+    $('#tp').change(deadlineObj.getTotalPriceHint);
+    $('#deadline-ok').click(function () {
+        const select = SpreadJsObj.getSelectObject(paySpread.getActiveSheet());
+        const data = {
+            type: 'info',
+            updateData: {
+                id: select.id,
+            }
+        };
+        data.updateData.dl_type = parseInt($('[name=dl-type]:checked').val());
+        data.updateData.dl_count = parseInt($('#stage-count').val());
+        data.updateData.dl_tp_type = $('[name=tp-type]:checked').val();
+        data.updateData.dl_tp = parseFloat($('#tp').val());
+        console.log(data);
+        postData(window.location.pathname + '/save', data, function (result) {
+            _.assign(select, result);
+            $('#deadline').modal('hide');
+        });
+    });
+});

+ 4 - 1
app/router.js

@@ -10,7 +10,8 @@ module.exports = app => {
     const projectManagerCheck = app.middlewares.projectManagerCheck();
     // 标段读取中间件
     const tenderCheck = app.middlewares.tenderCheck();
-    const tenderSelect = app.middlewares.tenderSelect();
+    // 期读取中间件
+    const stageCheck = app.middlewares.stageCheck();
 
     // 登入登出相关
     app.get('/login', 'loginController.index');
@@ -124,6 +125,8 @@ module.exports = app => {
     app.post('/tender/:id/measure/stage/:order/detail/merge-img', sessionAuth, tenderCheck, 'stageController.mergeCalcImage');
     // 合同支付
     app.get('/tender/:id/measure/stage/:order/pay', sessionAuth, tenderCheck, 'stageController.pay');
+    app.post('/tender/:id/measure/stage/:order/pay/detail', sessionAuth, tenderCheck, 'stageController.chapterDetail');
+    app.post('/tender/:id/measure/stage/:order/pay/save', sessionAuth, tenderCheck, 'stageController.savePayData');
     // 变更令
     app.get('/tender/:id/measure/stage/:order/change', sessionAuth, tenderCheck, 'stageController.change');
     // 审批

+ 5 - 0
app/service/ledger.js

@@ -1908,6 +1908,11 @@ module.exports = app => {
             }
         }
 
+        /**
+         * 导入Excel数据
+         * @param excelData
+         * @returns {Promise<void>}
+         */
         async importExcel(excelData) {
             const AnalysisExcel = require('../lib/analysis_excel');
             const analysisExcel = new AnalysisExcel();

+ 169 - 0
app/service/pay.js

@@ -0,0 +1,169 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+const payConst = require('../const/deal_pay.js');
+const writableFields = ['name', 'minus', 'sprice', 'sexpr', 'rprice', 'rexpr', 'is_yf', 'dl_type', 'dl_count', 'dl_tp_type', 'dl_tp'];
+
+module.exports = app => {
+    class Pay extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'pay';
+        }
+
+        /**
+         * 初始化 合同支付数据
+         * @param transaction
+         * @returns {Promise<boolean>}
+         */
+        async addDefaultPayData(transaction) {
+            if (!this.ctx.tender || !this.ctx.stage) { return false; }
+            const pays = payConst.payTemplate;
+            for (const p of pays) {
+                p.tid = this.ctx.tender.id;
+                p.csid = this.ctx.stage.id;
+                p.cstimes = this.ctx.stage.times;
+                p.csorder = this.ctx.stage.order;
+            }
+            let result;
+            if (transaction) {
+                result = await transaction.insert(this.tableName, pays);
+            } else {
+                result = await this.db.insert(this.tableName, pays);
+            }
+            return result.affectedRows === pays.length;
+        }
+
+        async _getMaxOrder(tenderId) {
+            const sql = 'SELECT Max(??) As value FROM ?? Where tid = ' + tenderId;
+            const sqlParam = ['order', this.tableName];
+            const queryResult = await this.db.queryOne(sql, sqlParam);
+            return queryResult.value;
+        }
+
+        /**
+         * 新增合同支付项
+         * @returns {Promise<*>}
+         */
+        async add() {
+            if (!this.ctx.tender || !this.ctx.stage) {
+                throw '数据错误';
+            }
+            const order = await this._getMaxOrder(this.ctx.tender.id);
+            const pay = {
+                tid: this.ctx.tender.id,
+                csid: this.ctx.stage.id,
+                cstimes: this.ctx.stage.times,
+                csorder: this.ctx.stage.order,
+                uid: this.ctx.session.sessionUser.accountId,
+                minus: false,
+                ptype: payConst.payType.normal,
+                order: order + 1,
+            };
+
+            let result;
+            result = await this.db.insert(this.tableName, pay);
+            if (result.affectedRows !== 1) {
+                throw '新增合同支付项失败'
+            }
+
+            pay.id = result.insertId;
+            return pay;
+        }
+
+        /**
+         * 删除合同支付项
+         * @param id
+         * @returns {Promise<void>}
+         */
+        async del(id) {
+            if (!this.ctx.tender || !this.ctx.stage) {
+                throw '数据错误';
+            }
+            // 检查是否可以删除
+            const pay = await this.getDataByCondition({id: id});
+            if (!pay) {
+                throw '合同支付项不存在';
+            } else if (pay.uid === -1) {
+                throw '默认合同支付项不可删除';
+            }
+            // 删除合同支付
+            const transaction = await this.db.beginTransaction();
+            try {
+                const result = await transaction.delete(this.tableName, {id: id});
+                if (result.affectedRows !== 1) {
+                    throw '删除合同支付项失败'
+                }
+                // todo 删除期计量中数据
+                await transaction.commit();
+            } catch(err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        /**
+         * 交换两个合同支付项的顺序
+         * @param {Number} id1 - 合同支付项1的id
+         * @param {Number} id2 - 合同支付项1的id
+         * @returns {Promise<void>}
+         */
+        async changeOrder(id1, id2) {
+            if (!this.ctx.tender || !this.ctx.stage) {
+                throw '数据错误';
+            }
+            const pay1 = await this.getDataByCondition({tid: this.ctx.tender.id, id: id1});
+            const pay2 = await this.getDataByCondition({tid: this.ctx.tender.id, id: id2});
+            if (!pay1 || !pay2) {
+                throw '数据错误';
+            }
+
+            const transaction = await this.db.beginTransaction();
+            try {
+                const order = pay1.order;
+                pay1.order = pay2.order;
+                pay2.order = order;
+                await transaction.update(this.tableName, {id: pay1.id, order: pay1.order});
+                await transaction.update(this.tableName, {id: pay2.id, order: pay2.order});
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        /**
+         * 保存合同支付项数据
+         * @param data
+         * @returns {Promise<boolean>}
+         */
+        async save(data) {
+            if (!this.ctx.tender || !this.ctx.stage) { return false; }
+            const pay = await this.getDataByCondition({tid: this.ctx.tender.id, id: data.id});
+            if(!pay) {
+                throw '数据错误';
+            }
+            const updateData = this.ctx.helper.filterValidFields(data, writableFields);
+            updateData.id = data.id;
+            const result = await this.db.update(this.tableName, updateData);
+            if (result.affectedRows !== 1) {
+                throw '更新数据失败';
+            }
+            return updateData;
+        }
+    }
+
+    return Pay;
+};

+ 43 - 0
app/service/stage_bills.js

@@ -192,6 +192,49 @@ module.exports = app => {
                 await this._insertStageBillsData(transaction, posGather, ledgerBills);
             }
         }
+
+        async getSumTotalPrice(stage) {
+            const sql = 'SELECT Sum(`contract_tp`) As `contract_tp`, Sum(`qc_tp`) As `qc_tp` FROM ' + this.tableName + ' As Bills ' +
+                '  INNER JOIN ( ' +
+                '    SELECT MAX(`times`) As `times`, MAX(`order`) As `order`, `lid` From ' + this.tableName +
+                '      WHERE `times` <= ? AND `order` <= ?' +
+                '      GROUP BY `lid`' +
+                '  ) As MaxFilter ' +
+                '  ON Bills.times = MaxFilter.times And Bills.order = MaxFilter.order And Bills.lid = MaxFilter.lid' +
+                '  WHERE Bills.sid = ?';
+            const sqlParam = [stage.times, stage.curAuditor ? stage.curAuditor.order : 0, stage.id];
+            const result = await this.db.queryOne(sql, sqlParam);
+            return result;
+            //return this._.add(result.contract_tp, result.qc_tp);
+        }
+
+        async getSumTotalPriceFilter(stage, operate, filter) {
+            const sql = 'SELECT Sum(`contract_tp`) As `contract_tp`, Sum(`qc_tp`) As `qc_tp` FROM ( ' + this.tableName + ' As Bills ' +
+                '  INNER JOIN ( ' +
+                '    SELECT MAX(`times`) As `times`, MAX(`order`) As `order`, `lid` From ' + this.tableName +
+                '      WHERE `times` <= ? AND `order` <= ?' +
+                '      GROUP BY `lid`' +
+                '  ) As MaxFilter ' +
+                '  ON Bills.times = MaxFilter.times And Bills.order = MaxFilter.order And Bills.lid = MaxFilter.lid, ' +
+                '  ' + this.ctx.service.ledger.tableName + ' As Ledger ) ' +
+                '  WHERE Bills.sid = ? AND Bills.lid = Ledger.id And Ledger.b_code ' + operate + ' ?';
+            const sqlParam = [stage.times, stage.curAuditor ? stage.curAuditor.order : 0, stage.id, filter];
+            const result = await this.db.queryOne(sql, sqlParam);
+            return result;
+            //return this._.add(result.contract_tp, result.qc_tp);
+        }
+
+        async getSumTotalPriceGcl(stage, regText) {
+            if (regText) {
+                return await this.getSumTotalPriceFilter(stage, 'REGEXP', regText);
+            } else {
+                return await this.getSumTotalPriceFilter(stage, '<>', this.db.escape(''));
+            }
+        }
+
+        async getSumTotalPriceNotGcl(stage) {
+            return await this.getSumTotalPriceFilter(stage, '=', this.db.escape(''));
+        }
     }
 
     return StageBills;

+ 252 - 0
app/service/stage_pay.js

@@ -0,0 +1,252 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+module.exports = app => {
+    class StagePay extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'stage_pos';
+            this.qtyFields = ['contract_qty', 'qc_qty']
+        }
+
+        /**
+         * 查询期计量最后审核人数据
+         * @param {Number} tid - 标段id
+         * @param {Number} sid - 期id
+         * @param {Number|Array} pid - 部位明细id(可以为空)
+         * @returns {Promise<*>}
+         */
+        async getLastestStageData(tid, sid) {
+            const sql = 'SELECT * FROM ' + this.tableName + ' As Pos ' +
+                '  INNER JOIN ( ' +
+                '    SELECT MAX(`times`) As `times`, MAX(`order`) As `order`, `pid` From ' + this.tableName +
+                '      GROUP BY `pid`' +
+                '  ) As MaxFilter ' +
+                '  ON Pos.times = MaxFilter.times And Pos.order = MaxFilter.order And Pos.pid = MaxFilter.pid' +
+                '  WHERE Pos.tid = ? And Pos.sid = ?';
+            const sqlParam = [tid, sid];
+            return await this.db.query(sql, sqlParam);
+        }
+        /**
+         * 查询 某期 某轮审批 某审核人数据
+         * @param {Number} tid - 标段id
+         * @param {Number} sid - 期id
+         * @param {Number} times - 期第几轮审批
+         * @param {Number} order - 审核人顺序
+         * @param {Number|Array|Null} pid - 部位明细id - 为空则查询全部
+         * @returns {Promise<*>}
+         */
+        async getAuditorStageData(tid, sid, times, order) {
+            const sql = 'SELECT * FROM ' + this.tableName + ' As Pos ' +
+                '  INNER JOIN ( ' +
+                '    SELECT MAX(`times`) As `times`, MAX(`order`) As `order`, `pid` From ' + this.tableName +
+                '      WHERE `times` <= ? AND `order` <= ?' +
+                '      GROUP BY `pid`' +
+                '  ) As MaxFilter ' +
+                '  ON Pos.times = MaxFilter.times And Pos.order = MaxFilter.order And Pos.pid = MaxFilter.pid' +
+                '  WHERE Pos.tid = ? And Pos.sid = ?';
+            const sqlParam = [times, order, tid, sid];
+            return await this.db.query(sql, sqlParam);
+        }
+
+        /**
+         * 新增部位明细数据(仅供updateStageData调用)
+         *
+         * @param transaction - 事务
+         * @param data - 新增数据
+         * @returns {Promise<{}>}
+         * @private
+         */
+        // async _addStagePosData(transaction, data) {
+        //     const bills = await this.ctx.service.ledger.getDataById(data.lid);
+        //     const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, bills.unit);
+        //     const  result = {};
+        //     // 在主表pos中新增数据
+        //     const p = JSON.parse(JSON.stringify(data.updateData));
+        //     p.tid = this.ctx.tender.id;
+        //     p.add_stage = this.ctx.stage.id;
+        //     p.add_times = this.ctx.stage.times;
+        //     p.add_user = this.ctx.session.sessionUser.accountId;
+        //     if (p.contract_qty) { delete p.contract_qty; }
+        //     if (p.qc_qty) { delete p.qc_qty; }
+        //     if (p.postil) { delete p.postil; }
+        //     if (p.quantity) {
+        //         p.quantity = this.round(p.quantity, precision.value);
+        //     }
+        //     const addRst = await transaction.insert(this.ctx.service.pos.tableName, data.updateData);
+        //     p.id = addRst.insertId;
+        //     result.pos = p.id;
+        //     // 如果存在复核数据,更新计算主表清单
+        //     if (p.quantity) {
+        //         await this.ctx.service.ledger.calc(this.ctx.tender.id, p.lid, transaction);
+        //         result.ledger = p.lid;
+        //     }
+        //     // 如果存在本期计算数据,更新计算清单本期计量数据
+        //     if (data.contract_qty || data.qc_qty || data.postil) {
+        //         const ps = {
+        //             pid: p.id,
+        //             lid: p.lid,
+        //             tid: this.ctx.tender.id,
+        //             sid: this.ctx.stage.id,
+        //             said: this.ctx.session.sessionUser.accountId,
+        //             times: this.ctx.stage.times,
+        //             order: 0,
+        //         };
+        //         if (data.contract_qty) { ps.contract_qty = this.round(data.contract_qty, precision.value); }
+        //         if (data.qc_qty) { ps.qc_qty = this.round(data.qc_qty, precision.value); }
+        //         if (data.postil) { ps.postil = data.postil; }
+        //         await transaction.insert(ps);
+        //         await this.ctx.service.stageBills.calc(ctx.tender.id, ctx.stage.id, ps.lid, transaction);
+        //         result.stageUpdate = true;
+        //     }
+        //     return result;
+        // }
+
+        /**
+         * 更新部位明细数据(仅供updateStageData调用)
+         *
+         * @param transaction - 事务
+         * @param data - 更新数据(允许一次性提交多条)
+         * @returns {Promise<{ledger: Array, pos: Array}>}
+         * @private
+         */
+        // async _updateStagePosData(transaction, data) {
+        //     let bills, precision;
+        //     const result = {ledger: [], pos: [], stageUpdate: true};
+        //     const datas = data instanceof Array ? data : [data];
+        //     const orgStagePos = await this.getLastestStageData(this.ctx.tender.id, this.ctx.stage.id, this._.map(datas, 'pid'));
+        //     const userOrder = this.ctx.stage.curAuditor ? this.ctx.stage.curAuditor.order : 0;
+        //     for (const d of datas) {
+        //         if (!bills || bills.id !== data.lid) {
+        //             bills = await this.ctx.service.ledger.getDataById(data.lid);
+        //             precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, bills.unit);
+        //         }
+        //         const osp = this._.find(orgStagePos, function (p) { return p.pid === d.pid; });
+        //         this.ctx.helper.checkFieldPrecision(d, this.qtyFields, precision.value);
+        //         if (osp && osp.times === this.ctx.stage.times && osp.order === userOrder) {
+        //             await transaction.update(this.tableName, d, {where: {id: osp.id}});
+        //         } else {
+        //             console.log(osp);
+        //             d.tid = this.ctx.tender.id;
+        //             d.sid = this.ctx.stage.id;
+        //             d.said = this.ctx.session.sessionUser.accountId;
+        //             d.times = this.ctx.stage.times;
+        //             d.order = userOrder;
+        //             await transaction.insert(this.tableName, d);
+        //         }
+        //         result.pos.push(d.pid);
+        //         if ((d.contract_qty === undefined || d.qc_qty === undefined) && (result.ledger.indexOf(d.lid) === -1)) {
+        //             result.ledger.push(d.lid);
+        //         }
+        //     }
+        //     for (const lid of result.ledger) {
+        //         await this.ctx.service.stageBills.calc(this.ctx.tender.id, this.ctx.stage.id, lid, transaction);
+        //     }
+        //     return result;
+        // }
+
+        /**
+         * 删除部位明细数据(仅供updateStageData调用)
+         *
+         * @param transaction - 事务
+         * @param data - 删除的部位明细(允许一次提醒多条,也允许跨清单(但前端操作不允许))
+         * @returns {Promise<{}>}
+         * @private
+         */
+        // async _deleteStagePosData(transaction, data) {
+        //     const result = {};
+        //     const pos = await this.ctx.service.pos.getPosData({tid: this.ctx.tender.id, id: data});
+        //     if (pos instanceof Array) {
+        //         for (const p of pos) {
+        //             if (p.add_stage !== this.ctx.stage.id || p.add_times !== this.ctx.stage.times || p.add_user !== this.ctx.session.sessionUser.accountId) {
+        //                 throw '您无权删除该数据';
+        //             }
+        //         }
+        //     } else if (pos.add_stage !== this.ctx.stage.id || pos.add_times !== this.ctx.stage.times || pos.add_user !== this.ctx.session.sessionUser.accountId) {
+        //         throw '您无权删除该数据';
+        //     }
+        //     const ledgerIds = this._.map(pos, 'lid');
+        //     // 删除部位明细
+        //     await transaction.delete(this.ctx.service.pos.tableName, {tid: this.ctx.tender.id, id: data});
+        //     for (const lid of ledgerIds) {
+        //         await this.ctx.service.ledger.calc(tid, lid, transaction);
+        //     }
+        //     // 删除部位明细计量数据
+        //     await transaction.delete(this.tableName, {tid: this.ctx.tender.id, lid: data});
+        //     for (const lid of ledgerIds) {
+        //         await this.ctx.service.stageBills.calc(this.ctx.tender.id, this.ctx.stage.id, lid, transaction);
+        //     }
+        //     // 获取需要更新的数据
+        //     result.ledger = ledgerIds;
+        //     result.stageUpdate = true;
+        //     return result;
+        // }
+
+        /**
+         * 根据前端提交数据,更新并计算
+         *
+         * @param data
+         * @returns {Promise<{ledger: {}, pos: {}}>}
+         */
+        // async updateStageData(data) {
+        //     let refreshData;
+        //     const transaction = await this.db.beginTransaction();
+        //     try {
+        //         if ((data.updateType === 'add' || data.upateType === 'delete') && this.ctx.tender.measure_type === measureType.tz) {
+        //             throw '台账模式下,不可在计量中新增或删除部位明细,如需操作,请进行台账修订';
+        //         }
+        //         if (data.updateType === 'add') {
+        //             refreshData = await this._addStagePosData(transaction, data.updateData);
+        //         } else if (data.updateType === 'update') {
+        //             refreshData = await this._updateStagePosData(transaction, data.updateData);
+        //             console.log(refreshData);
+        //         } else if (data.updateType === 'delete') {
+        //             if (!data.updateData || data.updateData.length === 0) {
+        //                 throw '提交数据错误';
+        //             }
+        //             refreshData = await this._deleteStagePosData(transaction, data.updateData);
+        //         } else {
+        //             throw '提交数据错误';
+        //         }
+        //         await transaction.commit();
+        //     } catch (err) {
+        //         await transaction.rollback();
+        //         throw err;
+        //     }
+        //
+        //     try {
+        //         const result = {ledger: {}, pos: {}};
+        //         if (refreshData.ledger && refreshData.ledger.length > 0) {
+        //             result.ledger.bills = await this.ctx.service.ledger.getDataByIds(refreshData.ledger);
+        //             if (refreshData.stageUpdate) {
+        //                 result.ledger.curStageData = await await this.ctx.service.stageBills.getLastestStageData(this.ctx.tender.id, this.ctx.stage.id, refreshData.ledger);
+        //             }
+        //         }
+        //         if (refreshData.pos && refreshData.pos.length > 0) {
+        //             result.pos.pos = await this.ctx.service.pos.getPosData({id: refreshData.pos});
+        //             if (refreshData.stageUpdate) {
+        //                 result.pos.curStageData = await this.getLastestStageData(this.ctx.tender.id, this.ctx.stage.id, refreshData.pos);
+        //             }
+        //         }
+        //         return result;
+        //     } catch(err) {
+        //         throw '获取数据异常,请刷新页面。';
+        //     }
+        // }
+    }
+
+    return StagePay;
+};

+ 41 - 184
app/view/stage/pay.ejs

@@ -2,7 +2,23 @@
 <div class="panel-content">
     <div class="panel-title">
         <div class="title-main d-flex justify-content-between">
-            <h2>合同支付</h2>
+            <div>
+                <div class="d-inline-block">
+                    <a href="javascript: void(0);" id="add" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="添加"><i class="fa fa-plus" aria-hidden="true"></i></a>
+                    <a href="javascript: void(0);" id="del" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="删除"><i class="fa fa-remove" aria-hidden="true"></i></a>
+                    <a href="javascript: void(0);" id="down-move" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="下移"><i class="fa fa-arrow-down" aria-hidden="true"></i></a>
+                    <a href="javascript: void(0);" id="up-move" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="上移"><i class="fa fa-arrow-up" aria-hidden="true"></i></a>
+                    <a href="javascript: void(0);" id="unlock" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="解锁"><i class="fa fa-unlock" 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="basic-addon1">表达式</span>
+                        </div>
+                        <input type="text" class="form-control m-0">
+                    </div>
+                </div>
+            </div>
             <div>
                 <a href="#sub-sp" data-toggle="modal" data-target="#sub-sp" class="btn btn-primary btn-sm pull-right">上报审批</a>
                 <a href="#sub-sp2" data-toggle="modal" data-target="#sub-sp2" class="btn btn-primary btn-sm pull-right">重新上报</a>
@@ -16,196 +32,37 @@
     </div>
     <div class="content-wrap">
         <div class="c-header p-0"></div>
-        <div class="c-body">
-            <!--上部-->
-            <div class="body-height-top">
-                <div class="row">
-                    <!--左栏-->
-                    <div class="col-8">
-                        <!--工具栏-->
-                        <div class="row">
-                            <div class="col-7">
-                                <div class="btn-group">
-                                    <a href="#" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="插入"><i class="fa fa-sign-in" aria-hidden="true"></i></a>
-                                    <a href="#" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="添加"><i class="fa fa-plus" aria-hidden="true"></i></a>
-                                    <a href="#" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="删除"><i class="fa fa-remove" aria-hidden="true"></i></a>
-                                    <a href="#" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="下移"><i class="fa fa-arrow-down" aria-hidden="true"></i></a>
-                                    <a href="#" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="上移"><i class="fa fa-arrow-up" aria-hidden="true"></i></a>
-                                    <a href="#" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="上移"><i class="fa fa-unlock" aria-hidden="true"></i> 解锁</a>
-                                </div>
-                            </div>
-                            <div class="col-5">
-                                <div class="input-group input-group-sm mb-1">
-                                    <div class="input-group-prepend">
-                                        <span class="input-group-text" id="basic-addon1">表达式</span>
-                                    </div>
-                                    <input type="text" class="form-control" >
-                                </div>
-                            </div>
-                        </div>
-                        <div style="height:330px;overflow: auto;">
-                            <table class="table table-bordered">
-                                <tr><th></th><th>名称</th><th>扣款</th><th>本期金额(表达式)</th><th>累计金额</th><th>起扣金额</th><th>付(扣)款限额</th><th>附件</th><th>操作</th></tr>
-                                <tr><td>1</td><td>本期应付</td><td><input type="checkbox"></td><td></td><td></td><td></td><td></td><td><a class="btn btn-sm" href="#file" data-toggle="modal" data-target="#file"><i class="fa fa-paperclip "></i> 0</a></td>
-                                    <td>
-                                    </td>
-                                </tr>
-                                <tr><td>2</td><td>本期实付</td><td><input type="checkbox"></td><td></td><td></td><td></td><td></td><td><a class="btn btn-sm" href="#file" data-toggle="modal" data-target="#file"><i class="fa fa-paperclip "></i> 3</a></td>
-                                    <td>
-                                    </td>
-                                </tr>
-                                <tr><td>3</td><td>本期完成计量</td><td><input type="checkbox"></td><td></td><td></td><td></td><td></td><td><a class="btn btn-sm" href="#file" data-toggle="modal" data-target="#file"><i class="fa fa-paperclip "></i> 0</a></td>
-                                    <td>
-                                    </td>
-                                </tr>
-                                <!--被停用了-->
-                                <tr class="table-secondary"><td>4</td><td>质量保证金</td><td><input type="checkbox"></td><td></td><td></td><td></td><td></td><td><a class="btn btn-sm" href="#file" data-toggle="modal" data-target="#file"><i class="fa fa-paperclip "></i> 0</a></td>
-                                    <td>
-                                        <a class="btn btn-sm btn-success" href="#">启用</a>
-                                    </td>
-                                </tr>
-                                <tr><td>5</td><td>扣回开工预付款</td><td><input type="checkbox"></td><td></td><td></td><td></td><td></td><td><a class="btn btn-sm" href="#file" data-toggle="modal" data-target="#file"><i class="fa fa-paperclip "></i> 0</a></td>
-                                    <td>
-                                        <div class="dropdown dropleft">
-                                            <button class="btn btn-sm btn-primary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                                                操作
-                                            </button>
-                                            <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
-                                                <a class="dropdown-item" href="#op2" data-toggle="modal" data-target="#op2">设置计提期限</a>
-                                                <a class="dropdown-item" href="#op3">不参与本期应付计算</a>
-                                                <a class="dropdown-item" href="#op4">停用</a>
-                                            </div>
-                                        </div>
-                                    </td>
-                                </tr>
-                                <!--不参与本期应付计算-->
-                                <tr class="table-secondary"><td>6</td><td>扣回材料预付款</td><td><input type="checkbox"></td><td></td><td></td><td></td><td></td><td><a class="btn btn-sm" href="#file" data-toggle="modal" data-target="#file"><i class="fa fa-paperclip "></i> 1</a></td>
-                                    <td>
-                                        <div class="dropdown dropleft">
-                                            <button class="btn btn-sm btn-primary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                                                操作
-                                            </button>
-                                            <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
-                                                <a class="dropdown-item" href="#op2" data-toggle="modal" data-target="#op2">设置计提期限</a>
-                                                <a class="dropdown-item" href="#op3">加入本期应付计算</a>
-                                                <a class="dropdown-item" href="#op4">停用</a>
-                                            </div>
-                                        </div>
-                                    </td>
-                                </tr>
-                                <!--新增-->
-                                <tr class="table-warning"><td>7</td><td>审核人新增数据</td><td><input type="checkbox"></td><td></td><td></td><td></td><td></td><td><a class="btn btn-sm" href="#file" data-toggle="modal" data-target="#file"><i class="fa fa-paperclip "></i> 1</a></td>
-                                    <td>
-                                        <button class="btn btn-sm btn-primary" type="button"  href="#op2" data-toggle="modal" data-target="#op2">
-                                            设置计提期限
-                                        </button>
-                                    </td>
-                                </tr>
-                            </table>
-                        </div>
-                    </div>
-                    <!--右栏-->
-                    <div class="col-4">
-                        <div style="height:365px;overflow: hidden;">
-                            <table class="table table-bordered">
-                                <tr><th></th><th>可选基数</th><th>计算代号</th></tr>
-                                <tr><td>1</td><td>签约合同价</td><td>htj</td></tr>
-                                <tr><td>2</td><td>签约合同价(不含暂列金)</td><td>htjszl</td></tr>
-                                <tr><td>3</td><td>签约开工预付款</td><td>kgyfk</td></tr>
-                                <tr><td>4</td><td>签约材料预付款</td><td>clyfk</td></tr>
-                                <tr><td>5</td><td>本期完成计量</td><td>bqwc</td></tr>
-                                <tr><td>6</td><td>100章本期完成计量</td><td>ybbqwc</td></tr>
-                            </table>
-                        </div>
-                    </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="body-height-bottom">
-                <legend>章节明细 <a href="#zjset" data-toggle="modal" data-target="#zjset" class="btn btn-sm">章节设置</a></legend>
-                <div class="sjs-height-6">
+            <div class="c-body col">
+                <div class="side-bar-1 sjs-bar"></div>
+                <div class="sjs-sh-1">
                     <table class="table table-bordered">
-                        <tr class="text-center"><th>章节名称</th><th>本期合同计量金额</th><th>本期变更计量金额</th><th>本期完成计量基金</th><th>累计完成计量金额</th></tr>
-                        <tr>
-                            <td>清单 第100章 总则</td>
-                            <td class="text-right">1000</td>
-                            <td class="text-right">1000</td>
-                            <td class="text-right">2000</td>
-                            <td class="text-right">2000</td>
-                        </tr>
-                        <tr>
-                            <td>清单 第200章 路基</td>
-                            <td class="text-right"></td>
-                            <td class="text-right"></td>
-                            <td class="text-right"></td>
-                            <td class="text-right"></td>
-                        </tr>
-                        <tr>
-                            <td>清单 第300章 路面</td>
-                            <td class="text-right"></td>
-                            <td class="text-right"></td>
-                            <td class="text-right"></td>
-                            <td class="text-right"></td>
-                        </tr>
-                        <tr>
-                            <td>清单 第400章 桥梁、涵洞</td>
-                            <td class="text-right"></td>
-                            <td class="text-right"></td>
-                            <td class="text-right"></td>
-                            <td class="text-right"></td>
-                        </tr>
-                        <tr>
-                            <td>清单 第500章 隧道</td>
-                            <td class="text-right"></td>
-                            <td class="text-right"></td>
-                            <td class="text-right"></td>
-                            <td class="text-right"></td>
-                        </tr>
-                        <tr>
-                            <td>清单 第600章 安全设施及预埋管线</td>
-                            <td class="text-right"></td>
-                            <td class="text-right"></td>
-                            <td class="text-right"></td>
-                            <td class="text-right"></td>
-                        </tr>
-                        <tr>
-                            <td>清单 第700章 绿化及环境保护</td>
-                            <td class="text-right"></td>
-                            <td class="text-right"></td>
-                            <td class="text-right"></td>
-                            <td class="text-right"></td>
-                        </tr>
-                        <tr>
-                            <td>未计入章节清单合计</td>
-                            <td class="text-right"></td>
-                            <td class="text-right"></td>
-                            <td class="text-right"></td>
-                            <td class="text-right"></td>
-                        </tr>
-                        <tr>
-                            <td>清单小计(A)</td>
-                            <td class="text-right">1000</td>
-                            <td class="text-right">1000</td>
-                            <td class="text-right">2000</td>
-                            <td class="text-right">2000</td>
-                        </tr>
-                        <tr>
-                            <td>非清单项费用(B)</td>
-                            <td class="text-right">500</td>
-                            <td class="text-right"></td>
-                            <td class="text-right">500</td>
-                            <td class="text-right">500</td>
-                        </tr>
+                        <tr><th></th><th>可选基数</th><th>计算代号</th><th>值</th></tr>
+                        <% for (let iBase = 0; iBase < calcBase.length; iBase++) { %>
                         <tr>
-                            <td>合计(C=A+B)</td>
-                            <td class="text-right">1500</td>
-                            <td class="text-right">1000</td>
-                            <td class="text-right">2500</td>
-                            <td class="text-right">2500</td>
+                            <td><%- iBase + 1 %></td>
+                            <td><%- calcBase[iBase].name %></td>
+                            <td><%- calcBase[iBase].code %></td>
+                            <td><%- calcBase[iBase].value %></td>
                         </tr>
+                        <% } %>
                     </table>
                 </div>
             </div>
         </div>
     </div>
-</div>
+</div>
+<img src="/public/images/13.png" id="rela-file-icon">
+<img src="/public/images/coins.png" id="rela-file-hover">
+<img src="/public/images/credit_card.png" id="rela-file-down">
+<script>
+    GC.Spread.Sheets.LicenseKey = "559432293813965#A0y3iTOzEDOzkjMyMDN9UTNiojIklkI1pjIEJCLi4TPB9mM5AFNTd4cvZ7SaJUVy3CWKtWYXx4VVhjMpp7dYNGdx2ia9sEVlZGOTh7NRlTUwkWR9wEV4gmbjBDZ4ElR8N7cGdHVvEWVBtCOwIGW0ZmeYVWVr3mI0IyUiwCMzETN8kzNzYTM0IicfJye&Qf35VfiEzRwEkI0IyQiwiIwEjL6ByUKBCZhVmcwNlI0IiTis7W0ICZyBlIsIyNyMzM5ADI5ADNwcTMwIjI0ICdyNkIsIibj9SbvNmL4N7bjRnch56ciojIz5GRiwiI8+Y9sWY9QmZ0Jyp96uL9v6L0wap9biY9qiq95q197Wr9g+89iojIh94Wiqi";
+</script>
+<script>
+    const dealPay = JSON.parse('<%- JSON.stringify(dealPay) %>');
+    const calcBase = JSON.parse('<%- JSON.stringify(calcBase) %>');
+</script>

+ 39 - 48
app/view/stage/pay_modal.ejs

@@ -1,37 +1,5 @@
-<!--章节设置-->
-<div class="modal fade" id="zjset" 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-sm table-bordered">
-                    <tr><th width="200">章节</th><th>名称</th></tr>
-                    <tr><td>100</td><td><input type="text" class="form-control form-control-sm" value="总则"></td></tr>
-                    <tr><td>200</td><td><input type="text" class="form-control form-control-sm" value="路基"></td></tr>
-                    <tr><td>300</td><td><input type="text" class="form-control form-control-sm" value="路面"></td></tr>
-                    <tr><td>400</td><td><input type="text" class="form-control form-control-sm" value="桥梁、涵洞"></td></tr>
-                    <tr><td>500</td><td><input type="text" class="form-control form-control-sm" value="隧道"></td></tr>
-                    <tr><td>600</td><td><input type="text" class="form-control form-control-sm" value="安全设施及预埋管线"></td></tr>
-                    <tr><td>700</td><td><input type="text" class="form-control form-control-sm" value="绿化及环境保护"></td></tr>
-                    <tr><td>800</td><td><input type="text" class="form-control form-control-sm" value="公路沿线管理用房设施"></td></tr>
-                    <tr><td>900</td><td><input type="text" class="form-control form-control-sm" value="监控系统"></td></tr>
-                    <tr><td>1000</td><td><input type="text" class="form-control form-control-sm" value="收费系统"></td></tr>
-                    <tr><td>1100</td><td><input type="text" class="form-control form-control-sm" value="通信系统"></td></tr>
-                    <tr><td>1200</td><td><input type="text" class="form-control form-control-sm" value="消防系统"></td></tr>
-                    <tr><td>1300</td><td><input type="text" class="form-control form-control-sm" value="供配电及照明系统"></td></tr>
-                </table>
-            </div>
-            <div class="modal-footer">
-                <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
-                <button type="button" class="btn btn-primary">确定修改</button>
-            </div>
-        </div>
-    </div>
-</div>
 <!--设置计提期限-->
-<div class="modal fade" id="op2" data-backdrop="static">
+<div class="modal fade" id="deadline" data-backdrop="static">
     <div class="modal-dialog" role="document">
         <div class="modal-content">
             <div class="modal-header">
@@ -43,54 +11,56 @@
                     <label for="formGroupExampleInput">限制模式为:</label>
                     <div>
                         <div class="form-check form-check-inline">
-                            <input class="form-check-input" type="radio" name="inlineRadioOptions" id="inlineRadio1" value="option1">
+                            <input class="form-check-input" type="radio" name="dl-type" id="inlineRadio1" value="0">
                             <label class="form-check-label" for="inlineRadio1">无</label>
                         </div>
                         <div class="form-check form-check-inline">
-                            <input class="form-check-input" type="radio" name="inlineRadioOptions" id="inlineRadio2" value="option2">
+                            <input class="form-check-input" type="radio" name="dl-type" id="inlineRadio2" value="1">
                             <label class="form-check-label" for="inlineRadio2">计量期数</label>
                         </div>
                         <div class="form-check form-check-inline">
-                            <input class="form-check-input" type="radio" name="inlineRadioOptions" id="inlineRadio3" value="option3">
+                            <input class="form-check-input" type="radio" name="dl-type" id="inlineRadio3" value="2">
                             <label class="form-check-label" for="inlineRadio3">计量金额</label>
                         </div>
                     </div>
                 </div>
                 <!--计量期数模式-->
-                <div class="form-group">
+                <div class="form-group" dl-type="1">
                     <label>限制期数</label>
-                    <input class="form-control" type="number">
+                    <input class="form-control" id="stage-count" type="number">
                 </div>
                 <!--计量金额模式-->
-                <div class="form-group">
+                <div class="form-group" dl-type="2">
                     <label for="formGroupExampleInput"></label>
                     <div>
                         <div class="form-check form-check-inline">
-                            <input class="form-check-input" type="radio" name="iROptions" id="ilRadio1" value="option1">
+                            <input class="form-check-input" type="radio" name="tp-type" id="ilRadio1" value="gather">
                             <label class="form-check-label" for="ilRadio1">累计完成计量金额</label>
                         </div>
                         <div class="form-check form-check-inline">
-                            <input class="form-check-input" type="radio" name="iROptions" id="ilRadio2" value="option2">
+                            <input class="form-check-input" type="radio" name="tp-type" id="ilRadio2" value="deal">
                             <label class="form-check-label" for="ilRadio2">累计合同计量金额</label>
                         </div>
                         <div class="form-check form-check-inline">
-                            <input class="form-check-input" type="radio" name="iROptions" id="ilRadio3" value="option3">
+                            <input class="form-check-input" type="radio" name="tp-type" id="ilRadio3" value="qc">
                             <label class="form-check-label" for="ilRadio3">累计变更计量金额</label>
                         </div>
                     </div>
                 </div>
-                <div class="form-group">
+                <div class="form-group" dl-type="2">
                     <label>限制金额</label>
-                    <input class="form-control" type="number">
+                    <input class="form-control" id="tp" type="number">
                 </div>
                 <!--公用提示-->
-                <p>设置为:</p>
-                <p class="pl-3 text-danger">当 累计完成计量金额 >= 50000.00 时</p>
-                <p class="pl-3">当期金额直接计量至(扣款限额 - 截止上期金额)</p>
+                <div id="dl-hint">
+                    <p>设置为:</p>
+                    <p id="range-hint" class="pl-3 text-danger">当 累计完成计量金额 >= 50000.00 时</p>
+                    <p class="pl-3">当期金额直接计量至(扣款限额 - 截止上期金额)</p>
+                </div>
             </div>
             <div class="modal-footer">
                 <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
-                <button type="button" class="btn btn-primary" >确定</button>
+                <button type="button" class="btn btn-primary" id="deadline-ok">确定</button>
             </div>
         </div>
     </div>
@@ -124,4 +94,25 @@
         </div>
     </div>
 </div>
+<!--明细-->
+<div class="modal fade" id="detail" data-backdrop="static">
+    <div class="modal-dialog modal-lg" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">章节明细</h5>
+            </div>
+            <div class="modal-body">
+                <table class="table table-sm table-bordered">
+                    <thead>
+                    <tr class="text-center"><th>章节名称</th><th>本期合同计量金额</th><th>本期变更计量金额</th><th>本期完成计量基金</th><th>累计完成计量金额</th></tr>
+                    </thead>
+                    <tbody id="detail-list"></tbody>
+                </table>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
+            </div>
+        </div>
+    </div>
+</div>
 <% include ./audit_modal.ejs %>

+ 0 - 32
app/view/tender/detail.ejs

@@ -391,14 +391,6 @@
                                             <div class="col-2">
                                                 <div class="input-group input-group-sm">
                                                     <div class="input-group-prepend">
-                                                        <span class="input-group-text">数量</span>
-                                                    </div>
-                                                    <input type="number" class="form-control" value="3" id="decimal-qty" min="0" max="6">
-                                                </div>
-                                            </div>
-                                            <div class="col-2">
-                                                <div class="input-group input-group-sm">
-                                                    <div class="input-group-prepend">
                                                         <span class="input-group-text">单价</span>
                                                     </div>
                                                     <input type="number" class="form-control" value="3" id="decimal-up" min="0" max="4">
@@ -419,30 +411,6 @@
                                         <div class="row">
                                             <div class="col-auto">
                                                 <div class="form-group form-check mt-1">
-                                                    <input type="checkbox" class="form-check-input" id="decimal-deal" checked="">
-                                                    <label class="form-check-label" for="exampleCheck1">签约清单</label>
-                                                </div>
-                                            </div>
-                                            <div class="col-2">
-                                                <div class="input-group input-group-sm">
-                                                    <div class="input-group-prepend">
-                                                        <span class="input-group-text">数量</span>
-                                                    </div>
-                                                    <input type="number" class="form-control" value="3" id="decimal-deal-qty" min="0" max="6">
-                                                </div>
-                                            </div>
-                                            <div class="col-2">
-                                                <div class="input-group input-group-sm">
-                                                    <div class="input-group-prepend">
-                                                        <span class="input-group-text">金额</span>
-                                                    </div>
-                                                    <input type="number" class="form-control" value="2" id="decimal-deal-tp" min="0" max="4">
-                                                </div>
-                                            </div>
-                                        </div>
-                                        <div class="row">
-                                            <div class="col-auto">
-                                                <div class="form-group form-check mt-1">
                                                     <input type="checkbox" class="form-check-input" id="decimal-pay" onchange="CalculateAllDealParam()">
                                                     <label class="form-check-label" for="exampleCheck2">合同支付</label>
                                                 </div>

+ 11 - 0
config/web.js

@@ -126,6 +126,17 @@ const JsFiles = {
                 ],
                 mergeFile: 'stage_detail',
             },
+            pay: {
+                files: [
+                    "/public/js/spreadjs/sheets/gc.spread.sheets.all.10.0.1.min.js",
+                    "https://cdnjs.cloudflare.com/ajax/libs/mathjs/5.0.0/math.js",
+                ],
+                mergeFiles: [
+                    "/public/js/spreadjs_rela/spreadjs_zh.js",
+                    "/public/js/stage_pay.js",
+                ],
+                mergeFile: 'stage_pay',
+            },
             gather: {
                 files: [
                     "/public/js/spreadjs/sheets/gc.spread.sheets.all.10.0.1.min.js",

+ 31 - 0
test/app/service/stage_bills.test.js

@@ -0,0 +1,31 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const { app, assert } = require('egg-mock/bootstrap');
+
+describe('test/app/service/stage_bills.test.js', () => {
+    it('test getSumTotalPrice', function* () {
+        const ctx = app.mockContext();
+        const stage = yield ctx.service.stage.getDataByCondition({
+            id: 4
+        });
+        const result = yield ctx.service.stageBills.getSumTotalPrice(stage);
+        const bills  = yield ctx.service.stageBills.getStage
+        assert(result === 209151.2);
+    });
+    it('test getSumTotalPriceGcl', function* () {
+        const ctx = app.mockContext();
+        const stage = yield ctx.service.stage.getDataByCondition({
+            id: 4
+        });
+        const result = yield ctx.service.stageBills.getSumTotalPriceGcl(stage, '1[0-9]3-');
+        assert(result === 169151.2);
+    });
+});