Browse Source

支付审批 no.2 up

laiguoran 2 years ago
parent
commit
973ac24688

+ 35 - 0
app/const/payment.js

@@ -27,10 +27,45 @@ const permission_list = [
 const permission_type_array = ['tender', 'folder', 'process'];
 const permission_type_array = ['tender', 'folder', 'process'];
 const permission_value_array = [0, 1];
 const permission_value_array = [0, 1];
 
 
+// 跟随着标段同时生成的固定报表表单
+const const_rpt_list = [
+    {
+        rpt_id: 666,
+        rpt_name: '安全生产费',
+    },
+];
+
+const rpt_dataType = {
+    intact_type_text: 'text',
+    intact_type_number: 'number',
+    intact_type_multitext: 'textarea',
+};
+
+const rpt_control_name = ['Title', 'Header', 'Footer'];
+
+const rpt_control = {
+    Title: '标题',
+    Header: '表头',
+    content: '表单',
+    Footer: '表尾',
+};
+
+const signature_msg = {
+    sign_path: null,
+    company_stamp: null,
+    stamp_path: null,
+    date: null,
+    content: null,
+};
+
 module.exports = {
 module.exports = {
     audit_admin_permission,
     audit_admin_permission,
     audit_permission,
     audit_permission,
     permission_list,
     permission_list,
     permission_type_array,
     permission_type_array,
     permission_value_array,
     permission_value_array,
+    const_rpt_list,
+    rpt_dataType,
+    rpt_control,
+    signature_msg,
 };
 };

+ 438 - 25
app/controller/payment_controller.js

@@ -2,6 +2,8 @@
 const accountGroup = require('../const/account_group').group;
 const accountGroup = require('../const/account_group').group;
 const JV = require('../reports/rpt_component/jpc_value_define');
 const JV = require('../reports/rpt_component/jpc_value_define');
 const shenpiConst = require('../const/shenpi');
 const shenpiConst = require('../const/shenpi');
+const auditConst = require('../const/audit').stage;
+const paymentConst = require('../const/payment');
 
 
 module.exports = app => {
 module.exports = app => {
 
 
@@ -67,6 +69,12 @@ module.exports = app => {
             const tenderList = await ctx.service.paymentTender.getList(ctx.session.sessionUser.accountId);
             const tenderList = await ctx.service.paymentTender.getList(ctx.session.sessionUser.accountId);
             // 获取你创建的目录及对应目录下的所有目录
             // 获取你创建的目录及对应目录下的所有目录
             const folderList = await ctx.service.paymentFolder.getList(ctx.session.sessionUser.accountId, tenderList);
             const folderList = await ctx.service.paymentFolder.getList(ctx.session.sessionUser.accountId, tenderList);
+            if (tenderList.length > 0) {
+                for (const t of tenderList) {
+                    t.have_notice = await ctx.service.paymentDetail.haveNotice2Tender(t.id, ctx.session.sessionUser.accountId);
+                    t.have_detail = await ctx.service.paymentDetail.haveDetail2Tender(t.id);
+                }
+            }
             responseData.data.folderList = folderList;
             responseData.data.folderList = folderList;
             responseData.data.tenderList = tenderList;
             responseData.data.tenderList = tenderList;
             ctx.body = responseData;
             ctx.body = responseData;
@@ -197,6 +205,35 @@ module.exports = app => {
                 ctx.body = { err: 1, msg: err.toString(), data: null };
                 ctx.body = { err: 1, msg: err.toString(), data: null };
             }
             }
         }
         }
+
+        /**
+         * 获取审批界面所需的 原报、审批人数据等
+         * @param ctx
+         * @return {Promise<void>}
+         * @private
+         */
+        async _getDetailAuditViewData(ctx) {
+            const times = ctx.detail.status === auditConst.status.checkNo ? ctx.detail.times - 1 : ctx.detail.times;
+            ctx.detail.user = await ctx.service.projectAccount.getAccountInfoById(ctx.detail.uid);
+            ctx.detail.auditHistory = [];
+            if (times >= 1) {
+                for (let i = 1; i <= times; i++) {
+                    ctx.detail.auditHistory.push(await ctx.service.paymentDetailAudit.getAuditors(ctx.detail.id, i));
+                }
+            }
+            // 获取审批流程中左边列表
+            ctx.detail.auditors2 = ctx.detail.status === auditConst.status.checkNo && ctx.detail.uid !== ctx.session.sessionUser.accountId ?
+                await ctx.service.paymentDetailAudit.getAuditorsWithOwner(ctx.detail.id, times) :
+                await ctx.service.paymentDetailAudit.getAuditorsWithOwner(ctx.detail.id, ctx.detail.times);
+            if (ctx.detail.status === auditConst.status.uncheck || ctx.detail.status === auditConst.status.checkNo) {
+                ctx.detail.auditorList = await ctx.service.paymentDetailAudit.getAuditors(ctx.detail.id, ctx.detail.times);
+            }
+
+            // 是否已验证手机短信
+            const pa = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
+            ctx.detail.authMobile = pa.auth_mobile;
+        }
+
         /**
         /**
          * 支付表单页面
          * 支付表单页面
          *
          *
@@ -205,16 +242,91 @@ module.exports = app => {
          */
          */
         async detail(ctx) {
         async detail(ctx) {
             try {
             try {
-                const id = parseInt(ctx.params.did);
-                if (!id) throw '参数错误';
-                const info = await ctx.service.paymentDetail.getDataById(id);
-                const rptTpl = await ctx.service.rptTpl.getDataById(3029);
-                const pageRst = ctx.service.jpcReport.getAllPreviewPagesCommon(rptTpl, 'A4');
+                await this._getDetailAuditViewData(ctx);
+                const report_json = JSON.parse(ctx.detail.report_json);
+                const content = [];
+                // 获取当前报表人
+                const rptAudit = await ctx.service.paymentRptAudit.getDataByCondition({ td_id: ctx.detail.id, uid: ctx.session.sessionUser.accountId });
+                // 获取个人签字,单位章,个人章地址
+                const userInfo = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
+                const stampPathList = userInfo.stamp_path ? userInfo.stamp_path.split('!;!') : [];
+                const companyInfo = userInfo.company ? await ctx.service.constructionUnit.getDataByCondition({ pid: ctx.session.sessionProject.id, name: userInfo.company }) : {};
+                if (report_json.items[0].interact_cells.length > 0) {
+                    for (const [i, cell] of report_json.items[0].interact_cells.entries()) {
+                        const push_item = {
+                            type: paymentConst.rpt_dataType[cell.DataType],
+                            value: cell.Prefix ? ctx.helper._.replace(cell.Value, cell.Prefix, '') : cell.Value,
+                            label: cell.Label,
+                            index: i,
+                        };
+                        const thisControl = paymentConst.rpt_control[cell.control] ? cell.control : 'content';
+                        const oneShowContent = ctx.helper._.find(content, { control: thisControl });
+                        if (oneShowContent) {
+                            oneShowContent.items.push(push_item);
+                        } else {
+                            content.push({
+                                control: thisControl,
+                                title: paymentConst.rpt_control[thisControl],
+                                items: [push_item],
+                            });
+                        }
+                    }
+                    if (rptAudit.signature_msg) {
+                        const sign_msg = JSON.parse(rptAudit.signature_msg);
+                        // 签名签章
+                        const signCells = ctx.helper._.find(report_json.items[0].signature_cells, { signature_name: rptAudit.signature_name });
+                        if (signCells && signCells.Value === '' && (sign_msg.sign_path || sign_msg.company_stamp || sign_msg.stamp_path)) {
+                            const signArray = [];
+                            if (sign_msg.sign_path) signArray.push(sign_msg.sign_path);
+                            if (sign_msg.company_stamp) signArray.push(sign_msg.company_stamp);
+                            if (sign_msg.stamp_path) signArray.push(sign_msg.stamp_path);
+                            signCells.path = signArray.join('!;!');
+                        }
+                        // 日期
+                        const dateCells = ctx.helper._.find(report_json.items[0].signature_date_cells, { signature_name: rptAudit.signature_name + '_签字日期' });
+                        if (dateCells && dateCells.Value === '' && sign_msg.date) {
+                            dateCells.Value = sign_msg.date;
+                        }
+                        // 意见
+                        const contentCells = ctx.helper._.find(report_json.items[0].signature_audit_cells, { signature_name: rptAudit.signature_name + '_审核意见' });
+                        if (contentCells && contentCells.Value === '' && sign_msg.content) {
+                            contentCells.Value = sign_msg.content;
+                        }
+                        rptAudit.signature_msg = sign_msg;
+                    } else {
+                        rptAudit.signature_msg = paymentConst.signature_msg;
+                        rptAudit.signature_msg.sign_path = userInfo.sign_path ? userInfo.sign_path : '';
+                    }
+                }
                 const renderData = {
                 const renderData = {
-                    info,
-                    pageRst,
+                    trInfo: ctx.trInfo,
+                    report_json,
+                    paymentConst,
+                    content,
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.payment.detail),
+                    rptAudit,
+                    auditConst,
+                    shenpiConst,
+                    signPath: userInfo.sign_path ? userInfo.sign_path : '',
+                    stampPathList,
+                    currentStamp: rptAudit && rptAudit.signature_msg.stamp_path ? rptAudit.signature_msg.stamp_path : (stampPathList.length > 0 ? stampPathList[0] : ''),
+                    companyStamp: companyInfo && companyInfo.sign_path ? companyInfo.sign_path : '',
+                    preUrl: '/payment/' + ctx.tender.id + '/detail/' + ctx.detail.id,
                 };
                 };
-                await this.layout('payment/detail.ejs', renderData);
+                if ((ctx.detail.status === auditConst.status.uncheck || ctx.detail.status === auditConst.status.checkNo) && ctx.session.sessionUser.accountId === ctx.detail.uid) {
+                    // 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', 'mobile'],
+                    });
+                    renderData.accountList = accountList;
+                    renderData.accountGroup = accountGroup.map((item, idx) => {
+                        const groupList = accountList.filter(item => item.account_group === idx);
+                        return { groupName: item, groupList };
+                    });
+                }
+                await this.layout('payment/detail.ejs', renderData, 'payment/detail_modal.ejs');
             } catch (err) {
             } catch (err) {
                 console.log(err);
                 console.log(err);
                 this.log(err);
                 this.log(err);
@@ -222,29 +334,146 @@ module.exports = app => {
             }
             }
         }
         }
 
 
+        async detailSave(ctx) {
+            try {
+                // 检查权限等
+                if (ctx.detail.uid !== ctx.session.sessionUser.accountId) {
+                    throw '您无权操作';
+                }
+                if (ctx.detail.status === auditConst.status.checking || ctx.detail.status === auditConst.status.checked) {
+                    throw '您无权操作';
+                }
+                const responseData = {
+                    err: 0, msg: '', data: {},
+                };
+
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.type) {
+                    throw '提交数据错误';
+                }
+                switch (data.type) {
+                    case 'update_rpt':
+                        responseData.data = await ctx.service.paymentDetail.updateReportJson(ctx.detail.id, data.report_json);
+                        break;
+                    case 'update_sign':
+                        responseData.data = await ctx.service.paymentRptAudit.updateSignatureMsg(ctx.detail.id, ctx.session.sessionUser.accountId, data.signature_msg);
+                        break;
+                    case 'add_audit':
+                        const auditorId = this.app._.toInteger(data.auditorId);
+                        if (isNaN(auditorId) || auditorId <= 0) {
+                            throw '参数错误';
+                        }
+                        ctx.detail.auditorList = await ctx.service.paymentDetailAudit.getAuditors(ctx.detail.id, ctx.detail.times);
+                        // 检查审核人是否已存在
+                        const exist = this.app._.find(ctx.detail.auditorList, { aid: auditorId });
+                        if (exist) {
+                            throw '该审核人已存在,请勿重复添加';
+                        }
+                        const shenpiInfo = await ctx.service.paymentShenpiAudit.getDataByCondition({ tr_id: ctx.trInfo.id, sp_status: shenpiConst.sp_status.gdzs });
+                        const is_gdzs = shenpiInfo && ctx.trinfo.sp_status === shenpiConst.sp_status.gdzs ? 1 : 0;
+                        const result = await ctx.service.paymentDetailAudit.addAuditor(ctx.detail.id, auditorId, ctx.detail.times, is_gdzs);
+                        if (!result) {
+                            throw '添加审核人失败';
+                        }
+                        responseData.data = await ctx.service.paymentDetailAudit.getAuditorsWithOwner(ctx.detail.id, ctx.detail.times);
+                        break;
+                    case 'del_audit':
+                        const auditorId2 = data.auditorId instanceof Number ? data.auditorId : this.app._.toNumber(data.auditorId);
+                        if (isNaN(auditorId2) || auditorId2 <= 0) {
+                            throw '参数错误';
+                        }
+                        const result2 = await ctx.service.paymentDetailAudit.deleteAuditor(ctx.detail.id, auditorId2, ctx.detail.times);
+                        if (!result2) {
+                            throw '移除审核人失败';
+                        }
+                        responseData.data = await ctx.service.paymentDetailAudit.getAuditors(ctx.detail.id, ctx.detail.times);
+                        break;
+                    default: throw '参数有误';
+                }
+                ctx.body = responseData;
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+        }
+
+        async deleteDetail(ctx) {
+            try {
+                const detail_id = ctx.request.body.detail_id;
+                const detailInfo = await ctx.service.paymentDetail.getDataById(detail_id);
+                if (!detailInfo) {
+                    throw '所选报表表单详情已删除';
+                }
+                // 获取最新的期数
+                const detail_highOrder = await ctx.service.paymentDetail.count({
+                    tr_id: detailInfo.tr_id,
+                });
+                if (!(ctx.session.sessionUser.is_admin || ((detailInfo.status === auditConst.status.uncheck || detailInfo.status === auditConst.status.checkNo) && detailInfo.uid === ctx.session.sessionUser.accountId)) || detail_highOrder !== detailInfo.order) {
+                    throw '您无权删除所选报表表单详情';
+                }
+                const result = await ctx.service.paymentDetail.deleteDetail(detail_id);
+                if (!result) {
+                    throw '删除报表表单详情失败,请重试';
+                }
+                ctx.redirect('/payment/' + ctx.tender.id + '/list/' + detailInfo.tr_id);
+            } catch (err) {
+                this.log(err);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 期审批流程(Get)
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async detailAuditors(ctx) {
+            try {
+                const responseData = {
+                    err: 0, msg: '', data: {},
+                };
+                const order = JSON.parse(ctx.request.body.data).order;
+                const tr_id = ctx.params.trid;
+                const detailInfo = await ctx.service.paymentDetail.getDataByCondition({ tr_id, order });
+                // 获取审批流程中右边列表
+                const auditHistory = [];
+                const times = detailInfo.status === auditConst.status.checkNo ? detailInfo.times - 1 : detailInfo.times;
+                if (times >= 1) {
+                    for (let i = 1; i <= times; i++) {
+                        auditHistory.push(await ctx.service.paymentDetailAudit.getAuditors(detailInfo.id, i));
+                    }
+                }
+                responseData.data.auditHistory = auditHistory;
+                // 获取审批流程中左边列表
+                responseData.data.auditors = await ctx.service.paymentDetailAudit.getAuditorsWithOwner(detailInfo.id, times);
+
+                responseData.data.user = await ctx.service.projectAccount.getAccountInfoById(detailInfo.uid);
+                ctx.body = responseData;
+            } catch (error) {
+                this.log(error);
+                ctx.body = { err: 1, msg: error.toString(), data: null };
+            }
+        }
+
+        async _returnRptProjectList(ctx, formProcess = false) {
+            // 获取报表表单列表
+            if (ctx.tender.uid === ctx.session.sessionUser.accountId || formProcess) {
+                const rptProject = await ctx.service.rptTreeNode.getDataByCondition({ name: '01.支付审批报表' });
+                const rptProjectList = rptProject.items ? JSON.parse(rptProject.items) : [];
+                const tenderRptList = await ctx.service.paymentTenderRpt.getProcessList(ctx.tender.id);
+                return await ctx.service.paymentTenderRpt.checkAndUpdateList(tenderRptList, rptProjectList, formProcess);
+            }
+            return await ctx.service.paymentTenderRpt.getList(ctx.tender.id, ctx.session.sessionUser.accountId);
+        }
+
         async process(ctx) {
         async process(ctx) {
             try {
             try {
                 const auditPermission = await this.ctx.service.paymentPermissionAudit.getOnePermission(ctx.session.sessionUser.is_admin, ctx.session.sessionUser.accountId);
                 const auditPermission = await this.ctx.service.paymentPermissionAudit.getOnePermission(ctx.session.sessionUser.is_admin, ctx.session.sessionUser.accountId);
                 if (!auditPermission || !auditPermission.process) {
                 if (!auditPermission || !auditPermission.process) {
                     throw '权限不足';
                     throw '权限不足';
                 }
                 }
-                // 获取报表表单列表
-                const rptProject = await ctx.service.rptTreeNode.getDataByCondition({ name: '01.支付审批报表' });
-                let rptProjectList = rptProject.items ? JSON.parse(rptProject.items) : [];
-                // const rptTplList = [];
-                // if (rptProjectList.length > 0) {
-                //     const params = { tender_id: id };
-                //     for (const rpt of rptProjectList) {
-                //         const rptTpl = await ctx.service.rptTpl.getDataById(rpt.ID);
-                //         // 根据模板ID获取报表JSON
-                //         const pageRst = ctx.service.jpcReport.getAllPreviewPagesCommon(rptTpl, 'A4');
-                //         console.log(pageRst.items[0]);
-                //         // return;
-                //         rptTplList.push(pageRst.items[0]);
-                //     }
-                // }
-                let tenderRptList = await ctx.service.paymentTenderRpt.getProcessList(ctx.tender.id, rptProjectList);
-                [tenderRptList, rptProjectList] = await ctx.service.paymentTenderRpt.checkAndUpdateList(tenderRptList, rptProjectList);
+                let [tenderRptList, rptProjectList] = await this._returnRptProjectList(ctx);
+                tenderRptList = ctx.helper._.filter(tenderRptList, { is_const: 0, is_del: 0 });
                 // for (const tr of tenderRptList) {
                 // for (const tr of tenderRptList) {
                 //     if (tr.status === shenpiConst.sp_status.gdspl) {
                 //     if (tr.status === shenpiConst.sp_status.gdspl) {
                 //         tr.auditList = await ctx.service.paymentShenpiAudit.getAuditList(ctx.tender.id, tr.id, tr.sp_status);
                 //         tr.auditList = await ctx.service.paymentShenpiAudit.getAuditList(ctx.tender.id, tr.id, tr.sp_status);
@@ -318,7 +547,191 @@ module.exports = app => {
         }
         }
 
 
         async rptList(ctx) {
         async rptList(ctx) {
+            try {
+                const tenderRptList = await this._returnRptProjectList(ctx);
+                if (tenderRptList.length === 0) {
+                    throw '当前报表表单您无权打开';
+                }
+                for (const tr of tenderRptList) {
+                    tr.have_notice = await ctx.service.paymentDetail.haveNotice2TenderRpt(tr.id, ctx.session.sessionUser.accountId);
+                }
+                const trid = ctx.params.trid ? parseInt(ctx.params.trid) : 0;
+                const trInfo = trid ? ctx.helper._.find(tenderRptList, { id: trid }) : tenderRptList[0];
+                // 获取列表
+                const trDetailList = await ctx.service.paymentDetail.getValidDetails(trInfo.id);
+                for (const s of trDetailList) {
+                    // s.curAuditor = null;
+                    // 根据期状态返回展示用户
+                    s.curAuditor = await ctx.service.paymentDetailAudit.getAuditorByStatus(s.id, s.status, s.times);
+                }
+                const renderData = {
+                    tender: ctx.tender,
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.payment.list),
+                    tenderRptList,
+                    trInfo,
+                    trDetailList,
+                    rptMsg: null,
+                    auditConst,
+                    accountGroup: [],
+                    accountList: [],
+                    preUrl: '/payment/' + ctx.tender.id + '/list/' + trInfo.id,
+                };
+                // 获取报表信息,新增时及设置报表角色时使用
+                if (trInfo.uid === ctx.session.sessionUser.accountId && trInfo.is_del === 0 && trInfo.rpt_id) {
+                    const rptTpl = await ctx.service.rptTpl.getDataById(trInfo.rpt_id);
+                    if (rptTpl) {
+                        const pageRst = await ctx.service.jpcReport.getAllPreviewPagesCommon(rptTpl, 'A4');
+                        renderData.rptMsg = pageRst.items[0];
+                        // 判断与原有的报表审批人列表是否有区别
+                        const deffierence = ctx.helper._.differenceWith(renderData.rptMsg.signature_cells, JSON.parse(trInfo.signature_cells), ctx.helper._.isEqual);
+                        if (!ctx.helper._.isEmpty(deffierence)) {
+                            // 删除rpt_audit重新配置
+                            await ctx.service.paymentTenderRpt.defaultUpdate({
+                                id: trInfo.id,
+                                signature_cells: JSON.stringify(renderData.rptMsg.signature_cells),
+                                rpt_audit: null,
+                                is_first: 1,
+                            });
+                            trInfo.rpt_audit = null;
+                            trInfo.is_first = 1;
+                            trInfo.signature_cells = renderData.rptMsg.signature_cells;
+                        }
+                        if (trInfo.rpt_audit) {
+                            trInfo.rpt_audit = JSON.parse(trInfo.rpt_audit);
+                            const prjAccList = await ctx.service.projectAccount.getAllAccountByProjectId(ctx.session.sessionProject.id);
+                            for (const audit of trInfo.rpt_audit) {
+                                if (audit.uid) {
+                                    const info = ctx.helper._.find(prjAccList, { id: audit.uid });
+                                    audit.name = info.name;
+                                }
+                            }
+                        } else {
+                            trInfo.rpt_audit = [];
+                            for (const sc of renderData.rptMsg.signature_cells) {
+                                trInfo.rpt_audit.push({
+                                    uid: null,
+                                    rpt_name: sc.signature_name,
+                                });
+                            }
+                        }
+                        renderData.trInfo = trInfo;
+                    }
+                    if (renderData.rptMsg) {
+                        // 获取所有项目参与者
+                        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', 'mobile'],
+                        });
+                        const accountGroupList = accountGroup.map((item, idx) => {
+                            const groupList = accountList.filter(item => item.account_group === idx);
+                            return { groupName: item, groupList };
+                        });
+                        renderData.accountList = accountList;
+                        renderData.accountGroup = accountGroupList;
+                    }
+                }
+                await this.layout('payment/list.ejs', renderData, 'payment/list_modal.ejs');
+            } catch (err) {
+                console.log(err);
+                this.log(err);
+                ctx.redirect(this.request && this.request.headers && this.request.headers.referer ? this.request.headers.referer : this.menu.menu.dashboard.url);
+            }
+        }
 
 
+        async rptSave(ctx) {
+            try {
+                const tr_id = ctx.params.trid;
+                if (!tr_id) {
+                    throw '参数有误';
+                }
+                const trInfo = await ctx.service.paymentTenderRpt.getDataById(tr_id);
+                if (!trInfo) {
+                    throw '标段报表不存在';
+                }
+                if (ctx.session.sessionUser.accountId !== trInfo.uid) {
+                    throw '权限不足';
+                }
+                const responseData = {
+                    err: 0, msg: '', data: {},
+                };
+
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.type) {
+                    throw '提交数据错误';
+                }
+                switch (data.type) {
+                    case 'rpt_audit':
+                        responseData.data = await ctx.service.paymentTenderRpt.updateRptAudit(trInfo.id, data.rpt_audit);
+                        break;
+                    case 'add-detail':
+                        responseData.data = await ctx.service.paymentDetail.addDetail(trInfo, data.code, data.s_time);
+                        break;
+                    default: throw '参数有误';
+                }
+                ctx.body = responseData;
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+        }
+
+        /**
+         * 上报和重新上报
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async startAudit(ctx) {
+            try {
+                if (ctx.detail.uid !== ctx.session.sessionUser.accountId) {
+                    throw '您无权上报报表表单详情数据';
+                }
+                if (ctx.detail.status === auditConst.status.checking || ctx.detail.status === auditConst.status.checked) {
+                    throw '该报表表单详情数据当前无法上报';
+                }
+
+                await ctx.service.paymentDetailAudit.start(ctx.detail.id, ctx.detail.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
+         * @return {Promise<void>}
+         */
+        async checkAudit(ctx) {
+            try {
+                if (!ctx.detail || ctx.detail.status !== auditConst.status.checking) {
+                    throw '当前报表表单详情数据有误';
+                }
+                if (!ctx.detail.curAuditor || ctx.detail.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.paymentDetailAudit.check(ctx.detail.id, data, ctx.detail.times);
+
+                ctx.redirect(ctx.request.header.referer);
+            } catch (err) {
+                this.log(err);
+                ctx.session.postError = err.toString();
+                ctx.redirect(ctx.request.header.referer);
+            }
         }
         }
     }
     }
 
 

+ 2 - 2
app/controller/profile_controller.js

@@ -370,8 +370,8 @@ module.exports = app => {
                     if (spIndex === -1) {
                     if (spIndex === -1) {
                         throw '不存在此签章';
                         throw '不存在此签章';
                     }
                     }
-                    // 删除oss文件
-                    await ctx.app.fujianOss.delete(ctx.app.config.fujianOssFolder + stamp_path_list[spIndex]);
+                    // 不删除地址,只删除数据库数据,防止已签章的报表丢失
+                    // await ctx.app.fujianOss.delete(ctx.app.config.fujianOssFolder + stamp_path_list[spIndex]);
                     stamp_path_list.splice(spIndex, 1);
                     stamp_path_list.splice(spIndex, 1);
                     // 删除库
                     // 删除库
                     result = await ctx.service.projectAccount.update({ stamp_path: stamp_path_list.length === 0 ? null : stamp_path_list.join('!;!') }, { id: sessionUser.accountId });
                     result = await ctx.service.projectAccount.update({ stamp_path: stamp_path_list.length === 0 ? null : stamp_path_list.join('!;!') }, { id: sessionUser.accountId });

+ 2 - 1
app/controller/setting_controller.js

@@ -390,7 +390,8 @@ module.exports = app => {
                     case 'del-sign':
                     case 'del-sign':
                         const info = await ctx.service.constructionUnit.getDataById(data.id);
                         const info = await ctx.service.constructionUnit.getDataById(data.id);
                         if (info.sign_path) {
                         if (info.sign_path) {
-                            await ctx.app.fujianOss.delete(ctx.app.config.fujianOssFolder + info.sign_path);
+                            // 不删除地址,只删除数据库数据,防止已签章的报表丢失
+                            // await ctx.app.fujianOss.delete(ctx.app.config.fujianOssFolder + info.sign_path);
                         }
                         }
                         await ctx.service.constructionUnit.update({ sign_path: null }, { id: info.id });
                         await ctx.service.constructionUnit.update({ sign_path: null }, { id: info.id });
                         break;
                         break;

+ 145 - 0
app/middleware/payment_detail_check.js

@@ -0,0 +1,145 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const messageType = require('../const/message_type');
+const status = require('../const/audit').stage.status;
+const shenpiConst = require('../const/shenpi');
+const _ = require('lodash');
+
+module.exports = options => {
+    /**
+     * 标段校验 中间件
+     * 1. 读取标段数据(包括属性)
+     * 2. 检验用户是否可见标段(不校验具体权限)
+     *
+     * @param {function} next - 中间件继续执行的方法
+     * @return {void}
+     */
+    return function* paymentDetailCheck(next) {
+        try {
+            const id = parseInt(this.params.did);
+            if (!id) throw '参数错误';
+            const detail = yield this.service.paymentDetail.getDataById(id);
+            if (!detail) {
+                throw '支付审批表单不存在';
+            }
+            const trInfo = yield this.service.paymentTenderRpt.getDataById(detail.tr_id);
+            if (!trInfo) {
+                throw '支付审批报表不存在';
+            }
+            // 读取原报、审核人数据
+            detail.auditors = yield this.service.paymentDetailAudit.getAuditors(detail.id, detail.times);
+            detail.curAuditor = yield this.service.paymentDetailAudit.getCurAuditor(detail.id, detail.times);
+
+            const accountId = this.session.sessionUser.accountId,
+                auditorIds = _.map(detail.auditors, 'aid');
+            if (accountId === detail.uid) { // 原报
+                detail.curTimes = detail.times;
+                if (detail.status === status.uncheck || detail.status === status.checkNo) {
+                    detail.curOrder = 0;
+                } else if (detail.status === status.checked) {
+                    detail.curOrder = _.max(_.map(detail.auditors, 'order'));
+                } else {
+                    detail.curOrder = detail.curAuditor.aid === accountId ? detail.curAuditor.order : detail.curAuditor.order - 1;
+                }
+            } else if (this.tender.isTourist) {
+                detail.curTimes = detail.times;
+                if (detail.status === status.uncheck || detail.status === status.checkNo) {
+                    detail.curOrder = 0;
+                } else if (detail.status === status.checked) {
+                    detail.curOrder = _.max(_.map(detail.auditors, 'order'));
+                } else {
+                    detail.curOrder = detail.curAuditor.order;
+                }
+            } else if (auditorIds.indexOf(accountId) !== -1) { // 审批人
+                if (detail.status === status.uncheck) {
+                    throw '您无权查看该数据';
+                }
+                // detail.readOnly = detail.status !== status.checking || accountId !== detail.curAuditor.aid;
+                detail.curTimes = detail.status === status.checkNo ? detail.times - 1 : detail.times;
+                if (detail.status === status.checked) {
+                    detail.curOrder = _.max(_.map(detail.auditors, 'order'));
+                } else if (detail.status === status.checkNo) {
+                    const audit = this.service.paymentDetailAudit.getDataByCondition({
+                        td_id: detail.id, times: detail.times, status: status.checkNo,
+                    });
+                    detail.curOrder = audit.order;
+                } else {
+                    detail.curOrder = accountId === detail.curAuditor.aid ? detail.curAuditor.order : detail.curAuditor.order - 1;
+                }
+            } else { // 其他不可见
+                throw '您无权查看该数据';
+            }
+
+            // 获取最新的期
+            detail.highOrder = yield this.service.paymentDetail.count({
+                tr_id: detail.tr_id,
+            });
+            detail.readOnly = !((detail.status === status.uncheck || detail.status === status.checkNo) && accountId === detail.uid);
+            this.detail = detail;
+            this.trInfo = trInfo;
+            // 根据状态判断是否需要更新审批人列表
+            if ((detail.status === status.uncheck || detail.status === status.checkNo) && trInfo.sp_status !== shenpiConst.sp_status.sqspr) {
+                const shenpi_status = trInfo.sp_status;
+                // 进一步比较审批流是否与审批流程设置的相同,不同则替换为固定审批流或固定的终审
+                const auditList = yield this.service.paymentDetailAudit.getAllDataByCondition({ where: { td_id: detail.id, times: detail.times }, orders: [['order', 'asc']] });
+                const auditIdList = _.map(auditList, 'aid');
+                if (shenpi_status === shenpiConst.sp_status.gdspl) {
+                    const shenpiList = yield this.service.paymentShenpiAudit.getAllDataByCondition({ where: { tr_id: trInfo.id, sp_status: shenpi_status } });
+                    const shenpiIdList = _.map(shenpiList, 'audit_id');
+                    // 判断2个id数组是否相同,不同则删除原审批流,切换成固定的审批流
+                    if (!_.isEqual(auditIdList, shenpiIdList)) {
+                        yield this.service.paymentDetailAudit.updateNewAuditList(detail, shenpiIdList);
+                    }
+                } else if (shenpi_status === shenpiConst.sp_status.gdzs) {
+                    const shenpiInfo = yield this.service.paymentShenpiAudit.getDataByCondition({ tr_id: trInfo.id, sp_status: shenpi_status });
+                    // 判断最后一个id是否与固定终审id相同,不同则删除原审批流中如果存在的id和添加终审
+                    if (shenpiInfo && shenpiInfo.audit_id !== _.last(auditIdList)) {
+                        yield this.service.paymentDetailAudit.updateLastAudit(detail, auditList, shenpiInfo.audit_id);
+                    } else if (!shenpiInfo) {
+                        // 不存在终审人的状态下这里恢复为授权审批人
+                        this.detail.sp_status = shenpiConst.sp_status.sqspr;
+                    }
+                }
+            }
+            yield next;
+        } catch (err) {
+            // 输出错误到日志
+            if (err.stack) {
+                this.logger.error(err);
+            } else {
+                this.session.message = {
+                    type: messageType.ERROR,
+                    icon: 'exclamation-circle',
+                    message: err,
+                };
+                this.getLogger('fail').info(JSON.stringify({
+                    error: err,
+                    project: this.session.sessionProject,
+                    user: this.session.sessionUser,
+                    body: this.session.body,
+                }));
+            }
+            if (this.helper.isAjax(this.request)) {
+                if (err.stack) {
+                    this.body = {err: 4, msg: '标段数据未知错误', data: null};
+                } else {
+                    this.body = {err: 3, msg: err.toString(), data: null};
+                }
+            } else {
+                if (this.helper.isWap(this.request)) {
+                    this.redirect('/wap/list');
+                } else {
+                    err === '您无权查看该内容' ? this.redirect(this.request.headers.referer) : this.redirect('/payment');
+                }
+            }
+        }
+    };
+};

+ 0 - 2
app/middleware/payment_tender_check.js

@@ -8,9 +8,7 @@
  * @version
  * @version
  */
  */
 
 
-const auditConst = require('../const/audit').ledger;
 const messageType = require('../const/message_type');
 const messageType = require('../const/message_type');
-const scPermission = require('../const/schedule').permission;
 
 
 module.exports = options => {
 module.exports = options => {
     /**
     /**

+ 133 - 0
app/public/js/payment_detail.js

@@ -0,0 +1,133 @@
+function iniPage() {
+    dynamicLoadJs('/public/jspdf/Arial Narrow-normal.js');
+    dynamicLoadJs('/public/jspdf/Arial Narrow-bold.js');
+    dynamicLoadJs('/public/jspdf/Arial Narrow-italic.js');
+    dynamicLoadJs('/public/jspdf/Arial Narrow-bolditalic.js');
+
+    rptTplObj.isLoading = true;
+    dynamicLoadJs('https://d2.smartcost.com.cn/cach/SmartSimsun-normal2.js', 'normal', getPdfFontCallbackLight);
+    dynamicLoadJs('https://d2.smartcost.com.cn/cach/SmartSimsun-bold.js', 'bold', getPdfFontCallbackLight);
+}
+function getPdfFontCallbackLight(fontProperty) {
+    rptTplObj.pdfFont['SmartSimsun'].push(fontProperty);
+    if (rptTplObj.pdfFont['SmartSimsun'].length === 2) {
+        rptTplObj.isLoading = false;
+    }
+}
+function getPdfFontCallback(fontProperty) {
+    if (rptTplObj.pdfFont['SmartSimsun'].length === 2) {
+        downloadPDFReport([tesRpttData], 'A4', ['测试审核表'], [], [], [-1], []);
+    }
+}
+
+$(function () {
+
+    autoFlashHeight();
+    auditRptPrintHelper.showPage();
+    iniPage();
+
+    $('#rpt-form input').on('change', function () {
+       const newVal = $(this).val();
+       const index = parseInt($(this).data('index'));
+        checkAndUpdate(index, newVal, $(this));
+    });
+
+    $('#rpt-form textarea').on('change', function () {
+        const newVal = $(this).val();
+        const index = parseInt($(this).data('index'));
+        checkAndUpdate(index, newVal, $(this));
+    });
+
+    $('#chose-private-stamp-path .stamp-img').on('click', function () {
+        if (!$(this).hasClass('card-gk-active')) {
+            $('#chose-private-stamp-path .stamp-img').removeClass('card-gk-active');
+            $('#chose-private-stamp-path .stamp-img').find('.sel-width').removeClass('sel-blue');
+            $(this).addClass('card-gk-active');
+            $(this).find('.sel-width').addClass('sel-blue');
+        }
+    });
+
+    $('#chose-private-stamp-path').on('show.bs.modal', function () {
+        $('#chose-private-stamp-path .stamp-img').removeClass('card-gk-active');
+        $('#chose-private-stamp-path .stamp-img').find('.sel-width').removeClass('sel-blue');
+        $('#chose-private-stamp-path .stamp-img').each(function () {
+            const src = $(this).find('img').attr('data-src');
+            if (src === currentStamp) {
+                $(this).addClass('card-gk-active');
+                $(this).find('.sel-width').addClass('sel-blue');
+                return;
+            }
+        });
+    });
+
+    $('#select_stamp_path_btn').click(function () {
+        const src = $('#chose-private-stamp-path .card-gk-active').find('img').attr('data-src');
+        currentStamp = src;
+        $('#stamp_path').val(src);
+        $('#chose-private-stamp-path').modal('hide');
+    });
+
+    let signatureDate = null;
+    $('#sub-sp5').on('show.bs.modal', function () {
+        $('#sign_path').prop('checked', rptAudit.signature_msg.sign_path !== null);
+        $('#company_stamp').prop('checked', rptAudit.signature_msg.company_stamp !== null);
+        $('#stamp_path').prop('checked', rptAudit.signature_msg.stamp_path !== null);
+        $('#signature_date').val(rptAudit.signature_msg.date ? rptAudit.signature_msg.date : '');
+        signatureDate = !signatureDate ? $('#signature_date').datepicker().data('datepicker') : signatureDate;
+        if (rptAudit.signature_msg.date) {
+            signatureDate.selectDate(new Date(rptAudit.signature_msg.date));
+        } else {
+            signatureDate.clear();
+        }
+        $('#signature_content').val(rptAudit.signature_msg.content ? rptAudit.signature_msg.content : '');
+    });
+
+    $('#commit_sign').click(function () {
+        rptAudit.signature_msg.sign_path = $('#sign_path').is(':checked') ? $('#sign_path').val() : null;
+        rptAudit.signature_msg.company_stamp = $('#company_stamp').is(':checked') ? $('#company_stamp').val() : null;
+        rptAudit.signature_msg.stamp_path = $('#stamp_path').is(':checked') ? $('#stamp_path').val() : null;
+        rptAudit.signature_msg.date = $('#signature_date').val() ? $('#signature_date').val() : null;
+        rptAudit.signature_msg.content = $('#signature_content').val() ? $('#signature_content').val() : null;
+        console.log(rptAudit.signature_msg);
+        // 签章
+        if (rptAudit.signature_msg.sign_path || rptAudit.signature_msg.company_stamp || rptAudit.signature_msg.stamp_path) {
+            const signArray = [];
+            if (rptAudit.signature_msg.sign_path) signArray.push(rptAudit.signature_msg.sign_path);
+            if (rptAudit.signature_msg.company_stamp) signArray.push(rptAudit.signature_msg.company_stamp);
+            if (rptAudit.signature_msg.stamp_path) signArray.push(rptAudit.signature_msg.stamp_path);
+            if (signArray.length > 0) {
+                tesRpttData.items[0].signature_cells[rptAudit.signature_index].path = signArray.length > 0 ? signArray.join('!;!') : null;
+                const date_index = _.findIndex(tesRpttData.items[0].signature_date_cells, { signature_name: rptAudit.signature_name + '_签字日期' });
+                if (date_index !== -1) {
+                    tesRpttData.items[0].signature_date_cells[date_index].Value = rptAudit.signature_msg.date ? rptAudit.signature_msg.date : '';
+                }
+                const content_index = _.findIndex(tesRpttData.items[0].signature_audit_cells, { signature_name: rptAudit.signature_name + '_审核意见' });
+                if (content_index !== -1) {
+                    tesRpttData.items[0].signature_audit_cells[content_index].Value = rptAudit.signature_msg.content ? rptAudit.signature_msg.content : '';
+                }
+                postData('/payment/' + tenderId + '/detail/' + detailId + '/save', { type: 'update_sign', signature_msg: rptAudit.signature_msg }, function (result) {
+                    auditRptPrintHelper.showPage();
+                    iniPage();
+                });
+            }
+            $('#sub-sp5').modal('hide');
+        } else {
+            toastr.error('至少选择一个签字/签章');
+        }
+
+    });
+
+    let timer = null
+    function checkAndUpdate(index, newVal, _self) {
+        console.log(index, newVal);
+        clearTimeout(timer);
+        timer = setTimeout(() => {
+            tesRpttData.items[0].interact_cells[index].Value = tesRpttData.items[0].interact_cells[index].Prefix ? tesRpttData.items[0].interact_cells[index].Prefix + newVal : newVal;
+            postData('/payment/' + tenderId + '/detail/' + detailId + '/save', { type: 'update_rpt', report_json: tesRpttData }, function (result) {
+                auditRptPrintHelper.showPage();
+                iniPage();
+            });
+            clearTimeout(timer);
+        }, 500);
+    }
+});

+ 262 - 0
app/public/js/payment_detail_audit.js

@@ -0,0 +1,262 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2019/2/27
+ * @version
+ */
+
+$(document).ready(function () {
+    let timer = null
+    let oldSearchVal = null
+    $('#gr-search').bind('input propertychange', function(e) {
+        oldSearchVal = e.target.value
+        timer && clearTimeout(timer)
+        timer = setTimeout(() => {
+            const newVal = $('#gr-search').val()
+            let html = ''
+            if (newVal && newVal === oldSearchVal) {
+                accountList.filter(item => item && cur_uid !== item.id && (item.name.indexOf(newVal) !== -1 || (item.mobile && item.mobile.indexOf(newVal) !== -1))).forEach(item => {
+                    html += `<dd class="border-bottom p-2 mb-0 " data-id="${item.id}" >
+                        <p class="mb-0 d-flex"><span class="text-primary">${item.name}</span><span
+                                class="ml-auto">${item.mobile || ''}</span></p>
+                        <span class="text-muted">${item.role || ''}</span>
+                    </dd>`
+                })
+                $('.book-list').empty()
+                $('.book-list').append(html)
+            } else {
+                if (!$('.acc-btn').length) {
+                    accountGroup.forEach((group, idx) => {
+                        if (!group) return
+                        html += `<dt><a href="javascript: void(0);" class="acc-btn" data-groupid="${idx}" data-type="hide"><i class="fa fa-plus-square"></i>
+                        </a> ${group.groupName}</dt>
+                        <div class="dd-content" data-toggleid="${idx}">`
+                        group.groupList.forEach(item => {
+                            if (item.id !== cur_uid) {
+                                html += `<dd class="border-bottom p-2 mb-0 " data-id="${item.id}" >
+                                    <p class="mb-0 d-flex"><span class="text-primary">${item.name}</span><span
+                                            class="ml-auto">${item.mobile || ''}</span></p>
+                                    <span class="text-muted">${item.role || ''}</span>
+                                </dd>`
+                            }
+                        });
+                        html += '</div>'
+                    })
+                    $('.book-list').empty()
+                    $('.book-list').append(html)
+                }
+            }
+        }, 400);
+    })
+
+    // 添加审批流程按钮逻辑
+    $('.book-list').on('click', 'dt', function () {
+        const idx = $(this).find('.acc-btn').attr('data-groupid')
+        const type = $(this).find('.acc-btn').attr('data-type')
+        if (type === 'hide') {
+            $(this).parent().find(`div[data-toggleid="${idx}"]`).show(() => {
+                $(this).children().find('i').removeClass('fa-plus-square').addClass('fa-minus-square-o')
+                $(this).find('.acc-btn').attr('data-type', 'show')
+
+            })
+        } else {
+            $(this).parent().find(`div[data-toggleid="${idx}"]`).hide(() => {
+                $(this).children().find('i').removeClass('fa-minus-square-o').addClass('fa-plus-square')
+                $(this).find('.acc-btn').attr('data-type', 'hide')
+            })
+        }
+        return false
+    })
+
+    // 添加到审批流程中
+    $('dl').on('click', 'dd', function () {
+        const id = parseInt($(this).data('id'));
+        if (id) {
+            postData('/payment/' + tenderId + '/detail/' + detailId + '/save', { type: 'add_audit', auditorId: id }, (datas) => {
+                const html = [];
+                // 如果是重新上报,添加到重新上报列表中
+                const auditorshtml = [];
+                for (const [index,data] of datas.entries()) {
+                    if (index !== 0) {
+                        html.push('<li class="list-group-item" auditorId="'+ data.aid +'">');
+                        if (shenpi_status === shenpiConst.sp_status.sqspr || (shenpi_status === shenpiConst.sp_status.gdzs && index+1 !== datas.length)) {
+                            html.push('<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>');
+                    }
+                    // 添加新审批人流程修改
+                    auditorshtml.push('<li class="list-group-item" data-auditorid="' + data.aid + '">');
+                    auditorshtml.push('<i class="fa ' + (index+1 === datas.length ? 'fa-stop-circle' : 'fa-chevron-circle-down') + '"></i> ');
+                    auditorshtml.push(data.name + ' <small class="text-muted">' + data.role + '</small>');
+                    if (index === 0) {
+                        auditorshtml.push('<span class="pull-right">原报</span>');
+                    } else if (index+1 === datas.length) {
+                        auditorshtml.push('<span class="pull-right">终审</span>');
+                    } else {
+                        auditorshtml.push('<span class="pull-right">'+ transFormToChinese(index) +'审</span>');
+                    }
+                    auditorshtml.push('</li>');
+                }
+                $('#auditors').html(html.join(''));
+
+
+                // 重新上报时。令其它的审批人流程图标转换
+                // $('#auditors-list li i').removeClass('fa-stop-circle').addClass('fa-chevron-circle-down');
+                // for (let i = 0; i < $('#auditors-list li').length; i++) {
+                //     $('#auditors-list li').eq(i).find('.pull-right').text(transFormToChinese(i+1) + '审');
+                //     $('#auditors-list2 li').eq(i).find('.pull-right').text(transFormToChinese(i+1) + '审');
+                // }
+
+                $('#auditors-list').html(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>');
+                // auditorshtml2.push('<span class="pull-right">终审</span>');
+                // auditorshtml2.push('</h5></li>');
+                // $('#auditors-list2').append(auditorshtml2.join(''));
+            });
+        }
+    });
+    // 删除审批人
+    $('body').on('click', '#auditors li>a', function () {
+        const li = $(this).parent();
+        postData('/payment/' + tenderId + '/detail/' + detailId + '/save', { type: 'del_audit', auditorId: parseInt(li.attr('auditorId')) }, (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-auditorid="' + parseInt(li.attr('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');
+            // }
+            for (let i = 0; i < $('#auditors-list li').length; i++) {
+                $('#auditors-list li').eq(i).find('.pull-right').text(i === 0 ? '原报' : (i+1 === $('#auditors-list li').length ? '终' : transFormToChinese(i)) + '审');
+                // $('#auditors-list2').eq(i).find('.pull-right').text((i+1 === $('#auditors-list2').length ? '终' : transFormToChinese(i+1)) + '审');
+            }
+        });
+    });
+    // 退回选择修改审批人流程
+    $('#hideSp').click(function () {
+        $('#sp-list').modal('hide');
+    });
+    $('a[f-target]').click(function () {
+        $($(this).attr('f-target')).modal('show');
+    });
+
+    // 多层modal关闭后的滚动bug修复
+    $('#sp-list').on('hidden.bs.modal', function (e) {
+        $(document.body).addClass('modal-open');
+    });
+
+    // 重新审批获取手机验证码
+    // 获取验证码
+    let isPosting = false;
+    $("#get-code").click(function() {
+        if (isPosting) {
+            return false;
+        }
+        const btn = $(this);
+
+        $.ajax({
+            url: '/profile/code?_csrf_j=' + csrf,
+            type: 'post',
+            data: { mobile: authMobile, type: 'shenpi' },
+            dataTye: 'json',
+            error: function() {
+                isPosting = false;
+            },
+            beforeSend: function() {
+                isPosting = true;
+            },
+            success: function(response) {
+                isPosting = false;
+                if (response.err === 0) {
+                    codeSuccess(btn);
+                    $("input[name='code']").removeAttr('readonly');
+                    $("#re-shenpi-btn").removeAttr('disabled');
+                } else {
+                    toastr.error(response.msg);
+                }
+            }
+        });
+    });
+});
+// 检查上报情况
+function checkAuditorFrom () {
+    if ($('#auditors li').length === 0) {
+        if(shenpi_status === shenpiConst.sp_status.gdspl) {
+            toastr.error('请联系管理员添加审批人');
+        } else {
+            toastr.error('请先选择审批人,再上报数据');
+        }
+        return false;
+    }
+    $('#hide-all').show();
+}
+// texterea换行
+function auditCheck(i) {
+    const inlineRadio1 = $('#inlineRadio1:checked').val()
+    const inlineRadio2 = $('#inlineRadio2:checked').val()
+    const opinion = $('textarea[name="opinion"]').eq(i).val().replace(/\r\n/g, '<br/>').replace(/\n/g, '<br/>').replace(/\s/g, ' ');
+    $('textarea[name="opinion"]').eq(i).val(opinion);
+    if (i === 1) {
+        if (!inlineRadio1 && !inlineRadio2) {
+            if (!$('#warning-text').length) {
+                $('#reject-process').prepend('<p id="warning-text" style="color: red; margin: 0;">请选择退回流程</p>');
+            }
+            return false;
+        }
+        if ($('#warning-text').length) $('#warning-text').remove()
+    }
+    return true;
+}
+
+/**
+ * 获取成功后的操作
+ *
+ * @param {Object} btn - 点击的按钮
+ * @return {void}
+ */
+function codeSuccess(btn) {
+    let counter = 60;
+    btn.addClass('disabled').text('重新获取 ' + counter + 'S');
+    btn.parent().siblings('input').removeAttr('readonly').attr('placeholder', '输入短信中的6位验证码');
+    const bindBtn = $("#bind-btn");
+    bindBtn.removeClass('btn-secondary disabled').addClass('btn-primary');
+
+    const countDown = setInterval(function() {
+        const countString = counter - 1 <= 0 ? '' : ' ' + (counter - 1) + 'S';
+        // 倒数结束后
+        if (countString === '') {
+            clearInterval(countDown);
+            btn.removeClass('disabled');
+        }
+        const text = '重新获取' + countString;
+        btn.text(text);
+        counter -= 1;
+    }, 1000);
+}

+ 10 - 2
app/public/js/payment_index.js

@@ -50,7 +50,10 @@ $(function () {
             html.push(arr.indexOf(node) === arr.length - 1 ? '└' : '├');
             html.push(arr.indexOf(node) === arr.length - 1 ? '└' : '├');
             html.push('</span>');
             html.push('</span>');
             //html.push('<a href="/tender/' + node.id + '">', node[c.field], '</a>');
             //html.push('<a href="/tender/' + node.id + '">', node[c.field], '</a>');
-            html.push('<a href="javascript: void(0)" id="' + node.id + '">', node.name, '</a>');
+            html.push('<a href="/payment/'+ node.id +'/list" target="_blank" id="' + node.id + '">', node.name, '</a>');
+            if (node.have_notice){
+                html.push('<i class="fa fa-bell text-warning float-right mt-1" data-toggle="tooltip" data-placement="bottom" title="待处理提醒"></i>');
+            }
         }
         }
         html.push('</td>');
         html.push('</td>');
         // 创建人
         // 创建人
@@ -74,7 +77,11 @@ $(function () {
             if (uid === node.uid) {
             if (uid === node.uid) {
                 html.push('<a class="dropdown-item edit_name_btn" data-type="'+ (node.parent_id === undefined ? 'tender' : 'folder') +'" data-id="'+ node.id +'" href="javascript:void(0);"><i class="fa fa-edit mr-2"></i>重命名</a>\n');
                 html.push('<a class="dropdown-item edit_name_btn" data-type="'+ (node.parent_id === undefined ? 'tender' : 'folder') +'" data-id="'+ node.id +'" href="javascript:void(0);"><i class="fa fa-edit mr-2"></i>重命名</a>\n');
                 if (!node.had_tender) {
                 if (!node.had_tender) {
-                    html.push('<a class="dropdown-item show_del_btn" data-type="'+ (node.parent_id === undefined ? 'tender' : 'folder') +'" data-id="'+ node.id +'" href="javascript:void(0);"><i class="fa fa-remove mr-2"></i>删除</a>\n');
+                    if (node.have_detail) {
+                        html.push('<a class="dropdown-item" style="cursor:not-allowed" href="javascript:void(0);" data-toggle="tooltip" data-placement="bottom" title="请先删除所有报表表单详情"><i class="fa fa-remove mr-2"></i>删除</a>\n');
+                    } else {
+                        html.push('<a class="dropdown-item show_del_btn" data-type="'+ (node.parent_id === undefined ? 'tender' : 'folder') +'" data-id="'+ node.id +'" href="javascript:void(0);"><i class="fa fa-remove mr-2"></i>删除</a>\n');
+                    }
                 }
                 }
             }
             }
             if (uid === node.uid && ((auditPermission.tender && node.is_leaf) || (auditPermission.folder && node.parent_id !== undefined && !node.is_tender))) {
             if (uid === node.uid && ((auditPermission.tender && node.is_leaf) || (auditPermission.folder && node.parent_id !== undefined && !node.is_tender))) {
@@ -126,6 +133,7 @@ $(function () {
         initTenderTree(folderList, tenderList);
         initTenderTree(folderList, tenderList);
         $('.c-body').html(getTenderTreeHtml());
         $('.c-body').html(getTenderTreeHtml());
         setTopTr();
         setTopTr();
+        $('[data-toggle="tooltip"]').tooltip()
     }
     }
 
 
     // 首次加载展示目录
     // 首次加载展示目录

+ 360 - 0
app/public/js/payment_list.js

@@ -0,0 +1,360 @@
+$(function () {
+    autoFlashHeight();
+
+    let timer = null;
+    let oldSearchVal = null;
+    $('body').on('input propertychange', '.gr-search', function(e) {
+        oldSearchVal = e.target.value;
+        timer && clearTimeout(timer);
+        timer = setTimeout(() => {
+            const newVal = $(this).val();
+            const code = $(this).attr('data-code');
+            let html = '';
+            if (newVal && newVal === oldSearchVal) {
+                accountList.filter(item => item && (item.name.indexOf(newVal) !== -1 || (item.mobile && item.mobile.indexOf(newVal) !== -1))).forEach(item => {
+                    html += `<dd class="border-bottom p-2 mb-0 " data-id="${item.id}" >
+                        <p class="mb-0 d-flex"><span class="text-primary">${item.name}</span><span
+                                class="ml-auto">${item.mobile || ''}</span></p>
+                        <span class="text-muted">${item.role || ''}</span>
+                    </dd>`
+                });
+                $('#' + code + '_dropdownMenu .book-list').empty();
+                $('#' + code + '_dropdownMenu .book-list').append(html);
+            } else {
+                if (!$('#' + code + '_dropdownMenu .acc-btn').length) {
+                    accountGroup.forEach((group, idx) => {
+                        if (!group) return;
+                        html += `<dt><a href="javascript: void(0);" class="acc-btn" data-groupid="${idx}" data-type="hide"><i class="fa fa-plus-square"></i>
+                        </a> ${group.groupName}</dt>
+                        <div class="dd-content" data-toggleid="${idx}">`;
+                        group.groupList.forEach(item => {
+                            html += `<dd class="border-bottom p-2 mb-0 " data-id="${item.id}" >
+                                <p class="mb-0 d-flex"><span class="text-primary">${item.name}</span><span
+                                        class="ml-auto">${item.mobile || ''}</span></p>
+                                <span class="text-muted">${item.role || ''}</span>
+                            </dd>`;
+                        });
+                        html += '</div>';
+                    });
+                    $('#' + code + '_dropdownMenu .book-list').empty();
+                    $('#' + code + '_dropdownMenu .book-list').append(html);
+                }
+            }
+        }, 400);
+    });
+
+    // 添加审批流程按钮逻辑
+    $('body').on('click', '.book-list dt', function () {
+        const idx = $(this).find('.acc-btn').attr('data-groupid');
+        const type = $(this).find('.acc-btn').attr('data-type');
+        if (type === 'hide') {
+            $(this).parent().find(`div[data-toggleid="${idx}"]`).show(() => {
+                $(this).children().find('i').removeClass('fa-plus-square').addClass('fa-minus-square-o');
+                $(this).find('.acc-btn').attr('data-type', 'show');
+
+            })
+        } else {
+            $(this).parent().find(`div[data-toggleid="${idx}"]`).hide(() => {
+                $(this).children().find('i').removeClass('fa-minus-square-o').addClass('fa-plus-square');
+                $(this).find('.acc-btn').attr('data-type', 'hide');
+            })
+        }
+        return false;
+    });
+
+    // 选中审批人
+    $('body').on('click', 'dl dd', function () {
+        const id = parseInt($(this).attr('data-id'));
+        if (!id) return;
+        const this_code = $(this).parents('.dropdown').data('code');
+        if (_.findIndex(rpt_audit, { uid: id }) !== -1) {
+            toastr.error('该表单角色已存在,请勿重复添加');
+            return;
+        }
+        const user = _.find(accountList, function (item) {
+            return item.id === id;
+        });
+        $('#' + this_code + '_user').html(`<span>${user.name}</span><i role="button" class="fa fa-close text-danger remove-audit stamp-img" data-code="${this_code}"></i>`);
+        $(this).parents('.select-audit').hide();
+        $('#' + this_code + '_user').show();
+        rpt_audit[this_code].uid = id;
+        rpt_audit[this_code].name = user.name;
+        console.log(rpt_audit);
+    });
+
+    // 移除审批人
+    $('body').on('click', '.remove-audit', function () {
+        const this_code = parseInt($(this).attr('data-code'));
+        $('#' + this_code + '_user').html(``);
+        $('#' + this_code + '_user').hide();
+        $('#' + this_code + '_user').siblings('.select-audit').show();
+        rpt_audit[this_code].uid = null;
+        delete rpt_audit[this_code].name;
+        console.log(rpt_audit);
+    });
+
+    // 重新加载角色数据
+    $('#set-bdjs').on('show.bs.modal', function () {
+        if (old_rpt_audit) {
+            for (const [i, r] of old_rpt_audit.entries()) {
+                if (r.uid) {
+                    $('#' + i + '_user').html(`<span>${r.name}</span><i role="button" class="fa fa-close text-danger remove-audit stamp-img" data-code="${i}"></i>`);
+                    $('#' + i + '_user').show();
+                    $('#' + i + '_user').siblings('.select-audit').hide();
+                } else {
+                    $('#' + i + '_user').html(``);
+                    $('#' + i + '_user').hide();
+                    $('#' + i + '_user').siblings('.select-audit').show();
+                }
+            }
+        }
+        rpt_audit = _.cloneDeep(old_rpt_audit);
+    });
+
+    // 绑定表单角色
+    $('#bind_rpt_audit_btn').click(function () {
+        postData('/payment/' + tenderId + '/list/' + trId + '/save', { type: 'rpt_audit', rpt_audit }, function (result) {
+            toastr.success('设置成功');
+            old_rpt_audit = _.cloneDeep(rpt_audit);
+            if (result.is_first) {
+                $('#first_msg').show();
+            } else {
+                $('#first_msg').hide();
+            }
+            $('#set-bdjs').modal('hide');
+        });
+    });
+
+    $('#show-add-btn').click(function () {
+        if (_.findIndex(old_rpt_audit, { uid: null }) !== -1) {
+            toastr.error('未配置好表单角色无法新建表单');
+            $('#set-bdjs').modal('show');
+        } else {
+            $('#add-catalogue').modal('show');
+        }
+    });
+
+    $('#add-detail-btn').click(function () {
+        if (_.trim($('#add-detail-code').val()) === '') {
+            toastr.error('请输入编号');
+            return false;
+        }
+        if ($('#add-detail-time').val() === '') {
+            toastr.error('请输入日期');
+            return false;
+        }
+        console.log($('#add-detail-time').val());
+        postData('/payment/' + tenderId + '/list/' + trId + '/save', { type: 'add-detail', code: _.trim($('#add-detail-code').val()), s_time: $('#add-detail-time').val() }, function (result) {
+            window.location.href = '/payment/' + tenderId + '/detail/' + result.id;
+        });
+    });
+
+    // 获取审批流程
+    $('a[data-target="#sp-list" ]').on('click', function () {
+        const data = {
+            order: $(this).attr('m-order'),
+        };
+        postData('/payment/' + tenderId + '/list/'+ trId + '/auditors', data, function (result) {
+            const { auditHistory, auditors, user } = result
+            let auditorsHTML = ''
+            let historyHTML = ''
+            auditors.forEach((auditor, idx) => {
+                if (idx === 0) {
+                    auditorsHTML += `<li class="list-group-item">
+                        <i class="fa fa fa-play-circle fa-rotate-90"></i> ${auditor.name}
+                        <small class="text-muted">${auditor.role}</small>
+                        <span class="pull-right">原报</span>
+                    </li>`
+                } else if(idx === auditors.length -1 && idx !== 0) {
+                    auditorsHTML += `<li class="list-group-item">
+                        <i class="fa fa fa-stop-circle"></i> ${auditor.name}
+                        <small class="text-muted">${auditor.role}</small>
+                        <span class="pull-right">终审</span>
+                    </li>`
+                } else {
+                    auditorsHTML += `<li class="list-group-item">
+                        <i class="fa fa-chevron-circle-down"></i> ${auditor.name}
+                        <small class="text-muted">${auditor.role}</small>
+                        <span class="pull-right">${transFormToChinese(idx)}审</span>
+                    </li>`
+                }
+            })
+            $('#auditor-list').empty()
+            $('#auditor-list').append(auditorsHTML)
+            const leftAuditors = auditors;
+            auditHistory.forEach((auditors, idx) => {
+                if(idx === auditHistory.length - 1 && auditHistory.length !== 1) {
+                    historyHTML += `<div class="text-right"><a href="javascript: void(0);" id="fold-btn" data-target="show"
+                    >展开历史审批流程</a></div>`
+                }
+                historyHTML += `<div class="${idx < auditHistory.length - 1 ? 'fold-card' : ''}">
+                <div class="text-center text-muted">${idx + 1}#</div>
+                <ul class="timeline-list list-unstyled mt-2">`
+                auditors.forEach((auditor, index) => {
+                    if (index === 0) {
+                        historyHTML += `<li class="timeline-list-item pb-2">
+                            <div class="timeline-item-date">
+                                ${formatDate(auditor.begin_time)}
+                            </div>
+                            <div class="timeline-item-tail"></div>
+                            <div class="timeline-item-icon bg-success text-light">
+                                <i class="fa fa-caret-down"></i>
+                            </div>
+                            <div class="timeline-item-content">
+                                <div class="card">
+                                    <div class="card-body p-3">
+                                        <div class="card-text">
+                                            <p class="mb-1"><span
+                                                    class="h5">${user.name}</span><span
+                                                    class="pull-right text-success">${idx !== 0 ? '重新' : ''}上报审批</span>
+                                            </p>
+                                            <p class="text-muted mb-0">${user.role}</p>
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                        </li>
+                        <li class="timeline-list-item pb-2">
+                            <div class="timeline-item-date">
+                                ${formatDate(auditor.end_time)}
+                            </div>`
+
+                        if(index < auditors.length - 1) {
+                            historyHTML += `<div class="timeline-item-tail"></div>`
+                        }
+                        if(auditor.status === auditConst.status.checked) {
+                            historyHTML += `<div class="timeline-item-icon bg-success text-light">
+                                    <i class="fa fa-check"></i>
+                                </div>`
+
+                        } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {
+                            historyHTML += `<div class="timeline-item-icon bg-warning text-light">
+                                    <i class="fa fa-level-up"></i>
+                                </div>`
+                        } else if(auditor.status === auditConst.status.checking) {
+                            historyHTML += `<div class="timeline-item-icon bg-warning text-light">
+                                    <i class="fa fa-ellipsis-h"></i>
+                                </div>`
+                        } else {
+                            historyHTML += `<div class="timeline-item-icon bg-secondary text-light"></div>`
+
+                        }
+                        historyHTML += `<div class="timeline-item-content">
+                                <div class="card">
+                                    <div class="card-body p-3">
+                                        <div class="card-text">
+                                            <p class="mb-1"><span class="h5">${auditor.name}</span><span
+                                                    class="pull-right ${auditConst.statusClass[auditor.status]}">${auditConst.statusString[auditor.status]}</span>
+                                            </p>
+                                            <p class="text-muted mb-0">${auditor.role}</p>
+                                        </div>
+                                    </div>`
+                        if (auditor.opinion) {
+                            historyHTML += `<div class="card-body p-3 border-top">
+                                    <p style="margin: 0;">${auditor.opinion}</p>
+                                </div>`
+                        }
+                        historyHTML += `</div></div></li>`
+                    } else {
+                        historyHTML += `<li class="timeline-list-item pb-2">
+                        <div class="timeline-item-date">
+                            ${formatDate(auditor.end_time)}
+                        </div>`
+
+                        if(index < auditors.length - 1) {
+                            historyHTML += `<div class="timeline-item-tail"></div>`
+                        }
+                        if(auditor.status === auditConst.status.checked) {
+                            historyHTML += `<div class="timeline-item-icon bg-success text-light">
+                                <i class="fa fa-check"></i>
+                            </div>`
+                        } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {
+                            historyHTML += `<div class="timeline-item-icon bg-warning text-light">
+                                <i class="fa fa-level-up"></i>
+                            </div>`
+
+                        } else if(auditor.status === auditConst.status.checking) {
+                            historyHTML += `<div class="timeline-item-icon bg-warning text-light">
+                                <i class="fa fa-ellipsis-h"></i>
+                            </div>`
+                        } else {
+                            historyHTML += `<div class="timeline-item-icon bg-secondary text-light"></div>`
+                        }
+                        historyHTML += `<div class="timeline-item-content">
+                        <div class="card">
+                            <div class="card-body p-3">
+                                <div class="card-text">
+                                    <p class="mb-1"><span class="h5">${auditor.name}</span>
+                                        <span
+                                            class="pull-right
+                                                            ${auditConst.statusClass[auditor.status]}">${auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''}
+                                            ${auditor.status === auditConst.status.checkNo ? user.name : ''}
+                                            ${auditor.status === auditConst.status.checkNoPre ? leftAuditors.find(item => item.sort === auditor.sort-1).name : ''}
+                                        </span>
+                                    </p>
+                                    <p class="text-muted mb-0">${auditor.role}</p>
+                                </div>
+                            </div>`
+
+                        if (auditor.opinion) {
+                            historyHTML += `<div class="card-body p-3 border-top">
+                            <p style="margin: 0;">${auditor.opinion} </p>
+                        </div>`
+                        }
+                        historyHTML += `</div></div></li>`
+                    }
+                })
+                historyHTML += '</ul></div>'
+            })
+            $('#audit-list').empty()
+            $('#audit-list').append(historyHTML)
+        });
+    });
+
+    // 展开/收起历史审核记录
+    $('#audit-list').on('click', 'a', function() {
+        const type = $(this).data('target')
+        const auditCard = $(this).parent().parent()
+        console.log('auditCard', auditCard)
+        if (type === 'show') {
+            $(this).data('target', 'hide')
+            auditCard.find('.fold-card').slideDown('swing', () => {
+                auditCard.find('#end-target').text($(this).data('idx') + '#')
+                auditCard.find('#fold-btn').text('收起历史审核记录')
+            })
+        } else {
+            $(this).data('target', 'show')
+            auditCard.find('.fold-card').slideUp('swing', () => {
+                auditCard.find('#end-target').text('1#')
+                auditCard.find('#fold-btn').text('展开历史审核记录')
+            })
+        }
+    });
+
+    function formatDate(date) {
+        if (!date) return '';
+        date = new Date(date)
+        const year = date.getFullYear();
+        let mon = date.getMonth() + 1;
+        let day = date.getDate();
+        let hour = date.getHours();
+        let minute = date.getMinutes();
+        let scond = date.getSeconds();
+        if (mon < 10) {
+            mon = '0' + mon.toString();
+        }
+        if (day < 10) {
+            day = '0' + day.toString();
+        }
+        if (hour < 10) {
+            hour = '0' + hour.toString();
+        }
+        if (minute < 10) {
+            minute = '0' + minute.toString();
+        }
+        if (scond < 10) {
+            scond = '0' + scond.toString();
+        }
+        return `${year}<span>${mon}-${day}</span><span>${hour}:${minute}:${scond}</span>`;
+    };
+});

+ 16 - 4
app/public/js/payment_process.js

@@ -23,9 +23,15 @@ $(function () {
                 html.push(`<td>${data.user_name}</td>\n`);
                 html.push(`<td>${data.user_name}</td>\n`);
                 html.push('</tr>\n');
                 html.push('</tr>\n');
             }
             }
+            tenderRptList = result;
             $('#tender_rpt_table').html(html.join(''));
             $('#tender_rpt_table').html(html.join(''));
+            if (result.length === 0) {
+                $('#process_set').hide();
+            } else {
+                fisrtSetProcess();
+            }
             $('#add-rpt').modal('hide');
             $('#add-rpt').modal('hide');
-            tenderRptList = result;
+
         })
         })
     });
     });
 
 
@@ -103,16 +109,20 @@ $(function () {
         }
         }
     }
     }
 
 
-    // 初始化选中table
-    if ($('#tender_rpt_table tr').length > 0) {
+    function fisrtSetProcess() {
         $('#tender_rpt_table tr').eq(0).addClass('table-warning');
         $('#tender_rpt_table tr').eq(0).addClass('table-warning');
         const tr_id = parseInt($('#tender_rpt_table tr').eq(0).attr('data-id'));
         const tr_id = parseInt($('#tender_rpt_table tr').eq(0).attr('data-id'));
         $('#process_set').show();
         $('#process_set').show();
         makeProcess(tr_id);
         makeProcess(tr_id);
     }
     }
 
 
+    // 初始化选中table
+    if ($('#tender_rpt_table tr').length > 0) {
+        fisrtSetProcess();
+    }
+
     // 选中切换table
     // 选中切换table
-    $('#tender_rpt_table tr').click(function () {
+    $('body').on('click', '#tender_rpt_table tr', function () {
         if (!$(this).hasClass('table-warning')) {
         if (!$(this).hasClass('table-warning')) {
             $('#tender_rpt_table tr').removeClass('table-warning');
             $('#tender_rpt_table tr').removeClass('table-warning');
             $(this).addClass('table-warning');
             $(this).addClass('table-warning');
@@ -172,6 +182,8 @@ $(function () {
             status: this_status
             status: this_status
         };
         };
         postData('/payment/' + tenderId + '/process/save', prop, function (data) {
         postData('/payment/' + tenderId + '/process/save', prop, function (data) {
+            const trInfo = _.find(tenderRptList, { id: this_tr_id });
+            trInfo.sp_status = this_status;
             setLcShowHtml(this_status, this_tr_id, data);
             setLcShowHtml(this_status, this_tr_id, data);
         });
         });
     });
     });

+ 10 - 2
app/router.js

@@ -43,6 +43,7 @@ module.exports = app => {
     const subProjectCheck = app.middlewares.subProjectCheck();
     const subProjectCheck = app.middlewares.subProjectCheck();
     // 支付审批中间件
     // 支付审批中间件
     const paymentTenderCheck = app.middlewares.paymentTenderCheck();
     const paymentTenderCheck = app.middlewares.paymentTenderCheck();
+    const paymentDetailCheck = app.middlewares.paymentDetailCheck();
     // 登入登出相关
     // 登入登出相关
     app.get('/login', 'loginController.index');
     app.get('/login', 'loginController.index');
     app.get('/login/:code', 'loginController.index');
     app.get('/login/:code', 'loginController.index');
@@ -738,12 +739,19 @@ module.exports = app => {
     // 支付审批
     // 支付审批
     app.get('/payment', sessionAuth, 'paymentController.index');
     app.get('/payment', sessionAuth, 'paymentController.index');
     app.post('/payment/permission/save', sessionAuth, 'paymentController.permissionSave');
     app.post('/payment/permission/save', sessionAuth, 'paymentController.permissionSave');
-    app.get('/payment/:id/detail/:did', sessionAuth, 'paymentController.detail');
+    app.get('/payment/:id/detail/:did', sessionAuth, paymentTenderCheck, paymentDetailCheck, 'paymentController.detail');
+    app.post('/payment/:id/detail/:did/save', sessionAuth, paymentTenderCheck, paymentDetailCheck, 'paymentController.detailSave');
     app.post('/payment/save', sessionAuth, 'paymentController.save');
     app.post('/payment/save', sessionAuth, 'paymentController.save');
     app.post('/payment/list/load', sessionAuth, 'paymentController.listLoad');
     app.post('/payment/list/load', sessionAuth, 'paymentController.listLoad');
     app.get('/payment/:id/process', sessionAuth, paymentTenderCheck, 'paymentController.process');
     app.get('/payment/:id/process', sessionAuth, paymentTenderCheck, 'paymentController.process');
     app.post('/payment/:id/process/save', sessionAuth, paymentTenderCheck, 'paymentController.processSave');
     app.post('/payment/:id/process/save', sessionAuth, paymentTenderCheck, 'paymentController.processSave');
-    app.post('/payment/:id/rpt', sessionAuth, paymentTenderCheck, 'paymentController.rptList');
+    app.get('/payment/:id/list', sessionAuth, paymentTenderCheck, 'paymentController.rptList');
+    app.get('/payment/:id/list/:trid', sessionAuth, paymentTenderCheck, 'paymentController.rptList');
+    app.post('/payment/:id/list/:trid/save', sessionAuth, paymentTenderCheck, 'paymentController.rptSave');
+    app.post('/payment/:id/list/:trid/delete', sessionAuth, paymentTenderCheck, 'paymentController.deleteDetail');
+    app.post('/payment/:id/list/:trid/auditors', sessionAuth, paymentTenderCheck, 'paymentController.detailAuditors');
+    app.post('/payment/:id/detail/:did/audit/start', sessionAuth, paymentTenderCheck, paymentDetailCheck, 'paymentController.startAudit');
+    app.post('/payment/:id/detail/:did/audit/check', sessionAuth, paymentTenderCheck, paymentDetailCheck, 'paymentController.checkAudit');
 
 
     // 企业微信回调
     // 企业微信回调
     app.get('/wx/work/callback/command', 'wechatController.command');
     app.get('/wx/work/callback/command', 'wechatController.command');

+ 145 - 1
app/service/payment_detail.js

@@ -1,5 +1,5 @@
 'use strict';
 'use strict';
-
+const auditConst = require('../const/audit').stage;
 module.exports = app => {
 module.exports = app => {
     class PaymentDetail extends app.BaseService {
     class PaymentDetail extends app.BaseService {
         constructor(ctx) {
         constructor(ctx) {
@@ -7,10 +7,154 @@ module.exports = app => {
             this.tableName = 'payment_detail';
             this.tableName = 'payment_detail';
         }
         }
 
 
+        async getValidDetails(tr_id) {
+            const details = await this.db.select(this.tableName, {
+                column: ['id', 'in_time', 'tr_id', 'uid', 'status', 'order', 'times', 'code', 's_time'],
+                where: { tr_id },
+                orders: [['order', 'desc']],
+            });
+            if (details.length !== 0) {
+                const lastDetail = details[details.length - 1];
+                if (lastDetail.status === auditConst.status.uncheck && lastDetail.uid !== this.ctx.session.sessionUser.accountId) {
+                    details.splice(details.length - 1, 1);
+                }
+            }
+            // 最新一期计量(未审批完成),当前操作人的期详细数据,应实时计算
+            if (details.length > 0 && details[0].status !== auditConst.status.checked) {
+                const detail = details[0];
+                const curAuditor = await this.ctx.service.paymentDetailAudit.getCurAuditor(detail.id, detail.times);
+                const isActive = curAuditor ? curAuditor.id === this.ctx.session.sessionUser.accountId : detail.uid === this.ctx.session.sessionUser.accountId;
+                if (isActive) {
+                    detail.curTimes = detail.times;
+                    detail.curOrder = curAuditor ? curAuditor.order : 0;
+                }
+            }
+            return details;
+        }
+
         async hadDetail(trId) {
         async hadDetail(trId) {
             const result = await this.count({ tr_id: trId });
             const result = await this.count({ tr_id: trId });
             return result !== 0;
             return result !== 0;
         }
         }
+
+        async addDetail(trInfo, code, s_time) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                if (!(trInfo.is_del === 0 && trInfo.rpt_audit)) {
+                    throw '报表已删除或表单人员数据有误,无法新建';
+                }
+                const details = await this.getAllDataByCondition({
+                    where: { tr_id: trInfo.id },
+                    order: ['order'],
+                });
+                const preDetail = details[details.length - 1];
+                if (details.length > 0 && details[details.length - 1].status !== auditConst.status.checked) {
+                    throw '上一期未审批通过,请等待上一期审批通过后,再新增';
+                }
+
+                trInfo.rpt_audit = JSON.parse(trInfo.rpt_audit);
+                if (this._.findIndex(trInfo.rpt_audit, { uid: null }) !== -1) {
+                    throw '未配置好表单角色,无法新建';
+                }
+                const rptTpl = await this.ctx.service.rptTpl.getDataById(trInfo.rpt_id);
+                const pageRst = this.ctx.service.jpcReport.getAllPreviewPagesCommon(rptTpl, 'A4');
+                const order = details.length + 1;
+                const newDetail = {
+                    tender_id: this.ctx.tender.id,
+                    tr_id: trInfo.id,
+                    order,
+                    times: 1,
+                    status: auditConst.status.uncheck,
+                    uid: this.ctx.session.sessionUser.accountId,
+                    s_time,
+                    code,
+                    report_json: JSON.stringify(pageRst),
+                    in_time: new Date(),
+                };
+                const result = await transaction.insert(this.tableName, newDetail);
+                if (result.affectedRows === 1) {
+                    newDetail.id = result.insertId;
+                } else {
+                    throw '新增支付审批数据失败';
+                }
+                // 报表角色创建
+                const insertRptAudit = [];
+                for (const [i, r] of trInfo.rpt_audit.entries()) {
+                    insertRptAudit.push({
+                        tender_id: this.ctx.tender.id,
+                        tr_id: trInfo.id,
+                        td_id: newDetail.id,
+                        uid: r.uid,
+                        signature_index: i,
+                        signature_name: r.rpt_name,
+                        in_time: new Date(),
+                    });
+                }
+                if (insertRptAudit.length > 0) await transaction.insert(this.ctx.service.paymentRptAudit.tableName, insertRptAudit);
+                // 存在上一期时,复制上一期审批流程
+                if (preDetail) {
+                    const auditResult = await this.ctx.service.paymentDetailAudit.copyPreDetailAuditors(transaction, preDetail, newDetail);
+                    if (!auditResult) {
+                        throw '复制上一期审批流程失败';
+                    }
+                }
+                await transaction.commit();
+                return newDetail;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        async updateReportJson(id, report_json) {
+            return await this.db.update(this.tableName, { id, report_json: JSON.stringify(report_json) });
+        }
+
+        /**
+         * 删除报表表单详情
+         *
+         * @param {Number} id - 期Id
+         * @return {Promise<void>}
+         */
+        async deleteDetail(id) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                await transaction.delete(this.ctx.service.paymentDetailAudit.tableName, { td_id: id });
+                await transaction.delete(this.ctx.service.paymentRptAudit.tableName, { td_id: id });
+                await transaction.delete(this.tableName, { id });
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        async haveNotice2Tender(tid, uid) {
+            const sql = 'SELECT count(pd.`id`) as count FROM ?? as pd LEFT JOIN ?? as pda' +
+                ' ON pd.`id` = pda.`td_id` WHERE pd.`tender_id` = ? AND (pd.`uid` = ? AND (pd.`status` = ? OR pd.`status` = ?))' +
+                ' OR ((pd.`status` = ? OR pd.`status` = ?) AND pda.aid = ? AND pda.`status` = ?)';
+            const params = [this.tableName, this.ctx.service.paymentDetailAudit.tableName, tid,
+                uid, auditConst.status.uncheck, auditConst.status.checkNo,
+                auditConst.status.checking, auditConst.status.checkNoPre, uid, auditConst.status.checking];
+            const result = await this.db.queryOne(sql, params);
+            return result ? result.count : 0;
+        }
+
+        async haveNotice2TenderRpt(tr_id, uid) {
+            const sql = 'SELECT count(pd.`id`) as count FROM ?? as pd LEFT JOIN ?? as pda' +
+                ' ON pd.`id` = pda.`td_id` WHERE pd.`tr_id` = ? AND (pd.`uid` = ? AND (pd.`status` = ? OR pd.`status` = ?))' +
+                ' OR ((pd.`status` = ? OR pd.`status` = ?) AND pda.aid = ? AND pda.`status` = ?)';
+            const params = [this.tableName, this.ctx.service.paymentDetailAudit.tableName, tr_id,
+                uid, auditConst.status.uncheck, auditConst.status.checkNo,
+                auditConst.status.checking, auditConst.status.checkNoPre, uid, auditConst.status.checking];
+            const result = await this.db.queryOne(sql, params);
+            return result ? result.count : 0;
+        }
+
+        async haveDetail2Tender(tid) {
+            return this.count({ tender_id: tid });
+        }
     }
     }
     return PaymentDetail;
     return PaymentDetail;
 };
 };

+ 511 - 0
app/service/payment_detail_audit.js

@@ -0,0 +1,511 @@
+'use strict';
+
+/**
+ * 版本数据模型
+ *
+ * @author CaiAoLin
+ * @date 2017/10/25
+ * @version
+ */
+const auditConst = require('../const/audit').stage;
+const shenpiConst = require('../const/shenpi');
+
+module.exports = app => {
+
+    class PaymentDetailAudit extends app.BaseService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'payment_detail_audit';
+        }
+
+        /**
+         * 获取审核人流程列表
+         *
+         * @param auditorId
+         * @return {Promise<*>}
+         */
+        async getAuditGroupByList(detailId, times) {
+            const sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`td_id`, la.`aid`, la.`order` ' +
+                '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id`' +
+                '  WHERE la.`td_id` = ? and la.`times` = ? GROUP BY la.`aid` ORDER BY la.`order`';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, detailId, times];
+            return await this.db.query(sql, sqlParam);
+        }
+
+        /**
+         * 复制上一期的审批人列表给最新一期
+         *
+         * @param transaction - 新增一期的事务
+         * @param {Object} preStage - 上一期
+         * @param {Object} newStage - 最新一期
+         * @return {Promise<*>}
+         */
+        async copyPreDetailAuditors(transaction, preDetail, newDetail) {
+            const auditors = await this.getAuditGroupByList(preDetail.id, preDetail.times);
+            const newAuditors = [];
+            for (const a of auditors) {
+                const na = {
+                    tender_id: preDetail.tender_id,
+                    tr_id: preDetail.tr_id,
+                    td_id: newDetail.id,
+                    aid: a.aid,
+                    times: newDetail.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} detailId - 支付审批表单详情id
+         * @param {Number} times - 第几次审批
+         * @return {Promise<*>}
+         */
+        async getAuditors(detailId, 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`, g.`sort` ' +
+                'FROM ?? AS la, ?? AS pa, (SELECT `aid`,(@i:=@i+1) as `sort` FROM ??, (select @i:=0) as it WHERE `td_id` = ? AND `times` = ? GROUP BY `aid`) as g ' +
+                'WHERE la.`td_id` = ? and la.`times` = ? and la.`aid` = pa.`id` and g.`aid` = la.`aid` order by la.`order`';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, this.tableName, detailId, times, detailId, times];
+            const result = await this.db.query(sql, sqlParam);
+            const sql2 = 'SELECT COUNT(a.`aid`) as num FROM (SELECT `aid` FROM ?? WHERE `td_id` = ? AND `times` = ? GROUP BY `aid`) as a';
+            const sqlParam2 = [this.tableName, detailId, times];
+            const count = await this.db.queryOne(sql2, sqlParam2);
+            for (const i in result) {
+                result[i].max_sort = count.num;
+            }
+            return result;
+        }
+
+        /**
+         * 获取 当前审核人
+         *
+         * @param {Number} materialId - 支付审批表单详情id
+         * @param {Number} times - 第几次审批
+         * @return {Promise<*>}
+         */
+        async getCurAuditor(detailId, 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 Left Join ?? AS pa On la.`aid` = pa.`id` ' +
+                '  WHERE la.`td_id` = ? and la.`status` = ? and la.`times` = ?';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, detailId, auditConst.status.checking, times];
+            return await this.db.queryOne(sql, sqlParam);
+        }
+
+        async updateNewAuditList(detail, newIdList) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 先删除旧的审批流,再添加新的
+                await transaction.delete(this.tableName, { td_id: detail.id, times: detail.times });
+                const newAuditors = [];
+                let order = 1;
+                for (const aid of newIdList) {
+                    newAuditors.push({
+                        tender_id: detail.tender_id, tr_id: detail.tr_id, td_id: detail.id, aid,
+                        times: detail.times, order, status: auditConst.status.uncheck,
+                    });
+                    order++;
+                }
+                if (newAuditors.length > 0) await transaction.insert(this.tableName, newAuditors);
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        async updateLastAudit(detail, auditList, lastId) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 先判断auditList里的aid是否与lastId相同,相同则删除并重新更新order
+                const idList = this._.map(auditList, 'aid');
+                let order = idList.length + 1;
+                if (idList.indexOf(lastId) !== -1) {
+                    await transaction.delete(this.tableName, { td_id: detail.id, times: detail.times, aid: lastId });
+                    const audit = this._.find(auditList, { aid: lastId });
+                    // 顺移之后审核人流程顺序
+                    await this._syncOrderByDelete(transaction, detail.id, audit.order, detail.times);
+                    order = order - 1;
+                }
+
+                // 添加终审
+                const newAuditor = {
+                    tender_id: detail.tender_id, tr_id: detail.tr_id, td_id: detail.id, aid: lastId,
+                    times: detail.times, order, status: auditConst.status.uncheck,
+                };
+                await transaction.insert(this.tableName, newAuditor);
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        /**
+         * 移除审核人时,同步其后审核人order
+         * @param transaction - 事务
+         * @param {Number} materialId - 材料调差期id
+         * @param {Number} auditorId - 审核人id
+         * @param {Number} times - 第几次审批
+         * @return {Promise<*>}
+         * @private
+         */
+        async _syncOrderByDelete(transaction, detailId, order, times, selfOperate = '-') {
+            this.initSqlBuilder();
+            this.sqlBuilder.setAndWhere('td_id', {
+                value: detailId,
+                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} times 审核次数
+         * @return {Promise<Array>} 查询结果集(包括原报)
+         */
+        async getAuditorsWithOwner(detailId, times = 1) {
+            const result = await this.getAuditGroupByList(detailId, times);
+            const sql =
+                'SELECT pa.`id` As aid, pa.`name`, pa.`company`, pa.`role`, ? As times, ? As mid, 0 As `order`' +
+                '  FROM ' +
+                this.ctx.service.paymentDetail.tableName +
+                ' As s' +
+                '  LEFT JOIN ' +
+                this.ctx.service.projectAccount.tableName +
+                ' As pa' +
+                '  ON s.uid = pa.id' +
+                '  WHERE s.id = ?';
+            const sqlParam = [times, detailId, detailId];
+            const user = await this.db.queryOne(sql, sqlParam);
+            result.unshift(user);
+            return result;
+        }
+
+        /**
+         * 获取 最新审核顺序
+         *
+         * @param {Number} materialId - 材料调差期id
+         * @param {Number} times - 第几次审批
+         * @return {Promise<number>}
+         */
+        async getNewOrder(detailId, times = 1) {
+            const sql = 'SELECT Max(??) As max_order FROM ?? Where `td_id` = ? and `times` = ?';
+            const sqlParam = ['order', this.tableName, detailId, 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(detailId, auditorId, times = 1, is_gdzs = 0) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                let newOrder = await this.getNewOrder(detailId, times);
+                // 判断是否存在固定终审,存在则newOrder - 1并使终审order+1
+                newOrder = is_gdzs === 1 ? newOrder - 1 : newOrder;
+                if (is_gdzs) await this._syncOrderByDelete(transaction, detailId, newOrder, times, '+');
+                const data = {
+                    tender_id: this.ctx.tender.id,
+                    tr_id: this.ctx.trInfo.id,
+                    td_id: detailId,
+                    aid: auditorId,
+                    times,
+                    order: newOrder,
+                    status: auditConst.status.uncheck,
+                };
+                const result = await transaction.insert(this.tableName, data);
+                await transaction.commit();
+                return result.affectedRows === 1;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+            return false;
+        }
+
+        /**
+         * 移除审核人
+         *
+         * @param {Number} materialId - 材料调差期id
+         * @param {Number} auditorId - 审核人id
+         * @param {Number} times - 第几次审批
+         * @return {Promise<boolean>}
+         */
+        async deleteAuditor(detailId, auditorId, times = 1) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                const condition = { td_id: detailId, aid: auditorId, times };
+                const auditor = await this.getDataByCondition(condition);
+                if (!auditor) {
+                    throw '该审核人不存在';
+                }
+                await this._syncOrderByDelete(transaction, detailId, 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} status - 期状态
+         * @param {Number} status - 期次数
+         * @return {Promise<boolean>}
+         */
+        async getAuditorByStatus(detailId, 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.`tr_id`, la.`td_id`, la.`aid`, la.`order` ' +
+                        '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id` ' +
+                        '  WHERE la.`td_id` = ? and la.`status` = ? ' +
+                        '  ORDER BY la.`times` desc, la.`order` desc';
+                    sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, detailId, 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.`tr_id`, la.`td_id`, la.`aid`, la.`order` ' +
+                        '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id`' +
+                        '  WHERE la.`td_id` = ? and la.`status` = ? and la.`times` = ?' +
+                        '  ORDER BY la.`times` desc, la.`order` desc';
+                    sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, detailId, auditConst.status.checkNo, parseInt(times) - 1];
+                    auditor = await this.db.queryOne(sql, sqlParam);
+                    break;
+                case auditConst.status.uncheck :
+                default:break;
+            }
+            return auditor;
+        }
+
+        /**
+         * 开始审批
+         * @param {Number} materialId - 材料调差期id
+         * @param {Number} times - 第几次审批
+         * @return {Promise<boolean>}
+         */
+        async start(detailId, times = 1) {
+            const audit = await this.getDataByCondition({ td_id: detailId, times, order: 1 });
+            if (!audit) {
+                if (this.ctx.trinfo.sp_status === shenpiConst.sp_status.gdspl) {
+                    throw '请联系管理员添加审批人';
+                } else {
+                    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 transaction.update(this.ctx.service.paymentDetail.tableName, {
+                    id: detailId, status: auditConst.status.checking,
+                });
+                // 判断用户是否有权限查看支付审批,没有则自动加入到权限中
+                const auditList = await this.ctx.service.paymentDetailAudit.getAllDataByCondition({ where: { td_id: detailId, times } });
+                const auditIdList = this._.map(auditList, 'aid');
+                const permissionAuditList = await this.ctx.service.paymentPermissionAudit.getAllDataByCondition({ where: { pid: this.ctx.session.sessionProject.id } });
+                const paIdList = this._.map(permissionAuditList, 'uid');
+                const newAudits = this._.difference(auditIdList, paIdList);
+                if (newAudits.length > 0) {
+                    const accountList = await this.ctx.service.projectAccount.getAllDataByCondition({
+                        where: { project_id: this.ctx.session.sessionProject.id, id: newAudits },
+                        columns: ['id', 'name', 'company', 'role', 'enable', 'is_admin', 'account_group', 'mobile'],
+                    });
+                    await this.ctx.service.paymentPermissionAudit.saveAudits(this.ctx.session.sessionProject.id, accountList, transaction);
+                }
+                // todo 更新标段tender状态 ?
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+            return true;
+        }
+
+        async _checked(pid, detailId, checkData, times) {
+            const time = new Date();
+
+            // 整理当前流程审核人状态更新
+            const audit = await this.getDataByCondition({ td_id: detailId, times, status: auditConst.status.checking });
+            if (!audit) {
+                throw '审核数据错误';
+            }
+
+            const nextAudit = await this.getDataByCondition({ td_id: detailId, 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 transaction.update(this.tableName, { id: nextAudit.id, status: auditConst.status.checking, begin_time: time });
+
+                    // 同步 期信息
+                    await transaction.update(this.ctx.service.paymentDetail.tableName, {
+                        id: detailId, status: auditConst.status.checking,
+                    });
+                } else {
+                    // 本期结束
+                    // 同步 期信息
+                    await transaction.update(this.ctx.service.paymentDetail.tableName, {
+                        id: detailId, status: checkData.checkType,
+                    });
+                }
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        async _checkNo(pid, detailId, checkData, times) {
+            const time = new Date();
+            // 整理当前流程审核人状态更新
+            const audit = await this.getDataByCondition({ td_id: detailId, times, status: auditConst.status.checking });
+            if (!audit) {
+                throw '审核数据错误';
+            }
+            const sql = 'SELECT `tid`, `tr_id`, `td_id`, `aid`, `order` FROM ?? WHERE `td_id` = ? and `times` = ? GROUP BY `aid` ORDER BY `id` ASC';
+            const sqlParam = [this.tableName, detailId, 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.paymentDetail.tableName, {
+                    id: detailId, status: checkData.checkType,
+                    times: times + 1,
+                });
+                // 拷贝新一次审核流程列表
+                await transaction.insert(this.tableName, auditors);
+
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        async _checkNoPre(pid, detailId, checkData, times) {
+            const time = new Date();
+            // 整理当前流程审核人状态更新
+            const audit = await this.getDataByCondition({ td_id: detailId, times, status: auditConst.status.checking });
+            if (!audit || audit.order <= 1) {
+                throw '审核数据错误';
+            }
+            // 添加重新审批后,不能用order-1,取groupby值里的上一个才对
+            const auditors2 = await this.getAuditGroupByList(detailId, times);
+            const auditorIndex = await auditors2.findIndex(function(item) {
+                return item.aid === audit.aid;
+            });
+            const preAuditor = auditors2[auditorIndex - 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 });
+                // 顺移气候审核人流程顺序
+                this.initSqlBuilder();
+                this.sqlBuilder.setAndWhere('td_id', { value: detailId, operate: '=' });
+                this.sqlBuilder.setAndWhere('order', { value: audit.order, operate: '>' });
+                this.sqlBuilder.setUpdateData('order', { value: 2, selfOperate: '+' });
+                const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'update');
+                const data = await transaction.query(sql, sqlParam);
+                const newAuditors = [];
+                newAuditors.push({
+                    tender_id: audit.tender_id, tr_id: audit.tr_id, td_id: audit.td_id, aid: preAuditor.aid,
+                    times: audit.times, order: audit.order + 1, status: auditConst.status.checking,
+                    begin_time: time,
+                });
+                newAuditors.push({
+                    tender_id: audit.tender_id, tr_id: audit.tr_id, td_id: audit.td_id, aid: audit.aid,
+                    times: audit.times, order: audit.order + 2, status: auditConst.status.uncheck,
+                });
+                await transaction.insert(this.tableName, newAuditors);
+                await transaction.commit();
+            } catch (error) {
+                await transaction.rollback();
+                throw error;
+            }
+        }
+
+        /**
+         * 审批
+         * @param {Number} materialId - 材料调差期id
+         * @param {auditConst.status.checked|auditConst.status.checkNo} checkType - 审批结果
+         * @param {Number} times - 第几次审批
+         * @return {Promise<void>}
+         */
+        async check(detailId, checkData, times = 1) {
+            if (checkData.checkType !== auditConst.status.checked && checkData.checkType !== auditConst.status.checkNo && checkData.checkType !== auditConst.status.checkNoPre) {
+                throw '提交数据错误';
+            }
+            const pid = this.ctx.session.sessionProject.id;
+            switch (checkData.checkType) {
+                case auditConst.status.checked:
+                    await this._checked(pid, detailId, checkData, times);
+                    break;
+                case auditConst.status.checkNo:
+                    await this._checkNo(pid, detailId, checkData, times);
+                    break;
+                case auditConst.status.checkNoPre:
+                    await this._checkNoPre(pid, detailId, checkData, times);
+                    break;
+                default:
+                    throw '无效审批操作';
+            }
+        }
+    }
+
+    return PaymentDetailAudit;
+};

+ 5 - 2
app/service/payment_permission_audit.js

@@ -22,7 +22,7 @@ module.exports = app => {
             return await this.db.queryOne(sql, sqlParam);
             return await this.db.queryOne(sql, sqlParam);
         }
         }
 
 
-        async saveAudits(pid, accountList) {
+        async saveAudits(pid, accountList, transaction = null) {
             const pushData = [];
             const pushData = [];
             for (const a of accountList) {
             for (const a of accountList) {
                 const data = {
                 const data = {
@@ -33,7 +33,10 @@ module.exports = app => {
                 };
                 };
                 pushData.push(data);
                 pushData.push(data);
             }
             }
-            return pushData.length > 0 ? await this.db.insert(this.tableName, pushData) : false;
+            if (pushData.length > 0) {
+                return transaction ? await transaction.insert(this.tableName, pushData) : await this.db.insert(this.tableName, pushData);
+            }
+            return false;
         }
         }
 
 
         async saveGroup(pid, groupid) {
         async saveGroup(pid, groupid) {

+ 33 - 0
app/service/payment_rpt_audit.js

@@ -0,0 +1,33 @@
+'use strict';
+
+/**
+ * 版本数据模型
+ *
+ * @author CaiAoLin
+ * @date 2017/10/25
+ * @version
+ */
+const auditConst = require('../const/audit').stage;
+
+module.exports = app => {
+
+    class PaymentRptAudit extends app.BaseService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'payment_rpt_audit';
+        }
+
+        async updateSignatureMsg(td_id, uid, signature_msg) {
+            return await this.db.update(this.tableName, { sign_time: new Date(), signature_msg: JSON.stringify(signature_msg) }, { where: { td_id, uid } });
+        }
+    }
+
+    return PaymentRptAudit;
+};

+ 4 - 0
app/service/payment_shenpi_audit.js

@@ -81,6 +81,10 @@ module.exports = app => {
                 throw err;
                 throw err;
             }
             }
         }
         }
+
+        async delDataFromtrids(transaction, tr_ids) {
+            return await transaction.delete(this.tableName, { tr_id: tr_ids });
+        }
     }
     }
 
 
     return PaymentShenpiAudit;
     return PaymentShenpiAudit;

+ 14 - 3
app/service/payment_tender.js

@@ -9,6 +9,7 @@
  */
  */
 const accountGroup = require('../const/account_group').group;
 const accountGroup = require('../const/account_group').group;
 const paymentConst = require('../const/payment');
 const paymentConst = require('../const/payment');
+const auditConst = require('../const/audit').stage;
 module.exports = app => {
 module.exports = app => {
     class paymentTender extends app.BaseService {
     class paymentTender extends app.BaseService {
         constructor(ctx) {
         constructor(ctx) {
@@ -17,8 +18,10 @@ module.exports = app => {
         }
         }
 
 
         async getList(uid) {
         async getList(uid) {
-            const sql = 'SELECT pt.*, pa.name as user_name FROM ?? as pt LEFT JOIN ?? as pa ON pt.`uid` = pa.`id` WHERE pt.`uid` = ?';
-            const params = [this.tableName, this.ctx.service.projectAccount.tableName, uid];
+            const sql = 'SELECT pt.*, pa.name as user_name FROM ?? as pt LEFT JOIN ?? as pa ON pt.`uid` = pa.`id` WHERE pt.`uid` = ? ' +
+                'OR pt.`id` in (SELECT pda.`tender_id` FROM ?? as pda LEFT JOIN ?? as pd ON pda.`tender_id` = pd.`tender_id` WHERE pd.`status` != ' + auditConst.status.uncheck + ' AND pda.`aid` = ?)';
+            const params = [this.tableName, this.ctx.service.projectAccount.tableName, uid,
+                this.ctx.service.paymentDetailAudit.tableName, this.ctx.service.paymentDetail.tableName, uid];
             return await this.db.query(sql, params);
             return await this.db.query(sql, params);
         }
         }
 
 
@@ -36,7 +39,7 @@ module.exports = app => {
                     folder_id: folderId,
                     folder_id: folderId,
                     in_time: new Date(),
                     in_time: new Date(),
                 };
                 };
-                await transaction.insert(this.tableName, insertData);
+                const result = await transaction.insert(this.tableName, insertData);
                 const updateData = [
                 const updateData = [
                     { id: folderInfo.id, had_tender: 1 },
                     { id: folderInfo.id, had_tender: 1 },
                 ];
                 ];
@@ -51,6 +54,8 @@ module.exports = app => {
                     }
                     }
                 }
                 }
                 await transaction.updateRows(this.ctx.service.paymentFolder.tableName, updateData);
                 await transaction.updateRows(this.ctx.service.paymentFolder.tableName, updateData);
+                // 同时生成固定的报表表单
+                await this.ctx.service.paymentTenderRpt.setConstRpt(transaction, result.insertId, uid);
                 await transaction.commit();
                 await transaction.commit();
             } catch (err) {
             } catch (err) {
                 await transaction.rollback();
                 await transaction.rollback();
@@ -65,6 +70,10 @@ module.exports = app => {
                 if (!tenderInfo) {
                 if (!tenderInfo) {
                     throw '标段不存在';
                     throw '标段不存在';
                 }
                 }
+                const had_detail = await this.ctx.service.paymentDetail.haveDetail2Tender(id);
+                if (had_detail) {
+                    throw '请先删除所有报表表单详情';
+                }
                 const folderInfo = await this.ctx.service.paymentFolder.getDataById(tenderInfo.folder_id);
                 const folderInfo = await this.ctx.service.paymentFolder.getDataById(tenderInfo.folder_id);
                 if (!folderInfo) {
                 if (!folderInfo) {
                     throw '目录不存在';
                     throw '目录不存在';
@@ -93,6 +102,8 @@ module.exports = app => {
                     }
                     }
                     await transaction.updateRows(this.ctx.service.paymentFolder.tableName, updateDatas);
                     await transaction.updateRows(this.ctx.service.paymentFolder.tableName, updateDatas);
                 }
                 }
+                await transaction.delete(this.ctx.service.paymentTenderRpt.tableName, { tender_id: id });
+                await transaction.delete(this.ctx.service.paymentShenpiAudit.tableName, { tid: id });
                 await transaction.delete(this.tableName, { id });
                 await transaction.delete(this.tableName, { id });
                 await transaction.commit();
                 await transaction.commit();
             } catch (err) {
             } catch (err) {

+ 85 - 32
app/service/payment_tender_rpt.js

@@ -9,6 +9,7 @@
  */
  */
 const accountGroup = require('../const/account_group').group;
 const accountGroup = require('../const/account_group').group;
 const paymentConst = require('../const/payment');
 const paymentConst = require('../const/payment');
+const auditConst = require('../const/audit').stage;
 module.exports = app => {
 module.exports = app => {
     class paymentTenderRpt extends app.BaseService {
     class paymentTenderRpt extends app.BaseService {
         constructor(ctx) {
         constructor(ctx) {
@@ -16,54 +17,72 @@ module.exports = app => {
             this.tableName = 'payment_tender_rpt';
             this.tableName = 'payment_tender_rpt';
         }
         }
 
 
+        async getList(tid, uid) {
+            const sql = 'SELECT * FROM ?? WHERE `tender_id` = ?' +
+                ' AND `id` in (SELECT pda.`tr_id` FROM ?? as pda LEFT JOIN ?? as pd ON pda.`tr_id` = pd.`tr_id` WHERE pd.`status` != ' + auditConst.status.uncheck + ' AND pda.`aid` = ?)';
+            const params = [this.tableName, tid, this.ctx.service.paymentDetailAudit.tableName, this.ctx.service.paymentDetail.tableName, uid];
+            return await this.db.query(sql, params);
+        }
+
         async getProcessList(id) {
         async getProcessList(id) {
-            const sql = 'SELECT ptr.*, pa.name as user_name FROM ?? as ptr LEFT JOIN ?? as pa ON ptr.`uid` = pa.`id` WHERE ptr.`tender_id` = ?';
+            const sql = 'SELECT ptr.*, pa.name as user_name FROM ?? as ptr LEFT JOIN ?? as pa ON ptr.`uid` = pa.`id` WHERE ptr.`tender_id` = ? ';
             const params = [this.tableName, this.ctx.service.projectAccount.tableName, id];
             const params = [this.tableName, this.ctx.service.projectAccount.tableName, id];
             return await this.db.query(sql, params);
             return await this.db.query(sql, params);
         }
         }
 
 
-        async checkAndUpdateList(tenderRptList, rptProjectList) {
+        async checkAndUpdateList(tenderRptList, rptProjectList, formProcess = false) {
             if (tenderRptList.length > 0) {
             if (tenderRptList.length > 0) {
-                const updateDatas = [];
-                const delDatas = [];
-                for (const tr of tenderRptList) {
-                    const rptInfo = this._.find(rptProjectList, { ID: tr.rpt_id });
-                    // 判断是否已经新建过报表次
-                    const had_rpt = await this.ctx.service.paymentDetail.hadDetail(tr.id);
-                    if (tr.is_del === 0 && !rptInfo) {
-                        if (had_rpt) {
+                const transaction = await this.db.beginTransaction();
+                try {
+                    const updateDatas = [];
+                    const delDatas = [];
+                    const delRptIds = [];
+                    const noConstRptList = this._.filter(tenderRptList, { is_const: 0 });
+                    for (const tr of noConstRptList) {
+                        const rptInfo = this._.find(rptProjectList, { ID: tr.rpt_id });
+                        // 判断是否已经新建过报表次
+                        const had_rpt = await this.ctx.service.paymentDetail.hadDetail(tr.id);
+                        if (tr.is_del === 0 && !rptInfo) {
+                            if (had_rpt) {
+                                updateDatas.push({
+                                    id: tr.id,
+                                    is_del: 1,
+                                });
+                                tr.is_del = 1;
+                            } else {
+                                delDatas.push(tr.id);
+                            }
+                            delRptIds.push(tr.id);
+                        } else if (rptInfo && tr.rpt_name !== rptInfo.name) {
                             updateDatas.push({
                             updateDatas.push({
                                 id: tr.id,
                                 id: tr.id,
-                                is_del: 1,
+                                rpt_name: rptInfo.name,
                             });
                             });
-                            tr.is_del = 1;
-                        } else {
-                            delDatas.push(tr.id);
+                            tr.rpt_name = rptInfo.name;
                         }
                         }
-                    } else if (rptInfo && tr.rpt_name !== rptInfo.name) {
-                        updateDatas.push({
-                            id: tr.id,
-                            rpt_name: rptInfo.name,
+                        if (rptInfo) rptInfo.had_rpt = had_rpt;
+                    }
+                    if (updateDatas.length > 0) await transaction.updateRows(this.tableName, updateDatas);
+                    if (delDatas.length > 0) {
+                        this._.remove(tenderRptList, function(item) {
+                            return delDatas.indexOf(item.id) !== -1;
                         });
                         });
-                        tr.rpt_name = rptInfo.name;
+                        await transaction.delete(this.tableName, { id: delDatas });
                     }
                     }
-                    rptInfo.had_rpt = had_rpt;
-                }
-                if (updateDatas.length > 0) await this.db.updateRows(this.tableName, updateDatas);
-                if (delDatas.length > 0) {
-                    this._.remove(tenderRptList, function(item) {
-                        return delDatas.indexOf(item.id) !== -1;
-                    });
-                    await this.db.delete(this.tableName, { id: delDatas });
+                    if (delRptIds.length > 0) await this.ctx.service.paymentShenpiAudit.delDataFromtrids(transaction, delRptIds);
+                    await transaction.commit();
+                } catch (err) {
+                    await transaction.rollback();
+                    throw err;
                 }
                 }
             }
             }
-            return [tenderRptList, rptProjectList];
+            return formProcess ? [tenderRptList, rptProjectList] : tenderRptList;
         }
         }
 
 
         async setRpt(tid, rpt_list) {
         async setRpt(tid, rpt_list) {
             const transaction = await this.db.beginTransaction();
             const transaction = await this.db.beginTransaction();
             try {
             try {
-                const originList = await this.getAllDataByCondition({ where: { tender_id: tid, is_del: 0 } });
+                const originList = await this.getAllDataByCondition({ where: { tender_id: tid, is_del: 0, is_const: 0 } });
                 const insertData = [];
                 const insertData = [];
                 let deleteData = [];
                 let deleteData = [];
                 if (originList.length === 0 && rpt_list.length !== 0) {
                 if (originList.length === 0 && rpt_list.length !== 0) {
@@ -92,7 +111,6 @@ module.exports = app => {
                             deleteData.push(orginInfo.id);
                             deleteData.push(orginInfo.id);
                         }
                         }
                     }
                     }
-                    console.log(insertRptIds, deleteData);
                     for (const id of insertRptIds) {
                     for (const id of insertRptIds) {
                         const info = this._.find(rpt_list, { id });
                         const info = this._.find(rpt_list, { id });
                         insertData.push({
                         insertData.push({
@@ -105,9 +123,15 @@ module.exports = app => {
                     }
                     }
                 }
                 }
                 if (insertData.length > 0) await transaction.insert(this.tableName, insertData);
                 if (insertData.length > 0) await transaction.insert(this.tableName, insertData);
-                if (deleteData.length > 0) await transaction.delete(this.tableName, { id: deleteData });
+                if (deleteData.length > 0) {
+                    await transaction.delete(this.tableName, { id: deleteData });
+                    // 也要删除对应的审批流数据
+                    await this.ctx.service.paymentShenpiAudit.delDataFromtrids(transaction, deleteData);
+                }
                 await transaction.commit();
                 await transaction.commit();
-                return await this.getProcessList(tid);
+                let tenderRptList = await this.getProcessList(tid);
+                tenderRptList = this._.filter(tenderRptList, { is_const: 0, is_del: 0 });
+                return tenderRptList;
             } catch (err) {
             } catch (err) {
                 await transaction.rollback();
                 await transaction.rollback();
                 throw err;
                 throw err;
@@ -125,6 +149,35 @@ module.exports = app => {
                 throw err;
                 throw err;
             }
             }
         }
         }
+
+        async setConstRpt(transaction, tid, uid) {
+            const insertData = [];
+            for (const rpt of paymentConst.const_rpt_list) {
+                insertData.push({
+                    tender_id: tid,
+                    uid,
+                    rpt_id: rpt.rpt_id,
+                    rpt_name: rpt.rpt_name,
+                    is_const: 1,
+                    in_time: new Date(),
+                });
+            }
+            if (insertData.length > 0) await transaction.insert(this.tableName, insertData);
+        }
+
+        async updateRptAudit(id, rpt_audit) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 判断是否存在null
+                const is_first = this._.findIndex(rpt_audit, { uid: null }) === -1 ? 0 : 1;
+                await transaction.update(this.tableName, { id, rpt_audit: JSON.stringify(rpt_audit), is_first });
+                await transaction.commit();
+                return { is_first };
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
     }
     }
     return paymentTenderRpt;
     return paymentTenderRpt;
 };
 };

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

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

+ 832 - 0
app/view/payment/audit_modal.ejs

@@ -0,0 +1,832 @@
+<% if ((ctx.detail.status === auditConst.status.uncheck || ctx.detail.status === auditConst.status.checkNo) && ctx.session.sessionUser.accountId === ctx.detail.uid) { %>
+    <!--上报审批-->
+    <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="dropdown text-right">
+                        <% if (trInfo.sp_status !== shenpiConst.sp_status.gdspl && ctx.session.sessionUser.accountId === ctx.detail.uid) { %>
+                            <button class="btn btn-outline-primary btn-sm dropdown-toggle" type="button"
+                                    id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true"
+                                    aria-expanded="false">
+                                添加审批流程
+                            </button>
+                            <div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton"
+                                 style="width:220px">
+                                <div class="mb-2 p-2"><input class="form-control form-control-sm"
+                                                             placeholder="姓名/手机 检索" id="gr-search" autocomplete="off"></div>
+                                <dl class="list-unstyled book-list">
+                                    <% accountGroup.forEach((group, idx) => { %>
+                                        <dt><a href="javascript: void(0);" class="acc-btn" data-groupid="<%- idx %>" data-type="hide"><i class="fa fa-plus-square"></i></a> <%- group.groupName %></dt>
+                                        <div class="dd-content" data-toggleid="<%- idx %>">
+                                            <% group.groupList.forEach(item => { %>
+                                                <% if (item.id !== ctx.session.sessionUser.accountId) { %>
+                                                    <dd class="border-bottom p-2 mb-0 " data-id="<%- item.id %>" >
+                                                        <p class="mb-0 d-flex"><span class="text-primary"><%- item.name %></span><span
+                                                                    class="ml-auto"><%- item.mobile %></span></p>
+                                                        <span class="text-muted"><%- item.role %></span>
+                                                    </dd>
+                                                <% } %>
+                                            <% });%>
+                                        </div>
+                                    <% }) %>
+                                </dl>
+                            </div>
+                        <% } %>
+                    </div>
+                    <div class="card mt-3">
+                        <div class="card-header">
+                            审批流程
+                        </div>
+                        <div class="modal-height-500" style="overflow: auto">
+                            <ul class="list-group list-group-flush" id="auditors">
+                                <% for (let i = 0, iLen = ctx.detail.auditorList.length; i < iLen; i++) { %>
+                                    <li class="list-group-item" auditorId="<%- ctx.detail.auditorList[i].aid %>">
+                                        <% if ((trInfo.sp_status === shenpiConst.sp_status.sqspr ||
+                                                (trInfo.sp_status === shenpiConst.sp_status.gdzs && i+1 !== iLen)) && ctx.session.sessionUser.accountId === ctx.detail.uid) { %>
+                                            <a href="javascript: void(0)" class="text-danger pull-right">移除</a>
+                                        <% } %>
+                                        <span><%- ctx.detail.auditorList[i].order %> <%- ctx.detail.auditorList[i].name %></span>
+                                        <small class="text-muted"><%- ctx.detail.auditorList[i].role %></small>
+                                    </li>
+                                <% } %>
+                            </ul>
+                        </div>
+                    </div>
+                </div>
+                <form class="modal-footer" method="post" action="<%- preUrl %>/audit/start" onsubmit="return checkAuditorFrom()">
+                    <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                    <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>">
+                    <% if (ctx.session.sessionUser.accountId === ctx.detail.uid) { %>
+                        <button class="btn btn-primary btn-sm" type="submit">确认上报</button>
+                    <% } %>
+                </form>
+            </div>
+        </div>
+    </div>
+<% } %>
+
+<!--审批流程/结果-->
+<div class="modal fade" id="sp-list" data-backdrop="static">
+    <div class="modal-dialog modal-lg" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title"><%- ctx.detail.status === auditConst.status.checking ? '审批流程' : '重新上报' %></h5>
+            </div>
+            <div class="modal-body">
+                <div class="row">
+                    <div class="col-4">
+                        <% if(ctx.detail.status === auditConst.status.checkNo) { %>
+                            <a class="sp-list-item" 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" id="auditors-list">
+                                <% ctx.detail.auditors2.forEach((item, idx) => { %>
+                                    <% if (idx === 0) { %>
+                                        <li class="list-group-item" data-auditorId="<%- item.aid %>">
+                                            <i class="fa fa fa-play-circle fa-rotate-90"></i> <%- item.name %>
+                                            <small class="text-muted"><%- item.role %></small>
+                                            <span class="pull-right">原报</span>
+                                        </li>
+                                    <% } else if(idx === ctx.detail.auditors2.length -1 && idx !== 0) { %>
+                                        <li class="list-group-item" data-auditorId="<%- item.aid %>">
+                                            <i class="fa fa fa-stop-circle"></i> <%- item.name %>
+                                            <small class="text-muted"><%- item.role %></small>
+                                            <span class="pull-right">终审</span>
+                                        </li>
+                                    <% } else {%>
+                                        <li class="list-group-item" data-auditorId="<%- item.aid %>">
+                                            <i class="fa fa-chevron-circle-down"></i> <%- item.name %>
+                                            <small class="text-muted"><%- item.role %></small>
+                                            <span class="pull-right"><%= ctx.helper.transFormToChinese(idx) %>审</span>
+                                        </li>
+                                    <% } %>
+                                <% }) %>
+                            </ul>
+                        </div>
+                    </div>
+                    <div class="col-8 modal-height-500" style="overflow: auto">
+                        <% ctx.detail.auditHistory.forEach((auditors, idx) => { %>
+                            <!-- 展开/收起历史流程 -->
+                            <% if(idx === ctx.detail.auditHistory.length - 1 && ctx.detail.auditHistory.length !== 1) { %>
+                                <div class="text-right">
+                                    <a href="javascript: void(0);" id="fold-btn" data-target="show">展开历史审批流程</a>
+                                </div>
+                            <% } %>
+                            <div class="<%- idx < ctx.detail.auditHistory.length - 1 ? 'fold-card' : '' %>">
+                                <div class="text-center text-muted"><%- idx+1 %>#</div>
+                                <ul class="timeline-list list-unstyled mt-2">
+                                    <% auditors.forEach((auditor, index) => { %>
+                                        <% if (index === 0) { %>
+                                            <li class="timeline-list-item pb-2">
+                                                <div class="timeline-item-date">
+                                                    <%- ctx.helper.formatDate(auditor.begin_time) %>
+                                                </div>
+                                                <div class="timeline-item-tail"></div>
+                                                <div class="timeline-item-icon bg-success text-light">
+                                                    <i class="fa fa-caret-down"></i>
+                                                </div>
+                                                <div class="timeline-item-content">
+                                                    <div class="card">
+                                                        <div class="card-body p-3">
+                                                            <div class="card-text">
+                                                                <p class="mb-1"><span
+                                                                            class="h5"><%- ctx.detail.user.name %></span><span
+                                                                            class="pull-right text-success"><%- idx !== 0 ? '重新' : '' %>上报审批</span>
+                                                                </p>
+                                                                <p class="text-muted mb-0"><%- ctx.detail.user.role %></p>
+                                                            </div>
+                                                        </div>
+                                                    </div>
+                                                </div>
+                                            </li>
+                                            <li class="timeline-list-item pb-2">
+                                                <div class="timeline-item-date">
+                                                    <%- ctx.helper.formatDate(auditor.end_time) %>
+                                                </div>
+                                                <% if(index < auditors.length - 1) { %>
+                                                    <div class="timeline-item-tail"></div>
+                                                <% } %>
+                                                <% if(auditor.status === auditConst.status.checked) { %>
+                                                    <div class="timeline-item-icon bg-success text-light">
+                                                        <i class="fa fa-check"></i>
+                                                    </div>
+                                                <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {%>
+                                                    <div class="timeline-item-icon bg-warning text-light">
+                                                        <i class="fa fa-level-up"></i>
+                                                    </div>
+                                                <% } else if(auditor.status === auditConst.status.checking) { %>
+                                                    <div class="timeline-item-icon bg-warning text-light">
+                                                        <i class="fa fa-ellipsis-h"></i>
+                                                    </div>
+                                                <% } else {%>
+                                                    <div class="timeline-item-icon bg-secondary text-light">
+                                                    </div>
+                                                <% } %>
+                                                <div class="timeline-item-content">
+                                                    <div class="card">
+                                                        <div class="card-body p-3">
+                                                            <div class="card-text">
+                                                                <p class="mb-1"><span class="h5"><%- auditor.name %></span><span
+                                                                            class="pull-right <%- auditConst.statusClass[auditor.status] %>"><%- auditConst.statusString[auditor.status] %></span>
+                                                                </p>
+                                                                <p class="text-muted mb-0"><%- auditor.role %></p>
+                                                            </div>
+                                                        </div>
+
+                                                        <!--审批意见-->
+                                                        <% if (auditor.opinion) { %>
+                                                            <div class="card-body p-3 border-top">
+                                                                <p style="margin: 0;"><%- auditor.opinion %></p>
+                                                            </div>
+                                                        <% } %>
+                                                    </div>
+                                                </div>
+                                            </li>
+                                        <% } else {%>
+                                            <li class="timeline-list-item pb-2">
+                                                <div class="timeline-item-date">
+                                                    <%- ctx.helper.formatDate(auditor.end_time) %>
+                                                </div>
+                                                <% if(index < auditors.length - 1) { %>
+                                                    <div class="timeline-item-tail"></div>
+                                                <% } %>
+                                                <% if(auditor.status === auditConst.status.checked) { %>
+                                                    <div class="timeline-item-icon bg-success text-light">
+                                                        <i class="fa fa-check"></i>
+                                                    </div>
+                                                <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {%>
+                                                    <div class="timeline-item-icon bg-warning text-light">
+                                                        <i class="fa fa-level-up"></i>
+                                                    </div>
+                                                <% } else if(auditor.status === auditConst.status.checking) { %>
+                                                    <div class="timeline-item-icon bg-warning text-light">
+                                                        <i class="fa fa-ellipsis-h"></i>
+                                                    </div>
+                                                <% } else { %>
+                                                    <div class="timeline-item-icon bg-secondary text-light">
+                                                    </div>
+                                                <% } %>
+                                                <div class="timeline-item-content">
+                                                    <div class="card">
+                                                        <div class="card-body p-3">
+                                                            <div class="card-text">
+                                                                <p class="mb-1"><span class="h5"><%- auditor.name %></span>
+                                                                    <span
+                                                                            class="pull-right
+                                                                            <%- auditConst.statusClass[auditor.status] %>"><%- auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''%>
+                                                                        <%- auditor.status === auditConst.status.checkNo ? ctx.detail.user.name : '' %>
+                                                                        <%- auditor.status === auditConst.status.checkNoPre ? auditors.find(item => item.sort === auditor.sort-1).name : '' %>
+                                                        </span>
+                                                                </p>
+                                                                <p class="text-muted mb-0"><%- auditor.role %></p>
+                                                            </div>
+                                                        </div>
+                                                        <!--审批意见-->
+                                                        <% if (auditor.opinion) { %>
+                                                            <div class="card-body p-3 border-top">
+                                                                <p style="margin: 0;"><%- auditor.opinion %></p>
+                                                            </div>
+                                                        <% } %>
+                                                    </div>
+                                                </div>
+                                            </li>
+                                        <% } %>
+                                    <% }) %>
+                                </ul>
+                            </div>
+
+                        <% }) %>
+                    </div>
+                </div>
+            </div>
+            <form class="modal-footer" method="post" action="<%- preUrl %>/audit/start" onsubmit="return checkAuditorFrom()">
+                <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>">
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                <% if(ctx.detail.status === auditConst.status.checkNo && ctx.session.sessionUser.accountId === ctx.detail.uid) { %>
+                    <button class="btn btn-primary btn-sm sp-list-item" type="submit">确认上报</button>
+                <% } %>
+            </form>
+        </div>
+    </div>
+</div>
+<% if (ctx.detail.status === auditConst.status.checking || ctx.detail.status === auditConst.status.checkNoPre) { %>
+    <% if (ctx.detail.curAuditor && ctx.detail.curAuditor.aid === ctx.session.sessionUser.accountId) { %>
+        <!--审批通过-->
+        <div class="modal fade sp-location-list" id="sp-done" data-backdrop="static">
+            <div class="modal-dialog modal-lg" role="document">
+                <form class="modal-content" action="<%- preUrl %>/audit/check" method="post" onsubmit="return auditCheck(0);">
+                    <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">
+                                        <% ctx.detail.auditors2.forEach((item, idx) => { %>
+                                            <% if (idx === 0) { %>
+                                                <li class="list-group-item" data-auditorId="<%- item.aid %>">
+                                                    <i class="fa fa fa-play-circle fa-rotate-90"></i> <%- item.name %>
+                                                    <small class="text-muted"><%- item.role %></small>
+                                                    <span class="pull-right">原报</span>
+                                                </li>
+                                            <% } else if(idx === ctx.detail.auditors2.length -1 && idx !== 0) { %>
+                                                <li class="list-group-item" data-auditorId="<%- item.aid %>">
+                                                    <i class="fa fa fa-stop-circle"></i> <%- item.name %>
+                                                    <small class="text-muted"><%- item.role %></small>
+                                                    <span class="pull-right">终审</span>
+                                                </li>
+                                            <% } else {%>
+                                                <li class="list-group-item" data-auditorId="<%- item.aid %>">
+                                                    <i class="fa fa-chevron-circle-down"></i> <%- item.name %>
+                                                    <small class="text-muted"><%- item.role %></small>
+                                                    <span class="pull-right"><%= ctx.helper.transFormToChinese(idx) %>审</span>
+                                                </li>
+                                            <% } %>
+                                        <% }) %>
+                                    </ul>
+                                </div>
+                            </div>
+                            <div class="col-8 modal-height-500" style="overflow: auto">
+                                <% ctx.detail.auditHistory.forEach((auditors, idx) => { %>
+                                    <!-- 展开/收起历史流程 -->
+                                    <% if(idx === ctx.detail.auditHistory.length - 1 && ctx.detail.auditHistory.length !== 1) { %>
+                                        <div class="text-right">
+                                            <a href="javascript: void(0);" id="fold-btn" data-target="show">展开历史审批流程</a>
+                                        </div>
+                                    <% } %>
+                                    <div class="<%- idx < ctx.detail.auditHistory.length - 1 ? 'fold-card' : '' %>">
+                                        <div class="text-center text-muted"><%- idx+1 %>#</div>
+                                        <ul class="timeline-list list-unstyled mt-2">
+                                            <% auditors.forEach((auditor, index) => { %>
+                                                <% if (index === 0) { %>
+                                                    <li class="timeline-list-item pb-2">
+                                                        <div class="timeline-item-date">
+                                                            <%- ctx.helper.formatDate(auditor.begin_time) %>
+                                                        </div>
+                                                        <div class="timeline-item-tail"></div>
+                                                        <div class="timeline-item-icon bg-success text-light">
+                                                            <i class="fa fa-caret-down"></i>
+                                                        </div>
+                                                        <div class="timeline-item-content">
+                                                            <div class="card">
+                                                                <div class="card-body p-3">
+                                                                    <div class="card-text">
+                                                                        <p class="mb-1"><span
+                                                                                    class="h5"><%- ctx.detail.user.name %></span><span
+                                                                                    class="pull-right text-success"><%- idx !== 0 ? '重新' : '' %>上报审批</span>
+                                                                        </p>
+                                                                        <p class="text-muted mb-0"><%- ctx.detail.user.role %></p>
+                                                                    </div>
+                                                                </div>
+                                                            </div>
+                                                        </div>
+                                                    </li>
+                                                    <li class="timeline-list-item pb-2">
+                                                        <div class="timeline-item-date">
+                                                            <%- ctx.helper.formatDate(auditor.end_time) %>
+                                                        </div>
+                                                        <% if(index < auditors.length - 1) { %>
+                                                            <div class="timeline-item-tail"></div>
+                                                        <% } %>
+                                                        <% if(auditor.status === auditConst.status.checked) { %>
+                                                            <div class="timeline-item-icon bg-success text-light">
+                                                                <i class="fa fa-check"></i>
+                                                            </div>
+                                                        <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {%>
+                                                            <div class="timeline-item-icon bg-warning text-light">
+                                                                <i class="fa fa-level-up"></i>
+                                                            </div>
+                                                        <% } else if(auditor.status === auditConst.status.checking) { %>
+                                                            <div class="timeline-item-icon bg-warning text-light">
+                                                                <i class="fa fa-ellipsis-h"></i>
+                                                            </div>
+                                                        <% } else {%>
+                                                            <div class="timeline-item-icon bg-secondary text-light">
+                                                            </div>
+                                                        <% } %>
+                                                        <div class="timeline-item-content">
+                                                            <div class="card">
+                                                                <div class="card-body p-3">
+                                                                    <div class="card-text">
+                                                                        <p class="mb-1"><span class="h5"><%- auditor.name %></span><span
+                                                                                    class="pull-right <%- auditConst.statusClass[auditor.status] %>"><%- auditConst.statusString[auditor.status] %></span>
+                                                                        </p>
+                                                                        <p class="text-muted mb-0"><%- auditor.role %></p>
+                                                                    </div>
+                                                                </div>
+                                                                <!--审批意见-->
+                                                                <% if(auditor.status !== auditConst.status.uncheck) { %>
+                                                                    <% if (ctx.detail.times === idx + 1 && auditor.status === auditConst.status.checking) { %>
+                                                                        <div class="card-body p-3 border-top">
+                                                                            <label>审批意见<b class="text-danger">*</b></label>
+                                                                            <textarea class="form-control form-control-sm"
+                                                                                      name="opinion">同意</textarea>
+                                                                        </div>
+                                                                    <% } else if (auditor.opinion) { %>
+                                                                        <div class="card-body p-3 border-top">
+                                                                            <p style="margin: 0;"><%- auditor.opinion %></p>
+                                                                        </div>
+                                                                    <% } %>
+                                                                <% } %>
+                                                            </div>
+                                                        </div>
+                                                    </li>
+                                                <% } else {%>
+                                                    <li class="timeline-list-item pb-2">
+                                                        <div class="timeline-item-date">
+                                                            <%- ctx.helper.formatDate(auditor.end_time) %>
+                                                        </div>
+                                                        <% if(index < auditors.length - 1) { %>
+                                                            <div class="timeline-item-tail"></div>
+                                                        <% } %>
+                                                        <% if(auditor.status === auditConst.status.checked) { %>
+                                                            <div class="timeline-item-icon bg-success text-light">
+                                                                <i class="fa fa-check"></i>
+                                                            </div>
+                                                        <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {%>
+                                                            <div class="timeline-item-icon bg-warning text-light">
+                                                                <i class="fa fa-level-up"></i>
+                                                            </div>
+                                                        <% } else if(auditor.status === auditConst.status.checking) { %>
+                                                            <div class="timeline-item-icon bg-warning text-light">
+                                                                <i class="fa fa-ellipsis-h"></i>
+                                                            </div>
+                                                        <% } else { %>
+                                                            <div class="timeline-item-icon bg-secondary text-light">
+                                                            </div>
+                                                        <% } %>
+                                                        <div class="timeline-item-content">
+                                                            <div class="card">
+                                                                <div class="card-body p-3">
+                                                                    <div class="card-text">
+                                                                        <p class="mb-1"><span class="h5"><%- auditor.name %></span>
+                                                                            <span
+                                                                                    class="pull-right
+                                                                                    <%- auditConst.statusClass[auditor.status] %>"><%- auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''%>
+                                                                                <%- auditor.status === auditConst.status.checkNo ? ctx.detail.user.name : '' %>
+                                                                                <%- auditor.status === auditConst.status.checkNoPre ? auditors.find(item => item.sort === auditor.sort-1).name : '' %>
+                                                                </span>
+                                                                        </p>
+                                                                        <p class="text-muted mb-0"><%- auditor.role %></p>
+                                                                    </div>
+                                                                </div>
+                                                                <!--审批意见-->
+                                                                <% if(auditor.status !== auditConst.status.uncheck) { %>
+                                                                    <% if (ctx.detail.times === idx + 1 && auditor.status === auditConst.status.checking) { %>
+                                                                        <div class="card-body p-3 border-top">
+                                                                            <label>审批意见<b class="text-danger">*</b></label>
+                                                                            <textarea class="form-control form-control-sm"
+                                                                                      name="opinion">同意</textarea>
+                                                                        </div>
+                                                                    <% } else if (auditor.opinion) { %>
+                                                                        <div class="card-body p-3 border-top">
+                                                                            <p style="margin: 0;"><%- auditor.opinion %></p>
+                                                                        </div>
+                                                                    <% } %>
+                                                                <% } %>
+                                                            </div>
+                                                        </div>
+                                                    </li>
+                                                <% } %>
+                                            <% }) %>
+                                        </ul>
+                                    </div>
+
+                                <% }) %>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="modal-footer">
+                        <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                        <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>" />
+                        <input type="hidden" name="checkType" value="<%= auditConst.status.checked %>" />
+                        <button type="submit" class="btn btn-success btn-sm">确认通过</button>
+                    </div>
+                </form>
+            </div>
+        </div>
+        <!--审批退回-->
+        <div class="modal fade sp-location-list" 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"
+                      onsubmit="return auditCheck(1);">
+                    <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">
+                                        <% ctx.detail.auditors2.forEach((item, idx) => { %>
+                                            <% if (idx === 0) { %>
+                                                <li class="list-group-item" data-auditorId="<%- item.aid %>">
+                                                    <i class="fa fa fa-play-circle fa-rotate-90"></i> <%- item.name %>
+                                                    <small class="text-muted"><%- item.role %></small>
+                                                    <span class="pull-right">原报</span>
+                                                </li>
+                                            <% } else if(idx === ctx.detail.auditors2.length -1 && idx !== 0) { %>
+                                                <li class="list-group-item" data-auditorId="<%- item.aid %>">
+                                                    <i class="fa fa fa-stop-circle"></i> <%- item.name %>
+                                                    <small class="text-muted"><%- item.role %></small>
+                                                    <span class="pull-right">终审</span>
+                                                </li>
+                                            <% } else {%>
+                                                <li class="list-group-item" data-auditorId="<%- item.aid %>">
+                                                    <i class="fa fa-chevron-circle-down"></i> <%- item.name %>
+                                                    <small class="text-muted"><%- item.role %></small>
+                                                    <span class="pull-right"><%= ctx.helper.transFormToChinese(idx) %>审</span>
+                                                </li>
+                                            <% } %>
+                                        <% }) %>
+                                    </ul>
+                                </div>
+                            </div>
+                            <div class="col-8 modal-height-500" style="overflow: auto">
+                                <% ctx.detail.auditHistory.forEach((auditors, idx) => { %>
+                                    <!-- 展开/收起历史流程 -->
+                                    <% if(idx === ctx.detail.auditHistory.length - 1 && ctx.detail.auditHistory.length !== 1) { %>
+                                        <div class="text-right">
+                                            <a href="javascript: void(0);" id="fold-btn" data-target="show">展开历史审批流程</a>
+                                        </div>
+                                    <% } %>
+                                    <div class="<%- idx < ctx.detail.auditHistory.length - 1 ? 'fold-card' : '' %>">
+                                        <div class="text-center text-muted"><%- idx+1 %>#</div>
+                                        <ul class="timeline-list list-unstyled mt-2">
+                                            <% auditors.forEach((auditor, index) => { %>
+                                                <% if (index === 0) { %>
+                                                    <li class="timeline-list-item pb-2">
+                                                        <div class="timeline-item-date">
+                                                            <%- ctx.helper.formatDate(auditor.begin_time) %>
+                                                        </div>
+                                                        <div class="timeline-item-tail"></div>
+                                                        <div class="timeline-item-icon bg-success text-light">
+                                                            <i class="fa fa-caret-down"></i>
+                                                        </div>
+                                                        <div class="timeline-item-content">
+                                                            <div class="card">
+                                                                <div class="card-body p-3">
+                                                                    <div class="card-text">
+                                                                        <p class="mb-1"><span
+                                                                                    class="h5"><%- ctx.detail.user.name %></span><span
+                                                                                    class="pull-right text-success"><%- idx !== 0 ? '重新' : '' %>上报审批</span>
+                                                                        </p>
+                                                                        <p class="text-muted mb-0"><%- ctx.detail.user.role %></p>
+                                                                    </div>
+                                                                </div>
+                                                            </div>
+                                                        </div>
+                                                    </li>
+                                                    <li class="timeline-list-item pb-2">
+                                                        <div class="timeline-item-date">
+                                                            <%- ctx.helper.formatDate(auditor.end_time) %>
+                                                        </div>
+                                                        <% if(index < auditors.length - 1) { %>
+                                                            <div class="timeline-item-tail"></div>
+                                                        <% } %>
+                                                        <% if(auditor.status === auditConst.status.checked) { %>
+                                                            <div class="timeline-item-icon bg-success text-light">
+                                                                <i class="fa fa-check"></i>
+                                                            </div>
+                                                        <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {%>
+                                                            <div class="timeline-item-icon bg-warning text-light">
+                                                                <i class="fa fa-level-up"></i>
+                                                            </div>
+                                                        <% } else if(auditor.status === auditConst.status.checking) { %>
+                                                            <div class="timeline-item-icon bg-warning text-light">
+                                                                <i class="fa fa-ellipsis-h"></i>
+                                                            </div>
+                                                        <% } else {%>
+                                                            <div class="timeline-item-icon bg-secondary text-light">
+                                                            </div>
+                                                        <% } %>
+                                                        <div class="timeline-item-content">
+                                                            <div class="card">
+                                                                <div class="card-body p-3">
+                                                                    <div class="card-text">
+                                                                        <p class="mb-1"><span class="h5"><%- auditor.name %></span><span
+                                                                                    class="pull-right <%- auditConst.statusClass[auditor.status] %>"><%- auditConst.statusString[auditor.status] %></span>
+                                                                        </p>
+                                                                        <p class="text-muted mb-0"><%- auditor.role %></p>
+                                                                    </div>
+                                                                </div>
+
+                                                                <!--审批意见-->
+                                                                <% if(auditor.times === ctx.detail.times && auditor.status !== auditConst.status.uncheck) { %>
+                                                                    <div class="card-body p-3 border-top">
+                                                                        <% if (ctx.detail.times === idx + 1 && auditor.status === auditConst.status.checking) { %>
+                                                                            <label>审批意见<b class="text-danger">*</b></label>
+                                                                            <textarea class="form-control form-control-sm"
+                                                                                      name="opinion">不同意</textarea>
+                                                                            <% if (ctx.detail.curAuditor.aid === auditor.aid) { %>
+                                                                                <div id="reject-process" class="alert alert-warning"
+                                                                                     style="margin-top: 15px;">
+                                                                                    <div class="form-check form-check-inline">
+                                                                                        <input class="form-check-input" type="radio" name="checkType"
+                                                                                               id="inlineRadio1" value="<%- auditConst.status.checkNo %>">
+                                                                                        <label class="form-check-label" for="inlineRadio1">退回原报
+                                                                                            <%- ctx.detail.user.name %></label>
+                                                                                    </div>
+                                                                                    <% if (auditor.order > 1 && auditor.aid !== auditors[0].aid) { %>
+                                                                                        <div class="form-check form-check-inline">
+                                                                                            <input class="form-check-input" type="radio" name="checkType"
+                                                                                                   id="inlineRadio2"
+                                                                                                   value="<%- auditConst.status.checkNoPre %>">
+                                                                                            <label class="form-check-label" for="inlineRadio2">退回上一审批人
+                                                                                                <%- auditors.find(item => item.sort === auditor.sort-1).name %></label>
+                                                                                        </div>
+                                                                                    <% } %>
+                                                                                </div>
+                                                                            <% } %>
+                                                                        <% } else if(auditor.status === auditConst.status.checked && auditor.opinion){ %>
+                                                                            <p style="margin: 0;"><%- auditor.opinion %></p>
+                                                                        <% } %>
+                                                                    </div>
+                                                                <% } %>
+                                                            </div>
+                                                        </div>
+                                                    </li>
+                                                <% } else {%>
+                                                    <li class="timeline-list-item pb-2">
+                                                        <div class="timeline-item-date">
+                                                            <%- ctx.helper.formatDate(auditor.end_time) %>
+                                                        </div>
+                                                        <% if(index < auditors.length - 1) { %>
+                                                            <div class="timeline-item-tail"></div>
+                                                        <% } %>
+                                                        <% if(auditor.status === auditConst.status.checked) { %>
+                                                            <div class="timeline-item-icon bg-success text-light">
+                                                                <i class="fa fa-check"></i>
+                                                            </div>
+                                                        <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {%>
+                                                            <div class="timeline-item-icon bg-warning text-light">
+                                                                <i class="fa fa-level-up"></i>
+                                                            </div>
+                                                        <% } else if(auditor.status === auditConst.status.checking) { %>
+                                                            <div class="timeline-item-icon bg-warning text-light">
+                                                                <i class="fa fa-ellipsis-h"></i>
+                                                            </div>
+                                                        <% } else { %>
+                                                            <div class="timeline-item-icon bg-secondary text-light">
+                                                            </div>
+                                                        <% } %>
+                                                        <div class="timeline-item-content">
+                                                            <div class="card">
+                                                                <div class="card-body p-3">
+                                                                    <div class="card-text">
+                                                                        <p class="mb-1"><span class="h5"><%- auditor.name %></span>
+                                                                            <span
+                                                                                    class="pull-right
+                                                                                    <%- auditConst.statusClass[auditor.status] %>"><%- auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''%>
+                                                                                <%- auditor.status === auditConst.status.checkNo ? ctx.detail.user.name : '' %>
+                                                                                <%- auditor.status === auditConst.status.checkNoPre ? auditors.find(item => item.sort === auditor.sort-1).name : '' %>
+                                                                </span>
+                                                                        </p>
+                                                                        <p class="text-muted mb-0"><%- auditor.role %></p>
+                                                                    </div>
+                                                                </div>
+                                                                <!--审批意见-->
+                                                                <% if(auditor.times === ctx.detail.times && auditor.status !== auditConst.status.uncheck) { %>
+                                                                    <div class="card-body p-3 border-top">
+                                                                        <% if (ctx.detail.times === idx + 1 && auditor.status === auditConst.status.checking) { %>
+                                                                            <label>审批意见<b class="text-danger">*</b></label>
+                                                                            <textarea class="form-control form-control-sm"
+                                                                                      name="opinion">不同意</textarea>
+                                                                            <% if (ctx.detail.curAuditor.aid === auditor.aid ) { %>
+                                                                                <div id="reject-process" class="alert alert-warning"
+                                                                                     style="margin-top: 15px;">
+                                                                                    <div class="form-check form-check-inline">
+                                                                                        <input class="form-check-input" type="radio" name="checkType"
+                                                                                               id="inlineRadio1" value="<%- auditConst.status.checkNo %>">
+                                                                                        <label class="form-check-label" for="inlineRadio1">退回原报
+                                                                                            <%- ctx.detail.user.name %></label>
+                                                                                    </div>
+                                                                                    <% if (auditor.order > 1 && auditor.aid !== auditors[0].aid) { %>
+                                                                                        <div class="form-check form-check-inline">
+                                                                                            <input class="form-check-input" type="radio" name="checkType"
+                                                                                                   id="inlineRadio2"
+                                                                                                   value="<%- auditConst.status.checkNoPre %>">
+                                                                                            <label class="form-check-label" for="inlineRadio2">退回上一审批人
+                                                                                                <%- auditors.find(item => item.sort === auditor.sort-1).name %></label>
+                                                                                        </div>
+                                                                                    <% } %>
+                                                                                </div>
+                                                                            <% } %>
+                                                                        <% } else if (auditor.opinion) { %>
+                                                                            <p style="margin: 0;"><%- auditor.opinion %></p>
+                                                                        <% } %>
+
+                                                                    </div>
+
+                                                                <% } %>
+                                                            </div>
+                                                        </div>
+                                                    </li>
+                                                <% } %>
+                                            <% }) %>
+                                        </ul>
+                                    </div>
+
+                                <% }) %>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="modal-footer">
+                        <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                        <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>" />
+                        <button type="submit" class="btn btn-warning btn-sm">确认退回</button>
+                    </div>
+                </form>
+            </div>
+        </div>
+    <% } %>
+<% } %>
+<% if (ctx.detail.auditors !== undefined && ctx.detail.auditors.length !== 0 && ctx.detail.auditors[ctx.detail.auditors.length-1].aid === ctx.session.sessionUser.accountId && ctx.detail.status === auditConst.status.checked && ctx.detail.order === ctx.detail.highOrder) { %>
+    <% if (ctx.detail && !ctx.detail.authMobile && ctx.session.sessionUser.loginStatus === 0) { %>
+        <!--终审重新审批-->
+        <div class="modal fade" id="sp-down-back" data-backdrop="static">
+            <div class="modal-dialog" role="document">
+                <div class="modal-content">
+                    <div class="modal-header">
+                        <h5 class="modal-title">重新审批</h5>
+                    </div>
+                    <div class="modal-body">
+                        <h5>重新审批需要您的手机短信验证</h5>
+                        <h5>您目前还没设置认证手机,请先设置。</h5>
+                    </div>
+                    <div class="modal-footer">
+                        <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                        <a href="/profile/sms" class="btn btn-sm btn-primary">去设置</a>
+                    </div>
+                </div>
+            </div>
+        </div>
+    <% } else { %>
+        <div class="modal fade" id="sp-down-back" data-backdrop="static">
+            <div class="modal-dialog" role="document">
+                <div class="modal-content">
+                    <div class="modal-header">
+                        <h5 class="modal-title">重新审批</h5>
+                    </div>
+                    <div class="modal-body">
+                        <h5>确认由「终审-<%= ctx.detail.auditors[ctx.detail.auditors.length-1].name %>」重新审批「第<%= ctx.detail.order %>期」?
+                        </h5>
+                        <% if (ctx.session.sessionUser.loginStatus === 0) { %>
+                            <div class="form-group">
+                                <label>重审需要验证码确认,验证码将发送至尾号<%- ctx.detail.authMobile.slice(-4) %>的手机</label>
+                                <div class="input-group input-group-sm mb-3">
+                                    <input class="form-control" type="text" readonly="readonly" name="code"
+                                           placeholder="输入短信中的6位验证码" />
+                                    <div class="input-group-append">
+                                        <button class="btn btn-outline-secondary" type="button" id="get-code">获取验证码</button>
+                                    </div>
+                                </div>
+                            </div>
+                        <% } %>
+                    </div>
+                    <div class="modal-footer">
+                        <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                        <!--<a href="<%- preUrl %>/audit/check/again" disabled class="btn btn-warning btn-sm">确定重审</a>-->
+                        <button <% if (ctx.session.sessionUser.loginStatus === 0) { %>disabled<% } %> id="re-shenpi-btn"
+                                class="btn btn-warning btn-sm">确定重审</button>
+                    </div>
+                </div>
+            </div>
+        </div>
+    <% } %>
+<% } %>
+<script type="text/javascript">
+    const csrf = '<%= ctx.csrf %>';
+    const authMobile = '<%= ctx.detail.authMobile %>';
+</script>
+<% if (ctx.session.sessionUser.accountId === ctx.detail.uid && (ctx.detail.status === auditConst.status.uncheck || ctx.detail.status === auditConst.status.checkNo)) { %>
+    <script>
+        const accountGroup = JSON.parse(unescape('<%- escape(JSON.stringify(accountGroup)) %>'));
+        const accountList = JSON.parse(unescape('<%- escape(JSON.stringify(accountList)) %>'));
+        const shenpi_status = <%- trInfo.sp_status %>;
+        const shenpiConst =  JSON.parse('<%- JSON.stringify(shenpiConst) %>');
+    </script>
+<% } %>
+<script>const cur_uid = parseInt('<%- ctx.session.sessionUser.accountId %>');</script>
+<script>
+    $('.sp-location-list').on('shown.bs.modal', function () {
+        const scrollBox = $(this).find('div[class="col-8 modal-height-500"]');
+        const bdiv = (scrollBox.offset() && scrollBox.offset().top) || 0;
+        scrollBox.scrollTop(0);
+        const hdiv = divSearch($(this).find('textarea')) ? $(this).find('textarea') : null;
+        const hdheight = hdiv ? hdiv.parents('.timeline-item-content').offset().top : null;
+        if (hdiv && scrollBox.length && scrollBox[0].scrollHeight > 200 && hdheight - bdiv > 200) {
+            scrollBox.scrollTop(hdheight - bdiv);
+        }
+    });
+    function divSearch(div) {
+        if (div.length > 0) {
+            return true;
+        }
+        return false;
+    }
+
+    // 展开历史审核记录
+    $('.modal-body #fold-btn').click(function () {
+        const type = $(this).data('target')
+        const auditCard = $(this).parent().parent()
+        if (type === 'show') {
+            $(this).data('target', 'hide')
+            auditCard.find('.fold-card').slideDown('swing', () => {
+                auditCard.find('#fold-btn').text('收起历史审核记录')
+            })
+        } else {
+            $(this).data('target', 'show')
+            auditCard.find('.fold-card').slideUp('swing', () => {
+                auditCard.find('#fold-btn').text('展开历史审核记录')
+            })
+        }
+    });
+
+    $('.sp-list-btn').click(function () {
+        const type = $(this).data('type')
+        if (type === 'hide') {
+            $('.sp-list-item').hide()
+            $('.modal-title').text('审批流程')
+        } else {
+            $('.sp-list-item').show()
+            $('.modal-title').text('重新上报')
+        }
+    });
+
+    // 重新审批按钮
+    $("#re-shenpi-btn").click(function () {
+        const data = {
+        };
+        <% if (ctx.session.sessionUser.loginStatus === 0) { %>
+        const code = $("#sp-down-back input[name='code']").val();
+        if ($(this).hasClass('disabled')) {
+            return false;
+        }
+        if (code.length < 6) {
+            // alert('请填写正确的验证码');
+            toastr.error('请填写正确的验证码');
+            return false;
+        }
+        data.code = code;
+        <% } %>
+        $.ajax({
+            url: '<%- preUrl %>/audit/check/again',
+            type: 'get',
+            data: data,
+            dataTye: 'json',
+            success: function (response) {
+                if (response.err === 0) {
+                    window.location.href = response.url;
+                } else {
+                    toastr.error(response.msg);
+                }
+            }
+        });
+    });
+</script>

+ 26 - 88
app/view/payment/detail.ejs

@@ -10,11 +10,12 @@
 <div class="panel-content">
 <div class="panel-content">
     <div class="panel-title fluid">
     <div class="panel-title fluid">
         <div class="title-main  d-flex justify-content-between">
         <div class="title-main  d-flex justify-content-between">
-            <div><a href="payment-approval-detail.html"><i class="fa fa-chevron-left mr-2"></i></a>土建01标 / 报表2 / YFK 003</div>
+            <div><a href="/payment/<%- ctx.tender.id %>/list/<%- trInfo.id %>"><i class="fa fa-chevron-left mr-2"></i></a><%- ctx.tender.name %> / <%- trInfo.rpt_name %> / <%- ctx.detail.code %></div>
             <div>
             <div>
-                <a href="#add-lot" data-toggle="modal" data-target="#add-lot" class="btn btn-sm btn-warning pull-right">审批退回</a>
-                <a href="#add-lot" data-toggle="modal" data-target="#add-lot" class="btn btn-sm btn-success pull-right mr-2">审批通过</a>
-                <a href="#add-lot" data-toggle="modal" data-target="#add-lot" class="btn btn-sm btn-primary pull-right mr-2">上报审批</a>
+                <% include ./audit_btn.ejs %>
+                <% if (rptAudit) { %>
+                <a href="#sub-sp5" data-toggle="modal" data-target="#sub-sp5" class="btn btn-sm btn-primary pull-right mr-2">签字意见</a>
+                <% } %>
             </div>
             </div>
         </div>
         </div>
     </div>
     </div>
@@ -22,63 +23,22 @@
         <div class="c-body">
         <div class="c-body">
             <div class="sjs-height-0">
             <div class="sjs-height-0">
                 <div class="row m-0 mt-3">
                 <div class="row m-0 mt-3">
-                    <div class="col-6">
-                        <form>
-                            <h5>表头内容</h5>
-                            <div class="form-group">
-                                <label>编号:</label>
-                                <input type="text" class="form-control form-control-sm" placeholder="请输入">
-                            </div>
-                            <div class="form-group">
-                                <label>合同号:</label>
-                                <input type="text" class="form-control form-control-sm" placeholder="请输入">
-                            </div>
-                            <div class="form-group">
-                                <label>工程名称:</label>
-                                <input type="text" class="form-control form-control-sm" placeholder="请输入">
-                            </div>
-                            <div class="form-group">
-                                <label>项目公司名称:</label>
-                                <input type="text" class="form-control form-control-sm" placeholder="请输入">
-                            </div>
+                    <div id="rpt-form" class="col-6">
+                        <% for (const c of content) { %>
+                        <div>
+                            <h5><%- c.title %>内容</h5>
+                            <% for (const item of c.items) { %>
                             <div class="form-group">
                             <div class="form-group">
-                                <label>合同价款:</label>
-                                <input type="text" class="form-control form-control-sm" placeholder="请输入">
+                                <label><%- item.label %></label>
+                                <% if (item.type === 'textarea') { %>
+                                    <textarea <% if (ctx.detail.readOnly) { %>readonly<% } %> class="form-control form-control-sm" data-index="<%- item.index %>"><%- item.value %></textarea>
+                                <% } else { %>
+                                    <input <% if (ctx.detail.readOnly) { %>readonly<% } %> type="<%- item.type %>" value="<%- item.value %>"  data-index="<%- item.index %>" class="form-control form-control-sm" placeholder="请输入">
+                                <% } %>
                             </div>
                             </div>
-                            <div class="form-group">
-                                <label>已付价款:</label>
-                                <input type="text" class="form-control form-control-sm" placeholder="请输入">
-                            </div>
-                            <div class="form-group">
-                                <label>结算价款:</label>
-                                <input type="text" class="form-control form-control-sm" placeholder="请输入">
-                            </div>
-                            <h5>表单内容</h5>
-                            <div class="form-group">
-                                <label>单位名称:</label>
-                                <input type="text" class="form-control form-control-sm" placeholder="请输入">
-                            </div>
-                            <div class="form-group">
-                                <label>申请内容及金额:</label>
-                                <input type="text" class="form-control form-control-sm" placeholder="请输入">
-                            </div>
-                            <div class="form-group">
-                                <label>开票或者收据编号:</label>
-                                <input type="text" class="form-control form-control-sm" placeholder="请输入">
-                            </div>
-                            <div class="form-group">
-                                <label>开户银行:</label>
-                                <input type="text" class="form-control form-control-sm" placeholder="请输入">
-                            </div>
-                            <div class="form-group">
-                                <label>账号:</label>
-                                <input type="text" class="form-control form-control-sm" placeholder="请输入">
-                            </div>
-                            <div class="form-group">
-                                <label>联系电话:</label>
-                                <input type="text" class="form-control form-control-sm" placeholder="请输入">
-                            </div>
-                        </form>
+                            <% } %>
+                        </div>
+                        <% } %>
                     </div>
                     </div>
                     <div class="col-6">
                     <div class="col-6">
                         <div class="d-flex flex-row">
                         <div class="d-flex flex-row">
@@ -97,10 +57,13 @@
 </div>
 </div>
 
 
 <script type="text/javascript">
 <script type="text/javascript">
-    let tesRpttData = JSON.parse(unescape('<%- escape(JSON.stringify(pageRst)) %>'));
-
+    const tenderId = parseInt('<%- ctx.tender.id %>');
+    const detailId = parseInt('<%- ctx.detail.id %>');
+    const rptAudit = JSON.parse(unescape('<%- escape(JSON.stringify(rptAudit)) %>'));
+    console.log(rptAudit);
+    let currentStamp = JSON.parse(unescape('<%- escape(JSON.stringify(currentStamp)) %>'));
+    let tesRpttData = JSON.parse(unescape('<%- escape(JSON.stringify(report_json)) %>'));
     console.log(tesRpttData);
     console.log(tesRpttData);
-
     const SCREEN_DPI = [];
     const SCREEN_DPI = [];
     const PAGE_SHOW = {closeWaterMark: 1};
     const PAGE_SHOW = {closeWaterMark: 1};
     const current_stage_status = -1;
     const current_stage_status = -1;
@@ -201,11 +164,7 @@
             }
             }
         },
         },
     };
     };
-    $(document).ready(() => {
-        autoFlashHeight();
-        auditRptPrintHelper.showPage();
-        iniPage();
-    });
+
     function downloadPDFReport(pageDataArr, pageSize, rpt_names, signatureRelArr, signatureRelInfo, refRptTplIds, STAGE_AUDIT) {
     function downloadPDFReport(pageDataArr, pageSize, rpt_names, signatureRelArr, signatureRelInfo, refRptTplIds, STAGE_AUDIT) {
         auditRptPrintHelper.currentDownloadIdx = 0;
         auditRptPrintHelper.currentDownloadIdx = 0;
         const private_download = function(newPageDataArr, new_rpt_names) {
         const private_download = function(newPageDataArr, new_rpt_names) {
@@ -291,25 +250,4 @@
         }
         }
         //*/
         //*/
     }
     }
-    function iniPage() {
-        dynamicLoadJs('/public/jspdf/Arial Narrow-normal.js');
-        dynamicLoadJs('/public/jspdf/Arial Narrow-bold.js');
-        dynamicLoadJs('/public/jspdf/Arial Narrow-italic.js');
-        dynamicLoadJs('/public/jspdf/Arial Narrow-bolditalic.js');
-
-        rptTplObj.isLoading = true;
-        dynamicLoadJs('https://d2.smartcost.com.cn/cach/SmartSimsun-normal2.js', 'normal', getPdfFontCallbackLight);
-        dynamicLoadJs('https://d2.smartcost.com.cn/cach/SmartSimsun-bold.js', 'bold', getPdfFontCallbackLight);
-    }
-    function getPdfFontCallbackLight(fontProperty) {
-        rptTplObj.pdfFont['SmartSimsun'].push(fontProperty);
-        if (rptTplObj.pdfFont['SmartSimsun'].length === 2) {
-            rptTplObj.isLoading = false;
-        }
-    }
-    function getPdfFontCallback(fontProperty) {
-        if (rptTplObj.pdfFont['SmartSimsun'].length === 2) {
-            downloadPDFReport([tesRpttData], 'A4', ['测试审核表'], [], [], [-1], []);
-        }
-    }
 </script>
 </script>

+ 99 - 0
app/view/payment/detail_modal.ejs

@@ -0,0 +1,99 @@
+<% if (rptAudit) { %>
+<!--签字意见-->
+<div class="modal fade" id="sub-sp5" 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 mb-3">
+                    <label class="mb-2">当前表单角色:<%- rptAudit.signature_name %></label>
+                </div>
+                <div class="form-group">
+                    <label class="mb-2">签字/签章</label>
+                    <div>
+                        <div class="custom-control custom-checkbox d-inline pt-2 pl-2">
+                            <div class="form-check form-check-inline px-2">
+                                <input class="form-check-input" type="checkbox" id="sign_path" value="<%- signPath %>" <% if (rptAudit.signature_msg.sign_path !== null) { %>checked<% } %>>
+                                <label class="form-check-label" for="sign_path">签字</label>
+                            </div>
+                            <div class="form-check form-check-inline mx-1">
+                                <div class="form-group">
+                                    <div class="form-check form-check-inline px-1">
+                                        <input class="form-check-input" type="checkbox" id="company_stamp" value="<%- companyStamp %>" <% if (rptAudit.signature_msg.company_stamp !== null) { %>checked<% } %>>
+                                        <label class="form-check-label" for="company_stamp">单位章</label>
+                                    </div>
+                                    <% if (stampPathList.length>1) { %>
+                                    <div class="form-check form-check-inline">
+                                        <input class="form-check-input" type="checkbox" id="stamp_path" value="<%- currentStamp %>" <% if (rptAudit.signature_msg.stamp_path !== null) { %>checked<% } %>>
+                                        <label class="form-check-label" for="stamp_path">个人章</label>
+                                        <a class="pl-2" href="#chose-private-stamp-path" data-toggle="modal" data-target="#chose-private-stamp-path">选择个人章</a>
+                                    </div>
+                                    <% } %>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+                <% if (ctx.helper._.findIndex(report_json.items[0].signature_date_cells, { signature_name: rptAudit.signature_name + '_签字日期' }) !== -1) { %>
+                <div class="form-group mb-3">
+                    <label class="mb-2">审批时间 <!-- <b class="text-danger">*</b>--></label>
+                    <div class="input-group input-group-sm margin-inputbox">
+                        <div class="input-group-prepend">
+                            <span class="input-group-text height-inputbox" id="inputGroup-sizing-sm"><i class="fa fa-calendar" title="添加签名日期" ></i></span>
+                        </div>
+                        <input id="signature_date" type="text" class="form-control datepicker-here height-inputbox" aria-label="Small" aria-describedby="inputGroup-sizing-sm" data-language="zh">
+                    </div>
+                </div>
+                <% } %>
+                <% if (ctx.helper._.findIndex(report_json.items[0].signature_audit_cells, { signature_name: rptAudit.signature_name + '_审核意见' }) !== -1) { %>
+                <div class="form-group mb-3">
+                    <label class="mb-2">审批意见</label>
+                    <textarea id="signature_content" class="form-control form-control-sm" rows="6" placeholder="上报、审批通过可以在这里输入意见"></textarea>
+                </div>
+                <% } %>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                <button type="button" class="btn btn-sm btn-primary" id="commit_sign">确定</button>
+            </div>
+        </div>
+    </div>
+</div>
+<!--选择个人章-->
+<div class="modal fade" id="chose-private-stamp-path" data-backdrop="static">
+    <div class="modal-dialog modal-lg" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title" id="select-personal-signature-title">选择个人章</h5>
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">&times;</span>
+                </button>
+            </div>
+            <div class="modal-body">
+                <div class='row justify-content-md-center'>
+                    <% if (stampPathList.length > 0) { %>
+                    <% for (const item of stampPathList) { %>
+                        <div class="card col-3 p-2 m-3 d-flex <% if (currentStamp === item) { %>card-gk-active<% } %> stamp-img">
+                            <div class="p-0 private-stamp-img">
+                                <div class="sel-width check-state <% if (currentStamp === item) { %>sel-blue<% } %>"></div>
+                                <img src="<%- ctx.app.config.fujianOssPath + item %>" data-src='<%- item %>' class="img-fluid" alt="...">
+                            </div>
+                        </div>
+                    <% } %>
+                    <% for(let i=0;i<stampPathList.length%3;i++){ %>
+                        <div class="col-3 p-2 m-3"></div>
+                    <% } %>
+                    <% } %>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                <button class="btn btn-primary btn-sm" id="select_stamp_path_btn">确定</button>
+            </div>
+        </div>
+    </div>
+</div>
+<% } %>
+<% include ./audit_modal.ejs %>

+ 90 - 0
app/view/payment/list.ejs

@@ -0,0 +1,90 @@
+<div class="panel-content">
+    <div class="panel-title fluid">
+        <div class="title-main  d-flex justify-content-between">
+            <div><%- ctx.tender.name %></div>
+            <div>
+                <% if (ctx.session.sessionUser.accountId === trInfo.uid && trInfo.is_del === 0 && rptMsg) { %>
+                <% if (trDetailList.length === 0 || trDetailList[0].status === auditConst.status.checked) { %>
+                <a href="javascript:void(0);" id="show-add-btn" class="btn btn-sm btn-primary pull-right ml-2">新建审批</a>
+                <% } %>
+                <a href="#set-bdjs" data-toggle="modal" data-target="#set-bdjs" class="btn btn-sm btn-primary pull-right">绑定表单角色</a>
+                <span class="text-danger pull-right" id="first_msg" <% if (trInfo.is_first === 0) { %>style="display:none;" <% } %>>未配置表单角色,请点击右侧的“绑定表单角色” &nbsp; &nbsp; </span>
+                <% } %>
+            </div>
+        </div>
+    </div>
+    <div class="content-wrap">
+        <div class="c-body">
+            <div class="sjs-height-0">
+                <div class="row m-0 my-3">
+                    <div class="col-3">
+                        <div class="list-group">
+                            <% for (const tr of tenderRptList) { %>
+                            <a href="/payment/<%- ctx.tender.id %>/list/<%- tr.id %>"
+                               class="list-group-item list-group-item-action <% if (trInfo.id === tr.id) { %>active<% } %>">
+                                <%- tr.rpt_name %><% if (tr.have_notice) { %><i class="fa fa-bell text-warning float-right mt-1" data-toggle="tooltip" data-placement="bottom" title="待处理提醒"></i><% } %>
+                            </a>
+                            <% } %>
+                        </div>
+                    </div>
+                    <div class="col-9">
+                        <div class="tab-content" id="v-pills-tabContent">
+                            <div class="tab-pane fade show active" id="v-pills-home" role="tabpanel" aria-labelledby="v-pills-home-tab">
+                                <table class="table table-bordered">
+                                    <thead>
+                                    <tr>
+                                        <th>编号</th>
+                                        <th>创建时间</th>
+                                        <th>审批进度</th>
+                                        <th>操作</th>
+                                    </tr>
+                                    </thead>
+                                    <tbody>
+                                    <% for (const info of trDetailList) { %>
+                                    <tr>
+                                        <td><a href="/payment/<%- info.tender_id %>/detail/<%- info.id %>"><%- info.code %></a></td>
+                                        <td><%- info.s_time %></td>
+                                        <td class="<%- auditConst.auditProgressClass[info.status] %>">
+                                            <% if (info.curAuditor) { %>
+                                                <a href="#sp-list" data-toggle="modal" data-target="#sp-list" m-order="<%- info.order %>"><%- info.curAuditor.name %><%if (info.curAuditor.role !== '' && info.curAuditor.role !== null) { %>-<%- info.curAuditor.role %><% } %></a>
+                                            <% } %>
+                                            <%- auditConst.auditProgress[info.status] %>
+                                        </td>
+                                        <td>
+                                            <% if (info.status === auditConst.status.uncheck && info.uid === ctx.session.sessionUser.accountId) { %>
+                                                <a href="<%- '/payment/' + ctx.tender.id + '/detail/' + info.id %>" class="btn <%- auditConst.statusButtonClass[info.status] %> btn-sm"><%- auditConst.statusButton[info.status] %></a>
+                                            <% } else if (info.status === auditConst.status.checkNo && info.curAuditor && info.uid === ctx.session.sessionUser.accountId) { %>
+                                                <a href="<%- '/payment/' + ctx.tender.id + '/detail/' + info.id %>" class="btn <%- auditConst.statusButtonClass[info.status] %> btn-sm"><%- auditConst.statusButton[info.status] %></a>
+                                            <% } else if (info.status === auditConst.status.checking && info.curAuditor && info.curAuditor.aid === ctx.session.sessionUser.accountId) { %>
+                                                <a href="<%- '/payment/' + ctx.tender.id + '/detail/' + info.id %>" class="btn <%- auditConst.statusButtonClass[info.status] %> btn-sm"><%- auditConst.statusButton[info.status] %></a>
+                                            <% } else { %>
+                                                <span class="<%- auditConst.auditProgressClass[info.status] %>"><%- auditConst.auditProgress[info.status] %></span>
+                                            <% } %>
+                                            <% if ((ctx.session.sessionUser.is_admin || ((info.status === auditConst.status.uncheck || info.status === auditConst.status.checkNo) && info.uid === ctx.session.sessionUser.accountId)) && info.order === trDetailList.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>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+<script>
+    const tenderId = parseInt('<%- ctx.tender.id %>');
+    const trId = parseInt('<%- trInfo.id %>');
+    const rptMsg = JSON.parse(unescape('<%- escape(JSON.stringify(rptMsg)) %>'));
+    console.log(rptMsg);
+    const accountGroup = JSON.parse(unescape('<%- escape(JSON.stringify(accountGroup)) %>'));
+    const accountList = JSON.parse(unescape('<%- escape(JSON.stringify(accountList)) %>'));
+    const auditConst = JSON.parse(unescape('<%- escape(JSON.stringify(auditConst)) %>'));
+    let rpt_audit = JSON.parse(unescape('<%- escape(JSON.stringify(trInfo.rpt_audit)) %>'));
+    let old_rpt_audit = _.cloneDeep(rpt_audit);
+    console.log(rpt_audit);
+</script>

+ 139 - 0
app/view/payment/list_modal.ejs

@@ -0,0 +1,139 @@
+<% if (rptMsg) { %>
+<!--绑定表单角色-->
+<div class="modal fade" id="set-bdjs" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">绑定表单角色</h5>
+            </div>
+            <div class="modal-body">
+                <table class="table table-bordered">
+                    <thead>
+                    <tr>
+                        <th>序号</th>
+                        <th>表单角色</th>
+                        <th>审批人</th>
+                    </tr>
+                    </thead>
+                    <tbody>
+                    <% if (rptMsg && rptMsg.signature_cells && rptMsg.signature_cells.length > 0) { %>
+                    <% for (const [index,audit] of rptMsg.signature_cells.entries()) { %>
+                    <tr>
+                        <td><%- index+1 %></td>
+                        <td><%- audit.signature_name %></td>
+                        <td>
+                            <div class="d-flex justify-content-between align-items-center mx-2" id="<%- index %>_user" <% if (!(trInfo.rpt_audit && trInfo.rpt_audit[index] && trInfo.rpt_audit[index].uid)) { %>style="display: none" <% } %>>
+                                <% if (trInfo.rpt_audit && trInfo.rpt_audit[index] && trInfo.rpt_audit[index].uid) { %><span><%- trInfo.rpt_audit[index].name %></span><i class="fa fa-close text-danger remove-audit stamp-img" data-code="<%- index %>"></i><% } %>
+                            </div>
+                            <div class="dropdown select-audit" data-code="<%- index %>" <% if (trInfo.rpt_audit && trInfo.rpt_audit[index] && trInfo.rpt_audit[index].uid) { %>style="display: none" <% } %>>
+                                <button class="btn btn-outline-primary btn-sm dropdown-toggle" type="button" id="<%- index %>_dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+                                    选择审批人
+                                </button>
+                                <div class="dropdown-menu dropdown-menu-right" id="<%- index %>_dropdownMenu" aria-labelledby="<%- index %>_dropdownMenuButton" style="width:220px">
+                                    <div class="mb-2 p-2"><input class="form-control form-control-sm gr-search"
+                                                                 placeholder="姓名/手机 检索" autocomplete="off" data-code="<%- index %>"></div>
+                                    <dl class="list-unstyled book-list">
+                                        <% accountGroup.forEach((group, idx) => { %>
+                                            <dt><a href="javascript: void(0);" class="acc-btn" data-groupid="<%- idx %>" data-type="hide"><i class="fa fa-plus-square"></i></a> <%- group.groupName %></dt>
+                                            <div class="dd-content" data-toggleid="<%- idx %>">
+                                                <% group.groupList.forEach(item => { %>
+                                                    <dd class="border-bottom p-2 mb-0 " data-id="<%- item.id %>" >
+                                                        <p class="mb-0 d-flex"><span class="text-primary"><%- item.name %></span><span
+                                                                    class="ml-auto"><%- item.mobile %></span></p>
+                                                        <span class="text-muted"><%- item.role %></span>
+                                                    </dd>
+                                                <% });%>
+                                            </div>
+                                        <% }) %>
+                                    </dl>
+                                </div>
+                            </div>
+                        </td>
+                    </tr>
+                    <% } %>
+                    <% } %>
+                    </tbody>
+                </table>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                <button type="button" class="btn btn-sm btn-primary" id="bind_rpt_audit_btn">确定</button>
+            </div>
+        </div>
+    </div>
+</div>
+
+<!-- 弹窗新建目录 -->
+<div class="modal fade" id="add-catalogue" 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">
+                <form>
+                    <div class="form-group">
+                        <!--<a href="#" class="float-right"><i class="fa fa-cog"></i></a>-->
+                        <label for="add-detail-code">编号</label>
+                        <input type="text" class="form-control form-control-sm" name="add_code" id="add-detail-code" placeholder="请输入编号" value="">
+                    </div>
+                    <div class="form-group">
+                        <label for="add-detail-time">日期</label>
+                        <input type="date" class="form-control form-control-sm" name="add_time" id="add-detail-time" placeholder="" value="<%- ctx.helper.dateTran(new Date(), 'YYYY-MM-DD')%>">
+                    </div>
+                </form>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                <button type="button" class="btn btn-sm btn-primary" id="add-detail-btn">确定添加</button>
+            </div>
+        </div>
+    </div>
+</div>
+<% } %>
+<% if (trDetailList && trDetailList.length >= 1) { %>
+    <!--删除期-->
+    <div class="modal fade" id="del-qi" data-backdrop="static">
+        <div class="modal-dialog" role="document">
+            <form class="modal-content" action="<%- preUrl + '/delete' %>" method="post">
+                <div class="modal-header">
+                    <h5 class="modal-title">删除</h5>
+                </div>
+                <div class="modal-body">
+                    <h5>确认删除「<%= trDetailList[0].code %>」?</h5>
+                    <h5>删除后,数据无法恢复,请谨慎操作。</h5>
+                </div>
+                <div class="modal-footer">
+                    <input type="hidden" name="detail_id" value="<%= trDetailList[0].id %>">
+                    <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>" />
+                    <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">取消</button>
+                    <button type="submit" class="btn btn-danger btn-sm">确定删除</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">
+                            </ul>
+                        </div>
+                    </div>
+                    <div class="col-8 modal-height-500" style="overflow: auto" id="audit-list">
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+            </div>
+        </div>
+    </div>
+</div>

+ 1 - 3
app/view/payment/process.ejs

@@ -92,15 +92,13 @@
                                             <th>添加人</th>
                                             <th>添加人</th>
                                         </tr>
                                         </tr>
                                         </thead>
                                         </thead>
-                                        <tbody id="tender_rpt_table">
+                                        <tbody id="tender_rpt_table" style="cursor: pointer">
                                         <% for (const tr of tenderRptList) { %>
                                         <% for (const tr of tenderRptList) { %>
-                                        <% if (tr.is_del === 0) { %>
                                         <tr data-id="<%- tr.id %>">
                                         <tr data-id="<%- tr.id %>">
                                             <td><%- tr.rpt_name  %></td>
                                             <td><%- tr.rpt_name  %></td>
                                             <td><%- tr.user_name %></td>
                                             <td><%- tr.user_name %></td>
                                         </tr>
                                         </tr>
                                         <% } %>
                                         <% } %>
-                                        <% } %>
                                         </tbody>
                                         </tbody>
                                     </table>
                                     </table>
                                     <div class="my-3"><a href="#add-rpt" data-toggle="modal" data-target="#add-rpt">添加表单</a></div>
                                     <div class="my-3"><a href="#add-rpt" data-toggle="modal" data-target="#add-rpt">添加表单</a></div>

+ 16 - 1
config/web.js

@@ -1142,7 +1142,22 @@ const JsFiles = {
                 mergeFiles: [
                 mergeFiles: [
                     '/public/js/payment_process.js',
                     '/public/js/payment_process.js',
                 ],
                 ],
-                mergeFile: 'process',
+                mergeFile: 'payment_process',
+            },
+            list: {
+                files: ['/public/js/moment/moment.min.js'],
+                mergeFiles: [
+                    '/public/js/payment_list.js',
+                ],
+                mergeFile: 'payment_list',
+            },
+            detail: {
+                files: ['/public/js/datepicker/datepicker.min.js', '/public/js/datepicker/datepicker.zh.js'],
+                mergeFiles: [
+                    '/public/js/payment_detail.js',
+                    '/public/js/payment_detail_audit.js',
+                ],
+                mergeFile: 'payment_detail',
             },
             },
         },
         },
     },
     },