Browse Source

1. 合同支付2.0
2. 上报、审批通过、审批退回相关调整

MaiXinRong 6 năm trước cách đây
mục cha
commit
7bdf029074

+ 2 - 0
app.js

@@ -12,6 +12,7 @@ const fs = require('fs');
 const moment = require('moment');
 const uuid = require('node-uuid');
 const _ = require('lodash');
+const calc = require('number-precision');
 
 const BaseService = require('./app/base/base_service');
 const BaseController = require('./app/base/base_controller');
@@ -23,6 +24,7 @@ module.exports = app => {
     app.uuid = uuid;
     app.moment = moment;
     app._ = _;
+    app.calc = calc;
     app.menu = menu;
     // 数据模型基类
     app.BaseService = BaseService;

+ 1 - 0
app/base/base_controller.js

@@ -24,6 +24,7 @@ class BaseController extends Controller {
         this.messageType = messageType;
         this.jsValidator = this.app.jsValidator;
         this.menu = this.app.menu;
+        this.calc = this.app.calc;
         // 当前菜单
         ctx.menu = menuList[ctx.controllerName] === undefined ? {} : menuList[ctx.controllerName];
         ctx.title = ctx.menu === {} ? '' : ctx.menu.name;

+ 1 - 0
app/base/base_service.js

@@ -26,6 +26,7 @@ class BaseService extends Service {
         this.transaction = null;
         this.sqlBuilder = null;
         this._ = this.app._;
+        this.calc = this.app.calc;
     }
 
     /**

+ 6 - 0
app/const/audit.js

@@ -48,6 +48,11 @@ statusClass[status.checkNo] = 'text-danger';
 statusClass[status.back] = 'text-warning';
 statusClass[status.backnew] = 'text-warning';
 
+const backType = {
+    org: 1,
+    pre: 2,
+};
+
 /* ------------------------------------------------------- */
 
 // 变更令审批人状态
@@ -101,6 +106,7 @@ module.exports = {
         statusButton,
         statusButtonClass,
         statusClass,
+        backType,
         auditStatus,
         auditStatusString,
         auditStatusClass,

+ 18 - 3
app/const/deal_pay.js

@@ -15,7 +15,7 @@ const payType = {
     wc: 4
 };
 const payTemplate = [
-    {order: 1, name: '本期应付', ptype: payType.yf, minus: false},
+    {order: 1, name: '本期应付', ptype: payType.yf, minus: false, expr: null, sexpr: null, rexpr: null},
     {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},
@@ -24,11 +24,12 @@ const payTemplate = [
 
 const calcBase = [
     {name: '签约合同价', code: 'htj'},
+    {name: '暂列金额', code: 'zlje'},
     {name: '签约合同价(不含暂列金)', code: 'htjszl'},
     {name: '签约开工预付款', code: 'kgyfk'},
     {name: '签约材料预付款', code: 'clyfk'},
-    {name: '本期完成计量', code: 'bqwc'},
-    {name: '100章本期完成计量', code: 'ybbqwc'},
+    {name: '本期完成计量', code: 'bqwc', limit: true},
+    {name: '100章本期完成计量', code: 'ybbqwc', limit: true},
 ];
 
 const chapterDetailType = {
@@ -39,6 +40,19 @@ const chapterDetailType = {
     sum: 41
 };
 
+const deadlineType = {
+    none: { value: 0, name: '无' },
+    count: { value: 1, name: '计量期数' },
+    tp: {
+        value: 2, name: '计量金额',
+        tpType: {
+            gather: { value: 'gather', name: '累计完成计量金额'},
+            contract: { value: 'contract', name: '累计合同计量金额'},
+            qc: { value: 'qc', name: '累计变更计量金额'}
+        },
+    },
+};
+
 const chapterDetail = [
     {name: '清单 第100章 总则', cType: 1, serialNo: 1, filter: '^1[0-9]{2}-'},
     {name: '清单 第200章 路基', cType: 1, serialNo: 2, filter: '^2[0-9]{2}-'},
@@ -58,4 +72,5 @@ module.exports = {
     payTemplate,
     calcBase,
     chapterDetail,
+    deadlineType,
 };

+ 90 - 54
app/controller/stage_controller.js

@@ -15,6 +15,7 @@ const tenderConst = require('../const/tender');
 const payConst = require('../const/deal_pay.js');
 const measureType = tenderConst.measureType;
 const path = require('path');
+const PayCalculator = require('../lib/pay_calc');
 
 module.exports = app => {
     class StageController extends app.BaseController {
@@ -109,6 +110,13 @@ module.exports = app => {
             });
             ctx.stage.auditors = await ctx.service.stageAudit.getAuditors(ctx.stage.id, ctx.stage.times);
             ctx.stage.curAuditor = await ctx.service.stageAudit.getCurAuditor(ctx.stage.id, ctx.stage.times);
+            ctx.stage.user = await ctx.service.projectAccount.getAccountInfoById(ctx.stage.user_id);
+            ctx.stage.auditHistory = [];
+            if (ctx.stage.times > 1) {
+                for (let i = 1; i < ctx.stage.times; i++) {
+                    ctx.stage.auditHistory.push(await ctx.service.stageAudit.getAuditors(ctx.stage.id, i));
+                }
+            }
         }
 
         /**
@@ -384,52 +392,6 @@ module.exports = app => {
         }
 
         /**
-         * 获取计算参数
-         * @param ctx
-         * @returns {Promise<*[]>}
-         * @private
-         */
-        async _getPayCalcBase(ctx) {
-            //const calcBase = JSON.parse(JSON.stringify(payConst.calcBase));
-            const calcBase = [
-                {name: '签约合同价', code: 'htj'},
-                {name: '签约合同价(不含暂列金)', code: 'htjszl'},
-                {name: '签约开工预付款', code: 'kgyfk'},
-                {name: '签约材料预付款', code: 'clyfk'},
-                {name: '本期完成计量', code: 'bqwc'},
-                {name: '100章本期完成计量', code: 'ybbqwc'},
-            ];
-            const param = ctx.tender.info.deal_param;
-            for (const cb of calcBase) {
-                switch (cb.code) {
-                    case 'htj':
-                        cb.value = param.contractPrice;
-                        break;
-                    case 'htjszl':
-                        cb.value = this.app._.subtract(param.contractPrice, param.zanLiePrice);
-                        break;
-                    case 'kgyfk':
-                        cb.value = param.startAdvance;
-                        break;
-                    case 'clyfk':
-                        cb.value = param.materialAdvance;
-                        break;
-                    case 'bqwc':
-                        const sum = await ctx.service.stageBills.getSumTotalPrice(ctx.stage);
-                        cb.value = this.app._.add(sum.contract_tp, sum.qc_tp);
-                        break;
-                    case 'ybbqwc':
-                        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;
-        }
-
-        /**
          * 合同支付
          * @param ctx
          * @returns {Promise<void>}
@@ -438,18 +400,20 @@ module.exports = app => {
             try {
                 await this._getStage(ctx);
                 const renderData = this._getDefaultRenderData(ctx);
-                const dealPay = await ctx.service.pay.getAllDataByCondition({
-                    where: { tid: ctx.tender.id },
-                    orders: [['order', 'acs']],
-                });
+                const dealPay = await ctx.service.stagePay.getStagePays(ctx.stage);
                 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 } });
+                    await ctx.service.stagePay.addInitialStageData(ctx.stage);
+                    renderData.dealPay = await ctx.service.stagePay.getStagePays(ctx.stage);
                 }
-                renderData.calcBase = await this._getPayCalcBase(ctx);
+                renderData.calcBase = await ctx.service.stage.getStagePayCalcBase();
                 renderData.jsFiles = this.app.jsFiles.common.concat(this.app.jsFiles.stage.pay);
+
+                // 计算 本期金额
+                const payCalculator = new PayCalculator(this.ctx, this.ctx.tender.info.decimal);
+                await payCalculator.calculateAll(renderData.dealPay);
                 await this.layout('stage/pay.ejs', renderData, 'stage/pay_modal.ejs');
             } catch (err) {
                 this.log(err);
@@ -457,6 +421,11 @@ module.exports = app => {
             }
         }
 
+        /**
+         * 合同支付 - 编辑合同支付项
+         * @param ctx
+         * @returns {Promise<void>}
+         */
         async savePayData(ctx) {
             try {
                 await this._getStage(ctx);
@@ -473,23 +442,40 @@ module.exports = app => {
 
                 const data = JSON.parse(ctx.request.body.data);
                 const responseData = { err: 0, msg: '', data: {}, };
+                const payCalculator = new PayCalculator(this.ctx, this.ctx.tender.info.decimal);
                 switch (data.type) {
                     case 'add':
                         responseData.data = await ctx.service.pay.add();
+                        responseData.data = await ctx.service.stagePay.getStagePay(ctx.stage, responseData.data.pid);
                         break;
                     case 'del':
-                        responseData.data = await ctx.service.pay.del(data.id);
+                        await ctx.service.pay.del(data.id);
+                        responseData.data = await ctx.service.stagePay.getStagePays(ctx.stage);
+                        await payCalculator.calculate(responseData.data);
                         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);
+                        if (data.updateData.sexpr !== undefined || data.updateData.sprice !== undefined
+                            || data.updateData.rexpr !== undefined || data.updateData.rprice !== undefined
+                            || data.updateData.minus !== undefined || data.updateData.is_yf !== undefined
+                            || data.updateData.dl_type !== undefined) {
+                            responseData.data = await ctx.service.stagePay.getStagePays(ctx.stage);
+                            await payCalculator.calculateAll(responseData.data);
+                            //console.log(responseData.data[responseData.data.length - 1]);
+                        } else {
+                            responseData.data = await ctx.service.stagePay.getStagePay(ctx.stage, responseData.data.id);
+                        }
                         break;
                     case 'stage':
-                        responseData.data = await ctx.service.stagePay.saveData(data.updateData);
+                        await ctx.service.stagePay.save(data.updateData);
+                        responseData.data = await ctx.service.stagePay.getStagePays(ctx.stage);
+                        await payCalculator.calculateAll(responseData.data);
                         break;
                 }
+
                 ctx.body = responseData;
             } catch(err) {
                 this.log(err);
@@ -648,7 +634,28 @@ module.exports = app => {
          * @returns {Promise<void>}
          */
         async startAudit(ctx) {
+            try {
+                await this._getStage(ctx);
+
+                // 检查权限等
+                if (!ctx.stage) {
+                    throw '数据错误';
+                }
+                if (ctx.stage.user_id !== ctx.session.sessionUser.accountId) {
+                    throw '您无权上报该期数据';
+                }
+                if (ctx.stage.status === auditConst.flow.status.checking || ctx.stage.status === auditConst.flow.status.checked) {
+                    throw '该期数据当前无法上报';
+                }
 
+                await ctx.service.stageAudit.start(ctx.stage.id, ctx.stage.times);
+
+                ctx.redirect(ctx.request.header.referer);
+            } catch (err) {
+                this.log(err);
+                ctx.session.postError = err.toString();
+                ctx.redirect(ctx.request.header.referer);
+            }
         }
         /**
          * 审批
@@ -656,7 +663,36 @@ module.exports = app => {
          * @returns {Promise<void>}
          */
         async checkAudit(ctx) {
+            try {
+                await this._getStage(ctx);
+                if (!this.ctx.stage || this.ctx.stage.status !== auditConst.flow.status.checking) {
+                    throw '当前期数据有误';
+                }
+                if (!this.ctx.stage.curAuditor || this.ctx.stage.curAuditor.aid !== ctx.session.sessionUser.accountId) {
+                    throw '您无权进行该操作';
+                }
+                const data = {
+                    checkType: parseInt(ctx.request.body.checkType),
+                    backType: parseInt(ctx.request.body.backType),
+                    opinion: ctx.request.body.opinion,
+                };
+                if (!data.checkType || isNaN(data.checkType)) {
+                    throw '提交数据错误';
+                }
+                if (data.checkType === auditConst.flow.status.checkNo) {
+                    if (!data.backType || isNaN(data.backType)) {
+                        throw '提交数据错误';
+                    }
+                }
+
+                await ctx.service.stageAudit.check(ctx.stage.id, data, ctx.stage.times);
 
+                ctx.redirect(ctx.request.header.referer);
+            } catch (err) {
+                this.log(err);
+                ctx.session.postError = err.toString();
+                ctx.redirect(ctx.request.header.referer);
+            }
         }
 
         _getGatherSpreadSetting() {

+ 52 - 6
app/extend/helper.js

@@ -12,7 +12,11 @@ const fs = require('fs');
 const path = require('path');
 const streamToArray = require('stream-to-array');
 const _ = require('lodash');
+const np = require('number-precision');
+const math = require('mathjs');
+
 module.exports = {
+    _: _,
 
     /**
      * 生成随机字符串
@@ -401,7 +405,7 @@ module.exports = {
                 }
             }
             return result;
-        }
+        };
         const getLevelNodes = function (nodes, level) {
             const children = nodes.filter(function (a) {
                 return a.level = level;
@@ -410,22 +414,22 @@ module.exports = {
                 return a.order - b.order;
             })
             return children;
-        }
+        };
         const getChildren = function (nodes, node) {
             const children = nodes.filter(function (a) {
                 return a[pidField] = node[idField];
             });
             children.sort(function (a, b) {
                 return a.order - b.order;
-            })
+            });
             return children;
-        }
+        };
         const addSortNodes = function (nodes) {
             for (let i = 0; i< nodes.length; i++) {
                 result.push(nodes[i]);
                 addSortNodes(getChildren(nodes[i]));
             }
-        }
+        };
 
         const firstLevel = getFirstLevel(treeNodes);
         addSortNodes(getLevelNodes(treeNodes, firstLevel));
@@ -588,6 +592,48 @@ module.exports = {
         }
     },
 
+    // 以下方法均调用number-precision处理
+    // 加减乘除方法,为方便调用,兼容num为空的情况
+    /**
+     * 加法 num1 + num2
+     * @param num1
+     * @param num2
+     * @returns {number}
+     */
+    plus(num1, num2) {
+        return np.plus(num1 ? num1 : 0, num2 ? num2: 0);
+    },
+    /**
+     * 减法 num1 - num2
+     * @param num1
+     * @param num2
+     * @returns {number}
+     */
+    minus(num1, num2) {
+        return np.minus(num1 ? num1 : 0, num2 ? num2 : 0);
+    },
+    /**
+     * 乘法 num1 * num2
+     * @param num1
+     * @param num2
+     * @returns {*}
+     */
+    times(num1, num2) {
+        return np.times(num1 ? num1 : 0, num2 ? num2 : 0);
+    },
+    /**
+     * 除法 num1 / num2
+     * @param num1 - 被除数
+     * @param num2 - 除数
+     * @returns {*}
+     */
+    divide(num1, num2) {
+        if (num2 && !this.checkZero(num2)) {
+            return np.divide(num1 ? num1: 0, num2);
+        } else {
+            return null;
+        }
+    },
     /**
      * 四舍五入(统一,方便以后万一需要置换)
      * @param {Number} value - 舍入的数字
@@ -595,6 +641,6 @@ module.exports = {
      * @returns {*}
      */
     round(value, decimal) {
-        return _.round(value, decimal);
+        return value ? np.round(value, decimal) : null;
     },
 };

+ 165 - 0
app/lib/pay_calc.js

@@ -0,0 +1,165 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const math = require('mathjs');
+const PayConst = require('../const/deal_pay.js');
+const payType = PayConst.payType;
+const deadlineType = PayConst.deadlineType;
+
+class PayCalculate {
+    constructor (ctx, decimal) {
+        this.ctx = ctx;
+        this.percentReg = /[0-9]+%/g;
+        this.decimal = decimal.pay ? decimal.payTp : decimal.tp;
+    }
+
+    /**
+     * 获取 计算基数
+     * @returns {Promise<void>}
+     */
+    async getCalcBase () {
+        if (this.bases) { return; }
+        const bases = await this.ctx.service.stage.getStagePayCalcBase();
+        this.bases = bases.sort(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');
+        }
+    }
+
+    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);
+    }
+
+    /**
+     * 计算起扣金额、付(扣)款限额
+     *
+     * @param {Array} pays - (标段)合同支付数据
+     */
+    async calculateStartRangePrice (pays) {
+        await this.getCalcBase();
+        const order = this.ctx.stage.curAuditor ? this.ctx.stage.curAuditor.order : 0;
+        for (const p of pays) {
+            // 非本期,本次添加的合同支付项,不允许计算,其中默认添加的合同支付项,归属于第一期原报
+            if (p.csorder === this.ctx.stage.order || (p.csorder === 0 || this.ctx.stage.order === 1)) {
+                if (p.csaorder === order) {
+                    if (!p.sprice && p.sexpr && p.sexpr !== '') {
+                        p.sprice = this.ctx.helper.round(this.calculateExpr(p.sexpr), this.decimal);
+                    }
+                    if (!p.rprice && p.rexpr && p.rexpr !== '') {
+                        p.rprice = this.ctx.helper.round(this.calculateExpr(p.rexpr), this.decimal);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * 累计 计量等数据
+     */
+    async getAddCalcRela() {
+        // todo 获取截止上期数据
+        const pre = null;
+        const cur = await this.ctx.service.stageBills.getSumTotalPrice(this.ctx.stage);
+        const add = {};
+        if (pre) {
+            add.contract_tp = this.ctx.helper.plus(pre.contract_tp, cur.contract_tp);
+            add.qc_tp = this.ctx.helper.plus(pre.qc_tp, cur.qc_tp);
+        } else {
+            add.contract_tp = cur.contract_tp;
+            add.qc_tp = cur.qc_tp;
+        }
+        add.gather_tp = this.ctx.helper.plus(add.contract_tp, add.qc_tp);
+        return add;
+    }
+
+    /**
+     * 检查是否到达 计提期限
+     * @param pay
+     */
+    checkDeadline(pay, addRela) {
+        if (pay.dl_type === deadlineType.tp.value) {
+            const deadlineTp = addRela[pay.dl_tp_type + '_tp'];
+            if (deadlineTp > pay.dl_tp) {
+                return true;
+            }
+        } else if (pay.dl_type === deadlineType.count.value) {
+            return this.ctx.stage.order >= pay.dl_count;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 计算本期、截止本期金额
+     * @param {Array} pays - (标段&期)合同支付数据
+     */
+    async calculate(pays) {
+        await this.getCalcBase();
+        const addRela = await this.getAddCalcRela();
+        const yfPay = pays.find(function (p) {
+            return p.ptype === payType.yf;
+        });
+        yfPay.tp = 0;
+        for (const p of pays) {
+            if (p.ptype === payType.normal || p.ptype === payType.wc) {
+                if (p.expr && p.expr !== '') {
+                    const value = this.ctx.helper.round(this.calculateExpr(p.expr), this.decimal);
+                    if (!p.pause && (!p.sprice || addRela.gather_tp > p.sprice)) {
+                        if (p.rprice) {
+                            if (this.checkDeadline(p)) {
+                                p.tp = this.ctx.helper.minus(p.rprice, p.pre_total_price);
+                            } else {
+                                p.tp = Math.min(this.ctx.helper.minus(p.rprice, p.pre_total_price), value);
+                            }
+                        } else {
+                            p.tp = value;
+                        }
+                    } else {
+                        p.tp = null;
+                    }
+                }
+                // 累加 至 应付
+                if (p.is_yf) {
+                    if (p.minus) {
+                        yfPay.tp = this.ctx.helper.minus(yfPay.tp, p.tp);
+                    } else {
+                        yfPay.tp = this.ctx.helper.plus(yfPay.tp, p.tp);
+                    }
+                }
+            }
+            p.end_tp = this.ctx.helper.round(this.ctx.helper.plus(p.tp, p.pre_tp), this.decimal);
+        }
+        yfPay.end_tp = this.ctx.helper.plus(yfPay.tp, yfPay.pre_tp);
+    }
+
+    async calculateAll(pays) {
+        await this.calculateStartRangePrice(pays);
+        await this.calculate(pays);
+    }
+}
+
+module.exports = PayCalculate;

+ 59 - 81
app/public/js/stage_pay.js

@@ -16,66 +16,18 @@ function getStageId() {
     return window.location.pathname.split('/')[5];
 }
 
-class PayCalculate {
-    constructor (bases, decimal) {
-        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;
-        this.decimal = decimal.pay ? decimal.payTp : decimal.tp;
-    }
-
-    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 = _.round(this.calculateExpr(p.sexpr), this.decimal);
-            }
-            if (!p.rprice && p.rexpr && p.rexpr !== '') {
-                p.rprice = _.round(this.calculateExpr(p.rexpr), this.decimal);
-            }
-        }
-    }
-
-    calculate(pays) {
-        for (const p of pays) {
-            if (p.ptype === 1 || p.ptype === 4) {
-                if (p.expr && p.expr !== '') {
-                    p.tp = _.round(this.calculateExpr(p.expr), this.decimal);
-                }
-            }
-            p.end_tp = _.round(_.add(p.tp, p.pre_tp), this.decimal);
+function loadUpdateDealPays(newPay) {
+    const newPays = newPay instanceof Array ? newPay : [newPay];
+    for (const np of newPays) {
+        const op = _.find(dealPay, {id: np.id});
+        for (const prop in np) {
+            op[prop] = np[prop];
         }
     }
 }
 
 $(document).ready(() => {
     autoFlashHeight();
-    const calcualtor = new PayCalculate(calcBase, decimal);
-    calcualtor.calculateStartRangePrice(dealPay);
-    calcualtor.calculate(dealPay);
-
 
     const paySpread = SpreadJsObj.createNewSpread($('#pay-spread')[0]);
     const paySpreadSetting = {
@@ -103,11 +55,11 @@ $(document).ready(() => {
     paySpreadSetting.getColor = function (data, col, defaultColor) {
         if (data) {
             if (data.pause) {
-                return '#666666';
-            } else if (data.csorder == getStageId() && data.uid > 0) {
-                return '#FFFFE1';
+                return '#f2f2f2';
             } else if (!data.is_yf) {
                 return '#d6d8db';
+            } else if (data.csorder == getStageId() && data.uid > 0) {
+                return '#FFFFE1';
             } else {
                 return defaultColor;
             }
@@ -189,10 +141,12 @@ $(document).ready(() => {
         del: function () {
             const sheet = paySpread.getActiveSheet();
             const select = SpreadJsObj.getSelectObject(sheet);
-            postData(window.location.pathname + '/save', {type: 'del', id: select.id}, function () {
+            postData(window.location.pathname + '/save', {type: 'del', id: select.pid}, function (result) {
                 const index = dealPay.indexOf(select);
                 dealPay.splice(index, 1);
                 sheet.deleteRows(index, 1);
+                loadUpdateDealPays(result);
+                SpreadJsObj.reLoadSheetData(paySpread.getActiveSheet());
                 const sel = sheet.getSelections();
                 sheet.setSelection(index > 0 ? index - 1 : 0, sel.length > 0 ? sel[0].col : 0, 1, 1);
                 paySpreadObj.refreshActn();
@@ -234,6 +188,21 @@ $(document).ready(() => {
             paySpreadObj.refreshActn();
         },
         editEnded: function (e, info) {
+            const checkExpr = function(text, data, priceField, exprField) {
+                if (text) {
+                    const num = _.toNumber(text);
+                    if (num) {
+                        data[priceField] = num;
+                        data[exprField] = null;
+                    } else {
+                        data[exprField] = text;
+                        data[priceField] = null;
+                    }
+                } else {
+                    data[priceField] = null;
+                    data[exprField] = null;
+                }
+            };
             if (info.sheet.zh_setting) {
                 const select = SpreadJsObj.getSelectObject(info.sheet);
                 const col = info.sheet.zh_setting.cols[info.col];
@@ -247,21 +216,29 @@ $(document).ready(() => {
                     type: col.field === 'tp' ? 'stage' : 'info',
                     updateData: {}
                 };
+                const validText = info.editingText ? info.editingText.replace('\n', '') : null;
                 // 获取更新数据
                 if (col.field === 'tp') {
-
+                    data.updateData.pid = select.pid;
+                    checkExpr(validText, data.updateData, 'tp', 'expr');
                 } else {
-                    data.updateData.id = select.id;
-                    if (info.editingText) {
-                        data.updateData[col.field] = col.type === 'Number' ? parseFloat(info.editingText) : info.editingText.replace('\n', '');
+                    data.updateData.id = select.pid;
+                    if (validText) {
+                        if (col.field === 'sprice') {
+                            checkExpr(validText, data.updateData, 'sprice', 'sexpr');
+                        } else if (col.field === 'rprice') {
+                            checkExpr(validText, data.updateData, 'rprice', 'rexpr');
+                        } else {
+                            data.updateData[col.field] = validText;
+                        }
                     } else {
                         data.updateData[col.field] = null;
                     }
                 }
                 // 更新至服务器
                 postData(window.location.pathname + '/save', data, function (result) {
-                    _.assign(select, result);
-                    SpreadJsObj.reLoadRowData(paySpread.getActiveSheet(), info.row);
+                    loadUpdateDealPays(result);
+                    SpreadJsObj.reLoadSheetData(paySpread.getActiveSheet());
                 });
             }
         },
@@ -274,13 +251,13 @@ $(document).ready(() => {
                     // 获取更新信息
                     const data = {
                         type: 'info',
-                        updateData: { id: select.id },
+                        updateData: { id: select.pid },
                     };
                     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);
+                        loadUpdateDealPays(result);
+                        SpreadJsObj.reLoadSheetData(paySpread.getActiveSheet());
                     });
                 }
             }
@@ -397,14 +374,14 @@ $(document).ready(() => {
                     const data = {
                         type: 'stage',
                         updateData: {
-                            id: select.id,
+                            pid: select.pid,
                             pause: false
                         }
                     };
                     // 更新至服务器
                     postData(window.location.pathname + '/save', data, function (result) {
-                        _.assign(select, result);
-                        SpreadJsObj.reLoadRowData(paySpread.getActiveSheet(), dealPay.indexOf(select));
+                        loadUpdateDealPays(result);
+                        SpreadJsObj.reLoadSheetData(paySpread.getActiveSheet());
                     });
                 },
                 visible: function (key, opt) {
@@ -420,14 +397,14 @@ $(document).ready(() => {
                     const data = {
                         type: 'stage',
                         updateData: {
-                            id: select.id,
+                            pid: select.pid,
                             pause: true
                         }
                     };
                     // 更新至服务器
                     postData(window.location.pathname + '/save', data, function (result) {
-                        _.assign(select, result);
-                        SpreadJsObj.reLoadRowData(paySpread.getActiveSheet(), dealPay.indexOf(select));
+                        loadUpdateDealPays(result);
+                        SpreadJsObj.reLoadSheetData(paySpread.getActiveSheet());
                     });
                 },
                 visible: function (key, opt) {
@@ -465,14 +442,14 @@ $(document).ready(() => {
                     const data = {
                         type: 'info',
                         updateData: {
-                            id: select.id,
+                            id: select.pid,
                             is_yf: false
                         }
                     };
                     // 更新至服务器
                     postData(window.location.pathname + '/save', data, function (result) {
-                        _.assign(select, result);
-                        SpreadJsObj.reLoadRowData(paySpread.getActiveSheet(), dealPay.indexOf(select));
+                        loadUpdateDealPays(result);
+                        SpreadJsObj.reLoadSheetData(paySpread.getActiveSheet());
                     });
                 }
             },
@@ -488,14 +465,14 @@ $(document).ready(() => {
                     const data = {
                         type: 'info',
                         updateData: {
-                            id: select.id,
+                            id: select.pid,
                             is_yf: true
                         }
                     };
                     // 更新至服务器
                     postData(window.location.pathname + '/save', data, function (result) {
-                        _.assign(select, result);
-                        SpreadJsObj.reLoadRowData(paySpread.getActiveSheet(), dealPay.indexOf(select));
+                        loadUpdateDealPays(result);
+                        SpreadJsObj.reLoadSheetData(paySpread.getActiveSheet());
                     });
                 }
             }
@@ -510,7 +487,7 @@ $(document).ready(() => {
         const data = {
             type: 'info',
             updateData: {
-                id: select.id,
+                id: select.pid,
             }
         };
         data.updateData.dl_type = parseInt($('[name=dl-type]:checked').val());
@@ -518,7 +495,8 @@ $(document).ready(() => {
         data.updateData.dl_tp_type = $('[name=tp-type]:checked').val();
         data.updateData.dl_tp = parseFloat($('#tp').val());
         postData(window.location.pathname + '/save', data, function (result) {
-            _.assign(select, result);
+            loadUpdateDealPays(result);
+            SpreadJsObj.reLoadSheetData(paySpread.getActiveSheet());
             $('#deadline').modal('hide');
         });
     });

+ 14 - 14
app/service/ledger.js

@@ -1598,28 +1598,28 @@ module.exports = app => {
                         let calcData = JSON.parse(JSON.stringify(row));
                         const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, row.unit ? row.unit : updateNode.unit);
                         this.ctx.helper.checkFieldPrecision(calcData, qtyFields, precision.value);
-                        if (calcData.quantity) {
-                            if (row.unit_price) {
-                                calcData.total_price = this._.multiply(row.quantity, row.unit_price);
+                        if (row.quantity !== undefined) {
+                            if (row.unit_price  !== undefined) {
+                                calcData.total_price = this.ctx.helper.times(row.quantity, row.unit_price);
                             } else {
-                                calcData.total_price = this._.multiply(row.quantity, updateNode.unit_price);
+                                calcData.total_price = this.ctx.helper.times(row.quantity, updateNode.unit_price);
                             }
-                        } else if (calcData.unit_price) {
-                            calcData.total_price = this._.multiply(updateNode.quantity, row.unit_price);
+                        } else if (row.unit_price !== undefined) {
+                            calcData.total_price = this.ctx.helper.times(updateNode.quantity, row.unit_price);
                         }
-                        if (row.total_price) {
+                        if (row.total_price !== undefined) {
                             calcData.quantity = null;
                         }
-                        if (row.deal_qty) {
-                            if (row.unit_price) {
-                                calcData.deal_tp = this._.multiply(row.deal_qty, row.unit_price);
+                        if (row.deal_qty !== undefined) {
+                            if (row.unit_price !== undefined) {
+                                calcData.deal_tp = this.ctx.helper.times(row.deal_qty, row.unit_price);
                             } else {
-                                calcData.deal_tp = this._.multiply(row.deal_qty, updateNode.unit_price);
+                                calcData.deal_tp = this.ctx.helper.times(row.deal_qty, updateNode.unit_price);
                             }
-                        } else if (row.unit_price) {
-                            calcData.deal_tp = this._.multiply(updateNode.deal_qty, row.unit_price);
+                        } else if (row.unit_price !== undefined) {
+                            calcData.deal_tp = this.ctx.helper.times(updateNode.deal_qty, row.unit_price);
                         }
-                        if (row.deal_tp) {
+                        if (row.deal_tp !== undefined) {
                             calcData.deal_qty = null;
                         }
                         updateData = this._filterUpdateInvalidField(updateNode.id, calcData);

+ 27 - 14
app/service/pay.js

@@ -29,13 +29,14 @@ module.exports = app => {
          * @returns {Promise<boolean>}
          */
         async addDefaultPayData(transaction) {
-            if (!this.ctx.tender || !this.ctx.stage) { return false; }
-            const pays = payConst.payTemplate;
+            if (!this.ctx.tender) { return false; }
+            const pays = JSON.parse(JSON.stringify(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;
+                p.csid = 0; //this.ctx.stage.id;
+                p.cstimes = 0; //this.ctx.stage.times;
+                p.csorder = 0; //this.ctx.stage.order;
+                p.csaorder = 0;
             }
             let result;
             if (transaction) {
@@ -67,20 +68,28 @@ module.exports = app => {
                 csid: this.ctx.stage.id,
                 cstimes: this.ctx.stage.times,
                 csorder: this.ctx.stage.order,
+                csaorder: this.ctx.stage.curAuditor ? this.ctx.stage.curAuditor : 0,
                 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 '新增合同支付项失败'
-            }
+            const transaction = await this.db.beginTransaction();
+            try {
+                const result = await transaction.insert(this.tableName, pay);
+                if (result.affectedRows !== 1) {
+                    throw '新增合同支付项失败'
+                }
+                pay.id = result.insertId;
 
-            pay.id = result.insertId;
-            return pay;
+                const stagePay = await this.ctx.service.stagePay.syncAdd(pay, transaction);
+                await transaction.commit();
+                return this._.assign(pay, stagePay);
+            } catch(err) {
+                await transaction.rollback();
+                throw err;
+            }
         }
 
         /**
@@ -102,11 +111,15 @@ module.exports = app => {
             // 删除合同支付
             const transaction = await this.db.beginTransaction();
             try {
-                const result = await transaction.delete(this.tableName, {id: id});
+                // 假删除
+                //const result = await transaction.delete(this.tableName, {id: id});
+                const result = await transaction.update(this.tableName, {
+                    id: id,
+                    valid: false,
+                });
                 if (result.affectedRows !== 1) {
                     throw '删除合同支付项失败'
                 }
-                // todo 删除期计量中数据
                 await transaction.commit();
             } catch(err) {
                 await transaction.rollback();

+ 57 - 2
app/service/stage.js

@@ -9,6 +9,7 @@
  */
 
 const audit = require('../const/audit');
+const payConst = require('../const/deal_pay.js');
 
 module.exports = app => {
     class Stage extends app.BaseService {
@@ -62,8 +63,27 @@ module.exports = app => {
                 status: audit.flow.status.uncheck,
                 user_id: this.ctx.session.sessionUser.accountId,
             };
-            const result = await this.db.insert(this.tableName, newStage);
-            return result.affectedRows === 1 ? newStage : null;
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 新增期记录
+                const result = await transaction.insert(this.tableName, newStage);
+                if (result.affectedRows === 1) {
+                    newStage.id = result.insertedId;
+                } else {
+                    throw '新增期数据失败';
+                }
+                // 新增期合同支付数据
+                const dealResult = await this.ctx.service.stagePay.addInitialStageData(newStage, transaction);
+                if (!dealResult) {
+                    throw '新增期合同支付数据失败';
+                }
+
+                await transaction.commit();
+                return newStage;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
         }
 
         /**
@@ -100,6 +120,41 @@ module.exports = app => {
                 throw err;
             }
         }
+
+        async getStagePayCalcBase() {
+            const calcBase = JSON.parse(JSON.stringify(payConst.calcBase));
+            const param = this.ctx.tender.info.deal_param;
+            for (const cb of calcBase) {
+                switch (cb.code) {
+                    case 'htj':
+                        cb.value = param.contractPrice;
+                        break;
+                    case 'zlje':
+                        cb.value = param.zanLiePrice;
+                        break;
+                    case 'htjszl':
+                        cb.value = this.app.calc.minus(param.contractPrice, param.zanLiePrice);
+                        break;
+                    case 'kgyfk':
+                        cb.value = param.startAdvance;
+                        break;
+                    case 'clyfk':
+                        cb.value = param.materialAdvance;
+                        break;
+                    case 'bqwc':
+                        const sum = await this.ctx.service.stageBills.getSumTotalPrice(this.ctx.stage);
+                        cb.value = this.app._.add(sum.contract_tp, sum.qc_tp);
+                        break;
+                    case 'ybbqwc':
+                        const sumGcl = await this.ctx.service.stageBills.getSumTotalPriceGcl(this.ctx.stage, '^1[0-9]{2}-');
+                        cb.value = this.app._.add(sumGcl.contract_tp, sumGcl.qc_tp);
+                        break;
+                    default:
+                        cb.value = 0;
+                }
+            }
+            return calcBase;
+        }
     }
 
     return Stage;

+ 21 - 3
app/service/stage_audit.js

@@ -183,8 +183,11 @@ module.exports = app => {
             try {
                 await transaction.update(this.tableName, {id: audit.id, status: auditConst.status.checking, begin_time: new Date()});
                 await transaction.update(this.ctx.service.stage.tableName, {id: stageId, status: auditConst.status.checking});
-                // todo 更新标段stage状态
-                //await transaction.update(this.ctx.service.stage.tableName, {id: stageId, status: auditConst.status.checking});
+                // 计算原报最终数据
+                await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
+                // 复制一份下一审核人数据
+                await this.ctx.service.stagePay.copyAuditStagePays(this.ctx.stage, this.ctx.stage.times, 1, transaction);
+                // todo 更新标段tender状态 ?
                 await transaction.commit();
             } catch(err) {
                 await transaction.rollback();
@@ -200,7 +203,8 @@ module.exports = app => {
          * @param {Number} times - 第几次审批
          * @returns {Promise<void>}
          */
-        async check(stageId, checkType, opinion, times = 1) {
+        async check(stageId, checkData, times = 1) {
+            console.log('check');
             if (checkType !== auditConst.status.checked && checkType !== auditConst.status.checkNo) {
                 throw '提交数据错误';
             }
@@ -216,15 +220,25 @@ module.exports = app => {
                 // 更新当前审核流程
                 await transaction.update(this.tableName, {id: audit.id, status: checkType, opinion: opinion, end_time: time});
                 if (checkType === auditConst.status.checked) {
+                    console.log('checked');
                     const nextAudit = await this.getDataByCondition({sid: stageId, times: times, order: audit.order + 1});
                     // 无下一审核人表示,审核结束
                     if (nextAudit) {
+                        // 计算该审批人最终数据
+                        await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
+                        // 复制一份下一审核人数据
+                        await this.ctx.service.stagePay.copyAuditStagePays(this.ctx.stage, this.ctx.stage.times, nextAudit.order, transaction);
+                        // 流程至下一审批人
                         await transaction.update(this.tableName, {id: nextAudit.id, status: auditConst.status.checking, begin_time: time});
                     } else {
+                        // 本期结束
+                        // 计算并合同支付最终数据
+                        await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
                         // 同步 期信息
                         await transaction.update(this.ctx.service.stage.tableName, {id: stageId, status: checkType});
                     }
                 } else {
+                    console.log('checkNo');
                     // 同步 期信息
                     await transaction.update(this.ctx.service.stage.tableName, {id: stageId, times: times+1, status: checkType});
                     // 拷贝新一次审核流程列表
@@ -237,6 +251,10 @@ module.exports = app => {
                         a.status = auditConst.status.uncheck;
                     }
                     await transaction.insert(this.tableName, auditors);
+                    // 计算该审批人最终数据
+                    await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
+                    // 复制一份最新数据给原报
+                    await this.ctx.service.stagePay.copyAuditStagePays(this.ctx.stage, this.ctx.stage.times + 1, 0, transaction);
                 }
 
                 await transaction.commit();

+ 190 - 195
app/service/stage_pay.js

@@ -18,234 +18,229 @@ module.exports = app => {
          */
         constructor(ctx) {
             super(ctx);
-            this.tableName = 'stage_pos';
-            this.qtyFields = ['contract_qty', 'qc_qty']
+            this.tableName = 'stage_pay';
         }
 
         /**
-         * 查询期计量最后审核人数据
-         * @param {Number} tid - 标段id
+         * 查询 某期 某轮审批 某审核人数据
+         *
+         * @param {Number} pid - 合同支付id
          * @param {Number} sid - 期id
-         * @param {Number|Array} pid - 部位明细id(可以为空)
+         * @param {Number} times - 期第几轮审批
+         * @param {Number} order - 审核人顺序
+         * @param {Number|Array|Null} 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);
+        async getAuditorStagePay(pid, sid, times, order) {
+            const sql = 'SELECT SP.*, P.`csorder`, P.`cstimes`, P.`csaorder`, P.`order`, P.uid, P.name, P.minus, P.ptype, P.sprice, P.sexpr, P.rprice, P.rexpr, P.is_yf, P.dl_type, P.dl_count, P.dl_tp_type, P.dl_tp ' +
+                '  FROM ?? As SP, ?? As P ' +
+                '  WHERE SP.`sid` = ? AND SP.`stimes` = ? AND SP.`sorder` = ? AND SP.`pid` = P.`id` AND SP.`pid` = ? AND P.`valid`' +
+                '  ORDER BY P.`order`';
+            const sqlParam = [this.tableName, this.ctx.service.pay.tableName, sid, times, order, pid];
+            return await this.db.queryOne(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];
+        async getAuditorStageData(sid, times, order) {
+            const sql = 'SELECT SP.*, P.`csorder`, P.`cstimes`, P.`csaorder`, P.`order`, P.uid, P.name, P.minus, P.ptype, P.sprice, P.sexpr, P.rprice, P.rexpr, P.is_yf, P.dl_type, P.dl_count, P.dl_tp_type, P.dl_tp ' +
+                '  FROM ?? As SP, ?? As P ' +
+                '  WHERE SP.`sid` = ? AND SP.`stimes` = ? AND SP.`sorder` = ? AND SP.`pid` = P.`id` AND P.`valid`' +
+                '  ORDER BY P.`order`';
+            const sqlParam = [this.tableName, this.ctx.service.pay.tableName, sid, times, order];
             return await this.db.query(sql, sqlParam);
         }
 
         /**
-         * 新增部位明细数据(仅供updateStageData调用)
-         *
+         * 新增期时,初始化 期--合同支付 数据
+         * @param {Object} stage - 新增的期
          * @param transaction - 事务
-         * @param data - 新增数据
-         * @returns {Promise<{}>}
-         * @private
+         * @returns {Promise<void>}
          */
-        // 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;
-        // }
+        async addInitialStageData(stage, transaction) {
+            if (!stage) {
+                throw '初始化期合同支付数据失败';
+            }
+            const pays = await this.ctx.service.pay.getAllDataByCondition({where: { tid: this.ctx.tender.id } });
+            const stagePays = [];
+            for (const p of pays) {
+                stagePays.push({
+                    tid: p.tid,
+                    sid: stage.id,
+                    pid: p.id,
+                    stimes: stage.times,
+                    sorder: 0,
+                    expr: p.expr,
+                });
+            }
+            // 获取截止上期数据
+            if (stage.order > 1) {
+                const preStage = this.ctx.service.stage.getDataByCondition({tid: stage.tid, order: stage.order - 1});
+                if (!preStage) {
+                    throw '标段数据有误';
+                }
+                const prePays = this.getStageLastestPays(preStage.id);
+                for (const pp of prePays) {
+                    const sp = this._.find(stagePays, {pid: pp.pid});
+                    sp.pre_total_price = pp.end_total_price;
+                }
+            }
+            let result;
+            if (transaction) {
+                result = await transaction.insert(this.tableName, stagePays);
+            } else {
+                result = await this.db.insert(this.tableName, stagePays);
+            }
+            return result.affectedRows === pays.length;
+        }
+
+        async getStagePay(stage, pid) {
+            return await this.getAuditorStagePay(pid, stage.id, stage.times, stage.curAuditor ? stage.curAuditor.order : 0);
+        }
 
         /**
-         * 更新部位明细数据(仅供updateStageData调用)
+         * 获取某期合同支付数据
          *
-         * @param transaction - 事务
-         * @param data - 更新数据(允许一次性提交多条)
-         * @returns {Promise<{ledger: Array, pos: Array}>}
-         * @private
+         * @param {Object} stage - 期数据(通过StageController._getStageData()获取)
+         * @returns {Promise<*>}
          */
-        // 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;
-        // }
+        async getStagePays(stage) {
+            return await this.getAuditorStageData(stage.id, stage.times, stage.curAuditor ? stage.curAuditor.order : 0);
+            // const sql = 'SELECT SP.*, P.`order`, P.uid, P.name, P.minus, P.ptype, P.sprice, P.sexpr, P.rprice, P.rexpr, P.is_yf, P.dl_type, P.dl_count, P.dl_tp_type, P.dl_tp ' +
+            //     '  FROM ?? As SP, ?? As P ' +
+            //     '  WHERE SP.`sid` = ? AND SP.`stimes` = ? AND SP.`sorder` = ? AND SP.`pid` = P.`id` ' +
+            //     '  ORDER BY P.`order`';
+            // const sqlParam = [this.tableName, this.ctx.service.pay.tableName, stage.id, stage.times, stage.curAuditor ? stage.curAuditor.order : 0];
+            // return await this.db.query(sql, sqlParam);
+        }
 
         /**
-         * 删除部位明细数据(仅供updateStageData调用)
+         * 获取某期最后数据(不限制该期是否已经审批通过)
          *
-         * @param transaction - 事务
-         * @param data - 删除的部位明细(允许一次提醒多条,也允许跨清单(但前端操作不允许))
-         * @returns {Promise<{}>}
-         * @private
+         * @param {Number} sid - 期id
+         * @returns {Promise<*>}
          */
-        // 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;
-        // }
+        async getStageLastestPays(sid) {
+            const sql = 'SELECT SP.*, P.`order`, P.uid, P.name, P.minus, P.ptype, P.sprice, P.sexpr, P.rprice, P.rexpr, P.is_yf, P.dl_type, P.dl_count, P.dl_tp_type, P.dl_tp ' +
+                '  FROM ?? As SP, ?? As P, ( ' +
+                '    SELECT MAX(`stimes`) As `stimes`, MAX(`sorder`) As `sorder` ' +
+                '      FROM ?? ' +
+                '      WHERE `sid` = ? ' +
+                '      GOURP BY `sid`) As M' +
+                '  WHERE SP.`sid` = ? AND SP.`stimes` = M.`stimes` AND SP.`sorder` = M.`sorder` AND SP.`pid` = P.`id` AND P.`valid` = true' +
+                '  ORDER BY P.`order`';
+            const sqlParam = [this.tableName, this.ctx.service.pay.tableName, this.ctx.service.stage.tableName, this.ctx.service.stageAudit.tableName,
+                stage.id, stage.id];
+            return await this.db.query(sql, sqlParam);
+        }
 
         /**
-         * 根据前端提交数据,更新并计算
-         *
+         * 同步新增 (仅供合同支付新增时调用)
+         * @param {Object} pay - 合同支付项数据
+         * @param transaction - 新增合同支付的事务
+         * @returns {Promise<{tid, sid: number|*, pid, stimes: number|*, sorder: number|*}>}
+         */
+        async syncAdd(pay, transaction) {
+            const stagePay = {
+                tid: pay.tid,
+                sid: pay.csid,
+                pid: pay.id,
+                stimes: pay.cstimes,
+                sorder: pay.csaorder,
+            };
+            const result = await transaction.insert(this.tableName, stagePay);
+            if (result.affectedRows !== 1) {
+                throw '新增数据失败';
+            }
+            stagePay.id = result.insertId;
+            return stagePay;
+        }
+
+        /**
+         * 保存本期金额/表达式
          * @param data
-         * @returns {Promise<{ledger: {}, pos: {}}>}
+         * @returns {Promise<void>}
          */
-        // 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 '获取数据异常,请刷新页面。';
-        //     }
-        // }
+        async save(data) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 更新数据
+                const stagePay = await this.getStagePay(this.ctx.stage, data.pid);
+                const updateData = { id: stagePay.id };
+                if (data.expr !== undefined) { updateData.expr = data.expr }
+                if (data.tp !== undefined) { updateData.tp = data.tp }
+                if (data.pause !== undefined) { updateData.pause = data.pause }
+                const result = await transaction.update(this.tableName, updateData);
+                if (result.affectedRows !== 1) {
+                    throw '保存数据失败';
+                }
+                // 缓存至pay
+                if (data.expr !== undefined) {
+                    const pr = await transaction.update(this.ctx.service.pay.tableName, {
+                        id: data.pid, expr: data.expr
+                    });
+                    if (pr.affectedRows !== 1) {
+                        throw '保存数据失败';
+                    }
+                }
+                await transaction.commit();
+            } catch(err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        /**
+         * 计算
+         * @param stage
+         * @param transaction
+         * @returns {Promise<boolean>}
+         */
+        async calcAllStagePays(stage, transaction) {
+            if (!stage || !transaction) {
+                throw '计算数据错误';
+            }
+            const stagePays = await this.getStagePays(stage);
+            const PayCalculator = require('../lib/pay_calc');
+            const payCalculator = new PayCalculator(this.ctx, this.ctx.tender.info.decimal);
+            await payCalculator.calculateAll(stagePays);
+            for (const sp of stagePays) {
+                await transaction.update(this.tableName, {
+                    id: sp.id,
+                    tp: sp.tp,
+                    end_tp: sp.end_tp
+                });
+            }
+        }
+
+        /**
+         * 拷贝上一操作人数据 为 下一操作人数据
+         * @param stage - 期数据
+         * @param times - 下一操作人 该期第几次
+         * @param order - 下一操作人顺序
+         * @param transaction - 事务
+         * @returns {Promise<*>}
+         */
+        async copyAuditStagePays(stage, times, order, transaction) {
+            console.log(times);
+            console.log(order);
+            if (!stage || !transaction || !times || !order) {
+                throw '数据错误';
+            }
+            const sql = 'INSERT INTO ?? (`tid`, `sid`, `pid`, `stimes`, `sorder`, `expr`, `pause`, `attachment`, `pre_tp`) ' +
+                        '  SELECT SP.`tid`, SP.`sid`, SP.`pid`, ?, ?, SP.`expr`, SP.`pause`, SP.`attachment`, SP.`pre_tp` ' +
+                        '  FROM ?? As SP, ?? As P ' +
+                        '  WHERE SP.`sid` = ? AND SP.`stimes` = ? AND SP.`sorder` = ? And SP.`pid` = P.`id` And P.`valid`';
+            const sqlParam = [this.tableName, times, order, this.tableName, this.ctx.service.pay.tableName,
+                stage.id, stage.times, stage.curAuditor ? stage.curAuditor.order : 0];
+            return await transaction.query(sql, sqlParam);
+        }
     }
 
     return StagePay;

+ 6 - 1
app/service/tender.js

@@ -143,10 +143,15 @@ module.exports = app => {
                 const tenderNodeTemplateData = await this.ctx.service.tenderNodeTemplate.getData();
                 // 复制模板数据到标段数据表
                 result = await this.ctx.service.ledger.innerAdd(tenderNodeTemplateData, operate.insertId, this.transaction);
-
                 if (!result) {
                     throw '新增标段项目节点失败';
                 }
+
+                // 获取合同支付模板 并添加到标段
+                result = await ctx.service.pay.addDefaultPayData(this.transaction);
+                if (!result) {
+                    throw '新增合同支付数据失败';
+                }
                 await this.transaction.commit();
                 return await this.getTender(operate.insertId);
             } catch (error) {

+ 1 - 1
app/view/stage/audit_btn.ejs

@@ -4,7 +4,7 @@
             <a href="#sub-sp" data-toggle="modal" data-target="#sub-sp" class="btn btn-primary btn-sm pull-right">上报审批</a>
         <% } %>
     <% } else if (ctx.stage.status === auditConst.status.checking) { %>
-        <% if (ctx.stage.curAuditor && ctx.stage.curAuditor.id === ctx.session.sessionUser.accountId) { %>
+        <% if (ctx.stage.curAuditor && ctx.stage.curAuditor.aid === ctx.session.sessionUser.accountId) { %>
             <a href="#sp-done" data-toggle="modal" data-target="#sp-done" class="btn btn-success btn-sm pull-right">审批通过</a>
             <a href="#sp-back" data-toggle="modal" data-target="#sp-back" class="btn btn-warning btn-sm pull-right">审批退回</a>
         <% } else { %>

+ 235 - 131
app/view/stage/audit_modal.ejs

@@ -1,5 +1,4 @@
-<!--上报审批-->
-<% if (ctx.stage.status === auditConst.status.uncheck) { %>
+<% if (ctx.stage.status === auditConst.status.uncheck && ctx.stage.user_id === ctx.session.sessionUser.accountId) { %>
 <!--上报审批-->
 <div class="modal fade" id="sub-sp" data-backdrop="static">
     <div class="modal-dialog" role="document">
@@ -42,7 +41,7 @@
                     </ul>
                 </div>
             </div>
-            <form class="modal-footer" method="post" action="<%- preUrl %>/start">
+            <form class="modal-footer" method="post" action="<%- preUrl %>/audit/start">
                 <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
                 <input type="hidden" name="_csrf" value="<%= ctx.csrf %>">
                 <button class="btn btn-primary" type="submit">确认上报</button>
@@ -51,6 +50,7 @@
     </div>
 </div>
 <% } %>
+<% if (ctx.stage.status === auditConst.status.checkNo && ctx.stage.user_id === ctx.session.sessionUser.accountId) { %>
 <!--重新上报-->
 <div class="modal fade" id="sub-sp2" data-backdrop="static">
     <div class="modal-dialog" role="document">
@@ -92,77 +92,63 @@
         </div>
     </div>
 </div>
-<!--审批流程/结果-->
-<div class="modal fade" id="sp-list" data-backdrop="static">
-    <div class="modal-dialog modal-lg" role="document">
-        <div class="modal-content">
+<% } %>
+<% if (ctx.stage.status === auditConst.status.checking && ctx.stage.curAuditor.aid === ctx.session.sessionUser.accountId) { %>
+<!--审批通过-->
+<div class="modal fade" id="sp-done" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <form class="modal-content" action="<%- preUrl %>/audit/check" method="post">
             <div class="modal-header">
-                <h5 class="modal-title">审批流程</h5>
+                <h5 class="modal-title">审批通过</h5>
             </div>
             <div class="modal-body">
                 <div class="row">
                     <div class="col-4">
                         <div class="card mt-3">
                             <ul class="list-group list-group-flush">
-                                <li class="list-group-item"><i class="fa fa fa-play-circle fa-rotate-90"></i> 布尔  <small class="text-muted">施工</small></li>
-                                <li class="list-group-item"><i class="fa fa-chevron-circle-down"></i> 张三  <small class="text-muted">监理</small></li>
-                                <li class="list-group-item"><i class="fa fa-chevron-circle-down"></i> 王五 <small class="text-muted">监理</small></li>
-                                <li class="list-group-item"><i class="fa fa fa-stop-circle"></i> 李四 <small class="text-muted">监理</small></li>
-                            </ul>
-                        </div>
-                    </div>
-                    <div class="col-8 modal-height-500" style="overflow: auto">
-                        <div class="card mt-3">
-                            <ul class="list-group list-group-flush">
-                                <li class="list-group-item">
-                                    <span class="text-success pull-right">上报</span>
-                                    <h5 class="card-title"><i class="fa fa-play-circle fa-rotate-90 text-success"></i> 布尔 <small class="text-muted">施工</small></h5>
-                                    <p class="card-text">2017-11-25</p>
-                                </li>
                                 <li class="list-group-item">
-                                    <span class="text-success pull-right">审批通过</span>
-                                    <h5 class="card-title"><i class="fa fa-chevron-circle-down text-success"></i> 张三 <small class="text-muted">监理</small></h5>
-                                    <p class="card-text">审批意见。2017-11-25</p>
+                                    <i class="fa fa fa-play-circle fa-rotate-90"></i> <%- ctx.stage.user.name %>  <small class="text-muted"><%- ctx.stage.user.role %></small>
                                 </li>
+                                <% for (let i = 0; i < ctx.stage.auditors.length; i++) { %>
                                 <li class="list-group-item">
-                                    <span class="text-success pull-right">审批通过</span>
-                                    <h5 class="card-title"><i class="fa fa-chevron-circle-down text-success"></i> 王五 <small class="text-muted">监理</small></h5>
-                                    <p class="card-text">审批通过。2017-11-26</p>
-                                </li>
-                                <li class="list-group-item">
-                                    <span class="text-warning pull-right">审批退回 布尔</span>
-                                    <h5 class="card-title"><i class="fa fa-stop-circle text-warning"></i> 李四 <small class="text-muted">监理</small></h5>
-                                    <p class="card-text">审批退回,审批意见文本。2017-11-27</p>
+                                    <% if (i < ctx.stage.auditors.length - 1) { %>
+                                    <i class="fa fa-chevron-circle-down"></i> <%- ctx.stage.auditors[i].name %>  <small class="text-muted"><%- ctx.stage.auditors[i].role %></small>
+                                    <% } else { %>
+                                    <i class="fa fa fa-stop-circle"></i> <%- ctx.stage.auditors[i].name %>  <small class="text-muted"><%- ctx.stage.auditors[i].role %></small>
+                                    <% } %>
                                 </li>
+                                <% } %>
                             </ul>
                         </div>
-                        <!--退回原报重新上报-->
+                    </div>
+                    <div class="col-8 modal-height-500" style="overflow: auto">
                         <div class="card mt-3">
                             <ul class="list-group list-group-flush">
                                 <li class="list-group-item">
-                                    <span class="text-success pull-right">重新上报</span>
-                                    <h5 class="card-title"><i class="fa fa-play-circle fa-rotate-90 text-success"></i> 布尔 <small class="text-muted">施工</small></h5>
-                                    <p class="card-text">2017-12-01</p>
-                                </li>
-                                <li class="list-group-item">
-                                    <span class="text-success pull-right">审批通过</span>
-                                    <h5 class="card-title"><i class="fa fa-chevron-circle-down text-success"></i> 张三 <small class="text-muted">监理</small></h5>
-                                    <p class="card-text">审批通过 2017-12-02</p>
-                                </li>
-                                <li class="list-group-item">
-                                    <span class="text-warning pull-right">审批退回 张三</span>
-                                    <h5 class="card-title"><i class="fa fa-chevron-circle-down text-warning"></i> 王五 <small class="text-muted">监理</small></h5>
-                                    <p class="card-text">审批退回 2017-12-02</p>
+                                    <span class="text-success pull-right"><small><%- ctx.stage.auditors[0].begin_time.toLocaleDateString() %></small> 上报</span>
+                                    <h5 class="card-title"><i class="fa fa-play-circle fa-rotate-90 text-success"></i> <%- ctx.stage.user.name %> <small class="text-muted"><%- ctx.stage.user.role %></small></h5>
                                 </li>
-                                <!--王五退回上一审批人 张三,张三重新审批-->
+                                <% for (let iA = 0; iA < ctx.stage.auditors.length; iA++) { %>
+                                <% const auditors = ctx.stage.auditors; %>
                                 <li class="list-group-item">
+                                    <% if (auditors[iA].status === auditConst.status.checked) { %>
+                                    <span class="text-success pull-right"><small><%- auditors[iA].end_time.toLocaleString() %></small> 审批通过</span>
+                                    <% } else if (auditors[iA].stauts == auditConst.status.checking) { %>
                                     <span class="pull-right">审批中</span>
-                                    <h5 class="card-title"><i class="fa fa-chevron-circle-down"></i> 张三 <small class="text-muted">监理</small></h5>
-                                    <p class="card-text"></p>
-                                </li>
-                                <li class="list-group-item">
-                                    <h5 class="card-title"><i class="fa fa-stop-circle"></i> 李四 <small class="text-muted">监理</small></h5>
+                                    <% } %>
+                                    <h5 class="card-title">
+                                        <i class="<%- (iA < auditors.length - 1 ? 'fa fa-chevron-circle-down' : 'fa fa-stop-circle') %>"></i> <%- auditors[iA].name %> <small class="text-muted"><%- auditors[iA].role %></small>
+                                    </h5>
+                                    <% if (auditors[iA].status === auditConst.status.checked) { %>
+                                    <p class="card-text"><%- auditors[iA].opinion %></p>
+                                    <% } else if (auditors[iA].status === auditConst.status.checking) { %>
+                                    <div class="form-group">
+                                        <label>审批意见<b class="text-danger">*</b></label>
+                                        <textarea class="form-control" name="opinion"></textarea>
+                                    </div>
+                                    <% } %>
                                 </li>
+                                <% } %>
                             </ul>
                         </div>
                     </div>
@@ -170,107 +156,225 @@
             </div>
             <div class="modal-footer">
                 <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
+                <input type="hidden" name="_csrf" value="<%= ctx.csrf %>" />
+                <input type="hidden" name="checkType" value="<%= auditConst.status.checked %>" />
+                <button type="button" class="btn btn-success" >确认通过</button>
             </div>
-        </div>
+        </form>
     </div>
 </div>
-<!--审批通过-->
-<div class="modal fade" id="sp-done" data-backdrop="static">
+<!--审批退回-->
+<div class="modal fade" id="sp-back" data-backdrop="static">
     <div class="modal-dialog" role="document">
         <div class="modal-content">
             <div class="modal-header">
-                <h5 class="modal-title">审批通过</h5>
+                <h5 class="modal-title">审批退回</h5>
             </div>
             <div class="modal-body">
-                <div class="card mt-3">
-                    <ul class="list-group list-group-flush">
-                        <li class="list-group-item">
-                            <span class="text-success pull-right">上报</span>
-                            <h5 class="card-title"><i class="fa fa-play-circle fa-rotate-90 text-success"></i> 布尔 <small class="text-muted">施工</small></h5>
-                            <p class="card-text">2017-11-25</p>
-                        </li>
-                        <li class="list-group-item">
-                            <span class="text-success pull-right">审批通过</span>
-                            <h5 class="card-title"><i class="fa fa-chevron-circle-down text-success"></i> 张三 <small class="text-muted">监理</small></h5>
-                            <p class="card-text">审批意见。2018-01-01</p>
-                        </li>
-                        <li class="list-group-item">
-                            <span class="text-success pull-right">审批通过</span>
-                            <h5 class="card-title"><i class="fa fa-chevron-circle-down text-success"></i> 王五 <small class="text-muted">监理</small></h5>
-                            <p class="card-text">审批意见。2018-01-01</p>
-                        </li>
-                        <li class="list-group-item">
-                            <span class="text-warning pull-right">审批退回 王五</span>
-                            <h5 class="card-title"><i class="fa fa-stop-circle text-warning"></i> 李四 <small class="text-muted">监理</small></h5>
-                            <p class="card-text">审批意见。2018-01-01</p>
-                        </li>
-                        <li class="list-group-item">
-                            <span class="pull-right">审批中</span>
-                            <h5 class="card-title"><i class="fa fa-chevron-circle-down"></i> 王五 <small class="text-muted">监理</small></h5>
-                            <div class="form-group">
-                                <label>审批意见<b class="text-danger">*</b></label>
-                                <textarea class="form-control" ></textarea>
-                            </div>
-                        </li>
-                        <li class="list-group-item">
-                            <h5 class="card-title"><i class="fa fa-stop-circle"></i> 李四 <small class="text-muted">监理</small></h5>
-                        </li>
-                    </ul>
+                <div class="row">
+                    <div class="col-4">
+                        <div class="card mt-3">
+                            <ul class="list-group list-group-flush">
+                                <li class="list-group-item">
+                                    <i class="fa fa fa-play-circle fa-rotate-90"></i> <%- ctx.stage.user.name %>  <small class="text-muted"><%- ctx.stage.user.role %></small>
+                                </li>
+                                <% for (let i = 0; i < ctx.stage.auditors.length; i++) { %>
+                                <li class="list-group-item">
+                                    <% if (i < ctx.stage.auditors.length - 1) { %>
+                                    <i class="fa fa-chevron-circle-down"></i> <%- ctx.stage.auditors[i].name %>  <small class="text-muted"><%- ctx.stage.auditors[i].role %></small>
+                                    <% } else { %>
+                                    <i class="fa fa fa-stop-circle"></i> <%- ctx.stage.auditors[i].name %>  <small class="text-muted"><%- ctx.stage.auditors[i].role %></small>
+                                    <% } %>
+                                </li>
+                                <% } %>
+                            </ul>
+                        </div>
+                    </div>
+                    <div class="col-8 modal-height-500" style="overflow: auto">
+                        <div class="card mt-3">
+                            <ul class="list-group list-group-flush">
+                                <li class="list-group-item">
+                                    <span class="text-success pull-right"><small><%- ctx.stage.auditors[0].begin_time.toLocaleDateString() %></small> 上报</span>
+                                    <h5 class="card-title"><i class="fa fa-play-circle fa-rotate-90 text-success"></i> <%- ctx.stage.user.name %> <small class="text-muted"><%- ctx.stage.user.role %></small></h5>
+                                </li>
+                                <% for (let iA = 0; iA < ctx.stage.auditors.length; iA++) { %>
+                                <% const auditors = ctx.stage.auditors; %>
+                                <li class="list-group-item">
+                                    <% if (auditors[iA].status === auditConst.status.checked) { %>
+                                    <span class="text-success pull-right"><small><%- auditors[iA].end_time.toLocaleString() %></small> 审批通过</span>
+                                    <% } else if (auditors[iA].stauts == auditConst.status.checking) { %>
+                                    <span class="pull-right">审批中</span>
+                                    <% } %>
+                                    <h5 class="card-title">
+                                        <i class="<%- (iA < auditors.length - 1 ? 'fa fa-chevron-circle-down' : 'fa fa-stop-circle') %>"></i> <%- auditors[iA].name %> <small class="text-muted"><%- auditors[iA].role %></small>
+                                    </h5>
+                                    <% if (auditors[iA].status === auditConst.status.checked) { %>
+                                    <p class="card-text"><%- auditors[iA].opinion %></p>
+                                    <% } else if (auditors[iA].status === auditConst.status.checking) { %>
+                                    <div class="form-group">
+                                        <label>审批意见<b class="text-danger">*</b></label>
+                                        <textarea class="form-control" name="opinion"></textarea>
+                                    </div>
+                                    <div class="alert alert-warning">
+                                        <div class="form-check form-check-inline">
+                                            <input class="form-check-input" type="radio" name="checkNoType" id="inlineRadio1" value="<%- auditConst.backType.org %>">
+                                            <label class="form-check-label" for="inlineRadio1">退回上报 <%- ctx.stage.user.name %></label>
+                                        </div>
+                                        <% if (auditors[iA].order > 1) { %>
+                                        <div class="form-check form-check-inline">
+                                            <input class="form-check-input" type="radio" name="checkNoType" id="inlineRadio2" value="<%- auditConst.backType.pre %>">
+                                            <label class="form-check-label" for="inlineRadio2">退回上一审批人 <%- auditors[iA-1].name %></label>
+                                        </div>
+                                        <% } %>
+                                    </div>
+                                    <% } %>
+                                </li>
+                                <% } %>
+                            </ul>
+                        </div>
+                    </div>
                 </div>
             </div>
             <div class="modal-footer">
                 <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
-                <button type="button" class="btn btn-success" >确认通过</button>
+                <input type="hidden" name="_csrf" value="<%= ctx.csrf %>" />
+                <input type="hidden" name="checkType" value="<%= auditConst.status.checkNo %>" />
+                <button type="button" class="btn btn-warning" >确认退回</button>
             </div>
         </div>
     </div>
 </div>
-<!--审批退回-->
-<div class="modal fade" id="sp-back" data-backdrop="static">
-    <div class="modal-dialog" role="document">
+<% } else { %>
+<!--审批流程/结果-->
+<div class="modal fade" id="sp-list" data-backdrop="static">
+    <div class="modal-dialog modal-lg" role="document">
         <div class="modal-content">
             <div class="modal-header">
-                <h5 class="modal-title">审批退回</h5>
+                <h5 class="modal-title">审批流程</h5>
             </div>
             <div class="modal-body">
-                <div class="card mt-3">
-                    <ul class="list-group list-group-flush">
-                        <li class="list-group-item">
-                            <span class="text-success pull-right">上报</span>
-                            <h5 class="card-title"><i class="fa fa-play-circle fa-rotate-90 text-success"></i> 布尔 <small class="text-muted">施工</small></h5>
-                            <p class="card-text">2017-11-25</p>
-                        </li>
-                        <li class="list-group-item">
-                            <span class="text-success pull-right">审批通过</span>
-                            <h5 class="card-title"><i class="fa fa-chevron-circle-down text-success"></i> 张三 <small class="text-muted">监理</small></h5>
-                            <p class="card-text">审批意见。2018-01-01</p>
-                        </li>
-                        <li class="list-group-item">
-                            <span class="pull-right">审批中</span>
-                            <h5 class="card-title"><i class="fa fa-chevron-circle-down"></i> 王五 <small class="text-muted">监理</small></h5>
-                            <div class="form-group">
-                                <label>审批意见<b class="text-danger">*</b></label>
-                                <textarea class="form-control" ></textarea>
-                            </div>
-                            <div class="alert alert-warning"><div class="form-check form-check-inline">
-                                    <input class="form-check-input" type="radio" name="inlineRadioOptions" id="inlineRadio1" value="option1">
-                                    <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">
-                                    <label class="form-check-label" for="inlineRadio2">退回上一审批人 张三</label>
-                                </div></data-min-view>
-                        </li>
-                        <li class="list-group-item">
-                            <h5 class="card-title"><i class="fa fa-stop-circle"></i> 李四 <small class="text-muted">监理</small></h5>
-                        </li>
-                    </ul>
+                <div class="row">
+                    <div class="col-4">
+                        <div class="card mt-3">
+                            <ul class="list-group list-group-flush">
+                                <li class="list-group-item">
+                                    <i class="fa fa fa-play-circle fa-rotate-90"></i> <%- ctx.stage.user.name %>  <small class="text-muted"><%- ctx.stage.user.role %></small>
+                                </li>
+                                <% for (let i = 0; i < ctx.stage.auditors.length; i++) { %>
+                                <li class="list-group-item">
+                                    <% if (i < ctx.stage.auditors.length - 1) { %>
+                                    <i class="fa fa-chevron-circle-down"></i> <%- ctx.stage.auditors[i].name %>  <small class="text-muted"><%- ctx.stage.auditors[i].role %></small>
+                                    <% } else { %>
+                                    <i class="fa fa fa-stop-circle"></i> <%- ctx.stage.auditors[i].name %>  <small class="text-muted"><%- ctx.stage.auditors[i].role %></small>
+                                    <% } %>
+                                </li>
+                                <% } %>
+                            </ul>
+                        </div>
+                    </div>
+                    <div class="col-8 modal-height-500" style="overflow: auto">
+                        <% for (const ah of ctx.stage.auditHistory) { %>
+                        <div class="card mt-3">
+                            <ul class="list-group list-group-flush">
+                                <% for (let iA = 0; iA < ah.length; iA++) { %>
+                                <% if (iA === 0) { %>
+                                <li class="list-group-item">
+                                    <span class="text-success pull-right"><% if (ctx.stage.auditHistory.indexOf(ah) > 0) { %>重新<% } %>上报</span>
+                                    <h5 class="card-title"><i class="fa fa-play-circle fa-rotate-90 text-success"></i> <%- ctx.stage.user.name %> <small class="text-muted"><%- ctx.stage.user.role %></small></h5>
+                                    <p class="card-text"><small class="text-muted"><%- ah[iA].begin_time.toLocaleDateString() %></small></p>
+                                </li>
+                                <li class="list-group-item">
+                                    <% if (ah[iA].status !== auditConst.status.uncheck) { %>
+                                    <span class="<%- auditConst.statusClass[ah[iA].status] %> pull-right"><%- auditConst.statusString[ah[iA].status]%><% if (ah[iA].status === auditConst.status.checkNo) { %> <%- user.name %><% } %></span>
+                                    <% } %>
+                                    <h5 class="card-title"><i class="fa fa-chevron-circle-down <%- auditConst.statusClass[ah[iA].status] %>"></i> <%- ah[iA].name %> <small class="text-muted"><%- ah[iA].role %></small></h5>
+                                    <% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo) { %>
+                                    <p class="card-text mb-1"><%- auditConst.statusString[ah[iA].status]%>,<%- ah[iA].opinion %>。</p>
+                                    <p class="card-text"><small class="text-muted"><%- ah[iA].end_time.toLocaleDateString() %></small></p>
+                                    <% } %>
+                                </li>
+                                <% } else if (iA === ah.length - 1) { %>
+                                <li class="list-group-item">
+                                    <% if (ah[iA].status !== auditConst.status.uncheck) { %>
+                                    <span class="<%- auditConst.statusClass[ah[iA].status] %> pull-right"><%- auditConst.statusString[ah[iA].status]%><% if (ah[iA].status === auditConst.status.checkNo) { %> <%- user.name %><% } %></span>
+                                    <% } %>
+                                    <h5 class="card-title"><i class="fa fa-stop-circle <%- auditConst.statusClass[ah[iA].status] %>"></i> <%- ah[iA].name %> <small class="text-muted"><%- ah[iA].role %></small></h5>
+                                    <% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo) { %>
+                                    <p class="card-text mb-1"><%- auditConst.statusString[ah[iA].status]%>,<%- ah[iA].opinion %>。</p>
+                                    <p class="card-text"><small class="text-muted"><%- ah[iA].end_time.toLocaleDateString() %></small></p>
+                                    <% } %>
+                                </li>
+                                <% } else { %>
+                                <li class="list-group-item">
+                                    <% if (ah[iA].status !== auditConst.status.uncheck) { %>
+                                    <span class="<%- auditConst.statusClass[ah[iA].status] %> pull-right"><%- auditConst.statusString[ah[iA].status]%><% if (ah[iA].status === auditConst.status.checkNo) { %> <%- user.name %><% } %></span>
+                                    <% } %>
+                                    <h5 class="card-title"><i class="fa fa-chevron-circle-down <%- auditConst.statusClass[ah[iA].status] %>"></i> <%- ah[iA].name %> <small class="text-muted"><%- ah[iA].role %></small></h5>
+                                    <% if (ah[iA].status === auditConst.status.checked || ah[iA].status === auditConst.status.checkNo) { %>
+                                    <p class="card-text mb-1"><%- auditConst.statusString[ah[iA].status]%>,<%- ah[iA].opinion %>。</p>
+                                    <p class="card-text"><small class="text-muted"><%- ah[iA].end_time.toLocaleDateString() %></small></p>
+                                    <% } %>
+                                </li>
+                                <% } %>
+                                <% } %>
+                            </ul>
+                        </div>
+                        <% } %>
+                        <% if (ctx.stage.status === auditConst.status.checking || tender.ledger_status === auditConst.status.checked) {%>
+                        <div class="card mt-3">
+                            <ul class="list-group list-group-flush">
+                                <% const auditors = ctx.stage.auditors; %>
+                                <% for (let iA = 0; iA < auditors.length; iA++) { %>
+                                <% if (iA === 0) { %>
+                                <li class="list-group-item">
+                                    <span class="text-success pull-right"><% if (ctx.stage.times > 1) { %>重新<% } %>上报</span>
+                                    <h5 class="card-title"><i class="fa fa-play-circle fa-rotate-90 text-success"></i> <%- ctx.stage.user.name %> <small class="text-muted"><%- ctx.stage.user.role %></small></h5>
+                                    <p class="card-text"><small class="text-muted"><%- auditors[iA].begin_time.toLocaleDateString() %></small></p>
+                                </li>
+                                <li class="list-group-item">
+                                    <% if (auditors[iA].status !== auditConst.status.uncheck) { %>
+                                    <span class="<%- auditConst.statusClass[auditors[iA].status] %> pull-right"><%- auditConst.statusString[auditors[iA].status]%><% if (auditors[iA].status === auditConst.status.checkNo) { %> <%- user.name %><% } %></span>
+                                    <% } %>
+                                    <h5 class="card-title"><i class="fa fa-chevron-circle-down <%- auditConst.statusClass[auditors[iA].status] %>"></i> <%- auditors[iA].name %> <small class="text-muted"><%- auditors[iA].role %></small></h5>
+                                    <% if (auditors[iA].status === auditConst.status.checked || auditors[iA].status === auditConst.status.checkNo) { %>
+                                    <p class="card-text mb-1"><%- auditConst.statusString[auditors[iA].status]%>,<%- auditors[iA].opinion %>。</p>
+                                    <p class="card-text"><small class="text-muted"><%- auditors[iA].end_time.toLocaleDateString() %></small></p>
+                                    <% } %>
+                                </li>
+                                <% } else if (iA === auditors.length - 1) { %>
+                                <li class="list-group-item">
+                                    <% if (auditors[iA].status !== auditConst.status.uncheck) { %>
+                                    <span class="<%- auditConst.statusClass[auditors[iA].status] %> pull-right"><%- auditConst.statusString[auditors[iA].status]%><% if (auditors[iA].status === auditConst.status.checkNo) { %> <%- user.name %><% } %></span>
+                                    <% } %>
+                                    <h5 class="card-title"><i class="fa fa-stop-circle <%- auditConst.statusClass[auditors[iA].status] %>"></i> <%- auditors[iA].name %> <small class="text-muted"><%- auditors[iA].role %></small></h5>
+                                    <% if (auditors[iA].status === auditConst.status.checked || auditors[iA].status === auditConst.status.checkNo) { %>
+                                    <p class="card-text"><%- auditConst.statusString[auditors[iA].status]%>,<%- auditors[iA].opinion %>。</p>
+                                    <p class="card-text"><small class="text-muted"><%- auditors[iA].end_time.toLocaleDateString() %></small></p>
+                                    <% } %>
+                                </li>
+                                <% } else { %>
+                                <li class="list-group-item">
+                                    <% if (auditors[iA].status !== auditConst.status.uncheck) { %>
+                                    <span class="<%- auditConst.statusClass[auditors[iA].status] %> pull-right"><%- auditConst.statusString[auditors[iA].status]%><% if (auditors[iA].status === auditConst.status.checkNo) { %> <%- user.name %><% } %></span>
+                                    <% } %>
+                                    <h5 class="card-title"><i class="fa fa-chevron-circle-down <%- auditConst.statusClass[auditors[iA].status] %>"></i> <%- auditors[iA].name %> <small class="text-muted"><%- auditors[iA].role %></small></h5>
+                                    <% if (auditors[iA].status === auditConst.status.checked || auditors[iA].status === auditConst.status.checkNo) { %>
+                                    <p class="card-text"><%- auditConst.statusString[auditors[iA].status]%>,<%- auditors[iA].opinion %>。</p>
+                                    <p class="card-text"><small class="text-muted"><%- auditors[iA].end_time.toLocaleDateString() %></small></p>
+                                    <% } %>
+                                </li>
+                                <% } %>
+                                <% } %>
+                            </ul>
+                        </div>
+                        <% } %>
+                    </div>
                 </div>
             </div>
             <div class="modal-footer">
                 <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
-                <button type="button" class="btn btn-warning" >确认退回</button>
             </div>
         </div>
     </div>
-</div>
+</div>
+<% } %>

+ 1 - 1
app/view/stage/pay_modal.ejs

@@ -38,7 +38,7 @@
                             <label class="form-check-label" for="ilRadio1">累计完成计量金额</label>
                         </div>
                         <div class="form-check form-check-inline">
-                            <input class="form-check-input" type="radio" name="tp-type" id="ilRadio2" value="deal">
+                            <input class="form-check-input" type="radio" name="tp-type" id="ilRadio2" value="contract">
                             <label class="form-check-label" for="ilRadio2">累计合同计量金额</label>
                         </div>
                         <div class="form-check form-check-inline">

+ 0 - 1
config/web.js

@@ -129,7 +129,6 @@ const JsFiles = {
             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",

+ 3 - 0
package.json

@@ -4,6 +4,7 @@
   "description": "calculation paying frontend",
   "private": true,
   "dependencies": {
+    "bignumber.js": "^8.1.1",
     "egg": "^1.13.0",
     "egg-js-validator": "^1.0.2",
     "egg-mysql": "^3.0.0",
@@ -19,9 +20,11 @@
     "koa-is-json": "^1.0.0",
     "lodash": "^4.17.11",
     "lz-string": "^1.4.4",
+    "mathjs": "^5.9.0",
     "moment": "^2.20.1",
     "node-uuid": "^1.4.8",
     "node-xlsx": "^0.12.0",
+    "number-precision": "^1.3.1",
     "stream-to-array": "^2.3.0",
     "stream-wormhole": "^1.1.0",
     "ueditor": "^1.2.3",

+ 15 - 6
test/app/extend/helper.test.js

@@ -8,10 +8,13 @@
  * @version
  */
 const { app, assert } = require('egg-mock/bootstrap');
+const _ = require('lodash');
+const math = require('mathjs');
+const np = require('number-precision');
 
 describe('test/app/extend/helper.test.js', () => {
 
-    it('generateRandomString test', function* () {
+    it('generateRandomString test', function () {
         // 创建 ctx
         const ctx = app.mockContext();
         const randomMix = ctx.helper.generateRandomString(4);
@@ -29,7 +32,7 @@ describe('test/app/extend/helper.test.js', () => {
         assert(stringReg.test(randomString));
     });
 
-    it('showSortFlag test', function* () {
+    it('showSortFlag test', function () {
         // 创建 ctx
         const ctx = app.mockContext({
             // sort: { test: 1 },
@@ -39,7 +42,7 @@ describe('test/app/extend/helper.test.js', () => {
         assert(flag === '-');
     });
 
-    it('isAjax test', function* () {
+    it('isAjax test', function () {
         // 创建 ctx
         const ctx = app.mockContext();
         const result = ctx.helper.isAjax(ctx);
@@ -54,7 +57,7 @@ describe('test/app/extend/helper.test.js', () => {
         assert(responseData.data.ip === '116.7.222.12');
     });
 
-    it('explode paths', function* () {
+    it('explode paths', function () {
         // 创建 ctx
         const ctx = app.mockContext();
         const paths = ['1.2.3.4.5', '1.2.3.5.7'];
@@ -73,7 +76,7 @@ describe('test/app/extend/helper.test.js', () => {
         assert(result);
     });
 
-    it('test permission', function* () {
+    it('test permission', function () {
         // 创建 ctx
         const ctx = app.mockContext();
         ctx.session = {
@@ -90,7 +93,7 @@ describe('test/app/extend/helper.test.js', () => {
         assert(result);
     });
 
-    it('test compareCode', function* () {
+    it('test compareCode', function () {
         const ctx = app.mockContext();
         // 测试项目节
         assert(ctx.helper.compareCode('1-1-1', '1-1-2') < 0);
@@ -120,4 +123,10 @@ describe('test/app/extend/helper.test.js', () => {
         assert(ctx.helper.compareCode('1.1.3', '1.2.2', '.') < 0);
         assert(ctx.helper.compareCode('2.4.3', '3.1.2', '.') < 0);
     });
+
+    it('test mul', function () {
+        const ctx = app.mockContext();
+        const a = 0.07, b = 10, m = 0.7;
+        console.log(a.mul(b) === m);
+    })
 });