فهرست منبع

材料调差页面和功能 no.1 up

laiguoran 6 سال پیش
والد
کامیت
90f2cb44a8

+ 35 - 1
app/const/spread.js

@@ -415,6 +415,39 @@ measure.compare.pos = {
     defaultRowHeight: 21,
 };
 
+// 材料调差 - 调差工料
+const material = {
+    bill: {
+        cols: [
+            {title: '调差类型', colSpan: '1', rowSpan: '2', field: 'b_code', hAlign: 0, width: 80, formatter: '@'},
+            {title: '编号', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 60, formatter: '@'},
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 230, formatter: '@', cellType: 'unit'},
+            {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, type: 'Number'},
+            {title: '规格', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, type: 'Number'},
+            {title: '工料分类', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, type: 'Number'},
+            {title: '本期应耗数量', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, type: 'Number'},
+            {title: '基准价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, type: 'Number'},
+            {title: '基准时间', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, type: 'Number'},
+            {title: '本期信息价|单价', colSpan: '3|1', rowSpan: '1|1', field: 'deal_bills_qty', hAlign: 2, width: 60, type: 'Number'},
+            {title: '|时间', colSpan: '|1', rowSpan: '|1', field: 'deal_bills_tp', hAlign: 2, width: 60, type: 'Number'},
+            {title: '|价差', colSpan: '1', rowSpan: '1|1', field: 'quantity', hAlign: 2, width: 60, type: 'Number'},
+            {title: '本期材料调差|风险幅度(%)', colSpan: '3|1', rowSpan: '1|1', field: 'contract_qty', hAlign: 2, width: 60, type: 'Number'},
+            {title: '|有效价差', colSpan: '|1', rowSpan: '|1', field: 'contract_tp', hAlign: 2, width: 60, type: 'Number'},
+            {title: '|调整金额', colSpan: '|1', rowSpan: '1|1', field: 'qc_qty', hAlign: 2, width: 60, type: 'Number'},
+            {title: '截止上期调差金额', colSpan: '|1', rowSpan: '|1', field: 'qc_tp', hAlign: 2, width: 60, type: 'Number'},
+            {title: '备注', colSpan: '|1', rowSpan: '|1', field: 'qc_tp_bgl', hAlign: 2, width: 60, formatter: '@'},
+        ],
+        emptyRows: 0,
+        headRows: 2,
+        headRowHeight: [32, 32],
+        defaultRowHeight: 21,
+        headerFont: '10pt 微软雅黑',
+        readOnly: true,
+        font: '10pt 微软雅黑',
+    },
+}
+
+
 module.exports = {
     withCl,
     withoutCl,
@@ -425,4 +458,5 @@ module.exports = {
     stageCompare,
     filterCols: { tzWithoutCols, dgnCols, clCols, stageDgnCols},
     measure,
-};
+    material,
+};

+ 397 - 0
app/controller/material_controller.js

@@ -0,0 +1,397 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author EllisRan
+ * @date 2018/6/20
+ * @version
+ */
+
+const moment = require('moment');
+const auditConst = require('../const/audit').material;
+const auditStageConst = require('../const/audit').stage;
+const spreadConst = require('../const/spread');
+const tenderConst = require('../const/tender');
+const measureType = tenderConst.measureType;
+const accountGroup = require('../const/account_group').group;
+
+module.exports = app => {
+    class MaterialController extends app.BaseController {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            ctx.showProject = true;
+            ctx.showTender = true;
+            ctx.showTitle = true;
+        }
+
+        /**
+         * 期列表(Get)
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async index(ctx) {
+            try {
+                const renderData = {
+                    tender: ctx.tender.data,
+                    tenderMenu: this.menu.tenderMenu,
+                    preUrl: '/tender/' + ctx.tender.id,
+                    auditConst,
+                    auditConst2: JSON.stringify(auditConst),
+                };
+                renderData.materials = await ctx.service.material.getValidMaterials(ctx.tender.id);
+                // 获取未选中和已完成的计量期
+                const stages = await ctx.service.stage.getAllDataByCondition({ where: { tid: ctx.tender.id, status: auditStageConst.status.checked } });
+                for (const s of renderData.materials) {
+                    // s.curAuditor = null;
+                    // 根据期状态返回展示用户
+                    s.curAuditor = await ctx.service.materialAudit.getAuditorByStatus(s.id, s.status, s.times);
+                    const materialStageList = s.stage_id.split(',');
+                    for (const ms of materialStageList) {
+                        const index = stages.findIndex(function(item) {
+                            return item.id === parseInt(ms);
+                        });
+                        stages.splice(index, 1);
+                    }
+                }
+                renderData.stages = stages;
+                await this.layout('material/index.ejs', renderData, 'material/modal.ejs');
+            } catch (err) {
+                this.log(err);
+                ctx.redirect(this.menu.menu.dashboard.url);
+            }
+        }
+
+        /**
+         * 期审批流程(Get)
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async materialAuditors(ctx) {
+            try {
+                const responseData = {
+                    err: 0, msg: '', data: {},
+                };
+                const order = JSON.parse(ctx.request.body.data).order;
+                const tenderId = ctx.params.id;
+                const materialInfo = await ctx.service.material.getDataByCondition({ tid: tenderId, order });
+                // 获取审批流程中右边列表
+                const auditHistory = [];
+                const times = materialInfo.status === auditConst.status.checkNo ? materialInfo.times - 1 : materialInfo.times;
+                if (times >= 1) {
+                    for (let i = 1; i <= times; i++) {
+                        auditHistory.push(await ctx.service.materialAudit.getAuditors(materialInfo.id, i));
+                    }
+                }
+                responseData.data.auditHistory = auditHistory;
+                // 获取审批流程中左边列表
+                responseData.data.auditors = await ctx.service.materialAudit.getAuditGroupByList(materialInfo.id, times);
+                // 获取原报信息
+                const materialAuditor = await ctx.service.projectAccount.getAccountInfoById(materialInfo.user_id);
+                responseData.data.materialAuditor = materialAuditor;
+                ctx.body = responseData;
+            } catch (error) {
+                this.log(error);
+                ctx.body = { err: 1, msg: error.toString(), data: null };
+            }
+        }
+
+        /**
+         * 新增期(Post)
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async add(ctx) {
+            try {
+                if (ctx.session.sessionUser.accountId !== ctx.tender.data.user_id) {
+                    throw '您无权创建材料调差期';
+                }
+                const data = ctx.request.body;
+                const newMaterial = await ctx.service.material.addMaterial(ctx.tender.id, data);
+                if (!newMaterial) {
+                    throw '新增材料调差期失败,请重试';
+                }
+                ctx.redirect('/tender/' + ctx.tender.id + '/measure/material/' + newMaterial.order);
+            } catch (err) {
+                this.log(err);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 删除期(Post)
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async delete(ctx) {
+            try {
+                const material_id = ctx.request.body.material_id;
+                const materialInfo = await ctx.service.material.getDataById(material_id);
+                // 获取最新的期数
+                const material_highOrder = await ctx.service.material.count({
+                    tid: ctx.tender.id,
+                });
+                if (materialInfo === undefined || ctx.session.sessionUser.accountId !== materialInfo.user_id || material_highOrder !== materialInfo.order) {
+                    throw '您无权删除材料调差期';
+                }
+                const result = await ctx.service.material.deleteMaterial(material_id);
+                if (!result) {
+                    throw '删除材料调差期失败,请重试';
+                }
+                ctx.redirect('/tender/' + ctx.tender.id + '/measure/material/');
+            } catch (err) {
+                this.log(err);
+                console.log(err);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 获取通用的renderData(用于layout, Menu, subMenu部分)
+         * @param ctx
+         * @returns {{tender, tenderMenu, auditConst}}
+         * @private
+         */
+        async _getDefaultRenderData(ctx) {
+            const data = {
+                tender: ctx.tender.data,
+                tenderMenu: JSON.parse(JSON.stringify(this.menu.stageMenu)),
+                auditConst,
+                measureType,
+                preUrl: '/tender/' + ctx.tender.id + '/measure/material/' + ctx.params.order,
+                material: ctx.material,
+            };
+            if ((ctx.material.status === auditConst.status.uncheck || ctx.material.status === auditConst.status.checkNo) && ctx.session.sessionUser.accountId === ctx.material.user_id) {
+                data.accountGroup = accountGroup;
+                // 获取所有项目参与者
+                const accountList = await ctx.service.projectAccount.getAllDataByCondition({
+                    where: { project_id: ctx.session.sessionProject.id, enable: 1 },
+                    columns: ['id', 'name', 'company', 'role', 'enable', 'is_admin', 'account_group'],
+                });
+                data.accountList = accountList;
+            }
+            data.tenderMenu.back.children[0].url = '/tender/' + ctx.tender.id + '/measure/material';
+            return data;
+        }
+
+        _materialReadOnly(material) {
+            return material.status === auditConst.status.checked;
+        }
+
+        /**
+         * 获取SpreadSetting
+         * @private
+         */
+        _getSpreadSetting() {
+            const _ = this.app._;
+            function removeFieldCols(setting, cols) {
+                _.remove(setting.cols, function (c) {
+                    return cols.indexOf(c.field) > -1;
+                });
+            }
+            function setColFormat(cols, field, formatter) {
+                const filterCols = cols.filter(function (c) {
+                    return c.field.search(field) !== -1;
+                });
+                for (const col of filterCols) {
+                    col.formatter = formatter;
+                }
+            }
+            // const tpFormatter = this.ctx.helper.getNumberFormatter(this.ctx.tender.info.decimal.tp);
+            // const upFormatter = this.ctx.helper.getNumberFormatter(2);
+            const tender = this.ctx.tender, stage = this.ctx.stage;
+            const stageSetting = tender.data.measure_type === measureType.tz.value ? spreadConst.stageTz :
+                (tender.info.display.ledger.clQty ? spreadConst.stageCl : spreadConst.stageNoCl);
+            const ledger = JSON.parse(JSON.stringify(stageSetting.ledger));
+            // setColFormat(ledger.cols, 'unit_price', upFormatter);
+            // setColFormat(ledger.cols, 'total_price', tpFormatter);
+            // setColFormat(ledger.cols, 'tp', tpFormatter);
+            if (!tender.info.display.ledger.dgnQty) {
+                removeFieldCols(ledger, spreadConst.filterCols.stageDgnCols);
+            }
+            const pos = JSON.parse(JSON.stringify(stageSetting.pos));
+            if (this.ctx.material.readOnly) {
+                ledger.readOnly = true;
+                pos.readOnly = true;
+            }
+            return [ledger, pos];
+        }
+
+        /**
+         * 获取审批界面所需的 原报、审批人数据等
+         * @param ctx
+         * @returns {Promise<void>}
+         * @private
+         */
+        async _getMaterialAuditViewData(ctx) {
+            const times = ctx.material.status === auditConst.status.checkNo ? ctx.material.times - 1 : ctx.material.times;
+            ctx.material.user = await ctx.service.projectAccount.getAccountInfoById(ctx.material.user_id);
+            ctx.material.auditHistory = [];
+            if (ctx.material.times > 1) {
+                for (let i = 1; i < ctx.material.times; i++) {
+                    ctx.material.auditHistory.push(await ctx.service.materialAudit.getAuditors(ctx.material.id, i));
+                }
+            }
+            // 获取审批流程中左边列表
+            ctx.material.auditors2 = await ctx.service.materialAudit.getAuditGroupByList(ctx.material.id, times);
+            if (ctx.material.status === auditConst.status.uncheck || ctx.material.status === auditConst.status.checkNo) {
+                ctx.material.auditorList = await ctx.service.materialAudit.getAuditors(ctx.material.id, ctx.material.times);
+            }
+        }
+
+        /**
+         * 期计量页面 (Get)
+         * @param {Object} ctx - egg全局变量
+         * @returns {Promise<void>}
+         */
+        async info(ctx) {
+            try {
+                await this._getMaterialAuditViewData(ctx);
+                const renderData = await this._getDefaultRenderData(ctx);
+                // [renderData.ledgerSpread, renderData.posSpread] = this._getSpreadSetting();
+                // renderData.ledgerData = await ctx.service.ledger.getData(ctx.tender.id);
+                renderData.jsFiles = this.app.jsFiles.common.concat(this.app.jsFiles.material.info);
+                await this.layout('material/info.ejs', renderData, 'material/info_modal.ejs');
+            } catch (err) {
+                this.log(err);
+                ctx.redirect('/tender/' + ctx.tender.id + '/measure/material');
+            }
+        }
+
+        // 审批相关
+        /**
+         * 添加审批人
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async addAudit(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const id = this.app._.toInteger(data.auditorId);
+                if (isNaN(id) || id <= 0) {
+                    throw '参数错误';
+                }
+                // 检查权限等
+                if (ctx.material.user_id !== ctx.session.sessionUser.accountId) {
+                    throw '您无权添加审核人';
+                }
+                if (ctx.material.status === auditConst.status.checking || ctx.material.status === auditConst.status.checked) {
+                    throw '当前不允许添加审核人';
+                }
+
+                ctx.material.auditorList = await ctx.service.materialAudit.getAuditors(ctx.material.id, ctx.material.times);
+                // 检查审核人是否已存在
+                const exist = this.app._.find(ctx.material.auditorList, {aid: id});
+                if (exist) {
+                    throw '该审核人已存在,请勿重复添加';
+                }
+
+                const result = await ctx.service.materialAudit.addAuditor(ctx.material.id, id, ctx.material.times);
+                if (!result) {
+                    throw '添加审核人失败';
+                }
+
+                const audit = await ctx.service.materialAudit.getAuditor(ctx.material.id, id, ctx.material.times);
+                ctx.body = {err: 0, msg: '', data: audit};
+            } catch (err) {
+                this.log(err);
+                ctx.body = {err: 1, msg: err.toString(), data: null};
+            }
+        }
+        /**
+         * 移除审批人
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async deleteAudit(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const id = data.auditorId instanceof Number ? data.auditorId : this.app._.toNumber(data.auditorId);
+                if (isNaN(id) || id <= 0) {
+                    throw '参数错误';
+                }
+
+                const result = await ctx.service.materialAudit.deleteAuditor(ctx.material.id, id, ctx.material.times);
+                if (!result) {
+                    throw '移除审核人失败';
+                }
+
+                const auditors = await ctx.service.materialAudit.getAuditors(ctx.material.id, ctx.material.times);
+                ctx.body = {err: 0, msg: '', data: auditors};
+            } catch (err) {
+                ctx.body = {err: 1, msg: err.toString(), data: null};
+            }
+        }
+        /**
+         * 上报
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async startAudit(ctx) {
+            try {
+                // 检查权限等
+                if (!ctx.material) {
+                    throw '数据错误';
+                }
+                if (ctx.material.user_id !== ctx.session.sessionUser.accountId) {
+                    throw '您无权上报该期数据';
+                }
+                if (ctx.material.status === auditConst.status.checking || ctx.material.status === auditConst.status.checked) {
+                    throw '该材料调差期数据当前无法上报';
+                }
+
+                await ctx.service.materialAudit.start(ctx.material.id, ctx.material.times);
+
+                ctx.redirect(ctx.request.header.referer);
+            } catch (err) {
+                this.log(err);
+                ctx.session.postError = err.toString();
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+        /**
+         * 审批
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async checkAudit(ctx) {
+            try {
+                if (!this.ctx.material || this.ctx.material.status !== auditConst.status.checking) {
+                    throw '当前材料调差期数据有误';
+                }
+                if (!this.ctx.material.curAuditor || this.ctx.material.curAuditor.aid !== ctx.session.sessionUser.accountId) {
+                    throw '您无权进行该操作';
+                }
+                const data = {
+                    checkType: parseInt(ctx.request.body.checkType),
+                    opinion: ctx.request.body.opinion,
+                };
+                if (!data.checkType || isNaN(data.checkType)) {
+                    throw '提交数据错误';
+                }
+                if (data.checkType === auditConst.status.checkNo) {
+                    if (!data.checkType || isNaN(data.checkType)) {
+                        throw '提交数据错误';
+                    }
+                }
+
+                await ctx.service.materialAudit.check(ctx.material.id, data, ctx.material.times);
+
+                ctx.redirect(ctx.request.header.referer);
+            } catch (err) {
+                console.log(err);
+                this.log(err);
+                ctx.session.postError = err.toString();
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+    }
+
+    return MaterialController;
+};

+ 118 - 0
app/middleware/material_check.js

@@ -0,0 +1,118 @@
+'use strict';
+
+/**
+ *
+ * @author EllisRan
+ * @date
+ * @version
+ */
+
+const status = require('../const/audit').material.status;
+const _ = require('lodash');
+
+module.exports = options => {
+    /**
+     * 材料调差校验 中间件
+     * 1. 读取材料调差数据
+     * 2. 检验用户是否参与材料调差(不校验具体权限)
+     *
+     * 写入ctx.material数据
+     * 其中:
+     * material.auditors: 审批人列表(退回原报时,加载上一流程)
+     * material.curAuditor: 当前审批人(未上报为空,审批通过 or 退回原报时,为空)
+     * material.readonly: 登录人,是否可操作
+     * material.curTimes: 当前登录人,操作、查阅数据times
+     * material.curOrder: 当前登录人,操作、查阅数据order
+     *
+     * 该方法为通用方法,如需material其他数据,请在controller中查询
+     *
+     * @param {function} next - 中间件继续执行的方法
+     * @return {void}
+     */
+    return function* materialCheck(next) {
+        try {
+            // 读取标段数据
+            const materialOrder = parseInt(this.params.order);
+            if (materialOrder <= 0) {
+                throw '您访问的期不存在';
+            }
+            const material = yield this.service.material.getDataByCondition({
+                tid: this.tender.id,
+                order: materialOrder,
+            });
+            if (!material) {
+                throw '材料调差期数据错误';
+            }
+
+            // 读取原报、审核人数据
+            material.auditors = yield this.service.materialAudit.getAuditors(material.id, material.times);
+            material.curAuditor = yield this.service.materialAudit.getCurAuditor(material.id, material.times);
+
+            // 权限相关
+            // todo 校验权限 (标段参与人、分享)
+            const accountId = this.session.sessionUser.accountId, auditorIds = _.map(material.auditors, 'aid'), shareIds = [];
+            if (accountId === material.user_id) { // 原报
+                if (material.curAuditor) {
+                    material.readOnly = material.status === status.checking && material.curAuditor.user_id === accountId;
+                } else {
+                    material.readOnly = material.status !== status.uncheck && material.status !== status.checkNo;
+                }
+                material.curTimes = material.times;
+                if (material.status === status.uncheck || material.status === status.checkNo) {
+                    material.curOrder = 0;
+                } else if (material.status === status.checked) {
+                    material.curOrder = _.max(_.map(material.auditors, 'order'));
+                } else {
+                    material.curOrder = material.curAuditor.aid === accountId ? material.curAuditor.order : material.curAuditor.order - 1;
+                }
+            } else if (auditorIds.indexOf(accountId) !== -1) { // 审批人
+                if (material.status === status.uncheck) {
+                    throw '您无权查看该数据';
+                }
+                material.readOnly = material.status !== status.checking || accountId !== material.curAuditor.aid;
+                material.curTimes = material.status === status.checkNo ? material.times - 1 : material.times;
+                if (material.status === status.checked) {
+                    material.curOrder = _.max(_.map(material.auditors, 'order'));
+                } else if (material.status === status.checkNo) {
+                    const audit = this.service.materialAudit.getDataByCondition({
+                        mid: material.id, times: material.times, status: status.checkNo
+                    });
+                    material.curOrder = audit.order;
+                } else {
+                    material.curOrder = accountId === material.curAuditor.aid ? material.curAuditor.order : material.curAuditor.order - 1;
+                }
+            } else if (shareIds.indexOf(accountId) !== -1) { // 分享人
+                if (material.status === status.uncheck) {
+                    throw '您无权查看该数据';
+                }
+                material.readOnly = true;
+                material.curTimes = material.status === status.checkNo ? material.times - 1 : material.times;
+                material.curOrder = material.status === status.checked ? _.max(_.map(material.auditors, 'order')) : material.curAuditor.order - 1;
+            } else { // 其他不可见
+                throw '您无权查看该数据';
+            }
+
+            // 获取最新的期
+            material.highOrder = yield this.service.material.count({
+                tid: this.tender.id,
+            });
+            this.material = material;
+            yield next;
+        } catch (err) {
+            console.log(err);
+            // 输出错误到日志
+            if (err.stack) {
+                this.logger.error(err);
+            } else {
+                this.getLogger('fail').info(JSON.stringify({
+                    error: err,
+                    project: this.session.sessionProject,
+                    user: this.session.sessionUser,
+                    body: this.session.body,
+                }));
+            }
+            // 重定向值标段管理
+            this.redirect(this.request.headers.referer);
+        }
+    };
+};

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1533 - 0
app/public/js/material.js


+ 147 - 0
app/public/js/material_audit.js

@@ -0,0 +1,147 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2019/2/27
+ * @version
+ */
+
+$(document).ready(function () {
+    // 获取审核相关url
+    function getUrlPre () {
+        const path = window.location.pathname.split('/');
+        return _.take(path, 6).join('/');
+    }
+
+    // 搜索审批人
+    $('#searchAccount').click(() => {
+        const data = {
+            keyword: $('#searchName').val(),
+        };
+        postData('/search/user', data, (data) => {
+            const resultDiv = $('#searchResult');
+            if (data) {
+                $('h5>span', resultDiv).text(data.name);
+                $('#addAuditor').attr('auditorId', data.id);
+                $('h6', resultDiv).text(data.role);
+                $('p', resultDiv).text(data.company);
+                resultDiv.show();
+            } else {
+                toast('未查询到该审核人', 'info');
+                resultDiv.hide();
+            }
+        }, () => {
+            $('#searchResult').hide();
+        });
+    });
+    // 添加审批人
+    $('#addAuditor').click(() => {
+        postData(getUrlPre() + '/audit/add', { auditorId: $('#addAuditor').attr('auditorId') }, (data) => {
+            const html = [];
+            html.push('<li class="list-group-item" auditorId="'+ data.aid +'"><a href="javascript: void(0)" class="text-danger pull-right">移除</a>');
+            html.push('<span>');
+            html.push(data.order + ' ');
+            html.push(data.name + ' ');
+            html.push('</span>');
+            html.push('<small class="text-muted">');
+            html.push(data.role);
+            html.push('</small></li>');
+            $('#auditors').append(html.join(''));
+        });
+    });
+    // 审批人分组选择
+    $('#account_group').change(function () {
+        let account_html = '<option value="0">选择审批人</option>';
+        for (const account of accountList) {
+            if (parseInt($(this).val()) === 0 || parseInt($(this).val()) === account.account_group) {
+                const role = account.role !== '' ? '(' + account.role + ')' : '';
+                const company = account.company !== '' ? ' -' + account.company : '';
+                account_html += '<option value="' + account.id + '">' + account.name + role + company + '</option>';
+            }
+        }
+        $('#account_list').html(account_html);
+    });
+    // 添加到审批流程中
+    $('body').on('change', '#account_list', function () {
+        let id = $(this).val();
+        id = parseInt(id);
+        if (id !== 0) {
+            postData(getUrlPre() + '/audit/add', { auditorId: id }, (data) => {
+                const html = [];
+                html.push('<li class="list-group-item" auditorId="'+ data.aid +'"><a href="javascript: void(0)" class="text-danger pull-right">移除</a>');
+                html.push('<span>');
+                html.push(data.order + ' ');
+                html.push(data.name + ' ');
+                html.push('</span>');
+                html.push('<small class="text-muted">');
+                html.push(data.role);
+                html.push('</small></li>');
+                $('#auditors').append(html.join(''));
+
+                // 如果是重新上报,添加到重新上报列表中
+                const auditorshtml = [];
+                // 重新上报时。令其它的审批人流程图标转换
+                $('#auditors-list li i').removeClass('fa-stop-circle').addClass('fa-chevron-circle-down');
+                // 添加新审批人
+                auditorshtml.push('<li class="list-group-item" data-auditid="' + data.aid + '">');
+                auditorshtml.push('<i class="fa fa-stop-circle"></i> ');
+                auditorshtml.push(data.name + ' <small class="text-muted">' + data.role + '</small>');
+                auditorshtml.push('</li>');
+                $('#auditors-list').append(auditorshtml.join(''));
+
+                const auditorshtml2 = [];
+                // 重新上报时。令其它的审批人流程图标转换
+                $('#auditors-list2 li i').removeClass('fa-stop-circle').addClass('fa-chevron-circle-down');
+                // 添加新审批人
+                auditorshtml2.push('<li class="list-group-item" data-auditid="' + data.aid + '">');
+                auditorshtml2.push('<h5 class="card-title"><i class="fa fa-stop-circle"></i> ');
+                auditorshtml2.push(data.name + ' <small class="text-muted">' + data.role + '</small></h5>');
+                auditorshtml2.push('</li>');
+                $('#auditors-list2').append(auditorshtml2.join(''));
+            });
+        }
+    });
+    // 删除审批人
+    $('body').on('click', '#auditors li>a', function () {
+        const li = $(this).parent();
+        const data = {
+            auditorId: parseInt(li.attr('auditorId')),
+        };
+        postData(getUrlPre() + '/audit/delete', data, (result) => {
+            li.remove();
+            for (const rst of result) {
+                const aLi = $('li[auditorId=' + rst.aid + ']');
+                $('span', aLi).text(rst.order + ' ' + rst.name + ' ');
+            }
+
+            // 如果是重新上报
+            // 令最后一个图标转换
+            $('#auditors-list li[data-auditid="' + data.auditorId + '"]').remove();
+            if ($('#auditors-list li').length !== 0 && !$('#auditors-list li i').hasClass('fa-stop-circle')) {
+                $('#auditors-list li').eq($('#auditors-list li').length-1).children('i')
+                    .removeClass('fa-chevron-circle-down').addClass('fa-stop-circle');
+            }
+            $('#auditors-list2 li[data-auditid="' + data.auditorId + '"]').remove();
+            if ($('#auditors-list2 li').length !== 0 && !$('#auditors-list2 li i').hasClass('fa-stop-circle')) {
+                $('#auditors-list2 li').eq($('#auditors-list2 li').length-1).children('i')
+                    .removeClass('fa-chevron-circle-down').addClass('fa-stop-circle');
+            }
+        });
+    });
+    // 退回选择修改审批人流程
+    $('#hideSp').click(function () {
+        $('#sp-list2').modal('hide');
+    });
+    $('a[f-target]').click(function () {
+        $($(this).attr('f-target')).modal('show');
+    })
+});
+// 检查上报情况
+function checkAuditorFrom () {
+    if ($('#auditors li').length === 0) {
+        toast('请先选择审批人,再上报数据', 'error', 'exclamation-circle');
+        return false;
+    }
+}

+ 100 - 0
app/public/js/measure_material.js

@@ -0,0 +1,100 @@
+'use strict';
+
+/**
+ * 期计量 - 期列表页面 js
+ *
+ * @author Mai
+ * @date 2018/12/7
+ * @version
+ */
+$(function () {
+    // 获取审批流程
+    $('a[data-target="#sp-list" ]').on('click', function () {
+        const data = {
+            order: $(this).attr('m-order'),
+        };
+        postData('/tender/' + tenderId + '/measure/material/auditors', data, function (result) {
+            const materialAuditor = result.materialAuditor;
+            const auditors = result.auditors;
+            const auditHistory = result.auditHistory;
+            // 生成左边列表流程
+            const lefthtml = [];
+            lefthtml.push('<li class="list-group-item"><i class="fa fa fa-play-circle fa-rotate-90"></i> '+ materialAuditor.name +'  <small class="text-muted">'+ materialAuditor.role +'</small></li>');
+            for (const [index,a] of auditors.entries()) {
+                if (index+1 === auditors.length) {
+                    lefthtml.push('<li class="list-group-item"><i class="fa fa-stop-circle"></i> '+ a.name +'  <small class="text-muted">'+ a.role +'</small></li>');
+                } else {
+                    lefthtml.push('<li class="list-group-item"><i class="fa fa-chevron-circle-down"></i> '+ a.name +'  <small class="text-muted">'+ a.role +'</small></li>');
+                }
+            }
+            $('#auditor-list').html(lefthtml.join(''));
+
+            // 生成右边列表流程
+            const righthtml = [];
+            for(const ah of auditHistory) {
+                righthtml.push('<div class="card mt-3"><ul class="list-group list-group-flush">');
+                for (let iA = 0; iA < ah.length; iA++) {
+                    if (iA === 0) {
+                        righthtml.push('<li class="list-group-item">');
+                        righthtml.push('<span class="text-success pull-right">'+ (auditHistory.indexOf(ah) > 0 ? '重新' : '') +'上报</span>');
+                        righthtml.push('<h5 class="card-title">');
+                        righthtml.push('<i class="fa fa-play-circle fa-rotate-90 text-success"></i> '+ materialAuditor.name +' <small class="text-muted">'+ materialAuditor.role +'</small></h5>');
+                        righthtml.push('<p class="card-text"><small class="text-muted">' + (ah[iA].begin_time ? moment(ah[iA].begin_time).format('YYYY-MM-DD') : '') + '</small></p></li>');
+                        righthtml.push('<li class="list-group-item">');
+                        if (ah[iA].status !== auditConst.status.uncheck) {
+                            righthtml.push('<span class="'+ auditConst.statusClass[ah[iA].status] +' pull-right">' + auditConst.statusString[ah[iA].status] + (ah[iA].status === auditConst.status.checkNo ? ' ' + materialAuditor.name : '') + '</span>');
+                        }
+                        righthtml.push('<h5 class="card-title"><i class="fa '+ (iA === ah.length - 1 ? 'fa-stop-circle ' : '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) {
+                            righthtml.push('<p class="card-text mb-1">'+ ah[iA].opinion +'</p>');
+                            righthtml.push('<p class="card-text"><small class="text-muted">'+ (ah[iA].end_time ? moment(ah[iA].end_time).format('YYYY-MM-DD') : '') +'</small></p>');
+                        }
+                        righthtml.push('</li>');
+                    } else if (iA === ah.length - 1) {
+                        righthtml.push('<li class="list-group-item">');
+                        if (ah[iA].status !== auditConst.status.uncheck) {
+                            righthtml.push('<span class="'+ auditConst.statusClass[ah[iA].status] +' pull-right">' + auditConst.statusString[ah[iA].status] + (ah[iA].status === auditConst.status.checkNo ? ' ' + materialAuditor.name : '') + '</span>');
+                        }
+                        righthtml.push('<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) {
+                            righthtml.push('<p class="card-text mb-1">'+ ah[iA].opinion +'</p>');
+                            righthtml.push('<p class="card-text"><small class="text-muted">'+ (ah[iA].end_time ? moment(ah[iA].end_time).format('YYYY-MM-DD') : '') +'</small></p>');
+                        }
+                        righthtml.push('</li>');
+                    } else {
+                        righthtml.push('<li class="list-group-item">');
+                        if (ah[iA].status !== auditConst.status.uncheck) {
+                            righthtml.push('<span class="'+ auditConst.statusClass[ah[iA].status] +' pull-right">' + auditConst.statusString[ah[iA].status] + (ah[iA].status === auditConst.status.checkNo ? ' ' + materialAuditor.name : '') + '</span>');
+                        }
+                        righthtml.push('<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) {
+                            righthtml.push('<p class="card-text mb-1">'+ ah[iA].opinion +'</p>');
+                            righthtml.push('<p class="card-text"><small class="text-muted">'+ (ah[iA].end_time ? moment(ah[iA].end_time).format('YYYY-MM-DD') : '') +'</small></p>');
+                        }
+                        righthtml.push('</li>');
+                    }
+                }
+                righthtml.push('</ul></div>');
+            }
+            $('#auditor-list2').html(righthtml.join(''));
+        });
+    });
+
+    // 计量期选中
+    $('.select-stage-order').on('click', function () {
+        const stageList = $('.select-stage-order:checked');
+        if (stageList.length === 0) {
+            $('#show_order').hide();
+            $('#s_order').val('');
+        } else {
+            const order_array = [];
+            for (let s = 0; s < stageList.length; s++) {
+                order_array.push(stageList.eq(s).data('order'));
+                // console.log(stageList.eq(s));
+            }
+            $('#show_order').html('第<b class="mx-2">' + order_array.join(',') + '</b>期');
+            $('#show_order').show();
+            $('#s_order').val(order_array.join(','));
+        }
+    })
+});

+ 13 - 0
app/router.js

@@ -12,6 +12,8 @@ module.exports = app => {
     const tenderCheck = app.middlewares.tenderCheck();
     // 期读取中间件
     const stageCheck = app.middlewares.stageCheck();
+    // 材料调差读取中间件
+    const materialCheck = app.middlewares.materialCheck();
 
     // 登入登出相关
     app.get('/login', 'loginController.index');
@@ -207,6 +209,17 @@ module.exports = app => {
     // 变更单位管理
     app.post('/change/update/company', sessionAuth, 'changeController.updateCompany');
 
+    // 材料调差
+    app.get('/tender/:id/measure/material', sessionAuth, tenderCheck, 'materialController.index');
+    app.get('/tender/:id/measure/material/:order', sessionAuth, tenderCheck, materialCheck, 'materialController.info');
+    app.post('/tender/:id/measure/material/add', sessionAuth, tenderCheck, 'materialController.add');
+    app.post('/tender/:id/measure/material/delete', sessionAuth, tenderCheck, 'materialController.delete');
+    // 审批
+    app.post('/tender/:id/measure/material/:order/audit/add', sessionAuth, tenderCheck, materialCheck, 'materialController.addAudit');
+    app.post('/tender/:id/measure/material/:order/audit/delete', sessionAuth, tenderCheck, materialCheck, 'materialController.deleteAudit');
+    app.post('/tender/:id/measure/material/:order/audit/start', sessionAuth, tenderCheck, materialCheck, 'materialController.startAudit');
+    app.post('/tender/:id/measure/material/:order/audit/check', sessionAuth, tenderCheck, materialCheck, 'materialController.checkAudit');
+
     // 个人账号相关
     app.get('/profile/info', sessionAuth, 'profileController.info');
     app.get('/profile/sms', sessionAuth, 'profileController.sms');

+ 175 - 0
app/service/material.js

@@ -0,0 +1,175 @@
+'use strict';
+
+/**
+ * 期计量 数据模型
+ *
+ * @author Mai
+ * @date 2018/8/13
+ * @version
+ */
+
+const auditConst = require('../const/audit').material;
+const payConst = require('../const/deal_pay.js');
+const fs = require('fs');
+const path = require('path');
+
+module.exports = app => {
+    class Material extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'material';
+        }
+
+        /**
+         * 获取 最新一期 材料调差期计量
+         * @param tenderId
+         * @param includeUnCheck
+         * @returns {Promise<*>}
+         */
+        async getLastestMaterial(tenderId, includeUnCheck = false) {
+            this.initSqlBuilder();
+            this.sqlBuilder.setAndWhere('tid', {
+                value: tenderId,
+                operate: '=',
+            });
+            if (!includeUnCheck) {
+                this.sqlBuilder.setAndWhere('status', {
+                    value: auditConst.status.uncheck,
+                    operate: '!=',
+                });
+            }
+            this.sqlBuilder.orderBy = [['order', 'desc']];
+            const [sql, sqlParam] = this.sqlBuilder.build(this.tableName);
+            const material = await this.db.queryOne(sql, sqlParam);
+            return material;
+        }
+
+        /**
+         * 获取 最新一期 审批完成的 材料调差期计量
+         * @param tenderId
+         * @returns {Promise<*>}
+         */
+        async getLastestCompleteMaterial(tenderId) {
+            this.initSqlBuilder();
+            this.sqlBuilder.setAndWhere('tid', {
+                value: tenderId,
+                operate: '=',
+            });
+            this.sqlBuilder.setAndWhere('status', {
+                value: auditConst.status.checked,
+                operate: '=',
+            });
+            this.sqlBuilder.orderBy = [['order', 'desc']];
+            const [sql, sqlParam] = this.sqlBuilder.build(this.tableName);
+            const material = await this.db.queryOne(sql, sqlParam);
+            return material;
+        }
+
+        /**
+         * 获取标段下的全部计量期,按倒序
+         * @param tenderId
+         * @returns {Promise<void>}
+         */
+        async getValidMaterials(tenderId) {
+            const materials = await this.db.select(this.tableName, {
+                where: { tid: tenderId },
+                orders: [['order', 'desc']],
+            });
+            if (materials.length !== 0) {
+                const lastMaterial = materials[materials.length - 1];
+                if (lastMaterial.status === auditConst.status.uncheck && lastMaterial.user_id !== this.ctx.session.sessionUser.accountId) {
+                    materials.splice(materials.length - 1, 1);
+                }
+            }
+            // 最新一期计量(未审批完成),当前操作人的期详细数据,应实时计算
+            if (materials.length > 0 && materials[0].status !== auditConst.status.checked) {
+                const material = materials[0];
+                const curAuditor = await this.ctx.service.materialAudit.getCurAuditor(material.id, material.times);
+                const isActive = curAuditor ? curAuditor.id === this.ctx.session.sessionUser.accountId : material.user_id === this.ctx.session.sessionUser.accountId;
+                if (isActive) {
+                    material.curTimes = material.times;
+                    material.curOrder = curAuditor ? curAuditor.order : 0;
+                }
+            }
+            return materials;
+        }
+
+        /**
+         * 添加材料调差期
+         * @param tenderId - 标段id
+         * @param data - post的数据
+         * @returns {Promise<void>}
+         */
+        async addMaterial(tenderId, data) {
+            const materials = await this.getAllDataByCondition({
+                where: { tid: tenderId },
+                order: ['order'],
+            });
+            const preMaterial = materials[materials.length - 1];
+            if (materials.length > 0 && materials[materials.length - 1].status !== auditConst.status.checked) {
+                throw '上一期未审批通过,请等待上一期审批通过后,再新增数据';
+            }
+            const order = materials.length + 1;
+            const newMaterial = {
+                tid: tenderId,
+                order,
+                in_time: new Date(),
+                times: 1,
+                status: auditConst.status.uncheck,
+                user_id: this.ctx.session.sessionUser.accountId,
+                stage_id: data.stage_id.join(','),
+                s_order: data.s_order,
+            };
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 新增期记录
+                const result = await transaction.insert(this.tableName, newMaterial);
+                if (result.affectedRows === 1) {
+                    newMaterial.id = result.insertId;
+                } else {
+                    throw '新增期数据失败';
+                }
+                // 存在上一期时,复制上一期审批流程
+                if (preMaterial) {
+                    const auditResult = await this.ctx.service.materialAudit.copyPreMaterialAuditors(transaction, preMaterial, newMaterial);
+                    if (!auditResult) {
+                        throw '复制上一期审批流程失败';
+                    }
+                }
+
+                await transaction.commit();
+                return newMaterial;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        /**
+         * 删除材料调差期
+         *
+         * @param {Number} id - 期Id
+         * @returns {Promise<void>}
+         */
+        async deleteMaterial(id) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                await transaction.delete(this.tableName, { id });
+                await transaction.delete(this.ctx.service.materialAudit.tableName, { mid: id });
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+    }
+
+    return Material;
+};

+ 549 - 0
app/service/material_audit.js

@@ -0,0 +1,549 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2019/2/27
+ * @version
+ */
+
+const auditConst = require('../const/audit').material;
+// const smsTypeConst = require('../const/sms_type');
+const SMS = require('../lib/sms');
+
+module.exports = app => {
+    class MaterialAudit extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'material_audit';
+        }
+
+        /**
+         * 获取 审核人信息
+         *
+         * @param {Number} materialId - 材料调差期id
+         * @param {Number} auditorId - 审核人id
+         * @param {Number} times - 第几次审批
+         * @returns {Promise<*>}
+         */
+        async getAuditor(materialId, auditorId, times = 1) {
+            const sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
+                'FROM ?? AS la, ?? AS pa ' +
+                'WHERE la.`mid` = ? and la.`aid` = ? and la.`times` = ?' +
+                '    and la.`aid` = pa.`id`';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, materialId, auditorId, times];
+            return await this.db.queryOne(sql, sqlParam);
+        }
+
+        /**
+         * 获取 审核列表信息
+         *
+         * @param {Number} materialId - 材料调差期id
+         * @param {Number} times - 第几次审批
+         * @returns {Promise<*>}
+         */
+        async getAuditors(materialId, times = 1) {
+            const sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
+                'FROM ?? AS la, ?? AS pa ' +
+                'WHERE la.`mid` = ? and la.`times` = ? and la.`aid` = pa.`id` order by la.`order`';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, materialId, times];
+            return await this.db.query(sql, sqlParam);
+        }
+
+        /**
+         * 获取 当前审核人
+         *
+         * @param {Number} materialId - 材料调差期id
+         * @param {Number} times - 第几次审批
+         * @returns {Promise<*>}
+         */
+        async getCurAuditor(materialId, times = 1) {
+            const sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
+                'FROM ?? AS la, ?? AS pa ' +
+                'WHERE la.`mid` = ? and la.`status` = ? and la.`times` = ?' +
+                '    and la.`aid` = pa.`id`';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, materialId, auditConst.status.checking, times];
+            return await this.db.queryOne(sql, sqlParam);
+        }
+
+        /**
+         * 获取 最新审核顺序
+         *
+         * @param {Number} materialId - 材料调差期id
+         * @param {Number} times - 第几次审批
+         * @returns {Promise<number>}
+         */
+        async getNewOrder(materialId, times = 1) {
+            const sql = 'SELECT Max(??) As max_order FROM ?? Where `mid` = ? and `times` = ?';
+            const sqlParam = ['order', this.tableName, materialId, times];
+            const result = await this.db.queryOne(sql, sqlParam);
+            return result && result.max_order ? result.max_order + 1 : 1;
+        }
+
+        /**
+         * 新增审核人
+         *
+         * @param {Number} materialId - 材料调差期id
+         * @param {Number} auditorId - 审核人id
+         * @param {Number} times - 第几次审批
+         * @return {Promise<number>}
+         */
+        async addAuditor(materialId, auditorId, times = 1) {
+            const newOrder = await this.getNewOrder(materialId, times);
+            const data = {
+                tid: this.ctx.tender.id,
+                mid: materialId,
+                aid: auditorId,
+                times,
+                order: newOrder,
+                status: auditConst.status.uncheck,
+            };
+            const result = await this.db.insert(this.tableName, data);
+            return result.effectRows = 1;
+        }
+
+        /**
+         * 移除审核人时,同步其后审核人order
+         * @param transaction - 事务
+         * @param {Number} materialId - 材料调差期id
+         * @param {Number} auditorId - 审核人id
+         * @param {Number} times - 第几次审批
+         * @returns {Promise<*>}
+         * @private
+         */
+        async _syncOrderByDelete(transaction, materialId, order, times) {
+            this.initSqlBuilder();
+            this.sqlBuilder.setAndWhere('mid', {
+                value: materialId,
+                operate: '=',
+            });
+            this.sqlBuilder.setAndWhere('order', {
+                value: order,
+                operate: '>=',
+            });
+            this.sqlBuilder.setAndWhere('times', {
+                value: times,
+                operate: '=',
+            });
+            this.sqlBuilder.setUpdateData('order', {
+                value: 1,
+                selfOperate: '-',
+            });
+            const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'update');
+            const data = await transaction.query(sql, sqlParam);
+
+            return data;
+        }
+
+        /**
+         * 移除审核人
+         *
+         * @param {Number} materialId - 材料调差期id
+         * @param {Number} auditorId - 审核人id
+         * @param {Number} times - 第几次审批
+         * @return {Promise<boolean>}
+         */
+        async deleteAuditor(materialId, auditorId, times = 1) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                const condition = { mid: materialId, aid: auditorId, times };
+                const auditor = await this.getDataByCondition(condition);
+                if (!auditor) {
+                    throw '该审核人不存在';
+                }
+                await this._syncOrderByDelete(transaction, materialId, auditor.order, times);
+                await transaction.delete(this.tableName, condition);
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+            return true;
+        }
+
+        /**
+         * 开始审批
+         *
+         * @param {Number} materialId - 材料调差期id
+         * @param {Number} times - 第几次审批
+         * @return {Promise<boolean>}
+         */
+        async start(materialId, times = 1) {
+            const audit = await this.getDataByCondition({ mid: materialId, times, order: 1 });
+            if (!audit) {
+                throw '请先选择审批人,再上报数据';
+            }
+
+            const transaction = await this.db.beginTransaction();
+            try {
+                await transaction.update(this.tableName, { id: audit.id, status: auditConst.status.checking, begin_time: new Date() });
+                // 计算原报最终数据
+                // await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
+                // 复制一份下一审核人数据
+                // await this.ctx.service.stagePay.copyAuditStagePays(this.ctx.stage, this.ctx.stage.times, 1, transaction);
+                // 更新期数据
+                // const tpData = await this.ctx.service.stageBills.getSumTotalPrice(this.ctx.stage);
+                await transaction.update(this.ctx.service.material.tableName, {
+                    id: materialId, status: auditConst.status.checking,
+                });
+
+                // 添加短信通知-需要审批提醒功能
+                // const smsUser = await this.ctx.service.projectAccount.getDataById(audit.aid);
+                // if (smsUser.auth_mobile !== '' && smsUser.auth_mobile !== undefined && smsUser.sms_type !== '') {
+                //     const smsType = JSON.parse(smsUser.sms_type);
+                //     if (smsType[smsTypeConst.const.JL] !== undefined && smsType[smsTypeConst.const.JL].indexOf(smsTypeConst.judge.approval.toString()) !== -1) {
+                //         const tenderInfo = await this.ctx.service.tender.getDataById(audit.tid);
+                //         const stageInfo = await this.ctx.service.stage.getDataById(audit.sid);
+                //         const sms = new SMS(this.ctx);
+                //         const tenderName = await sms.contentChange(tenderInfo.name);
+                //         const content = '【纵横计量支付】' + tenderName + '第' + stageInfo.order + '期,需要您审批。';
+                //         sms.send(smsUser.auth_mobile, content);
+                //     }
+                // }
+
+                // todo 更新标段tender状态 ?
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+            return true;
+        }
+
+        async _checked(materialId, checkData, times) {
+            const time = new Date();
+            // 整理当前流程审核人状态更新
+            const audit = await this.getDataByCondition({ mid: materialId, times, status: auditConst.status.checking });
+            if (!audit) {
+                throw '审核数据错误';
+            }
+            const nextAudit = await this.getDataByCondition({ mid: materialId, times, order: audit.order + 1 });
+
+            const transaction = await this.db.beginTransaction();
+            try {
+                await transaction.update(this.tableName, { id: audit.id, status: checkData.checkType, opinion: checkData.opinion, end_time: time });
+                // 无下一审核人表示,审核结束
+                if (nextAudit) {
+                    // 复制一份下一审核人数据
+                    // 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 });
+                    // 同步 期信息
+                    await transaction.update(this.ctx.service.material.tableName, {
+                        id: materialId, status: auditConst.status.checking,
+                    });
+
+                    // 添加短信通知-需要审批提醒功能
+                    // const smsUser = await this.ctx.service.projectAccount.getDataById(nextAudit.aid);
+                    // if (smsUser.auth_mobile !== '' && smsUser.auth_mobile !== undefined && smsUser.sms_type !== '') {
+                    //     const smsType = JSON.parse(smsUser.sms_type);
+                    //     if (smsType[smsTypeConst.const.JL] !== undefined && smsType[smsTypeConst.const.JL].indexOf(smsTypeConst.judge.approval.toString()) !== -1) {
+                    //         const tenderInfo = await this.ctx.service.tender.getDataById(nextAudit.tid);
+                    //         const stageInfo = await this.ctx.service.stage.getDataById(nextAudit.sid);
+                    //         const sms = new SMS(this.ctx);
+                    //         const tenderName = await sms.contentChange(tenderInfo.name);
+                    //         const content = '【纵横计量支付】' + tenderName + '第' + stageInfo.order + '期,需要您审批。';
+                    //         sms.send(smsUser.auth_mobile, content);
+                    //     }
+                    // }
+                } else {
+                    // 本期结束
+                    // 生成截止本期数据 final数据
+                    // console.time('generatePre');
+                    // await this.ctx.service.stageBillsFinal.generateFinalData(transaction, this.ctx.tender, this.ctx.stage);
+                    // await this.ctx.service.stagePosFinal.generateFinalData(transaction, this.ctx.tender, this.ctx.stage);
+                    // console.timeEnd('generatePre');
+                    // 同步 期信息
+                    await transaction.update(this.ctx.service.material.tableName, {
+                        id: materialId, status: checkData.checkType,
+                    });
+
+                    // 添加短信通知-审批通过提醒功能
+                    // const mobile_array = [];
+                    // const stageInfo = await this.ctx.service.stage.getDataById(stageId);
+                    // const auditList = await this.getAuditors(stageId, stageInfo.times);
+                    // const smsUser1 = await this.ctx.service.projectAccount.getDataById(stageInfo.user_id);
+                    // if (smsUser1.auth_mobile !== undefined && smsUser1.sms_type !== '') {
+                    //     const smsType = JSON.parse(smsUser1.sms_type);
+                    //     if (smsType[smsTypeConst.const.JL] !== undefined && smsType[smsTypeConst.const.JL].indexOf(smsTypeConst.judge.result.toString()) !== -1) {
+                    //         mobile_array.push(smsUser1.auth_mobile);
+                    //     }
+                    // }
+                    // for (const user of auditList) {
+                    //     const smsUser = await this.ctx.service.projectAccount.getDataById(user.aid);
+                    //     if (smsUser.auth_mobile !== '' && smsUser.auth_mobile !== undefined && smsUser.sms_type !== '') {
+                    //         const smsType = JSON.parse(smsUser.sms_type);
+                    //         if (mobile_array.indexOf(smsUser.auth_mobile) === -1 && smsType[smsTypeConst.const.JL] !== undefined && smsType[smsTypeConst.const.JL].indexOf(smsTypeConst.judge.result.toString()) !== -1) {
+                    //             mobile_array.push(smsUser.auth_mobile);
+                    //         }
+                    //     }
+                    // }
+                    // if (mobile_array.length > 0) {
+                    //     const tenderInfo = await this.ctx.service.tender.getDataById(stageInfo.tid);
+                    //     const sms = new SMS(this.ctx);
+                    //     const tenderName = await sms.contentChange(tenderInfo.name);
+                    //     const content = '【纵横计量支付】' + tenderName + '第' + stageInfo.order + '期,审批通过。';
+                    //     sms.send(mobile_array, content);
+                    // }
+                }
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        async _checkNo(materialId, checkData, times) {
+            const time = new Date();
+            // 整理当前流程审核人状态更新
+            const audit = await this.getDataByCondition({ mid: materialId, times, status: auditConst.status.checking });
+            if (!audit) {
+                throw '审核数据错误';
+            }
+            const sql = 'SELECT `tid`, `mid`, `aid`, `order` FROM ?? WHERE `mid` = ? and `times` = ? GROUP BY `aid`';
+            const sqlParam = [this.tableName, materialId, times];
+            const auditors = await this.db.query(sql, sqlParam);
+            let order = 1;
+            for (const a of auditors) {
+                a.times = times + 1;
+                a.order = order;
+                a.status = auditConst.status.uncheck;
+                order++;
+            }
+
+            const transaction = await this.db.beginTransaction();
+            try {
+                await transaction.update(this.tableName, { id: audit.id, status: checkData.checkType, opinion: checkData.opinion, end_time: time });
+                // 同步 期信息
+                await transaction.update(this.ctx.service.material.tableName, {
+                    id: materialId, status: checkData.checkType,
+                    times: times + 1,
+                });
+                // 拷贝新一次审核流程列表
+                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);
+
+                // 添加短信通知-审批退回提醒功能
+                // const mobile_array = [];
+                // const stageInfo = await this.ctx.service.stage.getDataById(stageId);
+                // const auditList = await this.getAuditors(stageId, stageInfo.times);
+                // const smsUser1 = await this.ctx.service.projectAccount.getDataById(stageInfo.user_id);
+                // if (smsUser1.auth_mobile !== '' && smsUser1.auth_mobile !== undefined && smsUser1.sms_type !== '') {
+                //     const smsType = JSON.parse(smsUser1.sms_type);
+                //     if (smsType[smsTypeConst.const.JL] !== undefined && smsType[smsTypeConst.const.JL].indexOf(smsTypeConst.judge.result.toString()) !== -1) {
+                //         mobile_array.push(smsUser1.auth_mobile);
+                //     }
+                // }
+                // for (const user of auditList) {
+                //     const smsUser = await this.ctx.service.projectAccount.getDataById(user.aid);
+                //     if (smsUser.auth_mobile !== '' && smsUser.auth_mobile !== undefined && smsUser.sms_type !== '') {
+                //         const smsType = JSON.parse(smsUser.sms_type);
+                //         if (mobile_array.indexOf(smsUser.auth_mobile) === -1 && smsType[smsTypeConst.const.JL] !== undefined && smsType[smsTypeConst.const.JL].indexOf(smsTypeConst.judge.result.toString()) !== -1) {
+                //             mobile_array.push(smsUser.auth_mobile);
+                //         }
+                //     }
+                // }
+                // if (mobile_array.length > 0) {
+                //     const tenderInfo = await this.ctx.service.tender.getDataById(stageInfo.tid);
+                //     const sms = new SMS(this.ctx);
+                //     const tenderName = await sms.contentChange(tenderInfo.name);
+                //     const content = '【纵横计量支付】' + tenderName + '第' + stageInfo.order + '期,审批退回。';
+                //     sms.send(mobile_array, content);
+                // }
+
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        /**
+         * 审批
+         * @param {Number} materialId - 材料调差期id
+         * @param {auditConst.status.checked|auditConst.status.checkNo} checkType - 审批结果
+         * @param {Number} times - 第几次审批
+         * @returns {Promise<void>}
+         */
+        async check(materialId, checkData, times = 1) {
+            if (checkData.checkType !== auditConst.status.checked && checkData.checkType !== auditConst.status.checkNo) {
+                throw '提交数据错误';
+            }
+            switch (checkData.checkType) {
+                case auditConst.status.checked:
+                    await this._checked(materialId, checkData, times);
+                    break;
+                case auditConst.status.checkNo:
+                    await this._checkNo(materialId, checkData, times);
+                    break;
+                default:
+                    throw '无效审批操作';
+            }
+        }
+
+        /**
+         * 审批
+         * @param {Number} materialId - 材料调差期id
+         * @param {Number} times - 第几次审批
+         * @returns {Promise<void>}
+         */
+        async checkAgain(materialId, times = 1) {
+            const time = new Date();
+            // 整理当前流程审核人状态更新
+            const audit = (await this.getAllDataByCondition({ where: { mid: materialId, times }, orders: [['order', 'desc']], limit: 1, offset: 0 }))[0];
+            if (!audit || audit.order <= 1) {
+                throw '审核数据错误';
+            }
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 当前审批人2次添加至流程中
+                const newAuditors = [];
+                newAuditors.push({
+                    tid: audit.tid, mid: audit.mid, aid: audit.aid,
+                    times: audit.times, order: audit.order + 1, status: auditConst.status.checkAgain,
+                    begin_time: time, end_time: time, opinion: '',
+                });
+                newAuditors.push({
+                    tid: audit.tid, mid: audit.mid, aid: audit.aid,
+                    times: audit.times, order: audit.order + 2, status: auditConst.status.checking,
+                    begin_time: time,
+                });
+                await transaction.insert(this.tableName, newAuditors);
+
+                // 复制一份最新数据给下一人
+                // await this.ctx.service.stagePay.copyAuditStagePays(this.ctx.stage, this.ctx.stage.times, audit.order + 2, transaction);
+
+                // 本期结束
+                // 生成截止本期数据 final数据
+                // await this.ctx.service.stageBillsFinal.delGenerateFinalData(transaction, this.ctx.tender, this.ctx.stage);
+                // await this.ctx.service.stagePosFinal.delGenerateFinalData(transaction, this.ctx.tender, this.ctx.stage);
+                // 同步 期信息
+                await transaction.update(this.ctx.service.material.tableName, {
+                    id: materialId, status: auditConst.status.checking,
+                });
+
+                // // 添加短信通知-需要审批提醒功能
+                // const smsUser = await this.ctx.service.projectAccount.getDataById(audit.aid);
+                // if (smsUser.auth_mobile !== undefined && smsUser.sms_type !== '') {
+                //     const smsType = JSON.parse(smsUser.sms_type);
+                //     if (smsType[smsTypeConst.const.JL] !== undefined && smsType[smsTypeConst.const.JL].indexOf(smsTypeConst.judge.approval.toString()) !== -1) {
+                //         const tenderInfo = await this.ctx.service.tender.getDataById(audit.tid);
+                //         const stageInfo = await this.ctx.service.stage.getDataById(audit.sid);
+                //         const sms = new SMS(this.ctx);
+                //         const tenderName = await sms.contentChange(tenderInfo.name);
+                //         const content = '【纵横计量支付】' + tenderName + '第' + stageInfo.order + '期,需要您审批。';
+                //         sms.send(smsUser.auth_mobile, content);
+                //     }
+                // }
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        /**
+         * 获取审核人需要审核的期列表
+         *
+         * @param auditorId
+         * @returns {Promise<*>}
+         */
+        async getAuditMaterial(auditorId) {
+            const sql = 'SELECT sa.`aid`, sa.`times`, sa.`order`, sa.`begin_time`, sa.`end_time`, sa.`tid`, sa.`mid`,' +
+                        '    s.`order` As `sorder`, s.`status` As `sstatus`,' +
+                        '    t.`name`, t.`project_id`, t.`type`, t.`user_id` ' +
+                        '  FROM ?? AS sa, ?? AS s, ?? As t ' +
+                        '  WHERE ((sa.`aid` = ? and sa.`status` = ?) OR (s.`user_id` = ? and sa.`status` = ? and s.`status` = ? and sa.`times` = (s.`times`-1)))' +
+                        '    and sa.`mid` = s.`id` and sa.`tid` = t.`id`';
+            const sqlParam = [this.tableName, this.ctx.service.material.tableName, this.ctx.service.tender.tableName, auditorId, auditConst.status.checking, auditorId, auditConst.status.checkNo, auditConst.status.checkNo];
+            return await this.db.query(sql, sqlParam);
+        }
+
+        /**
+         * 获取审核人流程列表
+         *
+         * @param auditorId
+         * @returns {Promise<*>}
+         */
+        async getAuditGroupByList(materialId, times) {
+            const sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`mid`, la.`aid`, la.`order` ' +
+                'FROM ?? AS la, ?? AS pa ' +
+                'WHERE la.`mid` = ? and la.`times` = ? and la.`aid` = pa.`id` GROUP BY la.`aid` ORDER BY la.`order`';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, materialId, times];
+            return await this.db.query(sql, sqlParam);
+        }
+
+        /**
+         * 复制上一期的审批人列表给最新一期
+         *
+         * @param transaction - 新增一期的事务
+         * @param {Object} preMaterial - 上一期
+         * @param {Object} newaMaterial - 最新一期
+         * @returns {Promise<*>}
+         */
+        async copyPreMaterialAuditors(transaction, preMaterial, newMaterial) {
+            const auditors = await this.getAuditGroupByList(preMaterial.id, preMaterial.times);
+            const newAuditors = [];
+            for (const a of auditors) {
+                const na = {
+                    tid: preMaterial.tid,
+                    mid: newMaterial.id,
+                    aid: a.aid,
+                    times: newMaterial.times,
+                    order: newAuditors.length + 1,
+                    status: auditConst.status.uncheck,
+                };
+                newAuditors.push(na);
+            }
+            const result = await transaction.insert(this.tableName, newAuditors);
+            return result.effectRows = auditors.length;
+        }
+
+        /**
+         * 移除审核人
+         *
+         * @param {Number} materialId - 材料调差期id
+         * @param {Number} status - 期状态
+         * @param {Number} status - 期次数
+         * @return {Promise<boolean>}
+         */
+        async getAuditorByStatus(materialId, status, times = 1) {
+            let auditor = null;
+            let sql = '';
+            let sqlParam = '';
+            switch (status) {
+                case auditConst.status.checking :
+                case auditConst.status.checked :
+                case auditConst.status.checkNoPre :
+                    sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`mid`, la.`aid`, la.`order` ' +
+                        'FROM ?? AS la, ?? AS pa ' +
+                        'WHERE la.`mid` = ? and la.`status` = ? and la.`aid` = pa.`id` order by la.`times` desc, la.`id` desc';
+                    sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, materialId, status];
+                    auditor = await this.db.queryOne(sql, sqlParam);
+                    break;
+                case auditConst.status.checkNo :
+                    sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`mid`, la.`aid`, la.`order` ' +
+                        'FROM ?? AS la, ?? AS pa ' +
+                        'WHERE la.`mid` = ? and la.`status` = ? and la.`times` = ? and la.`aid` = pa.`id` order by la.`times` desc, la.`id` desc';
+                    sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, materialId, auditConst.status.checkNo, parseInt(times) - 1];
+                    auditor = await this.db.queryOne(sql, sqlParam);
+                    break;
+                case auditConst.status.uncheck :
+                default:break;
+            }
+            return auditor;
+        }
+    }
+
+    return MaterialAudit;
+};

+ 4 - 4
app/service/project_account.js

@@ -119,7 +119,7 @@ module.exports = app => {
                 // let cooperation = 0;
                 if (loginType === 2) {
                     // 查找项目数据
-                    const projectData = await this.ctx.service.project.getProjectByCode(data.project.toString());
+                    const projectData = await this.ctx.service.project.getProjectByCode(data.project.toString().trim());
                     if (projectData === null) {
                         throw '不存在项目数据';
                     }
@@ -131,7 +131,7 @@ module.exports = app => {
 
                     // 查找对应数据
                     accountData = await this.db.get(this.tableName, {
-                        account: data.account,
+                        account: data.account.trim(),
                         project_id: projectData.id,
                         enable: 1,
                     });
@@ -140,7 +140,7 @@ module.exports = app => {
                         throw '不存在对应用户数据';
                     }
 
-                    projectList = await this.getProjectInfoByAccount(data.account);
+                    projectList = await this.getProjectInfoByAccount(data.account.trim());
                     // permission = accountData.permission;
                     // cooperation = accountData.cooperation;
 
@@ -151,7 +151,7 @@ module.exports = app => {
                     //      result = await sso.loginValid(data.account, data.project_password.toString());
                     // } else {
                         // 加密密码
-                        const encryptPassword = crypto.createHmac('sha1', data.account).update(data.project_password)
+                        const encryptPassword = crypto.createHmac('sha1', data.account.trim()).update(data.project_password.trim())
                             .digest().toString('base64');
                         result = encryptPassword === accountData.password;
                     //}

+ 1 - 1
app/service/stage_audit.js

@@ -602,7 +602,7 @@ module.exports = app => {
             const time = new Date();
             // 整理当前流程审核人状态更新
             const audit = (await this.getAllDataByCondition({ where: { sid: stageId, times }, orders: [['order', 'desc']], limit: 1, offset: 0 }))[0];
-            if (!audit || audit.order <= 1) {
+            if (!audit || audit.order < 1) {
                 throw '审核数据错误';
             }
             const transaction = await this.db.beginTransaction();

+ 1 - 1
app/view/change/index.ejs

@@ -15,7 +15,7 @@
                 </div>
             </div>
             <% if (tender.user_id === uid) { %>
-            <div>
+            <div class="ml-auto">
                 <a href="#add-bj" data-toggle="modal" data-target="#add-bj" class="btn btn-sm btn-primary pull-right">新建变更令</a>
                 <a href="#setting" data-toggle="modal" data-target="#setting" class="btn btn-sm btn-outline-primary pull-right"><i class="fa fa-cog"></i></a>
             </div>

+ 1 - 1
app/view/layout/layout.ejs

@@ -33,7 +33,7 @@
         <%- content %>
     </div>
 </div>
-<div class="toast" style="position: absolute; z-index: 10000;text-align: center">
+<div class="toast" style="text-align: center">
     <i class="icon fa"></i>
     <span class="message"></span>
 </div>

+ 21 - 0
app/view/material/audit_btn.ejs

@@ -0,0 +1,21 @@
+<div class="contarl-box">
+    <% if (ctx.material.status === auditConst.status.uncheck) { %>
+        <% if (ctx.session.sessionUser.accountId === ctx.material.user_id) { %>
+            <a id="sub-sp-btn" href="javascript: void(0);" data-toggle="modal" data-target="#sub-sp" class="btn btn-primary btn-sm btn-block">上报审批</a>
+        <% } %>
+    <% } else if (ctx.material.status === auditConst.status.checking) { %>
+        <% if (ctx.material.curAuditor && ctx.material.curAuditor.aid === ctx.session.sessionUser.accountId) { %>
+            <a id="sp-done-btn" href="javascript: void(0);" data-toggle="modal" data-target="#sp-done" class="btn btn-success btn-sm btn-block">审批通过</a>
+            <a href="#sp-back" data-toggle="modal" data-target="#sp-back" class="btn btn-warning btn-sm btn-block">审批退回</a>
+        <% } else { %>
+            <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-secondary btn-sm btn-block">审批中</a>
+        <% } %>
+    <% } else if (ctx.material.status === auditConst.status.checked) { %>
+        <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-secondary btn-sm btn-block">审批完成</a>
+    <% } else if (ctx.material.status === auditConst.status.checkNo) { %>
+        <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-warning btn-sm btn-block text-muted">审批退回</a>
+        <% if (ctx.session.sessionUser.accountId === ctx.material.user_id) { %>
+            <a id="sp-list2-btn" href="javascript: void(0);" data-toggle="modal" data-target="#sp-list2" class="btn btn-primary btn-sm btn-block">重新上报</a>
+        <% } %>
+    <% } %>
+</div>

+ 628 - 0
app/view/material/audit_modal.ejs

@@ -0,0 +1,628 @@
+<% if ((ctx.material.status === auditConst.status.uncheck || ctx.material.status === auditConst.status.checkNo) && ctx.session.sessionUser.accountId === ctx.material.user_id) { %>
+<!--上报审批-->
+<div class="modal fade" id="sub-sp" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">上报审批</h5>
+            </div>
+            <div class="modal-body">
+                <div class="form-group">
+                    <label>选择审批人</label>
+                    <div class="input-group">
+                        <div class="input-group-prepend">
+                            <select class="form-control" id="account_group">
+                                <option value="0">所有分组</option>
+                                <% for (const dw in accountGroup) { %>
+                                    <option value="<%= dw %>"><%= accountGroup[dw] %></option>
+                                <% } %>
+                            </select>
+                        </div>
+                        <select class="form-control" id="account_list">
+                            <option value="0">选择审批人</option>
+                            <% for (const account of accountList) { %>
+                                <option value="<%= account.id %>"><%= account.name %><% if (account.role !== '') { %>(<%= account.role %>)<% } %><% if (account.company !== '') { %> -<%= account.company %><% } %></option>
+                            <% } %>
+                        </select>
+                    </div>
+                </div>
+                <div class="card mt-3">
+                    <div class="card-header">
+                        审批流程
+                    </div>
+                    <ul class="list-group list-group-flush" id="auditors">
+                        <% for (let i = 0, iLen = ctx.material.auditorList.length; i < iLen; i++) { %>
+                        <li class="list-group-item" auditorId="<%- ctx.material.auditorList[i].aid %>">
+                            <a href="javascript: void(0)" class="text-danger pull-right">移除</a>
+                            <%- ctx.material.auditorList[i].order %> <%- ctx.material.auditorList[i].name %>
+                            <small class="text-muted"><%- ctx.material.auditorList[i].role %></small>
+                        </li>
+                        <% } %>
+                    </ul>
+                </div>
+            </div>
+            <form class="modal-footer" method="post" action="<%- preUrl %>/audit/start" onsubmit="return checkAuditorFrom()">
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
+                <input type="hidden" name="_csrf" value="<%= ctx.csrf %>">
+                <button class="btn btn-primary" type="submit">确认上报</button>
+            </form>
+        </div>
+    </div>
+</div>
+<% } %>
+<% if (ctx.material.status === auditConst.status.checking) { %>
+    <% if (ctx.material.curAuditor && ctx.material.curAuditor.aid === ctx.session.sessionUser.accountId) { %>
+        <!--审批通过-->
+        <div class="modal fade" id="sp-done" data-backdrop="static">
+    <div class="modal-dialog modal-lg" role="document">
+        <form class="modal-content" action="<%- preUrl %>/audit/check" method="post">
+            <div class="modal-header">
+                <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> <%- ctx.material.user.name %>  <small class="text-muted"><%- ctx.material.user.role %></small>
+                                </li>
+                                <% for (let i = 0; i < ctx.material.auditors2.length; i++) { %>
+                                <li class="list-group-item">
+                                    <% if (i < ctx.material.auditors2.length - 1) { %>
+                                    <i class="fa fa-chevron-circle-down"></i> <%- ctx.material.auditors2[i].name %>  <small class="text-muted"><%- ctx.material.auditors2[i].role %></small>
+                                    <% } else { %>
+                                    <i class="fa fa fa-stop-circle"></i> <%- ctx.material.auditors2[i].name %>  <small class="text-muted"><%- ctx.material.auditors2[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.material.auditors[0].begin_time.toLocaleDateString() %></small> 上报</span>
+                                    <h5 class="card-title"><i class="fa fa-play-circle fa-rotate-90 text-success"></i> <%- ctx.material.user.name %> <small class="text-muted"><%- ctx.material.user.role %></small></h5>
+                                </li>
+                                <% for (let iA = 0; iA < ctx.material.auditors.length; iA++) { %>
+                                <% const auditors = ctx.material.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>
+                                    <h5 class="card-title">
+                                        <i class="<%- (iA < auditors.length - 1 ? 'fa fa-chevron-circle-down text-success' : 'fa fa-stop-circle text-success') %>"></i> <%- auditors[iA].name %> <small class="text-muted"><%- auditors[iA].role %></small>
+                                    </h5>
+                                    <% } 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>
+                                    <% } else { %>
+                                    <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>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
+                <input type="hidden" name="_csrf" value="<%= ctx.csrf %>" />
+                <input type="hidden" name="checkType" value="<%= auditConst.status.checked %>" />
+                <button type="submit" class="btn btn-success" >确认通过</button>
+            </div>
+        </form>
+    </div>
+</div>
+        <!--审批退回-->
+        <div class="modal fade" id="sp-back" data-backdrop="static">
+    <div class="modal-dialog modal-lg" role="document">
+        <form class="modal-content modal-lg" action="<%- preUrl %>/audit/check" method="post">
+            <div class="modal-header">
+                <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> <%- ctx.material.user.name %>  <small class="text-muted"><%- ctx.material.user.role %></small>
+                                </li>
+                                <% for (let i = 0; i < ctx.material.auditors2.length; i++) { %>
+                                <li class="list-group-item">
+                                    <% if (i < ctx.material.auditors2.length - 1) { %>
+                                    <i class="fa fa-chevron-circle-down"></i> <%- ctx.material.auditors2[i].name %>  <small class="text-muted"><%- ctx.material.auditors2[i].role %></small>
+                                    <% } else { %>
+                                    <i class="fa fa fa-stop-circle"></i> <%- ctx.material.auditors2[i].name %>  <small class="text-muted"><%- ctx.material.auditors2[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.material.auditors[0].begin_time.toLocaleDateString() %></small> 上报</span>
+                                    <h5 class="card-title"><i class="fa fa-play-circle fa-rotate-90 text-success"></i> <%- ctx.material.user.name %> <small class="text-muted"><%- ctx.material.user.role %></small></h5>
+                                </li>
+                                <% for (let iA = 0; iA < ctx.material.auditors.length; iA++) { %>
+                                <% const auditors = ctx.material.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>
+                                    <h5 class="card-title">
+                                        <i class="<%- (iA < auditors.length - 1 ? 'fa fa-chevron-circle-down text-success' : 'fa fa-stop-circle text-success') %>"></i> <%- auditors[iA].name %> <small class="text-muted"><%- auditors[iA].role %></small>
+                                    </h5>
+                                    <% } 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>
+                                    <% } else { %>
+                                    <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>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
+                <input type="hidden" name="_csrf" value="<%= ctx.csrf %>" />
+                <input type="hidden" name="checkType" value="<%= auditConst.status.checked %>" />
+                <button type="submit" class="btn btn-warning" >确认退回</button>
+            </div>
+        </form>
+    </div>
+</div>
+    <% } 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>
+            </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> <%- ctx.material.user.name %>  <small class="text-muted"><%- ctx.material.user.role %></small>
+                                </li>
+                                <% for (let i = 0; i < ctx.material.auditors2.length; i++) { %>
+                                <li class="list-group-item">
+                                    <% if (i < ctx.material.auditors2.length - 1) { %>
+                                    <i class="fa fa-chevron-circle-down"></i> <%- ctx.material.auditors2[i].name %>  <small class="text-muted"><%- ctx.material.auditors2[i].role %></small>
+                                    <% } else { %>
+                                    <i class="fa fa fa-stop-circle"></i> <%- ctx.material.auditors2[i].name %>  <small class="text-muted"><%- ctx.material.auditors2[i].role %></small>
+                                    <% } %>
+                                </li>
+                                <% } %>
+                            </ul>
+                        </div>
+                    </div>
+                    <div class="col-8 modal-height-500" style="overflow: auto">
+                        <% for (const ah of ctx.material.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.material.auditHistory.indexOf(ah) > 0) { %>重新<% } %>上报</span>
+                                    <h5 class="card-title"><i class="fa fa-play-circle fa-rotate-90 text-success"></i> <%- ctx.material.user.name %> <small class="text-muted"><%- ctx.material.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) { %> <%- ctx.material.user.name %><% } %></span>
+                                    <% } %>
+                                    <h5 class="card-title"><i class="fa <%if (iA === ah.length - 1) { %>fa-stop-circle<% } else { %>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"><%- 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) { %> <%- ctx.material.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"><%- 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) { %> <%- ctx.material.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"><%- ah[iA].opinion %></p>
+                                    <p class="card-text"><small class="text-muted"><%- ah[iA].end_time.toLocaleDateString() %></small></p>
+                                    <% } %>
+                                </li>
+                                <% } %>
+                                <% } %>
+                            </ul>
+                        </div>
+                        <% } %>
+                        <% if (ctx.material.status === auditConst.status.checking) {%>
+                        <div class="card mt-3">
+                            <ul class="list-group list-group-flush">
+                                <% const auditors = ctx.material.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.material.times > 1) { %>重新<% } %>上报</span>
+                                    <h5 class="card-title"><i class="fa fa-play-circle fa-rotate-90 text-success"></i> <%- ctx.material.user.name %> <small class="text-muted"><%- ctx.material.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) { %> <%- ctx.material.user.name %><% } %></span>
+                                    <% } %>
+                                    <h5 class="card-title"><i class="fa <%if (iA === auditors.length - 1) { %>fa-stop-circle<% } else { %>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"><%- 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) { %> <%- ctx.material.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 mb-1"><%- 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) { %> <%- ctx.material.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"><%- 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>
+            </div>
+        </div>
+    </div>
+</div>
+    <% } %>
+<% } else if (ctx.material.status === auditConst.status.checked) { %>
+    <!--审批流程/结果-->
+    <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>
+            </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> <%- ctx.material.user.name %>  <small class="text-muted"><%- ctx.material.user.role %></small>
+                                </li>
+                                <% for (let i = 0; i < ctx.material.auditors2.length; i++) { %>
+                                <li class="list-group-item">
+                                    <% if (i < ctx.material.auditors2.length - 1) { %>
+                                    <i class="fa fa-chevron-circle-down"></i> <%- ctx.material.auditors2[i].name %>  <small class="text-muted"><%- ctx.material.auditors2[i].role %></small>
+                                    <% } else { %>
+                                    <i class="fa fa fa-stop-circle"></i> <%- ctx.material.auditors2[i].name %>  <small class="text-muted"><%- ctx.material.auditors2[i].role %></small>
+                                    <% } %>
+                                </li>
+                                <% } %>
+                            </ul>
+                        </div>
+                    </div>
+                    <div class="col-8 modal-height-500" style="overflow: auto">
+                        <% for (const ah of ctx.material.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.material.auditHistory.indexOf(ah) > 0) { %>重新<% } %>上报</span>
+                                    <h5 class="card-title"><i class="fa fa-play-circle fa-rotate-90 text-success"></i> <%- ctx.material.user.name %> <small class="text-muted"><%- ctx.material.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) { %> <%- ctx.material.user.name %><% } %></span>
+                                    <% } %>
+                                    <h5 class="card-title"><i class="fa <%if (iA === ah.length - 1) { %>fa-stop-circle<% } else { %>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"><%- 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) { %> <%- ctx.material.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"><%- 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) { %> <%- ctx.material.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"><%- ah[iA].opinion %></p>
+                                    <p class="card-text"><small class="text-muted"><%- ah[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>
+            </div>
+        </div>
+    </div>
+</div>
+<% } else if (ctx.material.status === auditConst.status.checkNo) { %>
+    <!--审批流程/结果-->
+    <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>
+            </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> <%- ctx.material.user.name %>  <small class="text-muted"><%- ctx.material.user.role %></small>
+                                </li>
+                                <% for (let i = 0; i < ctx.material.auditors2.length; i++) { %>
+                                <li class="list-group-item">
+                                    <% if (i < ctx.material.auditors2.length - 1) { %>
+                                    <i class="fa fa-chevron-circle-down"></i> <%- ctx.material.auditors2[i].name %>  <small class="text-muted"><%- ctx.material.auditors2[i].role %></small>
+                                    <% } else { %>
+                                    <i class="fa fa fa-stop-circle"></i> <%- ctx.material.auditors2[i].name %>  <small class="text-muted"><%- ctx.material.auditors2[i].role %></small>
+                                    <% } %>
+                                </li>
+                                <% } %>
+                            </ul>
+                        </div>
+                    </div>
+                    <div class="col-8 modal-height-500" style="overflow: auto">
+                        <% for (const ah of ctx.material.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.material.auditHistory.indexOf(ah) > 0) { %>重新<% } %>上报</span>
+                                    <h5 class="card-title"><i class="fa fa-play-circle fa-rotate-90 text-success"></i> <%- ctx.material.user.name %> <small class="text-muted"><%- ctx.material.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) { %> <%- ctx.material.user.name %><% } %></span>
+                                    <% } %>
+                                    <h5 class="card-title"><i class="fa <%if (iA === ah.length - 1) { %>fa-stop-circle<% } else { %>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"><%- 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) { %> <%- ctx.material.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"><%- 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) { %> <%- ctx.material.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"><%- ah[iA].opinion %></p>
+                                    <p class="card-text"><small class="text-muted"><%- ah[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>
+            </div>
+        </div>
+    </div>
+    </div>
+    <% if (ctx.session.sessionUser.accountId === ctx.material.user_id) { %>
+    <!--重新上报-->
+    <div class="modal fade" id="sp-list2" data-backdrop="static">
+        <div class="modal-dialog modal-lg" role="document">
+            <div class="modal-content">
+                <div class="modal-header">
+                    <h5 class="modal-title">重新上报</h5>
+                </div>
+                <div class="modal-body">
+                    <div class="row">
+                        <div class="col-4">
+                            <a href="#sub-sp" data-toggle="modal" data-target="#sub-sp" id="hideSp">修改审批流程</a>
+                            <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.material.user.name %>  <small class="text-muted"><%- ctx.material.user.role %></small>
+                                    </li>
+                                </ul>
+                                <ul class="list-group list-group-flush" id="auditors-list">
+                                    <% const auditorList = ctx.material.auditorList; %>
+                                    <% for (let i = 0; i < auditorList.length; i++) { %>
+                                        <li class="list-group-item" data-auditid="<%- auditorList[i].aid %>">
+                                            <% if (i < auditorList.length - 1) { %>
+                                                <i class="fa fa-chevron-circle-down"></i> <%- auditorList[i].name %>  <small class="text-muted"><%- auditorList[i].role %></small>
+                                            <% } else { %>
+                                                <i class="fa fa fa-stop-circle"></i> <%- auditorList[i].name %>  <small class="text-muted"><%- auditorList[i].role %></small>
+                                            <% } %>
+                                        </li>
+                                    <% } %>
+                                </ul>
+                            </div>
+                        </div>
+                        <div class="col-8 modal-height-500" style="overflow: auto">
+                            <% for (const ah of ctx.material.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.material.auditHistory.indexOf(ah) > 0) { %>重新<% } %>上报</span>
+                                                    <h5 class="card-title"><i class="fa fa-play-circle fa-rotate-90 text-success"></i> <%- ctx.material.user.name %> <small class="text-muted"><%- ctx.material.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) { %> <%- ctx.material.user.name %><% } %></span>
+                                                    <% } %>
+                                                    <h5 class="card-title"><i class="fa <%if (iA === ah.length - 1) { %>fa-stop-circle<% } else { %>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"><%- 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) { %> <%- ctx.material.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"><%- 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) { %> <%- ctx.material.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"><%- ah[iA].opinion %></p>
+                                                        <p class="card-text"><small class="text-muted"><%- ah[iA].end_time.toLocaleDateString() %></small></p>
+                                                    <% } %>
+                                                </li>
+                                            <% } %>
+                                        <% } %>
+                                    </ul>
+                                </div>
+                            <% } %>
+                            <% if (ctx.material.status === auditConst.status.checkNo) {%>
+                                <div class="card mt-3">
+                                    <ul class="list-group list-group-flush">
+                                        <li class="list-group-item">
+                                            <span class="pull-right">重新上报中</span>
+                                            <h5 class="card-title"><i class="fa fa-play-circle fa-rotate-90"></i> <%- ctx.material.user.name %> <small class="text-muted"><%- ctx.material.user.role %></small></h5>
+                                            <p class="card-text"><small class="text-muted"></small></p>
+                                        </li>
+                                    </ul>
+                                    <ul class="list-group list-group-flush" id="auditors-list2">
+                                        <% const auditorList = ctx.material.auditorList; %>
+                                        <% for (let iA = 0; iA < auditorList.length; iA++) { %>
+                                            <% if (iA === auditorList.length - 1) { %>
+                                                <li class="list-group-item" data-auditid="<%- auditorList[iA].aid %>">
+                                                    <h5 class="card-title"><i class="fa fa-stop-circle"></i> <%- auditorList[iA].name %> <small class="text-muted"><%- auditorList[iA].role %></small></h5>
+                                                </li>
+                                            <% } else { %>
+                                                <li class="list-group-item" data-auditid="<%- auditorList[iA].aid %>">
+                                                    <h5 class="card-title"><i class="fa fa-chevron-circle-down"></i> <%- auditorList[iA].name %> <small class="text-muted"><%- auditorList[iA].role %></small></h5>
+                                                </li>
+                                            <% } %>
+                                        <% } %>
+                                    </ul>
+                                </div>
+                            <% } %>
+                        </div>
+                    </div>
+                </div>
+                <form class="modal-footer" method="post" action="<%- preUrl %>/audit/start" onsubmit="return checkAuditorFrom()">
+                    <input type="hidden" name="_csrf" value="<%= ctx.csrf %>">
+                    <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
+                    <button class="btn btn-primary" type="submit">确认上报</button>
+                </form>
+            </div>
+        </div>
+    </div>
+    <% } %>
+<% } %>

+ 87 - 0
app/view/material/index.ejs

@@ -0,0 +1,87 @@
+<% include ../tender/tender_sub_menu.ejs %>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main d-flex">
+            <% include ../tender/tender_sub_mini_menu.ejs %>
+            <h2>
+                材料调差期列表
+            </h2>
+            <div class="ml-auto">
+                <% if (ctx.session.sessionUser.accountId === ctx.tender.data.user_id && stages.length > 0 &&
+                        (materials.length === 0 || materials[0].status === auditConst.status.checked)) { %>
+                <a href="#add-qi" data-toggle="modal" data-target="#add-qi" class="btn btn-primary btn-sm pull-right">开始新一期</a>
+                <% } %>
+            </div>
+        </div>
+    </div>
+    <div class="content-wrap">
+        <div class="c-body">
+            <table class="table table-bordered">
+                <thead>
+                <tr>
+                    <th>期数</th>
+                    <th class="text-center">添加时间</th>
+                    <th class="text-center">计量期</th>
+                    <th class="text-center">价差费用(含税)</th>
+                    <th class="text-center">价差费用</th>
+                    <th class="text-center">审批进度</th>
+                    <th class="text-center">操作</th>
+                </tr>
+                </thead>
+                <tbody>
+                <% for (const m of materials) { %>
+                <tr>
+                    <td>
+                        <a href="<%- '/tender/' + ctx.tender.id + '/measure/material/' + m.order %>">第 <%- m.order %> 期</a>
+                    </td>
+                    <td class="text-center"><%= moment(m.in_time).format('YYYY-MM-DD') %></td>
+                    <td class="text-center">第 <%= m.s_order %> 期</td>
+                    <td class="text-right"></td>
+                    <td class="text-right"></td>
+                    <td class="<%- auditConst.auditProgressClass[m.status] %>">
+                        <% if (m.curAuditor) { %>
+                            <a href="#sp-list" data-toggle="modal" data-target="#sp-list" m-order="<%- m.order %>"><%- m.curAuditor.name %><%if (m.curAuditor.role !== '' && m.curAuditor.role !== null) { %>-<%- m.curAuditor.role %><% } %></a>
+                        <% } %>
+                        <%- auditConst.auditProgress[m.status] %>
+                    </td>
+                    <td class="text-center">
+                    <% if (m.status === auditConst.status.uncheck && m.user_id === ctx.session.sessionUser.accountId) { %>
+                        <a href="<%- '/tender/' + ctx.tender.id + '/measure/material/' + m.order %>" class="btn <%- auditConst.statusButtonClass[m.status] %> btn-sm"><%- auditConst.statusButton[m.status] %></a>
+                    <% } else if (m.status === auditConst.status.checkNo && m.curAuditor && m.user_id === ctx.session.sessionUser.accountId) { %>
+                        <a href="<%- '/tender/' + ctx.tender.id + '/measure/material/' + m.order %>" class="btn <%- auditConst.statusButtonClass[m.status] %> btn-sm"><%- auditConst.statusButton[m.status] %></a>
+                    <% } else if (m.status === auditConst.status.checking && m.curAuditor && m.curAuditor.aid === ctx.session.sessionUser.accountId) { %>
+                        <a href="<%- '/tender/' + ctx.tender.id + '/measure/material/' + m.order %>" class="btn <%- auditConst.statusButtonClass[m.status] %> btn-sm"><%- auditConst.statusButton[m.status] %></a>
+                    <% } else { %>
+                        <span class="<%- auditConst.auditStringClass[m.status] %>"><%- auditConst.auditString[m.status] %></span>
+                    <% } %>
+                    <% if (m.user_id === ctx.session.sessionUser.accountId && m.order === materials.length) { %>
+                        <a href="#del-qi" class="btn btn-outline-danger btn-sm ml-1" data-toggle="modal" data-target="#del-qi">删除</a>
+                    <% } %>
+                    </td>
+                </tr>
+                <% } %>
+                </tbody>
+            </table>
+        </div>
+    </div>
+</div>
+<script src="/public/js/sub_menu.js"></script>
+<script>
+    const tenderId = '<%- ctx.tender.id %>';
+    const auditConst = JSON.parse('<%- auditConst2 %>');
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        //key: 'measure.memu.1.0.0',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            autoFlashHeight();
+        }
+    });
+</script>

+ 68 - 0
app/view/material/info.ejs

@@ -0,0 +1,68 @@
+<% include ./material_sub_menu.ejs %>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main d-flex justify-content-between">
+            <% include ./material_sub_mini_menu.ejs %>
+            <div>
+                <div class="d-inline-block">
+                    本期调差计量期:第<span class="mx-2"><%= ctx.material.s_order.split(',').join(',') %></span>期
+                </div>
+            </div>
+            <div class="ml-auto">
+            </div>
+        </div>
+    </div>
+    <div class="content-wrap pr-46">
+        <div class="c-header p-0">
+        </div>
+        <div class="row w-100 sub-content">
+            <div id="main-view" class="c-body" style="width: 100%">
+                <!--上部分-->
+                <div class="sjs-height-1" id="material-ledger">
+                </div>
+                <!--下部分-->
+                <div class="bcontent-wrap">
+                    <div class="bc-bar mb-1">
+                        <div class="input-group input-group-sm ">
+                            <div class="input-group-prepend">
+                                <span class="input-group-text" id="basic-addon1">增税税率</span>
+                            </div>
+                            <select class="form-control col-1"><option>9%</option><option>10%</option><option>11%</option></select>
+                        </div>
+                    </div>
+                    <div class="sp-wrap">
+                        <div class="col-4 p-0">
+                            <table class="table table-sm table-bordered">
+                                <tr><th></th><th>本期金额</th><th>截止本期金额</th></tr>
+                                <tr><td>材料价差费用</td><td></td><td></td></tr>
+                                <tr><td>材料价差费用(含税)</td><td></td><td></td></tr>
+                            </table>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+<% if (ctx.material.status === auditConst.status.uncheck && ctx.session.sessionUser.accountId === ctx.material.user_id) {%>
+<script>
+    const accountList = JSON.parse('<%- JSON.stringify(accountList) %>');
+</script>
+<% } %>
+<script>
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        //key: 'stage.compare.memu.1.0.0',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            autoFlashHeight();
+        }
+    });
+</script>

+ 2 - 0
app/view/material/info_modal.ejs

@@ -0,0 +1,2 @@
+
+<% include ./audit_modal.ejs %>

+ 30 - 0
app/view/material/material_sub_menu.ejs

@@ -0,0 +1,30 @@
+<div class="panel-sidebar" id="sub-menu">
+    <div class="panel-title">
+        <div class="title-bar">
+            <h2 class="text-truncate" style="white-space:nowrap; overflow:hidden; text-overflow:ellipsis;" data-toggle="tooltip" data-placement="right" title=""  data-original-title="第<%- ctx.material.order%>期 - <%- tender.name %>">第<%- ctx.material.order%>期 - <%- tender.name %></h2>
+        </div>
+    </div>
+    <div class="scrollbar-auto">
+        <div class="nav-box">
+                <ul class="nav-list list-unstyled">
+                    <li class=""><a href="/tender/<%- ctx.tender.id %>/measure/material"><i class="fa fa-chevron-left "></i> <span>返回</span></a></li>
+                </ul>
+            </div>
+        <div class="nav-box">
+                <ul class="nav-list list-unstyled">
+                    <li class="<% if (ctx.url === '/tender/' + ctx.tender.id + '/measure/material/' + ctx.material.order) { %>active<% } %>">
+                        <a href="/tender/<%- ctx.tender.id %>/measure/material/<%- ctx.material.order %>"><span class="ml-3">调差工料</span></a>
+                    </li>
+                </ul>
+            </div>
+        <div class="nav-box">
+                <ul class="nav-list list-unstyled">
+                    <li class="<% if (ctx.url === '/tender/' + ctx.tender.id + '/measure/material/' + ctx.material.order + '/list') { %>active<% } %>">
+                        <a href="/tender/<%- ctx.tender.id %>/measure/material/<%- material.order %>/list"><span class="ml-3">调差清单</span></a>
+                    </li>
+                </ul>
+            </div>
+        <% include ./audit_btn.ejs %>
+        <div class="side-fold"><a href="javascript: void(0)" data-toggle="tooltip" data-placement="top" data-original-title="折叠侧栏" id="to-mini-menu"><i class="fa fa-sign-out fa-flip-horizontal"></i></a></div>
+    </div>
+</div>

+ 29 - 0
app/view/material/material_sub_mini_menu.ejs

@@ -0,0 +1,29 @@
+<!--折起的菜单-->
+<div class="min-side" id="sub-mini-menu" style="display: none;">
+    <div class="side-switch">
+        <i class="fa fa-bars"></i>
+    </div>
+    <div class="side-menu" id="mini-menu-list" style="display: none">
+        <div class="nav-box">
+            <ul class="nav-list list-unstyled">
+                <li class=""><a href="/tender/<%- ctx.tender.id %>/measure/material"><i class="fa fa-chevron-left "></i> <span>返回</span></a></li>
+            </ul>
+        </div>
+        <div class="nav-box">
+            <ul class="nav-list list-unstyled">
+                <li class="<% if (ctx.url === '/tender/' + ctx.tender.id + '/measure/material/' + ctx.material.order) { %>active<% } %>">
+                    <a href="/tender/<%- ctx.tender.id %>/measure/material/<%- ctx.material.order %>"><span class="ml-3">调差工料</span></a>
+                </li>
+            </ul>
+        </div>
+        <div class="nav-box">
+            <ul class="nav-list list-unstyled">
+                <li class="<% if (ctx.url === '/tender/' + ctx.tender.id + '/measure/material/' + ctx.material.order + '/list') { %>active<% } %>">
+                    <a href="/tender/<%- ctx.tender.id %>/measure/material/<%- material.order %>/list"><span class="ml-3">调差清单</span></a>
+                </li>
+            </ul>
+        </div>
+        <% include ./audit_btn.ejs %>
+        <div class="side-fold"><a href="javascript: void(0);" data-toggle="tooltip" data-placement="top" data-original-title="展开侧栏" id="to-menu"><i class="fa fa-thumb-tack"></i></a></div>
+    </div>
+</div>

+ 144 - 0
app/view/material/modal.ejs

@@ -0,0 +1,144 @@
+<% if (ctx.session.sessionUser.accountId === ctx.tender.data.user_id && stages.length > 0 &&
+        (materials.length === 0 || materials[0].status === auditConst.status.checked)) { %>
+<!--弹出添加期-->
+<div class="modal fade" id="add-qi" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <form class="modal-content" action="<%- preUrl + '/measure/material/add' %>" method="post">
+            <div class="modal-header">
+                <h5 class="modal-title">添加新一期</h5>
+            </div>
+            <div class="modal-body">
+                <div class="form-group">
+                    <label>调差期</label>
+                    <input class="form-control" value="第 <%- materials.length + 1 %> 期" type="text" readonly="">
+                </div>
+                <div class="form-group">
+                    <label>本期调差,包含计量期<span class="ml-2 text-danger" id="show_order" style="display: none">第<b class="mx-2"></b>期</span></label>
+                    <div class="row">
+                        <% for (const stage of stages) { %>
+                        <div class="col-4">
+                            <div class="custom-control custom-checkbox">
+                                <input type="checkbox" class="custom-control-input select-stage-order" id="stage_<%= stage.id %>" name="stage_id[]" value="<%= stage.id %>" data-order="<%= stage.order %>">
+                                <label class="custom-control-label" for="stage_<%= stage.id %>">第<%= stage.order %>期</label>
+                            </div>
+                        </div>
+                        <% } %>
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <input type="hidden" name="_csrf" value="<%= ctx.csrf %>" />
+                <input type="hidden" name="s_order" value="" id="s_order" />
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
+                <button type="submit" class="btn btn-primary">确定添加</button>
+            </div>
+        </form>
+    </div>
+</div>
+<% } %>
+<% if (materials && materials.length >= 1) { %>
+<!--删除期-->
+<div class="modal fade" id="del-qi" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <form class="modal-content" action="<%- preUrl + '/measure/material/delete' %>" method="post">
+            <div class="modal-header">
+                <h5 class="modal-title">删除期</h5>
+            </div>
+            <div class="modal-body">
+                <h5>确认删除「第<%= materials.length %>期」?</h5>
+                <h5>删除后,数据无法恢复,请谨慎操作。</h5>
+            </div>
+            <div class="modal-footer">
+                <input type="hidden" name="stage_id" value="<%= materials[0].id %>">
+                <input type="hidden" name="_csrf" value="<%= ctx.csrf %>" />
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
+                <button type="submit" class="btn btn-danger">确定删除</button>
+            </div>
+        </form>
+    </div>
+</div>
+<% } %>
+<!--审批流程/结果-->
+<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>
+            </div>
+            <div class="modal-body">
+                <div class="row">
+                    <div class="col-4">
+                        <div class="card mt-3">
+                            <ul class="list-group list-group-flush" id="auditor-list">
+                                <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" id="auditor-list2">
+                        <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>
+                                </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-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>
+                                </li>
+                            </ul>
+                        </div>
+                        <!--退回原报重新上报-->
+                        <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>
+                                </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>
+                                    <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>
+                                </li>
+                            </ul>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
+            </div>
+        </div>
+    </div>
+</div>
+<script src="/public/js/moment/moment.min.js"></script>
+<script src="/public/js/measure_material.js"></script>

+ 2 - 2
app/view/measure/stage.ejs

@@ -1,12 +1,12 @@
 <% include ../tender/tender_sub_menu.ejs %>
 <div class="panel-content">
     <div class="panel-title">
-        <div class="title-main d-flex justify-content-between">
+        <div class="title-main d-flex">
             <% include ../tender/tender_sub_mini_menu.ejs %>
             <h2>
                 期列表
             </h2>
-            <div>
+            <div class="ml-auto">
                 <% if (ctx.session.sessionUser.accountId === ctx.tender.data.user_id && ctx.tender.data.ledger_status === auditConst.status.checked &&
                         (stages.length === 0 || stages[0].status === auditConst.status.checked)) { %>
                 <a href="#add-qi" data-toggle="modal" data-target="#add-qi" class="btn btn-primary btn-sm pull-right">开始新一期</a>

+ 2 - 0
app/view/stage/audit_modal.ejs

@@ -31,8 +31,10 @@
                         <select class="form-control" id="account_list">
                             <option value="0">选择审批人</option>
                             <% for (const account of accountList) { %>
+                            <% if (account.id !== ctx.session.sessionUser.accountId) { %>
                                 <option value="<%= account.id %>"><%= account.name %><% if (account.role !== '') { %>(<%= account.role %>)<% } %><% if (account.company !== '') { %> -<%= account.company %><% } %></option>
                             <% } %>
+                            <% } %>
                         </select>
                     </div>
                 </div>

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

@@ -34,6 +34,11 @@
         </div>
         <div class="nav-box">
             <ul class="nav-list list-unstyled">
+                <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/measure/material') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/measure/material"><i class="fa fa-line-chart"></i> <span>材料调差</span></a></li>
+            </ul>
+        </div>
+        <div class="nav-box">
+            <ul class="nav-list list-unstyled">
                 <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/report') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/report"><i class="fa fa-file-text-o"></i> <span>报表</span></a></li>
             </ul>
         </div>

+ 6 - 1
app/view/tender/tender_sub_mini_menu.ejs

@@ -31,9 +31,14 @@
         </div>
         <div class="nav-box">
             <ul class="nav-list list-unstyled">
+                <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/measure/material') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/measure/material"><i class="fa fa-line-chart"></i> <span>材料调差</span></a></li>
+            </ul>
+        </div>
+        <div class="nav-box">
+            <ul class="nav-list list-unstyled">
                 <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/report') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/report"><i class="fa fa-file-text-o"></i> <span>报表</span></a></li>
             </ul>
         </div>
         <div class="side-fold"><a href="javascript: void(0);" data-toggle="tooltip" data-placement="top" data-original-title="展开侧栏" id="to-menu"><i class="fa fa-thumb-tack"></i></a></div>
     </div>
-</div>
+</div>

+ 19 - 0
config/web.js

@@ -305,6 +305,25 @@ const JsFiles = {
                 mergeFile: 'measure_compare',
             }
         },
+        material: {
+            info: {
+                files: [
+                    "/public/js/spreadjs/sheets/gc.spread.sheets.all.10.0.1.min.js",
+                    "/public/js/decimal.min.js",
+                    "/public/js/toastr.min.js",
+                ],
+                mergeFiles: [
+                    "/public/js/sub_menu.js",
+                    "/public/js/div_resizer.js",
+                    "/public/js/spreadjs_rela/spreadjs_zh.js",
+                    "/public/js/zh_calc.js",
+                    "/public/js/path_tree.js",
+                    // "/public/js/material.js",
+                    "/public/js/material_audit.js",
+                ],
+                mergeFile: 'material',
+            },
+        },
     }
 
 };