ellisran 11 часов назад
Родитель
Сommit
ec41669793
36 измененных файлов с 4459 добавлено и 108 удалено
  1. 122 3
      app/const/audit.js
  2. 8 0
      app/const/shenpi.js
  3. 4 0
      app/const/sp_page_show.js
  4. 51 0
      app/const/sp_shenpi.js
  5. 1 0
      app/const/tender_info.js
  6. 261 10
      app/controller/contract_controller.js
  7. 82 0
      app/controller/sub_proj_controller.js
  8. 1 0
      app/controller/sub_proj_setting_controller.js
  9. 1 0
      app/controller/tender_controller.js
  10. 5 1
      app/middleware/contract_check.js
  11. 1 0
      app/middleware/sub_project_check.js
  12. 614 32
      app/public/js/contract_detail.js
  13. 10 0
      app/public/js/contract_setting.js
  14. 40 3
      app/public/js/shenpi.js
  15. 1211 0
      app/public/js/sp_shenpi.js
  16. 9 1
      app/router.js
  17. 49 2
      app/service/contract.js
  18. 2 0
      app/service/contract_audit.js
  19. 20 0
      app/service/contract_pay.js
  20. 1192 0
      app/service/contract_sp_audit.js
  21. 4 0
      app/service/contract_tree.js
  22. 1 1
      app/service/quality_inspection_audit.js
  23. 1 1
      app/service/safe_inspection_audit.js
  24. 46 22
      app/service/shenpi_audit.js
  25. 20 13
      app/service/shenpi_group.js
  26. 30 4
      app/service/sub_project.js
  27. 24 2
      app/view/contract/detail.ejs
  28. 115 2
      app/view/contract/detail_modal.ejs
  29. 4 4
      app/view/contract/setting.ejs
  30. 245 0
      app/view/contract/shenpi.ejs
  31. 204 0
      app/view/contract/shenpi_modal.ejs
  32. 11 0
      app/view/sp_setting/manage_modal.ejs
  33. 1 1
      app/view/tender/shenpi.ejs
  34. 11 0
      app/view/tender/shenpi_modal.ejs
  35. 18 0
      config/web.js
  36. 40 6
      sql/update.sql

+ 122 - 3
app/const/audit.js

@@ -11,9 +11,9 @@
 const auditType = (function () {
     const types = [
         { key: 'common', name: '个人', value: 1, short: '', long: '', class: '', },
-        { key: 'and', name: '会签', value: 2, short: '会', long: '多人会签', class: 'primary', valid: ['ledger', 'revise', 'stage', 'change', 'material', 'financial', 'phasePay', 'cost_stage_ledger', 'cost_stage_book', 'cost_stage_analysis'], setValid: ['stage'] },
-        { key: 'or', name: '或签', value: 3, short: '或', long: '多人或签', class: 'success', valid: ['ledger', 'revise', 'stage', 'change', 'material', 'financial', 'phasePay', 'cost_stage_ledger', 'cost_stage_book', 'cost_stage_analysis'], setValid: ['stage'] },
-        { key: 'union', name: '协同', value: 4, short: '协', long: '多人协同', class: 'warning', valid: ['stage']},
+        { key: 'and', name: '会签', value: 2, short: '会', long: '多人会签', class: 'primary', valid: ['ledger', 'revise', 'stage', 'change', 'material', 'financial', 'phasePay', 'cost_stage_ledger', 'cost_stage_book', 'cost_stage_analysis', 'contract'], setValid: ['stage'] },
+        { key: 'or', name: '或签', value: 3, short: '或', long: '多人或签', class: 'success', valid: ['ledger', 'revise', 'stage', 'change', 'material', 'financial', 'phasePay', 'cost_stage_ledger', 'cost_stage_book', 'cost_stage_analysis', 'contract'], setValid: ['stage'] },
+        { key: 'union', name: '协同', value: 4, short: '协', long: '多人协同', class: 'warning', valid: ['stage'] },
         { key: 'multi', name: '分组审批', value: 5, short: '组', long: '分组审批', class: 'danger', valid: ['stage'] },
     ];
     const key = {};
@@ -1518,6 +1518,124 @@ const inspection = (function() {
     return { status, statusString, statusClass, auditString, auditStringClass, auditProgress, auditProgressClass, filter, statusButton, statusButtonClass };
 })();
 
+// 合同审批流程
+const contract = (function() {
+    // 流程状态
+    const status = {
+        uncheck: 1, // 待上报
+        checking: 2, // 待审批|审批中
+        checked: 3, // 审批通过
+        checkNo: 4, // 审批退回原报
+        checkNoPre: 5, // 审批退回上一人
+        checkAgain: 6, // 重新审批 // 该状态仅可用于,终审退回时,修改原终审的审批状态,并同时新增一条新的终审审批中记录
+        checkCancel: 7, // 撤回 // 该状态为上一审批人可发起,回到它到审批阶段,并同时新增一条新的审批中记录
+        checkSkip: 8, // 跳过
+    };
+
+    // 流程状态提示
+    const statusString = [];
+    statusString[status.uncheck] = '待上报';
+    statusString[status.checking] = '审批中';
+    statusString[status.checked] = '审批通过';
+    statusString[status.checkNo] = '审批退回';
+    statusString[status.checkNoPre] = '审批退回';
+    statusString[status.checkAgain] = '重新审批';
+    statusString[status.checkCancel] = '撤回';
+
+    // 流程状态样式
+    const statusClass = [];
+    statusClass[status.uncheck] = '';
+    statusClass[status.checking] = 'text-warning';
+    statusClass[status.checked] = 'text-success';
+    statusClass[status.checkNo] = 'text-warning';
+    statusClass[status.checkNoPre] = 'text-warning';
+    statusClass[status.checkAgain] = 'text-warning';
+    statusClass[status.checkCancel] = 'text-warning';
+
+    /**
+     * 期列表,审批状态一列
+     */
+    // 按钮
+    const statusButton = [];
+    statusButton[status.uncheck] = '待上报';
+    statusButton[status.checking] = '审批';
+    statusButton[status.checked] = '';
+    statusButton[status.checkNo] = '重新上报';
+    statusButton[status.checkNoPre] = '重新审批';
+    statusButton[status.checkAgain] = '重新审批';
+    statusButton[status.checkCancel] = '撤回';
+    // 按钮样式
+    const statusButtonClass = [];
+    statusButtonClass[status.uncheck] = 'btn-primary';
+    statusButtonClass[status.checking] = 'btn-success';
+    statusButtonClass[status.checked] = '';
+    statusButtonClass[status.checkNo] = 'btn-warning';
+    statusButtonClass[status.checkNoPre] = 'btn-warning';
+    statusButtonClass[status.checkAgain] = 'btn-warning';
+    statusButtonClass[status.checkCancel] = 'btn-warning';
+
+    // 描述文本
+    const auditString = [];
+    auditString[status.uncheck] = '';
+    auditString[status.checking] = '审批中';
+    auditString[status.checked] = '审批通过';
+    auditString[status.checkNo] = '审批退回';
+    auditString[status.checkNoPre] = '审批退回';
+    auditString[status.checkAgain] = '重新审批';
+    auditString[status.checkCancel] = '撤回';
+    auditString[status.checkSkip] = '审批通过';
+    // 文字样式
+    const auditStringClass = [];
+    auditStringClass[status.uncheck] = '';
+    auditStringClass[status.checking] = 'text-warning';
+    auditStringClass[status.checked] = 'text-success';
+    auditStringClass[status.checkNo] = 'text-warning';
+    auditStringClass[status.checkNoPre] = 'text-warning';
+    auditStringClass[status.checkAgain] = 'text-warning';
+    auditStringClass[status.checkCancel] = 'text-warning';
+    auditStringClass[status.checkSkip] = 'text-success';
+    /* ------------------------------------------------------- */
+
+    /**
+     * 期列表,审批进度一列
+     */
+    // 描述文本
+    const auditProgress = [];
+    auditProgress[status.uncheck] = '待上报';
+    auditProgress[status.checking] = '审批中';
+    auditProgress[status.checked] = '审批完成';
+    auditProgress[status.checkNo] = '审批退回';
+    auditProgress[status.checkNoPre] = '审批中';
+    auditProgress[status.checkAgain] = '审批中';
+    auditProgress[status.checkCancel] = '';
+    // 样式
+    const auditProgressClass = [];
+    auditProgressClass[status.uncheck] = 'text-secondary';
+    auditProgressClass[status.checking] = 'text-warning';
+    auditProgressClass[status.checked] = 'text-success';
+    auditProgressClass[status.checkNo] = 'text-warning';
+    auditProgressClass[status.checkNoPre] = 'text-warning';
+    auditProgressClass[status.checkAgain] = 'text-warning';
+    auditProgressClass[status.checkCancel] = '';
+
+    const auditClassColor = [];
+    auditClassColor[status.uncheck] = '#6c757d';
+    auditClassColor[status.checking] = '#da9500';
+    auditClassColor[status.checked] = '#28a745';
+    auditClassColor[status.checkNo] = '#da9500';
+    auditClassColor[status.checkNoPre] = '#da9500';
+    // auditClassColor[status.checkAgain] = '#ffc107';
+    // auditClassColor[status.checkCancel] = '#ffc107';
+    /* ------------------------------------------------------- */
+    return {
+        status, statusString, statusClass,
+        statusButton, statusButtonClass,
+        auditString, auditStringClass,
+        auditProgress, auditProgressClass, auditClassColor,
+    };
+})();
+
+
 // 推送类型
 const pushType = {
     material: 1,
@@ -1571,4 +1689,5 @@ module.exports = {
     changePlan,
     financial,
     inspection,
+    contract,
 };

+ 8 - 0
app/const/shenpi.js

@@ -25,6 +25,7 @@ const sp_type = {
     // cost_stage_ledger: 13,
     // cost_stage_book: 14,
     // cost_stage_analysis: 15,
+    contract: 16,
 };
 const sp_other_type = {
     financial: 8,
@@ -55,6 +56,7 @@ const sp_lc = [
     { code: 'inspection', type: sp_type.inspection, name: '质量巡检审批' },
     { code: 'safe_payment', type: sp_type.safe_payment, name: '安全计量审批' },
     { code: 'safe_inspection', type: sp_type.safe_inspection, name: '安全巡检审批' },
+    { code: 'contract', type: sp_type.contract, name: '标段合同审批' },
 ];
 const cost_sp_lc = [
     { code: 'cost_stage_ledger', type: cost_sp_type.cost_stage_ledger, name: '成本报审' },
@@ -84,6 +86,11 @@ const change_type_list = [
     { code: 'changeApply', name: '变更申请' },
     { code: 'changePlan', name: '变更方案' },
 ];
+const contract_type_list = [
+    { code: 'contract', name: '合同审批' },
+    { code: 'expenses', name: '合同支付' },
+    { code: 'income', name: '合同回款' },
+];
 
 module.exports = {
     sp_type,
@@ -95,4 +102,5 @@ module.exports = {
     sp_status_list,
     change_type,
     change_type_list,
+    contract_type_list,
 };

+ 4 - 0
app/const/sp_page_show.js

@@ -119,6 +119,10 @@ const defaultSetting = {
     safePayment: 1,
     safeInspection: 1,
     posCalcDetail: 0,
+    openContractSubProjectShenpi: 0,
+    openContractPaySubProjectShenpi: 0,
+    openContractTenderShenpi: 0,
+    openContractPayTenderShenpi: 0,
 };
 
 module.exports = {

+ 51 - 0
app/const/sp_shenpi.js

@@ -0,0 +1,51 @@
+'use strict';
+
+/**
+ * 审批流程设置
+ *
+ * @author ELlisran
+ * @date 2020/10/20
+ * @version
+ */
+// 审批类型
+const sp_type = {
+    contract: 16,
+};
+const sp_other_type = {
+};
+
+const sp_lc = [
+    { code: 'contract', type: sp_type.contract, name: '项目合同审批' },
+];
+
+const sp_status = {
+    sqspr: 1, // 授权审批人
+    gdspl: 2, // 固定审批流
+    gdzs: 3, // 固定终审
+};
+const sp_status_list = [];
+sp_status_list[sp_status.sqspr] = { status: sp_status.sqspr, name: '授权审批人', msg: '由上报人设置审批流程' };
+sp_status_list[sp_status.gdspl] = { status: sp_status.gdspl, name: '固定审批流', msg: '审批流程固定,上报人只能按照设置好的审批流程进行' };
+sp_status_list[sp_status.gdzs] = { status: sp_status.gdzs, name: '固定终审', msg: '结束审批流为固定人,终审前的审批流程由上报人设置,即授权审批人' };
+
+const contract_type_list = [
+    { code: 'contract', name: '合同审批' },
+    { code: 'expenses', name: '合同支付' },
+    { code: 'income', name: '合同回款' },
+];
+
+const defaultInfo = {
+    shenpi: {
+        contract: 1,
+    },
+};
+
+module.exports = {
+    sp_type,
+    sp_other_type,
+    sp_lc,
+    sp_status,
+    sp_status_list,
+    contract_type_list,
+    defaultInfo,
+};

+ 1 - 0
app/const/tender_info.js

@@ -200,6 +200,7 @@ const defaultInfo = {
         cost_stage_ledger: 1,
         cost_stage_book: 1,
         cost_stage_analysis: 1,
+        contract: 1,
     },
     ledger_check: {
         same_code: true,

+ 261 - 10
app/controller/contract_controller.js

@@ -5,6 +5,7 @@ const stdDataAddType = {
     next: 3,
 };
 const auditConst = require('../const/audit');
+const shenpiConst = require('../const/sp_shenpi');
 const contractConst = require('../const/contract');
 const moment = require('moment');
 const sendToWormhole = require('stream-wormhole');
@@ -277,7 +278,8 @@ module.exports = app => {
                     ctx.contract.valuation, ctx.contract.measure_type) : await ctx.service.budgetStd.getStdList(ctx.subProject.std_id, 'yu');
                 const commonJson = ctx.subProject.common_json ? JSON.parse(ctx.subProject.common_json) : null;
                 const types = commonJson && commonJson[(ctx.contract_tender ? 'tender_' : '') + 'contract_type'] ? commonJson[(ctx.contract_tender ? 'tender_' : '') + 'contract_type'] : [];
-
+                const shenpi_status = ctx.contract_tender ? ctx.subProject.page_show.openContractTenderShenpi : ctx.subProject.page_show.openContractSubProjectShenpi;
+                const pay_shenpi_status = ctx.contract_tender ? ctx.subProject.page_show.openContractPayTenderShenpi : ctx.subProject.page_show.openContractPaySubProjectShenpi;
                 const renderData = {
                     contractTreeAudits,
                     audit_permission: ctx.contract_audit_permission,
@@ -290,19 +292,37 @@ module.exports = app => {
                     thisUrl: `/sp/${ctx.subProject.id}` + (ctx.contract_tender ? `/contract/tender/${ctx.contract.id}/detail` : '/contract/detail'),
                     stdChapters,
                     is_setting: false,
+                    shenpi_status,
+                    pay_shenpi_status,
+                    auditConst: auditConst.contract,
+                    shenpiConst,
+                    auditType: auditConst.auditType,
                 };
+                if (ctx.contract_tender) {
+                    const tenderInfo = await ctx.service.tenderInfo.getTenderInfo(ctx.contract.id);
+                    if (tenderInfo.shenpi.contract === shenpiConst.sp_status.gdspl) {
+                        renderData.spGroupList = await ctx.service.shenpiGroup.getGroupListByChangeType(ctx.contract.id, shenpiConst.sp_type.contract, 'contract');
+                        renderData.paySpGroupList = await ctx.service.shenpiGroup.getGroupListByChangeType(ctx.contract.id, shenpiConst.sp_type.contract, contractConst.typeMap[ctx.contract_type]);
+                    }
+                    renderData.shenpi = tenderInfo.shenpi;
+                } else {
+                    if (ctx.subProject.shenpi.contract === shenpiConst.sp_status.gdspl) {
+                        // 获取固定审批流列表
+                        renderData.spGroupList = await ctx.service.shenpiGroup.getGroupListByChangeType(ctx.subProject.id, shenpiConst.sp_type.contract, 'contract');
+                        renderData.paySpGroupList = await ctx.service.shenpiGroup.getGroupListByChangeType(ctx.subProject.id, shenpiConst.sp_type.contract, contractConst.typeMap[ctx.contract_type]);
+                    }
+                    renderData.shenpi = ctx.subProject.shenpi;
+                }
 
                 const contractColSet = await ctx.service.contractColSet.getContractColSet(ctx.subProject.id, ctx.contractOptions.tid, ctx.contract_type);
                 renderData.colSet = ctx.service.contractColSet.analysisColSetWithDefine(contractConst.colSet[ctx.contract_type], contractColSet.info, contractConst.defaultColSet[ctx.contract_type]);
-                if (ctx.session.sessionUser.is_admin) {
-                    const accountList = await ctx.service.projectAccount.getAllSubProjectAccount(ctx.subProject);
-                    renderData.accountList = accountList;
-                    const unitList = await ctx.service.constructionUnit.getAllDataByCondition({ where: { pid: ctx.session.sessionProject.id } });
-                    renderData.accountGroup = unitList.map(item => {
-                        const groupList = accountList.filter(item1 => item1.company === item.name);
-                        return { groupName: item.name, groupList };
-                    }).filter(x => { return x.groupList.length > 0; });
-                }
+                const accountList = await ctx.service.projectAccount.getAllSubProjectAccount(ctx.subProject);
+                renderData.accountList = accountList;
+                const unitList = await ctx.service.constructionUnit.getAllDataByCondition({ where: { pid: ctx.session.sessionProject.id } });
+                renderData.accountGroup = unitList.map(item => {
+                    const groupList = accountList.filter(item1 => item1.company === item.name);
+                    return { groupName: item.name, groupList };
+                }).filter(x => { return x.groupList.length > 0; });
                 await this.layout('contract/detail.ejs', renderData, 'contract/detail_modal.ejs');
             } catch (err) {
                 ctx.log(err);
@@ -366,6 +386,147 @@ module.exports = app => {
             }
         }
 
+        /**
+         * 审批及上报和审批人员增删改,切换审批组等
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async checkAudit(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.postType || !data.id) throw '数据错误';
+                const responseData = {
+                    err: 0, msg: '', data: {},
+                };
+                const contract = isNaN(data.id) ? await ctx.service.contract.getDataById(data.id) : await ctx.service.contractPay.getDataById(data.id);
+                if (!contract) {
+                    throw '未找到合同相关数据';
+                }
+                const tip = isNaN(data.id) ? '' : contractConst.typeName[contract.contract_type];
+                // 判断功能是否已关闭
+                const shenpi_status = ctx.contract_tender ? ctx.subProject.page_show.openContractTenderShenpi : ctx.subProject.page_show.openContractSubProjectShenpi;
+                const pay_shenpi_status = ctx.contract_tender ? ctx.subProject.page_show.openContractPayTenderShenpi : ctx.subProject.page_show.openContractPaySubProjectShenpi;
+                if (isNaN(data.id)) {
+                    if (!shenpi_status) {
+                        throw '合同审批功能已关闭, 无法进行审批操作, 请刷新页面';
+                    }
+                } else {
+                    if (!pay_shenpi_status) {
+                        throw '合同' + tip + '审批功能已关闭, 无法进行审批操作, 请刷新页面';
+                    }
+                }
+                let shenpi = '';
+                if (ctx.contract_tender) {
+                    const tenderInfo = await ctx.service.tenderInfo.getTenderInfo(ctx.contract.id);
+                    shenpi = tenderInfo ? tenderInfo.shenpi : '';
+                } else {
+                    shenpi = ctx.subProject.shenpi;
+                }
+                await ctx.service.contractSpAudit.loadUser(contract);
+                await ctx.service.contractSpAudit.loadAuditViewData(contract);
+                const cid = contract.cid ? contract.cid : contract.id;
+                const cpid = contract.cid ? contract.id : null;
+                switch (data.postType) {
+                    case 'start':
+                        if (contract.status !== auditConst.contract.status.uncheck && contract.status !== auditConst.contract.status.checkNo) {
+                            throw '该合同' + tip + '当前无法上报';
+                        }
+                        await ctx.service.contractSpAudit.start(ctx.contractOptions, shenpi, cid, cpid, contract.times);
+                        break;
+                    case 'check':
+                        if (contract.status !== auditConst.contract.status.checking && contract.status !== auditConst.contract.status.checkNoPre) {
+                            throw '当前合同' + tip + '数据有误';
+                        }
+                        if (contract.curAuditorIds.length === 0 || contract.curAuditorIds.indexOf(ctx.session.sessionUser.accountId) === -1) {
+                            throw '您无权进行该操作';
+                        }
+                        const checkData = {
+                            checkType: parseInt(data.checkType),
+                            opinion: data.opinion,
+                        };
+                        if (!checkData.checkType || isNaN(checkData.checkType)) {
+                            throw '提交数据错误';
+                        }
+                        await ctx.service.contractSpAudit.check(contract, checkData);
+                        break;
+                    case 'add-audit':
+                        if (shenpi.contract === shenpiConst.sp_status.gdspl) {
+                            throw '固定审批流程不允许添加人员,请刷新页面';
+                        }
+                        const add_id = this.app._.toInteger(data.auditorId);
+                        if (isNaN(add_id) || add_id <= 0) {
+                            throw '参数错误';
+                        }
+                        // 检查权限等
+                        if (!(contract.uid === ctx.session.sessionUser.accountId || ctx.contract_audit_permission.permission_edit_contract)) {
+                            throw '您无权添加审核人';
+                        }
+                        if (contract.status !== auditConst.contract.status.uncheck && contract.status !== auditConst.contract.status.checkNo) {
+                            throw '当前不允许添加审核人';
+                        }
+
+                        const auditorList = await ctx.service.contractSpAudit.getAuditors(cid, cpid, contract.times, 'asc', true);
+                        // 检查审核人是否已存在
+                        const exist = this.app._.find(auditorList, { aid: add_id });
+                        if (exist) {
+                            throw '该审核人已存在,请勿重复添加';
+                        }
+                        const shenpiCondition = ctx.helper._.assign({ sp_type: shenpiConst.sp_type.contract, sp_status: shenpiConst.sp_status.gdzs }, ctx.contractOptions);
+                        const shenpiInfo = await ctx.service.shenpiAudit.getDataByCondition(shenpiCondition);
+                        const is_gdzs = shenpiInfo && shenpi.contract === shenpiConst.sp_status.gdzs ? 1 : 0;
+                        const add_result = await ctx.service.contractSpAudit.addAuditor(ctx.contractOptions, cid, cpid, add_id, contract.times, is_gdzs);
+                        if (!add_result) {
+                            throw '添加审核人失败';
+                        }
+                        break;
+                    case 'del-audit':
+                        if (shenpi.contract === shenpiConst.sp_status.gdspl) {
+                            throw '固定审批流程不允许删除人员,请刷新页面';
+                        }
+                        const del_id = data.auditorId instanceof Number ? data.auditorId : this.app._.toNumber(data.auditorId);
+                        if (isNaN(del_id) || del_id <= 0) {
+                            throw '参数错误';
+                        }
+                        // 检查权限等
+                        if (!(contract.uid === ctx.session.sessionUser.accountId || ctx.contract_audit_permission.permission_edit_contract)) {
+                            throw '您无权移除审核人';
+                        }
+                        if (contract.status !== auditConst.contract.status.uncheck && contract.status !== auditConst.contract.status.checkNo) {
+                            throw '当前不允许移除审核人';
+                        }
+
+                        const del_result = await ctx.service.contractSpAudit.deleteAuditor(cid, cpid, del_id, contract.times);
+                        if (!del_result) {
+                            throw '移除审核人失败';
+                        }
+                        break;
+                    case 'change-sp':
+                        if (shenpi.contract !== shenpiConst.sp_status.gdspl) {
+                            throw '非固定审批流程不允许切换审批组,请刷新页面';
+                        }
+                        if (contract.status !== auditConst.contract.status.uncheck && contract.status !== auditConst.contract.status.checkNo) {
+                            throw '当前状态不允许切换审批组';
+                        }
+                        const chagne_result = await ctx.service.contractSpAudit.changeSpGroup(contract, data.sp_group);
+                        if (!chagne_result) {
+                            throw '切换审批组失败';
+                        }
+                        break;
+                    default:
+                        throw '参数错误';
+                }
+                const newContract = isNaN(data.id) ? await ctx.service.contract.getDataById(data.id) : await ctx.service.contractPay.getDataById(data.id);
+                await ctx.service.contractSpAudit.loadUser(newContract);
+                await ctx.service.contractSpAudit.loadAuditViewData(newContract);
+                responseData.data = newContract;
+                ctx.body = responseData;
+            } catch (err) {
+                console.log(err);
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+        }
+
         async updateBills(ctx) {
             try {
                 const data = JSON.parse(ctx.request.body.data);
@@ -405,6 +566,14 @@ module.exports = app => {
                         responseData.data = await ctx.service.contractTreeAudit.dels(options, data.postData.select, data.postData.auditIds);
                         break;
                     case 'get-contract':
+                        const contract = await ctx.service.contract.getDataById(data.postData);
+                        const shenpi_status = ctx.contract_tender ? ctx.subProject.page_show.openContractTenderShenpi : ctx.subProject.page_show.openContractSubProjectShenpi;
+                        if (shenpi_status) {
+                            await ctx.service.contractSpAudit.loadUser(contract);
+                            await ctx.service.contractSpAudit.loadAuditViewData(contract);
+                            await ctx.service.contractSpAudit.checkShenpi(contract);
+                        }
+                        responseData.data.contract = contract;
                         responseData.data.pays = await ctx.service.contractPay.getPays(options, data.postData);
                         responseData.data.files = await ctx.service.contractAtt.getAtt(data.postData);
                         break;
@@ -692,6 +861,88 @@ module.exports = app => {
                 ctx.redirect(`/sp/${ctx.subProject.id}/dashboard`);
             }
         }
+
+        /**
+         * 保存功能设置相关
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async updateSetting(ctx) {
+            try {
+                if (!ctx.subProject) throw '没有对应的项目数据';
+                if (ctx.session.sessionUser.is_admin === 0) throw '没有访问权限';
+
+                const data = JSON.parse(ctx.request.body.data);
+                if (data) ctx.request.body = data;
+
+                switch (data.type) {
+                    case 'page_show':
+                        this.ctx.subProject.page_show.openContractSubProjectShenpi = data.openContractSubProjectShenpi ? 1 : 0;
+                        this.ctx.subProject.page_show.openContractPaySubProjectShenpi = data.openContractPaySubProjectShenpi ? 1 : 0;
+                        this.ctx.subProject.page_show.openContractTenderShenpi = data.openContractTenderShenpi ? 1 : 0;
+                        this.ctx.subProject.page_show.openContractPayTenderShenpi = data.openContractPayTenderShenpi ? 1 : 0;
+                        const result = await ctx.service.contract.savePageShow(ctx.subProject.id, ctx.subProject.page_show);
+                        if (!result) throw '保存数据失败';
+                        break;
+                    default:
+                        throw '参数有误';
+                }
+                ctx.body = { err: 0, msg: '', data: null };
+            } catch (error) {
+                ctx.log(error);
+                this.ajaxErrorBody(error, '保存数据失败');
+            }
+        }
+
+        async shenpiSet(ctx) {
+            try {
+                if (!ctx.subProject) throw '没有对应的项目数据';
+                if (ctx.session.sessionUser.is_admin === 0) throw '没有访问权限';
+                // 获取所有项目参与者
+                const accountList = await ctx.service.projectAccount.getAllSubProjectAccount(ctx.subProject);
+                const unitList = await ctx.service.constructionUnit.getAllDataByCondition({ where: { pid: ctx.session.sessionProject.id } });
+                const accountGroupList = unitList.map(item => {
+                    const groupList = accountList.filter(item1 => item1.company === item.name);
+                    return { groupName: item.name, groupList };
+                }).filter(x => { return x.groupList.length > 0; });
+                // 获取固定审批流 or 固定终审
+                const spConst = ctx.helper._.cloneDeep(shenpiConst);
+                for (const sp of spConst.sp_lc) {
+                    sp.status = ctx.subProject.shenpi ? ctx.subProject.shenpi[sp.code] : spConst.sp_status.sqspr;
+                    if (sp.status === shenpiConst.sp_status.gdspl) {
+                        sp.groupList = await ctx.service.shenpiGroup.getGroupList(ctx.subProject.id, sp.type) || [];
+                        if (sp.groupList && sp.groupList.length > 0) {
+                            for (const group of sp.groupList) {
+                                if (group.change_type) group.change_type = JSON.parse(group.change_type);
+                                group.auditGroupList = await ctx.service.shenpiAudit.getAuditGroupList(ctx.subProject.id, sp.type, sp.status, group.id);
+                                if (group.is_select) sp.auditGroupList = group.auditGroupList;
+                            }
+                        } else {
+                            sp.auditGroupList = await ctx.service.shenpiAudit.getAuditGroupList(ctx.subProject.id, sp.type, sp.status);
+                        }
+                    } else if (sp.status === shenpiConst.sp_status.gdzs) {
+                        sp.audit = await ctx.service.shenpiAudit.getAudit(ctx.subProject.id, sp.type, sp.status);
+                    }
+                }
+                const renderData = {
+                    shenpi: spConst,
+                    accountList,
+                    accountGroup: accountGroupList,
+                    auditConst,
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.contract.shenpi),
+                    auditType: auditConst.auditType,
+                    types_from: 'subProject',
+                    thisUrl: `/sp/${ctx.subProject.id}/contract/setting`,
+                    contract_type_list: shenpiConst.contract_type_list,
+                    is_setting: true,
+                };
+                await this.layout('contract/shenpi.ejs', renderData, 'contract/shenpi_modal.ejs');
+            } catch (err) {
+                ctx.log(err);
+                ctx.session.postError = err.toString();
+                ctx.redirect(`/sp/${ctx.subProject.id}/dashboard`);
+            }
+        }
     }
 
     return ContractController;

+ 82 - 0
app/controller/sub_proj_controller.js

@@ -12,6 +12,7 @@ const accountGroup = require('../const/account_group').group;
 const sendToWormhole = require('stream-wormhole');
 const path = require('path');
 const projectSetting = require('../const/project_setting');
+const shenpiConst = require('../const/sp_shenpi');
 
 module.exports = app => {
     class SubProjController extends app.BaseController {
@@ -587,6 +588,87 @@ module.exports = app => {
                 ctx.ajaxErrorBody(err, '刷新金额概况数据失败');
             }
         }
+
+        async saveShenpi(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data) {
+                    throw '提交数据错误';
+                }
+                // 判断修改权限
+                if (ctx.session.sessionUser.is_admin === 0) {
+                    throw '你没有权限修改审批流程';
+                }
+                const postData = ctx.subProject.shenpi;
+                postData[data.code] = data.status;
+                await ctx.service.subProject.saveInfo(ctx.subProject.id, { shenpi: postData });
+                let auditList = [];
+                let groupList = [];
+                if (data.status === shenpiConst.sp_status.gdspl) {
+                    groupList = await ctx.service.shenpiGroup.getGroupList(ctx.subProject.id, shenpiConst.sp_type[data.code]) || [];
+                    if (groupList && groupList.length > 0) {
+                        for (const group of groupList) {
+                            if (group.change_type) group.change_type = JSON.parse(group.change_type);
+                            group.auditGroupList = await ctx.service.shenpiAudit.getAuditGroupList(ctx.subProject.id, shenpiConst.sp_type[data.code], data.status, group.id);
+                            if (group.is_select) auditList = group.auditGroupList;
+                        }
+                    } else {
+                        auditList = await ctx.service.shenpiAudit.getAuditGroupList(ctx.subProject.id, shenpiConst.sp_type[data.code], data.status);
+                    }
+                } else if (data.status === shenpiConst.sp_status.gdzs) {
+                    auditList = await ctx.service.shenpiAudit.getAudit(ctx.subProject.id, shenpiConst.sp_type[data.code], data.status);
+                }
+                ctx.body = { err: 0, msg: '', data: { auditList, groupList } };
+            } catch (err) {
+                this.log(err);
+                ctx.body = this.ajaxErrorBody(err, '保存审批流程设置失败');
+            }
+        }
+
+        async saveShenpiAudit(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data) {
+                    throw '提交数据错误';
+                }
+                // 判断修改权限
+                if (ctx.session.sessionUser.is_admin === 0) {
+                    throw '你没有权限修改审批流程';
+                }
+                let info = '';
+                switch (data.type) {
+                    case 'add':
+                        info = await ctx.service.shenpiAudit.addAudit(data, ctx.subProject.id);
+                        break;
+                    case 'del':
+                        await ctx.service.shenpiAudit.removeAudit(data, ctx.subProject.id);
+                        break;
+                    // case 'copy2ot':
+                    //     await ctx.service.shenpiAudit.copyAudit2otherTender(data, ctx.subProject.id);
+                    //     break;
+                    // case 'copy2os':
+                    //     await ctx.service.shenpiAudit.copyAudit2otherShenpi(data);
+                    //     break;
+                    case 'audit-type':
+                        await ctx.service.shenpiAudit.setAuditType(data, ctx.subProject.id);
+                        break;
+                    case 'save-group':
+                        info = await ctx.service.shenpiGroup.saveGroup(ctx.subProject.id, data);
+                        break;
+                    case 'change-group':
+                        await ctx.service.shenpiGroup.changeGroup(data);
+                        break;
+                    case 'delete-group':
+                        await ctx.service.shenpiGroup.deleteGroup(data);
+                        break;
+                    default:break;
+                }
+                ctx.body = { err: 0, msg: '', data: info };
+            } catch (err) {
+                this.log(err);
+                ctx.body = this.ajaxErrorBody(err, '保存审批流程设置失败');
+            }
+        }
     }
 
     return SubProjController;

+ 1 - 0
app/controller/sub_proj_setting_controller.js

@@ -592,6 +592,7 @@ module.exports = app => {
                     spid: ctx.subProject.id,
                     scPermission: scheduleConst.permission,
                     change_type_list: shenpiConst.change_type_list,
+                    contract_type_list: shenpiConst.contract_type_list,
                     subProjects,
                 };
                 const permissionKey = ['quality', 'inspection', 'safe_inspection', 'safe_payment', 'schedule'];

+ 1 - 0
app/controller/tender_controller.js

@@ -1073,6 +1073,7 @@ module.exports = app => {
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.tender.shenpi),
                     auditType: auditConst.auditType,
                     change_type_list: shenpiConst.change_type_list,
+                    contract_type_list: shenpiConst.contract_type_list,
                 };
                 await this.layout('tender/shenpi.ejs', renderData, 'tender/shenpi_modal.ejs');
             } catch (err) {

+ 5 - 1
app/middleware/contract_check.js

@@ -37,7 +37,11 @@ module.exports = options => {
             cloneOptions.uid = this.session.sessionUser.accountId;
             let result = false;
             if (tid) {
-                result = yield this.service.contractAudit.getDataByCondition(cloneOptions);
+                if (this.session.sessionUser.is_admin) {
+                    result = yield this.service.subProjPermission.getContractPermission(this.subProject.permission.contract_permission);
+                } else {
+                    result = yield this.service.contractAudit.getDataByCondition(cloneOptions);
+                }
             } else {
                 const contractPermission = this.subProject.permission.contract_permission;
                 if (contractPermission.length > 0 && _.intersection([3, 4, 5], contractPermission).length > 0) {

+ 1 - 0
app/middleware/sub_project_check.js

@@ -40,6 +40,7 @@ module.exports = options => {
             this.subProject.page_show = this.service.subProject.getPageShow(this.subProject.page_show);
             this.subProject.fun_set = this.service.subProject.getFunSet(this.subProject.fun_set);
             this.subProject.fun_rela = this.service.subProject.getFunRela(this.subProject);
+            this.subProject.shenpi = this.service.subProject.getShenpi(this.subProject.shenpi);
 
             if (this.session.sessionUser.is_admin) {
                 this.subProject.readOnly = false;

+ 614 - 32
app/public/js/contract_detail.js

@@ -72,6 +72,17 @@ $(document).ready(function() {
     const getStackedBarTipSf = function (data) {
         return data.stackedBarSfTips ? data.stackedBarSfTips.join('\n') : '';
     };
+    if (shenpi_status) {
+        contractSpreadSetting.cols.push(
+            {title: '审批进度', colSpan: '1', rowSpan: '2', field: 'shenpi_status', hAlign: 1, width: 100, formatter: '@', readOnly: true, getValue:'getValue.shenpi_status', foreColor:'foreColor.shenpi_status'},
+        );
+        contractCol.getValue.shenpi_status = function (data) {
+            return data.c_code && data.status && (!data.settle_code || (data.settle_code && data.status === auditConst.status.checked)) ? auditConst.auditProgress[data.status] : '';
+        };
+        contractCol.foreColor.shenpi_status = function (data) {
+            return data.c_code && data.status ? auditConst.auditClassColor[data.status] : '';
+        };
+    }
     const colMap = {
       yf_price: {title: '累计应付', colSpan: '1', rowSpan: '2', field: 'yf_price', hAlign: 2, width: 120, formatter: '@', readOnly: true},
       stackedBar: {title: '应付进度', colSpan: '1', rowSpan: '2', formatter: '@', readOnly: true, field: 'stackedBar', hAlign: 0, width: 200, cellType: 'stackedBar', stackedBarCover: true, bc_type: 'grid', getTip: getStackedBarTip, hintNum: true},
@@ -310,7 +321,7 @@ $(document).ready(function() {
         },
         changeContractTab: function (node, need = false) {
             if ($('.bc-bar .nav li .active').attr('href') === '#htdetail') {
-                if (node.settle_code) {
+                if (node.settle_code || (shenpi_status && node.status !== auditConst.status.uncheck && node.status !== auditConst.status.checkNo)) {
                     $('#edit_contract_btn').hide();
                 } else {
                     $('#edit_contract_btn').show();
@@ -336,6 +347,7 @@ $(document).ready(function() {
                 $('#edit_contract_btn').hide();
                 $('#save_contract_btn').hide();
                 $('#cancel_contract_btn').hide();
+                $('#add_contract_pay_btn').hide();
                 if (node.settle_code) {
                     $('#add_contract_pay_btn').hide();
                     if (need) {
@@ -343,10 +355,10 @@ $(document).ready(function() {
                         $('#htpay-table tbody').find('.pay-del').hide();
                     }
                 } else {
-                    if (node.uid === user_id || permission_add_pay) $('#add_contract_pay_btn').show();
+                    if ((node.uid === user_id || permission_add_pay) && (!shenpi_status || (shenpi_status && node.status === auditConst.status.checked))) $('#add_contract_pay_btn').show();
                     if (need && contractPays && contractPays.length > 0) {
                         for (const [i, cp] of contractPays.entries()) {
-                            if (cp.uid === user_id) {
+                            if (cp.uid === user_id && (!pay_shenpi_status || (pay_shenpi_status && (cp.status === auditConst.status.uncheck || cp.status === auditConst.status.checkNo)))) {
                                 $('#htpay-table tbody tr').eq(i).find('.pay-edit').show();
                                 $('#htpay-table tbody tr').eq(i).find('.pay-del').show();
                             }
@@ -365,6 +377,102 @@ $(document).ready(function() {
         openContractPayFiles: function (pay) {
             this.setContractFiles(pay.files, pay.cid, pay.id, '#cons-pay-file table tbody');
         },
+        checkCloseStatus: function (node) {
+            if (pay_shenpi_status) {
+                let have_uncheck = false;
+                for (const pay of contractPays) {
+                    if (pay.status !== auditConst.status.checked) {
+                        have_uncheck = true;
+                        break;
+                    }
+                }
+                if (have_uncheck) {
+                    $('a[href*="#cons-close"]').hide();
+                } else if ((node.uid === user_id || permission_edit_contract) && !node.settle_code) {
+                    $('a[href*="#cons-close"]').show();
+                }
+            } else if ((node.uid === user_id || permission_edit_contract) && !node.settle_code) {
+                $('a[href*="#cons-close"]').show();
+            }
+        },
+        setContractBtn: function (node) {
+            if (shenpi_status) {
+                let shenpi_html = '';
+                $('a[href*="#cons-unlock"]').hide();
+                $('a[href*="#cons-close"]').hide();
+                if ((node.uid === user_id || permission_edit_contract) && node.settle_code && node.status !== auditConst.status.checked) {
+                    $('a[href*="#cons-unlock"]').show();
+                } else if (!(node.settle_code && node.status !== auditConst.status.checked)) {
+                    if (node.status === auditConst.status.uncheck) {
+                        if (node.uid === user_id || is_admin || permission_edit_contract) {
+                            shenpi_html += '<a href="javascript:void(0);" node-cid="' + node.id + '" node-cpid="" class="btn btn-primary btn-sm show-sub-sp">待上报</a>';
+                        } else {
+                            shenpi_html += '<button class="btn btn-outline-secondary btn-sm" data-toggle="tooltip" data-placement="bottom" title="上报中">上报中</button>';
+                        }
+                    } else if (node.status === auditConst.status.checkNo) {
+                        if (node.uid === user_id || is_admin || permission_edit_contract) {
+                            shenpi_html += '<a href="javascript:void(0);" node-cid="' + node.id + '" node-cpid="" class="btn btn-primary btn-sm show-sub-sp mr-2">重新上报</a>';
+                        }
+                        shenpi_html += '<a href="javascript: void(0);" node-cid="' + node.id + '" node-cpid="" data-title="审批退回" class="btn btn-outline-warning btn-sm show-sp-list">审批退回</a>';
+                    } else if (node.status === auditConst.status.checked) {
+                        shenpi_html += '<a href="javascript:void(0);" node-cid="' + node.id + '" node-cpid="" class="btn btn-outline-success btn-sm mr-1 show-sp-list">审批完成</a>';
+                        if (node.settle_code) {
+                            $('a[href*="#cons-unlock"]').show();
+                            $('a[href*="#cons-close"]').hide();
+                        } else {
+                            $('a[href*="#cons-unlock"]').hide();
+                            contractTreeSpreadObj.checkCloseStatus(node);
+                        }
+                    } else if (node.status === auditConst.status.checking || node.status === auditConst.status.checkNoPre) {
+                        if (node.curAuditorIds.length > 0 && node.curAuditorIds.indexOf(user_id) > -1) {
+                            shenpi_html += '<a href="javascript: void(0);" node-cid="' + node.id + '" node-cpid="" data-operate="checked" data-title="审批通过" class="btn btn-success btn-sm mr-2 show-sp-list">审批通过</a>';
+                            shenpi_html += '<a href="javascript: void(0);" node-cid="' + node.id + '" node-cpid="" data-operate="checkNo" data-title="审批退回" class="btn btn-warning btn-sm show-sp-list">审批退回</a>';
+                        } else if (node.status === auditConst.status.checking) {
+                            shenpi_html += '<a href="javascript:void(0);" node-cid="' + node.id + '" node-cpid="" class="btn btn-outline-secondary btn-sm btn-block show-sp-list">审批中</a>';
+                        } else if (node.status === auditConst.status.checkNoPre) {
+                            shenpi_html += '<a href="javascript:void(0);" node-cid="' + node.id + '" node-cpid="" class="btn btn-outline-warning btn-sm btn-block show-sp-list">审批退回</a>';
+                        }
+                    }
+                }
+                $('#shenpi_btn').html(shenpi_html);
+                if (node.uid === user_id || permission_edit_contract) {
+                    contractTreeSpreadObj.changeContractTab(node);
+                } else {
+                    $('#edit_contract_btn').hide();
+                    $('#save_contract_btn').hide();
+                    $('#cancel_contract_btn').hide();
+                    $('a[href*="#cons-upfile"]').hide();
+                    if (node && node.c_code && $('.bc-bar .nav li .active').attr('href') === '#htfile' && permission_att) $('a[href*="#cons-upfile"]').show();
+                    if (node && node.c_code && $('.bc-bar .nav li .active').attr('href') === '#htpay' && (node.uid === user_id || permission_add_pay) && node.status === auditConst.status.checked) {
+                        $('#add_contract_pay_btn').show();
+                    } else {
+                        $('#add_contract_pay_btn').hide();
+                    }
+                }
+            } else if ((node.uid === user_id || permission_edit_contract)) {
+                if (node.settle_code) {
+                    $('a[href*="#cons-unlock"]').show();
+                    $('a[href*="#cons-close"]').hide();
+                } else {
+                    $('a[href*="#cons-unlock"]').hide();
+                    contractTreeSpreadObj.checkCloseStatus(node);
+                }
+                contractTreeSpreadObj.changeContractTab(node);
+            } else {
+                $('#edit_contract_btn').hide();
+                $('#save_contract_btn').hide();
+                $('#cancel_contract_btn').hide();
+                $('a[href*="#cons-unlock"]').hide();
+                $('a[href*="#cons-close"]').hide();
+                $('a[href*="#cons-upfile"]').hide();
+                if (node && node.c_code && $('.bc-bar .nav li .active').attr('href') === '#htfile' && permission_att) $('a[href*="#cons-upfile"]').show();
+                if (node && node.c_code && $('.bc-bar .nav li .active').attr('href') === '#htpay' && (node.uid === user_id || permission_add_pay)) {
+                    $('#add_contract_pay_btn').show();
+                } else {
+                    $('#add_contract_pay_btn').hide();
+                }
+            }
+        },
         setContractFiles: function (files, cid, cpid = null, _this = '#htfile-table tbody') {
             let filesHtml = '';
             const newFiles = files.map(file => {
@@ -393,13 +501,14 @@ $(document).ready(function() {
             let paysHtml = '';
             const newPays = pays.map(pay => {
                 let showEdit = false;
-                if (pay.uid === user_id && !node.settle_code) {
+                if (pay.uid === user_id && !node.settle_code && (!pay_shenpi_status || (pay_shenpi_status && (pay.status === auditConst.status.uncheck || pay.status === auditConst.status.checkNo)))) {
                     showEdit = true
                 }
                 return {...pay, showEdit}
             })
             console.log(pays);
             newPays.forEach((pay, idx) => {
+                const shenpi_html = setPayShenpiHtml(pay);
                 const operationHtml = !pay.fpcid ? `<a href="javascript:void(0);" class="text-primary pay-edit" data-id="${pay.id}" ${!pay.showEdit ? `style="display:none"` : ''}>编辑</a> <a href="javascript:void(0);" class="text-danger pay-del" data-id="${pay.id}" ${!pay.showEdit ? `style="display:none"` : ''}>删除</a>` : '';
                 paysHtml += `<tr class="text-center" data-cpid="${pay.id}">
                                         <td style="position: relative">${idx + 1}${ pay.fpcid ? '<a href="javascript:void(0);" style="position: absolute;right: 2px;top:50%;transform: translate(-50%, -50%);"><i class="fa fa-cny"></i></a>' : ''}</td>
@@ -413,6 +522,7 @@ $(document).ready(function() {
                                         <td>${moment(pay.create_time).format('YYYY-MM-DD HH:mm:ss')}</td>
                                         <td>${pay.remark}</td>
                                         <td><a href="javascript:void(0);" class="text-primary open-pay-files" data-cpid="${pay.id}"><i class="fa fa-paperclip fa-rotate-90"></i></a> <span class="files-num">${pay.files.length > 0 ? pay.files.length : ''}</span></td>
+                                        ${shenpi_html}
                                         <td>${operationHtml}</td>
                                     </tr>`;
             });
@@ -437,32 +547,12 @@ $(document).ready(function() {
                 }
                 $('#htdetail_df_price').text(ZhCalc.sub(node.yf_price, node.sf_price) || '');
                 postData(window.location.pathname + '/update', {postType: 'get-contract', postData: node.id}, function (result) {
+                    const refreshNode = contractTree.loadPostData({ update: result.contract });
+                    contractTreeSpreadObj.refreshTree(contractSheet, refreshNode);
                     contractTreeSpreadObj.setContractPays(result.pays, node);
                     contractTreeSpreadObj.setContractFiles(result.files, node.id);
+                    contractTreeSpreadObj.setContractBtn(result.contract);
                 });
-                if ((node.uid === user_id || permission_edit_contract)) {
-                    if (node.settle_code) {
-                        $('a[href*="#cons-unlock"]').show();
-                        $('a[href*="#cons-close"]').hide();
-                    } else {
-                        $('a[href*="#cons-unlock"]').hide();
-                        $('a[href*="#cons-close"]').show();
-                    }
-                    contractTreeSpreadObj.changeContractTab(node);
-                } else {
-                    $('#edit_contract_btn').hide();
-                    $('#save_contract_btn').hide();
-                    $('#cancel_contract_btn').hide();
-                    $('a[href*="#cons-unlock"]').hide();
-                    $('a[href*="#cons-close"]').hide();
-                    $('a[href*="#cons-upfile"]').hide();
-                    if (node && node.c_code && $('.bc-bar .nav li .active').attr('href') === '#htfile' && permission_att) $('a[href*="#cons-upfile"]').show();
-                    if (node && node.c_code && $('.bc-bar .nav li .active').attr('href') === '#htpay' && (node.uid === user_id || permission_add_pay)) {
-                        $('#add_contract_pay_btn').show();
-                    } else {
-                        $('#add_contract_pay_btn').hide();
-                    }
-                }
             } else {
                 $('#htdetail-table').hide();
                 $('#htpay-table').hide();
@@ -475,6 +565,7 @@ $(document).ready(function() {
                 $('a[href*="#cons-upfile"]').hide();
                 if (node && node.c_code && $('.bc-bar .nav li .active').attr('href') === '#htfile' && permission_att) $('a[href*="#cons-upfile"]').show();
                 $('#add_contract_pay_btn').hide();
+                $('#shenpi_btn').html('');
             }
         },
         selectionChanged: function (e, info) {
@@ -892,7 +983,17 @@ $(document).ready(function() {
     const contractContextMenuOptions = {
         selector: '#contract-spread',
         build: function ($trigger, e) {
+            const oldSelections = SpreadJsObj.getSelectObject(contractSheet);
+            const selections = contractSheet.getSelections();
+            const oldRow = selections && selections.length > 0 ? selections[0].row : null;
             const target = SpreadJsObj.safeRightClickSelection($trigger, e, contractSpread);
+            const newSelections = SpreadJsObj.getSelectObject(contractSheet);
+            const newRow = contractSheet.getSelections() && contractSheet.getSelections().length > 0 ? contractSheet.getSelections()[0].row : null;
+            if (oldRow !== newRow) {
+                contractTreeSpreadObj.refreshOperationValid(contractSheet);
+                contractTreeSpreadObj.setContract(contractSheet);
+                SpreadJsObj.saveTopAndSelect(contractSheet, ckBillsSpread);
+            }
             return target.hitTestType === spreadNS.SheetArea.viewport || target.hitTestType === spreadNS.SheetArea.rowHeader;
         },
         items: {}
@@ -1500,14 +1601,15 @@ $(document).ready(function() {
         $('#add_contract_pay_btn').hide();
         if (node && node.c_code) {
             if ($('.bc-bar .nav li .active').attr('href') === '#htfile' && permission_att) $('a[href*="#cons-upfile"]').show();
-            if ($('.bc-bar .nav li .active').attr('href') === '#htpay' && !node.settle_code && permission_add_pay) $('#add_contract_pay_btn').show();
+            if ($('.bc-bar .nav li .active').attr('href') === '#htpay' && !node.settle_code && permission_add_pay && (!shenpi_status || (shenpi_status && node.status === auditConst.status.checked))) $('#add_contract_pay_btn').show();
             if ((node.uid === user_id || permission_edit_contract)) contractTreeSpreadObj.changeContractTab(node, true);
         }
     });
 
     $('#edit_contract_btn').on('click', function () {
         const node = SpreadJsObj.getSelectObject(contractSheet);
-        if (node && node.c_code && (node.uid === user_id || permission_edit_contract)) {
+        console.log(node);
+        if (node && node.c_code && (node.uid === user_id || permission_edit_contract) && (!shenpi_status || (shenpi_status && node.status === auditConst.status.uncheck || node.status === auditConst.status.checkNo))) {
             $('#edit_contract_btn').hide();
             $('#save_contract_btn').show();
             $('#cancel_contract_btn').show();
@@ -1650,6 +1752,7 @@ $(document).ready(function() {
                 const refreshNode = contractTree.loadPostData(result.node);
                 contractTreeSpreadObj.refreshTree(contractSheet, refreshNode);
                 contractTreeSpreadObj.setContractPays(result.pays, node);
+                contractTreeSpreadObj.checkCloseStatus(node);
             })
         }, '确认删除该合同' + contractConst.typeName[contract_type] + '?');
     });
@@ -2161,6 +2264,10 @@ $(document).ready(function() {
             toastr.error('没有权限添加合同' + contractConst.typeName[contract_type]);
             return;
         }
+        if (!(!shenpi_status || (shenpi_status && node.status === auditConst.status.checked))) {
+            toastr.error('该合同审批状态未通过,不能添加');
+            return;
+        }
         if (node.settle_code) {
             toastr.error('该合同已结算,不能添加');
             return;
@@ -2183,6 +2290,7 @@ $(document).ready(function() {
             const refreshNode = contractTree.loadPostData(result.node);
             contractTreeSpreadObj.refreshTree(contractSheet, refreshNode);
             contractTreeSpreadObj.setContractPays(result.pays, node);
+            contractTreeSpreadObj.checkCloseStatus(node);
             // const selection = contractSheet.getSelections();
             // const sel = selection ? selection[0] : contractSheet.getSelections()[0];
             // const row = sel ? sel.row : -1;
@@ -2389,17 +2497,491 @@ $(document).ready(function() {
             const refreshNode = contractTree.loadPostData(result);
             contractTreeSpreadObj.refreshTree(contractSheet, refreshNode);
             const newNode = SpreadJsObj.getSelectObject(contractSheet);
-            contractTreeSpreadObj.changeContractTab(newNode, true);
+            console.log(newNode);
+            contractTreeSpreadObj.setContractBtn(newNode);
+            // contractTreeSpreadObj.changeContractTab(newNode, true);
             // const selection = contractSheet.getSelections();
             // const sel = selection ? selection[0] : contractSheet.getSelections()[0];
             // const row = sel ? sel.row : -1;
             // contractTreeSpreadObj.setForeColor(contractSheet, row);
             $('#cons-unlock').modal('hide');
-            $('a[href*="#cons-unlock"]').hide();
-            $('a[href*="#cons-close"]').show();
+            // $('a[href*="#cons-unlock"]').hide();
+            // if (shenpi_status && node.status !== auditConst.status.checked) {
+            //     $('a[href*="#cons-close"]').hide();
+            // } else {
+            //     $('a[href*="#cons-close"]').show();
+            // }
         });
     });
 
+    if (shenpi_status || pay_shenpi_status) {
+        $('body').on('click', '.show-sub-sp', function () {
+            console.log($(this).attr('node-cid'));
+            let node = _.find(contractTree.nodes, { id: $(this).attr('node-cid') });
+            const cpid = $(this).attr('node-cpid');
+            if (cpid) {
+                node = _.find(contractPays, { id: parseInt(cpid) });
+                $('#contract-add-group').hide();
+                $('#pay-add-group').show();
+                $('#change-pay-sp-group').val(node.sp_group);
+                $('#start-sp-cid').val(node.cid);
+                $('#start-sp-cpid').val(node.id);
+            } else {
+                $('#contract-add-group').show();
+                $('#pay-add-group').hide();
+                $('#change-sp-group').val(node.sp_group);
+                $('#start-sp-cid').val(node.id);
+                $('#start-sp-cpid').val('');
+            }
+            if (!node) {
+                toastr.error('合同数据获取失败');
+                return;
+            }
+            console.log(node);
+            setStartAuditsHtml(node);
+            $('#sub-sp').modal('show');
+        });
+
+        $('body').on('click', '.show-sp-list', function () {
+            let node = _.find(contractTree.nodes, { id: $(this).attr('node-cid') });
+            const cpid = $(this).attr('node-cpid');
+            if (cpid) {
+                node = _.find(contractPays, { id: parseInt(cpid) });
+                $('#check-sp-cid').val(node.cid);
+                $('#check-sp-cpid').val(node.id);
+            } else {
+                $('#check-sp-cid').val(node.id);
+                $('#check-sp-cpid').val('');
+            }
+            if (!node) {
+                toastr.error('合同数据获取失败');
+                return;
+            }
+            console.log(node);
+            const title = $(this).attr('data-title') || '审批流程';
+            $('#sp-list').find('.modal-title').text(title);
+            let html = '';
+            const operate = $(this).attr('data-operate') || null;
+            if (operate) {
+                if (operate === 'checked') {
+                    html += `<button type="button" class="btn btn-sm btn-success" id="check-sp-btn">确认通过</button>`;
+                } else if (operate === 'checkNo') {
+                    html += `<button type="button" class="btn btn-sm btn-warning" id="check-sp-btn">确认退回</button>`;
+                }
+            }
+            $('#check-sp-btn-html').html(html);
+            $('#check-operate').val(operate || '');
+            setCheckAuditsHtml(node, operate);
+            $('#sp-list').modal('show');
+        });
+
+        $('#sp-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;
+        }
+
+        // 展开历史审核记录
+        $('body').on('click', '.modal-body #fold-btn', 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('展开历史审核记录')
+                })
+            }
+        });
+
+        function getNode(from = 'start') {
+            const cid = $('#'+ from +'-sp-cid').val();
+            const cpid = $('#'+ from +'-sp-cpid').val();
+            let node = _.find(contractTree.nodes, { id: cid });
+            if (cpid) {
+                node = _.find(contractPays, { id: parseInt(cpid) });
+            }
+            if (!node) {
+                toastr.error('合同数据获取失败');
+                return;
+            }
+            return node;
+        }
+
+        function setStartAuditsHtml(node) {
+            let html = '';
+            for (let i = 0, iLen = node.auditorGroups.length; i < iLen; i++) {
+                html += `<li class="list-group-item d-flex" auditorId="${node.auditorGroups[i][0].aid}">
+                        <div class="col-auto">${i+1}</div>
+                    <div class="col">`;
+                for (const auditor of node.auditorGroups[i]) {
+                    html += `<div class="d-inline-block mx-1" auditorId="${auditor.aid}">
+                        <i class="fa fa-user text-muted"></i> ${auditor.name} <small class="text-muted">${auditor.role}</small>
+                        </div>`;
+                }
+                const common_html = node.auditorGroups[i][0].audit_type !== auditType.key.common ? `<span class="badge badge-pill badge-${auditType.info[node.auditorGroups[i][0].audit_type].class} badge-bg-small"><small>${auditType.info[node.auditorGroups[i][0].audit_type].long}</small></span>` : '';
+                const del_html = shenpi.contract === shenpiConst.sp_status.sqspr ||
+                (shenpi.contract === shenpiConst.sp_status.gdzs && i+1 !== iLen) && (user_id === node.uid || is_admin || permission_edit_contract) ?
+                    `<a href="javascript: void(0)" class="text-danger pull-right">移除</a>` : '';
+                html += `</div>
+                        <div class="col-auto">
+                            ${common_html}
+                            ${del_html}
+                            </div>
+                    </li>`;
+            }
+            $('#auditors').html(html);
+        }
+
+        const getGroupAuditHtml = function (group) {
+            return group.map(u => { return `<small class="d-inline-block text-dark mx-1" title="${u.role}" data-auditorId="${u.aid}">${u.name}</small>`; }).join('');
+        };
+
+        const getAuditTypeHtml = function (type) {
+            if (type === auditType.key.common) return '';
+            return `<div class="li-subscript"><span class="badge badge-pill badge-${auditType.info[type].class} p-1 badge-bg-small"><small>${auditType.info[type].short}</small></span></div>`;
+        };
+
+        const getAuditTypeText = function (type) {
+            if (type === auditType.key.common) return '';
+            return `<span class="text-${auditType.info[type].class}">${auditType.info[type].long}</span>`;
+        };
+
+        function setCheckAuditsHtml(node, operate) {
+            // 获取审批流程
+            const { auditHistory, auditors2, user } = node;
+            let auditorsHTML = [];
+                auditors2.forEach((group, idx) => {
+                    if (idx === 0) {
+                        auditorsHTML.push(`<li class="list-group-item d-flex justify-content-between align-items-center">
+                <span class="mr-1"><i class="fa fa fa-play-circle fa-rotate-90"></i></span>
+            <span class="text-muted">${getGroupAuditHtml(group)}</span>
+            <span class="badge badge-light badge-pill ml-auto"><small>原报</small></span>
+            </li>`);
+                    } else if(idx === auditors2.length -1 && idx !== 0) {
+                        auditorsHTML.push(`<li class="list-group-item d-flex justify-content-between align-items-center">
+                <span class="mr-1"><i class="fa fa fa-stop-circle fa-rotate-90"></i></span>
+            <span class="text-muted">${getGroupAuditHtml(group)}</span>
+            <div class="d-flex ml-auto">
+            ${getAuditTypeHtml(group[0].audit_type)}
+            <span class="badge badge-light badge-pill ml-auto"><small>终审</small></span>
+            </div>
+            </li>`);
+                    } else {
+                        auditorsHTML.push(`<li class="list-group-item d-flex justify-content-between align-items-center">
+                <span class="mr-1"><i class="fa fa fa-chevron-circle-down"></i></span>
+            <span class="text-muted">${getGroupAuditHtml(group)}</span>
+            <div class="d-flex ml-auto">
+            ${getAuditTypeHtml(group[0].audit_type)}
+            <span class="badge badge-light badge-pill"><small>${transFormToChinese(idx)}审</small></span>
+            </div>
+            </li>`);
+                    }
+                });
+            $('#auditor-list').empty();
+            $('#auditor-list').append(auditorsHTML.join(''));
+
+            let historyHTML = [];
+            auditHistory.forEach((his, idx) => {
+                if (idx === auditHistory.length - 1 && auditHistory.length !== 1) {
+                    historyHTML.push(`<div class="text-right"><a href="javascript: void(0);" id="fold-btn" data-target="show">展开历史审批流程</a></div>`);
+                }
+                historyHTML.push(`<div class="${idx < auditHistory.length - 1 ? 'fold-card' : ''}">`);
+                historyHTML.push(`<div class="text-center text-muted">${idx+1}#</div>`);
+                historyHTML.push(`<ul class="timeline-list list-unstyled mt-2 ${ idx === auditHistory.length - 1 && auditHistory.length !== 1 ? 'last-auditor-list' : '' }">`);
+                his.forEach((group, index) => {
+                    historyHTML.push(`<li class="timeline-list-item pb-2 ${ group.status === auditConst.status.uncheck && idx === auditHistory.length - 1 && auditHistory.length !== 1 ? 'is_uncheck' : ''}">`);
+                    if (group.endYear) {
+                        historyHTML.push(`<div class="timeline-item-date">${group.endYear}<span>${group.endDate}</span><span>${group.endTime}</span></div>`);
+                    }
+                    if (index < his.length - 1) {
+                        historyHTML.push('<div class="timeline-item-tail"></div>');
+                    }
+                    if (group.status === auditConst.status.checked) {
+                        historyHTML.push('<div class="timeline-item-icon bg-success text-light"><i class="fa fa-check"></i></div>');
+                    } else if (group.status === auditConst.status.checkNo || group.status === auditConst.status.checkNoPre) {
+                        historyHTML.push('<div class="timeline-item-icon bg-warning text-light"><i class="fa fa-level-up"></i></div>');
+                    } else if (group.status === auditConst.status.checking) {
+                        historyHTML.push('<div class="timeline-item-icon bg-warning text-light"><i class="fa fa-ellipsis-h"></i></div>');
+                        // } else if(group.status === auditConst.status.checkAgain) {
+                        //     historyHTML.push('<div class="timeline-item-icon bg-warning text-light"><i class="fa fa-check"></i></div>');
+                    } else {
+                        historyHTML.push('<div class="timeline-item-icon bg-secondary text-light"></div>');
+                    }
+
+                    historyHTML.push('<div class="timeline-item-content">');
+                    historyHTML.push('<div class="py-1">');
+                    if (index === 0) {
+                        historyHTML.push('<span class="text-black-50">原报</span>');
+                        historyHTML.push(`<span class="pull-right text-success">${idx !== 0 ? '重新' : ''}上报审批</span>`);
+                    } else {
+                        const statuStr = group.status !== auditConst.status.uncheck ?
+                            `<span class="pull-right ${auditConst.statusClass[group.status]}">${auditConst.statusString[group.status]}</span>` : '';
+                        historyHTML.push(`
+                    <span class="text-black-50">
+                    ${ group.audit_order === 0 ? '原报' : !group.is_final ? group.audit_order + '审' : '终审' } ${getAuditTypeText(group.audit_type)}
+                    </span>
+                    ${statuStr}`);
+                    }
+                    historyHTML.push('</div>');
+                    historyHTML.push('<div class="card"><div class="card-body px-3 py-0">');
+                    for (const [i, auditor] of group.auditors.entries()) {
+                        historyHTML.push(`<div class="card-text p-2 py-3 row ${ ( i > 0 ? 'border-top' : '') }">`);
+                        let companyRolePart = '';
+                        const rolePart = auditor.role ? ' - ' + auditor.role : '';
+                        companyRolePart = `<span class="text-muted ml-1">${auditor.company}${rolePart}</span>`;
+                        historyHTML.push(`<div class="col-10"><span class="h6">${auditor.name}</span>${companyRolePart}</div>`);
+                        historyHTML.push('<div class="col">');
+                        if (auditor.status === auditConst.status.checked) {
+                            historyHTML.push('<span class="pull-right text-success"><i class="fa fa-check-circle"></i></span>');
+                        } else if (auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {
+                            historyHTML.push('<span class="pull-right text-warning"><i class="fa fa-share-square fa-rotate-270"></i></span>');
+                        } else if (auditor.status === auditConst.status.checking) {
+                            historyHTML.push('<span class="pull-right text-warning"><i class="fa fa-commenting"></i></span>');
+                            // } else if (auditor.status === auditConst.status.checkAgain) {
+                            //     historyHTML.push('<span class="pull-right text-warning"><i class="fa fa-check"></i></span>');
+                        }
+                        historyHTML.push('</div>');
+                        if (auditor.opinion) {
+                            historyHTML.push(`<div class="col-12 py-1 bg-light"><i class="fa fa-commenting-o mr-1"></i>${auditor.opinion}</div>`);
+                        }
+                        if (auditor.status === auditConst.status.checking && auditor.aid === user_id && operate) {
+                            historyHTML.push(`<div class="col-12 py-1 bg-light">`);
+                            if (operate === 'checked') {
+                                historyHTML.push(`<textarea class="form-control form-control-sm" name="opinion">同意</textarea>`);
+                            } else if (operate === 'checkNo') {
+                                historyHTML.push(`<textarea class="form-control form-control-sm" name="opinion">不同意</textarea>`);
+                                historyHTML.push(`<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">退回原报</label>
+                                                                </div>`);
+                                if (auditor.audit_order > 1) {
+                                    const pre = his.find(x => { return x.audit_order === auditor.audit_order - 1});
+                                    const pre_audit = pre.audit_type === auditType.key.common ? pre.auditors[0].name : `${pre.audit_order}审`;
+                                    historyHTML.push(`
+                                        <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">退回上一审批人 ${pre_audit}</label>
+                                        </div>`);
+                                }
+                                historyHTML.push(`</div>`);
+                            }
+                            historyHTML.push(`</div>`);
+                        }
+                        historyHTML.push('</div>');
+                    }
+                    historyHTML.push('</div></div>');
+                    historyHTML.push('</div>');
+                    historyHTML.push('</li>');
+                });
+                historyHTML.push('</div>');
+                historyHTML.push('</ul>');
+            });
+            $('#audit-list').empty();
+            $('#audit-list').append(historyHTML.join(''));
+        }
+
+        function setPayShenpiHtml(pay, reload = false) {
+            let shenpi_html = '';
+            if (pay_shenpi_status) {
+                shenpi_html += !reload ? `<td class="shenpi-td ${auditConst.auditProgressClass[pay.status]}">` : '';
+                if ((pay.status === auditConst.status.uncheck || pay.status === auditConst.status.checkNo) && pay.uid === user_id) {
+                    shenpi_html += `<a href="javascript:void(0);" node-cid="${pay.cid}" node-cpid="${pay.id}" class="btn ${auditConst.statusButtonClass[pay.status]} btn-sm show-sub-sp">${auditConst.statusButton[pay.status]}</a>`;
+                } else if ((pay.status === auditConst.status.checking || pay.status === auditConst.status.checkNoPre) && pay.curAuditors && pay.curAuditors.findIndex(x => { return x.aid === user_id; }) >= 0) {
+                    shenpi_html += `<a href="javascript: void(0);" node-cid="${pay.cid}" node-cpid="${pay.id}" data-operate="checked" data-title="审批通过" class="btn btn-success btn-sm mr-2 show-sp-list">审批通过</a>`;
+                    shenpi_html += `<a href="javascript: void(0);" node-cid="${pay.cid}" node-cpid="${pay.id}" data-operate="checkNo" data-title="审批退回" class="btn btn-warning btn-sm show-sp-list">审批退回</a>`;
+                } else {
+                    if (pay.status === auditConst.status.checked && pay.final_auditor_str) {
+                        shenpi_html += `<a href="javascript:void(0);" node-cid="${pay.cid}" node-cpid="${pay.id}" class="show-sp-list">${pay.final_auditor_str}</a>`;
+                    } else if (pay.status === auditConst.status.checkNo && pay.curAuditors2 && pay.curAuditors2.length > 0) {
+                        if (pay.curAuditors2[0].audit_type === auditType.key.common) {
+                            shenpi_html += `<a href="javascript:void(0);" node-cid="${pay.cid}" node-cpid="${pay.id}" class="show-sp-list">${pay.curAuditors2[0].name}${pay.curAuditors2[0].role !== '' && pay.curAuditors2[0].role !== null ? '-' + pay.curAuditors2[0].role : ''}</a>`;
+                        } else {
+                            shenpi_html += `<a href="javascript:void(0);" node-cid="${pay.cid}" node-cpid="${pay.id}" class="show-sp-list">${transFormToChinese(pay.curAuditors2[0].audit_order) + '审'}</a>`;
+                        }
+                    } else if (pay.curAuditors.length > 0) {
+                        if (pay.curAuditors[0].audit_type === auditType.key.common) {
+                            shenpi_html += `<a href="javascript:void(0);" node-cid="${pay.cid}" node-cpid="${pay.id}" class="show-sp-list">${pay.curAuditors[0].name}${pay.curAuditors[0].role !== '' && pay.curAuditors[0].role !== null ? '-' + pay.curAuditors[0].role : ''}</a>`;
+                        } else {
+                            shenpi_html += `<a href="javascript:void(0);" node-cid="${pay.cid}" node-cpid="${pay.id}" class="show-sp-list">${transFormToChinese(pay.curAuditors[0].audit_order) + '审'}</a>`;
+                        }
+                    }
+                    shenpi_html += ` ${auditConst.auditProgress[pay.status]}`;
+                }
+                shenpi_html += !reload ? '</td>' : '';
+            }
+            return shenpi_html;
+        }
+
+        let timer3 = null
+        let oldSearchVal3 = null
+
+        $('#gr-search3').bind('input propertychange', function(e) {
+            oldSearchVal3 = e.target.value
+            timer3 && clearTimeout(timer3)
+            timer3 = setTimeout(() => {
+                const newVal = $('#gr-search3').val()
+                let html = ''
+                if (newVal && newVal === oldSearchVal3) {
+                    accountList3.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>`
+                    })
+                    $('.search-user-list').empty()
+                    $('.search-user-list').append(html)
+                } else {
+                    if (!$('.search-user-list .acc-btn').length) {
+                        accountGroup3.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>'
+                        })
+                        $('.search-user-list').empty()
+                        $('.search-user-list').append(html)
+                    }
+                }
+            }, 400);
+        });
+
+        // 添加到成员中
+        $('.search-user-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
+        });
+
+        // 添加到审批流程中
+        $('.search-user-list').on('click', 'dd', function () {
+            const node = getNode();
+            const id = parseInt($(this).data('id'));
+            if (id) {
+                postData(window.location.pathname + '/check', {postType: 'add-audit', id: node.id, auditorId: id}, function (result) {
+                    setStartAuditChange(node, result);
+                });
+            }
+        });
+        // 删除审批人
+        $('body').on('click', '#auditors li a', function () {
+            const node = getNode();
+            const li = $(this).parents('li');
+            const data = {
+                postType: 'del-audit', id: node.id,
+                auditorId: parseInt(li.attr('auditorId')),
+            };
+            postData(window.location.pathname + '/check', data, (result) => {
+                setStartAuditChange(node, result);
+            });
+        });
+
+        $('#change-sp-group').change(function () {
+            const node = getNode();
+            postData(window.location.pathname + '/check', {postType: 'change-sp', id: node.id, sp_group: $(this).val()}, function (result) {
+                setStartAuditChange(node, result);
+            });
+        });
+
+        function setStartAuditChange(node, result) {
+            if (node.cid) {
+                contractPays.splice(contractPays.findIndex(item => item.id === node.id), 1, result);
+            } else {
+                const refreshNode = contractTree.loadPostData({ update: result });
+                contractTreeSpreadObj.refreshTree(contractSheet, refreshNode);
+            }
+            setStartAuditsHtml(result);
+        }
+
+        $('#start-sp-btn').click(function () {
+            const node = getNode();
+            postData(window.location.pathname + '/check', {postType: 'start', id: node.id}, function (result) {
+                setShenpiChange(node, result);
+                $('#sub-sp').modal('hide');
+            });
+        });
+
+        $('body').on('click', '#check-sp-btn', function () {
+            const node = getNode('check');
+            const operate = $('#check-operate').val();
+            console.log(node, operate);
+            const checkType = operate === 'checked' ? auditConst.status.checked : parseInt($('#audit-list').find('input[name="checkType"]:checked').val());
+            if ($('#warning-text').length) $('#warning-text').remove();
+            if (!checkType && !$('#warning-text').length) {
+                $('#reject-process').prepend('<p id="warning-text" style="color: red; margin: 0;">请选择退回流程</p>');
+                return false;
+            }
+            const data = {
+                postType: 'check',
+                id: node.id,
+                checkType,
+                opinion: $('#audit-list').find('textarea').val().replace(/\r\n/g, '<br/>').replace(/\n/g, '<br/>').replace(/\s/g, ' '),
+            }
+            console.log(data);
+            postData(window.location.pathname + '/check', data, function (result) {
+                setShenpiChange(node, result);
+                $('#sp-list').modal('hide');
+            });
+        });
+
+        function setShenpiChange(node, result) {
+            if (node.cid) {
+                contractPays.splice(contractPays.findIndex(item => item.id === node.id), 1, result);
+                const shenpi_html = setPayShenpiHtml(result, true);
+                $("#htpay-table tbody").find(`tr[data-cpid="${node.id}"]`).children('.shenpi-td').html(shenpi_html);
+                $("#htpay-table tbody").find(`tr[data-cpid="${node.id}"]`).children('.shenpi-td').addClass(auditConst.auditProgressClass[result.status]);
+                $('#htpay-table tbody').find(`tr[data-cpid="${node.id}"]`).find('.pay-edit').hide();
+                $('#htpay-table tbody').find(`tr[data-cpid="${node.id}"]`).find('.pay-del').hide();
+                if (node.uid === user_id && (result.status === auditConst.status.uncheck || result.status === auditConst.status.checkNo)) {
+                    $('#htpay-table tbody').find(`tr[data-cpid="${node.id}"]`).find('.pay-edit').show();
+                    $('#htpay-table tbody').find(`tr[data-cpid="${node.id}"]`).find('.pay-del').show();
+                }
+            } else {
+                const refreshNode = contractTree.loadPostData({update: result});
+                contractTreeSpreadObj.refreshTree(contractSheet, refreshNode);
+                contractTreeSpreadObj.setContractBtn(result);
+            }
+        }
+    }
+
     const stdLibCellDoubleClick = function (updateData, stdNode, stdTree) {
         if (!permission_edit) return;
         const mainSheet = contractSheet;

+ 10 - 0
app/public/js/contract_setting.js

@@ -112,4 +112,14 @@ $(document).ready(() => {
         setTypeTable(subProject_types, 'subProject');
         checkAndShowTypesBtn();
     });
+
+    $('#contract-shenpi-set input[type="checkbox"]').change(function() {
+        postData(`/sp/${spid}/contract/setting/update`, { type: 'page_show',
+            openContractSubProjectShenpi: $('#openContractSubProjectShenpi')[0].checked,
+            openContractPaySubProjectShenpi: $('#openContractPaySubProjectShenpi')[0].checked,
+            openContractTenderShenpi: $('#openContractTenderShenpi')[0].checked,
+            openContractPayTenderShenpi: $('#openContractPayTenderShenpi')[0].checked,
+        }, function (result) {
+        });
+    });
 });

+ 40 - 3
app/public/js/shenpi.js

@@ -12,7 +12,7 @@ const tenderTree2 = [];
 let parentId2 = 0;
 let selects;
 let auditUtils;
-const needYB = ['advance', 'ledger', 'revise', 'change', 'audit-ass'];
+const needYB = ['advance', 'ledger', 'revise', 'change', 'audit-ass', 'contract'];
 // 查询方法
 function findNode2 (key, value, arr) {
     for (const a of arr) {
@@ -354,7 +354,7 @@ $(document).ready(function () {
                 for(const [i, auditGroup] of flow.auditGroupList.entries()) {
                     addhtml += this.getAuditGroupHtml(this_code, auditGroup, i + 1);
                 }
-                const addGroupHtml = (this_code === 'change' || this_code === 'stage') && (!flow.groupList || (flow.groupList && flow.groupList.length === 0)) ?
+                const addGroupHtml = (this_code === 'change' || this_code === 'stage' || this_code === 'contract') && (!flow.groupList || (flow.groupList && flow.groupList.length === 0)) ?
                     `<span class="pl-3"><a href="javascript:void(0);" class="show-spzsave" data-code="${this_code}"><i class="fa fa-save"></i> 存为审批组</a></span>\n` : '';
                 addhtml += '<li>\n' +
                     '                                            <span class="pl-3"><a href="javascript:void(0);" class="add-audit" ><i class="fa fa-plus"></i> 添加流程</a></span>\n' + addGroupHtml +
@@ -542,7 +542,7 @@ $(document).ready(function () {
                     const auditGroup = auditUtils.addAudit(this_code, data, prop.audit_order - 1);
                     if (_self.parents('ul').find('.add-audit').length === 0) {
                         const flow = sp_lc.find(x => { return x.code === this_code; });
-                        const addGroupHtml = (this_code === 'change'||this_code ==='stage') && (!flow.groupList || (flow.groupList && flow.groupList.length === 0)) ?
+                        const addGroupHtml = (this_code === 'change'||this_code ==='stage' || this_code === 'contract') && (!flow.groupList || (flow.groupList && flow.groupList.length === 0)) ?
                             `<span class="pl-3"><a href="javascript:void(0);" class="show-spzsave" data-code="${this_code}"><i class="fa fa-save"></i> 存为审批组</a></span>\n` : '';
                         _self.parents('ul').append('<li>\n' +
                             '                                            <span class="pl-3"><a href="javascript:void(0);" class="add-audit" ><i class="fa fa-plus"></i> 添加流程</a></span>\n' + addGroupHtml +
@@ -1370,11 +1370,34 @@ $(document).ready(function () {
                 });
                 $('#show-change-type input[data-type="change"]').prop('checked', true);
             }
+            $('#show-contract-type input').attr('disabled', true);
+            $('#show-contract-type').hide();
             $('#show-change-type input').removeAttr('disabled');
             $('#show-change-type').show();
+        } else if (this_code === 'contract') {
+            if (group) {
+                $('#show-contract-type input').each(function () {
+                    if (group.change_type[$(this).data('type')]) {
+                        $(this).prop('checked', true);
+                    } else {
+                        $(this).prop('checked', false);
+                    }
+                });
+            } else {
+                $('#show-contract-type input').each(function () {
+                    $(this).prop('checked', false);
+                });
+                $('#show-contract-type input[data-type="contract"]').prop('checked', true);
+            }
+            $('#show-change-type input').attr('disabled', true);
+            $('#show-change-type').hide();
+            $('#show-contract-type input').removeAttr('disabled');
+            $('#show-contract-type').show();
         } else {
             $('#show-change-type input').attr('disabled', true);
             $('#show-change-type').hide();
+            $('#show-contract-type input').attr('disabled', true);
+            $('#show-contract-type').hide();
         }
         $('#spzsave').modal('show');
     });
@@ -1425,6 +1448,20 @@ $(document).ready(function () {
                 return false;
             }
             prop.change_type = change_type;
+        } else if (code === 'contract') {
+            const change_type = {};
+            let contract_flag = false;
+            $('#show-contract-type input').each(function () {
+                change_type[$(this).data('type')] = $(this).prop('checked');
+                if ($(this).prop('checked')) {
+                    contract_flag = true;
+                }
+            });
+            if (!contract_flag) {
+                toastr.error('请至少选择一种合同显示模块');
+                return false;
+            }
+            prop.change_type = change_type;
         }
         console.log(prop);
         postData('/tender/' + cur_tenderid + '/shenpi/audit/save', prop, function (data) {

Разница между файлами не показана из-за своего большого размера
+ 1211 - 0
app/public/js/sp_shenpi.js


+ 9 - 1
app/router.js

@@ -238,6 +238,9 @@ module.exports = app => {
 
     app.get('/sp/:id/nop/:block', sessionAuth, subProjectCheck, 'subProjController.noPermission');
 
+    app.post('/sp/:id/shenpi/save', sessionAuth, subProjectCheck, 'subProjController.saveShenpi');
+    app.post('/sp/:id/shenpi/audit/save', sessionAuth, subProjectCheck, 'subProjController.saveShenpiAudit');
+
     // 项目列表跳转项目内部 todo 除标段内,均改为/sp/:id/xxx的链接形式
     // **控制面板
     app.get('/sp/:id/dashboard', sessionAuth, subProjectCheck, 'dashboardController.index');
@@ -334,6 +337,8 @@ module.exports = app => {
     app.post('/sp/:id/contract/tender/:tid/detail/:type/:cid/pay/:cpid/file/delete', sessionAuth, subProjectCheck, contractCheck, 'contractController.deleteFile');
     app.get('/sp/:id/contract/tender/:tid/detail/:type/:cid/pay/:cpid/file/:fid/download', sessionAuth, subProjectCheck, contractCheck, 'contractController.downloadFile');
     app.post('/sp/:id/contract/tender/:tid/col-set', sessionAuth, subProjectCheck, contractCheck, 'contractController.colSet');
+    app.post('/sp/:id/contract/tender/:tid/detail/check', sessionAuth, subProjectCheck, contractCheck, 'contractController.checkAudit');
+    app.post('/sp/:id/contract/tender/:tid/detail/:type/check', sessionAuth, subProjectCheck, contractCheck, 'contractController.checkAudit');
     // 项目合同管理
     app.get('/sp/:id/contract', sessionAuth, subProjectCheck, contractCheck, 'contractController.detail');
     app.post('/sp/:id/contract/audit/save', sessionAuth, subProjectCheck, contractCheck, 'contractController.auditSave');
@@ -351,10 +356,13 @@ module.exports = app => {
     app.post('/sp/:id/contract/detail/:type/:cid/pay/:cpid/file/upload', sessionAuth, subProjectCheck, contractCheck, 'contractController.uploadFile');
     app.post('/sp/:id/contract/detail/:type/:cid/pay/:cpid/file/delete', sessionAuth, subProjectCheck, contractCheck, 'contractController.deleteFile');
     app.get('/sp/:id/contract/detail/:type/:cid/pay/:cpid/file/:fid/download', sessionAuth, subProjectCheck, contractCheck, 'contractController.downloadFile');
+    app.post('/sp/:id/contract/detail/check', sessionAuth, subProjectCheck, contractCheck, 'contractController.checkAudit');
+    app.post('/sp/:id/contract/detail/:type/check', sessionAuth, subProjectCheck, contractCheck, 'contractController.checkAudit');
     app.post('/sp/:id/contract/col-set', sessionAuth, subProjectCheck, contractCheck, 'contractController.colSet');
     app.get('/sp/:id/contract/setting', sessionAuth, subProjectCheck, 'contractController.setting');
     app.get('/sp/:id/contract/tender/setting', sessionAuth, subProjectCheck, 'contractController.setting');
-    // app.get('/sp/:id/contract/setting/shenpi', sessionAuth, subProjectCheck, 'contractController.shenpi');
+    app.get('/sp/:id/contract/setting/shenpi', sessionAuth, subProjectCheck, 'contractController.shenpiSet');
+    app.post('/sp/:id/contract/setting/update', sessionAuth, subProjectCheck, 'contractController.updateSetting');
 
 
     // 资料归集-列表

+ 49 - 2
app/service/contract.js

@@ -3,7 +3,8 @@
 /**
  * Created by EllisRan on 2020/3/3.
  */
-
+const auditConst = require('../const/audit').contract;
+const auditType = require('../const/audit').auditType;
 const BaseService = require('../base/base_service');
 
 module.exports = app => {
@@ -59,6 +60,7 @@ module.exports = app => {
                     remark: data.remark,
                     type: data.type,
                     create_time: new Date(),
+                    need_shenpi: options.spid ? this.ctx.subProject.page_show.openContractSubProjectShenpi : this.ctx.subProject.page_show.openContractTenderShenpi,
                 };
                 insertData[this.setting.fullPath] = !node.c_code
                     ? node[this.setting.fullPath] + '-' + insertData[this.setting.kid]
@@ -138,8 +140,26 @@ module.exports = app => {
         async getListByUsers(options, user) {
             const _ = this._;
             const list = await this.getAllDataByCondition({ where: options });
+            const userList = list.length > 0 ? await this.ctx.service.projectAccount.getAllDataByCondition({ where: { id: this._.map(list, 'uid') } }) : [];
+            const shenpi_status = this.ctx.contract_tender ? this.ctx.subProject.page_show.openContractTenderShenpi : this.ctx.subProject.page_show.openContractSubProjectShenpi;
             for (const l of list) {
-                l.username = (await this.ctx.service.projectAccount.getAccountInfoById(l.uid)).name;
+                const userInfo = userList.find(item => item.id === l.uid);
+                l.username = userInfo ? userInfo.name : '';
+                if (shenpi_status) {
+                    // await this.ctx.service.contractSpAudit.loadUser(l);
+                    // await this.ctx.service.contractSpAudit.loadAuditViewData(l);
+                    // await this.ctx.service.contractSpAudit.checkShenpi(l);
+                    if (l.status !== auditConst.status.checked || !l.final_auditor_str) {
+                        l.curAuditors2 = await this.ctx.service.contractSpAudit.getAuditorsByStatus(l.id, null, l.status, l.times);
+                        if (l.status === auditConst.status.checked && !l.final_auditor_str) {
+                            const final_auditor_str = l.curAuditors2[0].audit_type === auditType.key.common
+                                ? l.curAuditors2[0].name + (l.curAuditors2[0].role ? '-' + l.curAuditors2[0].role : '')
+                                : this.ctx.helper.transFormToChinese(l.curAuditors2[0].audit_order) + '审';
+                            await this.defaultUpdate({ final_auditor_str, id: l.id });
+                            l.final_auditor_str = final_auditor_str;
+                        }
+                    }
+                }
             }
             if (user.is_admin) {
                 return list;
@@ -151,6 +171,12 @@ module.exports = app => {
             const userTreePermission = await this.ctx.service.contractTreeAudit.getAllDataByCondition({ where: cloneOptions });
             if (userTreePermission.length === 0) return list;
             const newList = this._.filter(list, { uid: user.accountId });
+            // 审批的列表也要可显示
+            const checkList = await this.ctx.service.contractSpAudit.getAuditList(options, user.accountId);
+            // 用 checkList里的数组和list的id对比,存在则合并到newList中
+            newList.push(...this._.filter(list, function(item) {
+                return checkList.some(checkItem => checkItem.id === item.id && item.uid !== user.accountId);
+            }));
             const userInfo = await this.ctx.service.projectAccount.getDataById(user.accountId);
             // const unit = userInfo.company ? await this.ctx.service.constructionUnit.getDataByCondition({ pid: userInfo.project_id, name: userInfo.company }) : null;
             const uids = await this.ctx.service.projectAccount.getAllDataByCondition({ columns: ['id'], where: { project_id: userInfo.project_id, company: userInfo.company } });
@@ -175,6 +201,27 @@ module.exports = app => {
             return newList;
         }
 
+        async savePageShow(spid, page_show) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                await this.ctx.service.subProject.updatePageshow(spid, page_show, transaction);
+                // 同步更新所有pay和contract状态
+                await transaction.update(this.tableName, { need_shenpi: page_show.openContractSubProjectShenpi }, { where: { spid } });
+                await transaction.update(this.ctx.service.contractPay.tableName, { need_shenpi: page_show.openContractPaySubProjectShenpi }, { where: { spid } });
+                const tenders = await this.ctx.service.tender.getAllDataByCondition({ columns: ['id'], where: { spid } });
+                if (tenders.length > 0) {
+                    await transaction.update(this.tableName, { need_shenpi: page_show.openContractTenderShenpi }, { where: { tid: this._.map(tenders, 'id') } });
+                    await transaction.update(this.ctx.service.contractPay.tableName, { need_shenpi: page_show.openContractPayTenderShenpi }, { where: { tid: this._.map(tenders, 'id') } });
+                }
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+            return false;
+        }
+
         // async getCountByUser(stid, type, user) {
         //
         // }

+ 2 - 0
app/service/contract_audit.js

@@ -142,6 +142,8 @@ module.exports = app => {
                                 uid: user.uid,
                                 permission_add: user.permission.add,
                                 permission_edit: user.permission.edit,
+                                permission_edit_contract: user.permission.edit_contract,
+                                permission_add_pay: user.permission.add_pay,
                                 permission_show_unit: user.permission.showUnit,
                                 permission_show_node: user.permission.showNode,
                                 permission_att: user.permission_att,

+ 20 - 0
app/service/contract_pay.js

@@ -6,6 +6,8 @@
 
 const BaseService = require('../base/base_service');
 const contractConst = require('../const/contract');
+const auditConst = require('../const/audit').contract;
+const auditType = require('../const/audit').auditType;
 
 module.exports = app => {
 
@@ -27,12 +29,28 @@ module.exports = app => {
             const sql = 'SELECT * FROM ?? WHERE ' + this.ctx.helper._getOptionsSql(options) + ' AND `cid` = ? ORDER BY `create_time` DESC';
             const sqlParams = [this.tableName, cid];
             const list = await this.db.query(sql, sqlParams);
+            const pay_shenpi_status = this.ctx.contract_tender ? this.ctx.subProject.page_show.openContractPayTenderShenpi : this.ctx.subProject.page_show.openContractPaySubProjectShenpi;
             if (list.length > 0) {
                 const userList = await this.ctx.service.projectAccount.getAllDataByCondition({ where: { id: list.map(item => item.uid) } });
                 for (const l of list) {
                     const userInfo = userList.find(item => item.id === l.uid);
                     l.username = userInfo ? userInfo.name : '';
                     l.files = await this.ctx.service.contractPayAtt.getAtt(l.id);
+                    if (pay_shenpi_status) {
+                        await this.ctx.service.contractSpAudit.loadUser(l);
+                        await this.ctx.service.contractSpAudit.loadAuditViewData(l);
+                        await this.ctx.service.contractSpAudit.checkShenpi(l);
+                        if (l.status !== auditConst.status.checked || !l.final_auditor_str) {
+                            l.curAuditors2 = await this.ctx.service.contractSpAudit.getAuditorsByStatus(l.cid, l.id, l.status, l.times);
+                            if (l.status === auditConst.status.checked && !l.final_auditor_str) {
+                                const final_auditor_str = l.curAuditors2[0].audit_type === auditType.key.common
+                                    ? l.curAuditors2[0].name + (l.curAuditors2[0].role ? '-' + l.curAuditors2[0].role : '')
+                                    : this.ctx.helper.transFormToChinese(l.curAuditors2[0].audit_order) + '审';
+                                await this.defaultUpdate({ final_auditor_str, id: l.id });
+                                l.final_auditor_str = final_auditor_str;
+                            }
+                        }
+                    }
                 }
             }
             return list;
@@ -59,6 +77,7 @@ module.exports = app => {
                     pay_type: data.pay_type,
                     remark: data.remark,
                     create_time: new Date(),
+                    need_shenpi: options.spid ? this.ctx.subProject.page_show.openContractPaySubProjectShenpi : this.ctx.subProject.page_show.openContractPayTenderShenpi,
                 };
                 await transaction.insert(this.tableName, insertData);
                 await this.calcContract(transaction, node);
@@ -116,6 +135,7 @@ module.exports = app => {
                 const attList = await this.ctx.service.contractPayAtt.getAllDataByCondition({ where: { cpid } });
                 await this.ctx.helper.delFiles(attList);
                 await transaction.delete(this.ctx.service.contractPayAtt.tableName, { cpid });
+                await transaction.delete(this.ctx.service.contractSpAudit.tableName, { cpid });
                 await this.calcContract(transaction, node);
                 await transaction.commit();
             } catch (err) {

Разница между файлами не показана из-за своего большого размера
+ 1192 - 0
app/service/contract_sp_audit.js


+ 4 - 0
app/service/contract_tree.js

@@ -385,6 +385,7 @@ module.exports = app => {
                     const attList = await this.ctx.service.contractAtt.getAllDataByCondition({ where: { cid: select.id } });
                     await this.ctx.helper.delFiles(attList);
                     await transaction.delete(this.ctx.service.contractAtt.tableName, { cid: select.id });
+                    await transaction.delete(this.ctx.service.contractSpAudit.tableName, { cid: select.id, cpid: null });
                 } else {
                     await transaction.delete(this.tableName, { id: select.id });
                     const delOptions = this._.cloneDeep(options);
@@ -401,6 +402,7 @@ module.exports = app => {
                         const attList = await this.ctx.service.contractAtt.getAllDataByCondition({ where: { cid: this.ctx.helper._.map(contracts, 'id') } });
                         await this.ctx.helper.delFiles(attList);
                         await transaction.delete(this.ctx.service.contractAtt.tableName, { cid: this.ctx.helper._.map(contracts, 'id') });
+                        await transaction.delete(this.ctx.service.contractSpAudit.tableName, { cid: this.ctx.helper._.map(contracts, 'id'), cpid: null });
                     }
                     const operate = await this._deletePosterity(options, select, transaction);
                 }
@@ -464,6 +466,7 @@ module.exports = app => {
                         const attList = await this.ctx.service.contractAtt.getAllDataByCondition({ where: { cid: s.id } });
                         await this.ctx.helper.delFiles(attList);
                         await transaction.delete(this.ctx.service.contractAtt.tableName, { cid: s.id });
+                        await transaction.delete(this.ctx.service.contractSpAudit.tableName, { cid: s.id, cpid: null });
                     } else {
                         await transaction.delete(this.tableName, { id: s.id });
                         const contracts = _.filter(deleteData, function (item) {
@@ -477,6 +480,7 @@ module.exports = app => {
                             const attList = await this.ctx.service.contractAtt.getAllDataByCondition({ where: { cid: _.map(contracts, 'id') } });
                             await this.ctx.helper.delFiles(attList);
                             await transaction.delete(this.ctx.service.contractAtt.tableName, { cid: _.map(contracts, 'id') });
+                            await transaction.delete(this.ctx.service.contractSpAudit.tableName, { cid: _.map(contracts, 'id'), cpid: null });
                         }
                     }
                     const operate = await this._deletePosterity(options, s, transaction);

+ 1 - 1
app/service/quality_inspection_audit.js

@@ -710,7 +710,7 @@ module.exports = app => {
                     records.push({ pid, tid: selfAudit.tid, type: pushType.inspection, uid: audit.aid, status: auditConst.status.checked, content: noticeContent });
                 });
                 await transaction.insert('zh_notice', records);
-                if (flowAudits.length === 1 || selfAudit.audit_type !== auditType.key.and) {
+                if (inspection.curAuditors.length === 1 || selfAudit.audit_type !== auditType.key.and) {
                     // 或签更新他人审批状态
                     if (selfAudit.audit_type === auditType.key.or) {
                         const updateOther = [];

+ 1 - 1
app/service/safe_inspection_audit.js

@@ -710,7 +710,7 @@ module.exports = app => {
                     records.push({ pid, tid: selfAudit.tid, type: pushType.safeInspection, uid: audit.aid, status: auditConst.status.checked, content: noticeContent });
                 });
                 await transaction.insert('zh_notice', records);
-                if (flowAudits.length === 1 || selfAudit.audit_type !== auditType.key.and) {
+                if (inspection.curAuditors.length === 1 || selfAudit.audit_type !== auditType.key.and) {
                     // 或签更新他人审批状态
                     if (selfAudit.audit_type === auditType.key.or) {
                         const updateOther = [];

+ 46 - 22
app/service/shenpi_audit.js

@@ -48,22 +48,25 @@ module.exports = app => {
         }
 
         async getAudit(tid, type, status) {
+            const idSql = isNaN(tid) ? 'sp.spid = ?' : 'sp.tid = ?';
             const sql = 'SELECT sp.audit_id, sp.audit_type, pa.name FROM ?? AS sp LEFT JOIN ?? AS pa ON sp.audit_id = pa.id' +
-                ' WHERE sp.tid = ? AND sp.sp_type = ? AND sp.sp_status = ?';
+                ` WHERE ${idSql} AND sp.sp_type = ? AND sp.sp_status = ?`;
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, tid, type, status];
             return await this.db.queryOne(sql, sqlParam);
         }
 
         async getUnionAudit(tid, type, audit_order) {
+            const idSql = isNaN(tid) ? 'sp.spid = ?' : 'sp.tid = ?';
             const sql = 'SELECT sp.id, sp.audit_id, sp.audit_ledger_id, pa.name, pa.company FROM ?? AS sp LEFT JOIN ?? AS pa ON sp.audit_id = pa.id' +
-                ' WHERE sp.tid = ? AND sp.sp_type = ? AND sp.sp_status = ? AND audit_order = ?';
+                ` WHERE ${idSql} AND sp.sp_type = ? AND sp.sp_status = ? AND audit_order = ?`;
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, tid, type, shenpiConst.sp_status.gdspl, audit_order];
             return await this.db.query(sql, sqlParam);
         }
 
         async getAuditList(tid, type, status, group_id = 0) {
+            const idSql = isNaN(tid) ? 'sp.spid = ?' : 'sp.tid = ?';
             const sql = 'SELECT sp.id, sp.audit_id, sp.audit_order, sp.audit_type, pa.name FROM ?? AS sp LEFT JOIN ?? AS pa ON sp.audit_id = pa.id' +
-                ' WHERE sp.tid = ? AND sp.sp_type = ? AND sp.sp_status = ? AND sp.sp_group = ? ORDER BY sp.audit_order ASC, sp.id ASC';
+                ` WHERE ${idSql} AND sp.sp_type = ? AND sp.sp_status = ? AND sp.sp_group = ? ORDER BY sp.audit_order ASC, sp.id ASC`;
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, tid, type, status, group_id];
             return await this.db.query(sql, sqlParam);
         }
@@ -95,9 +98,10 @@ module.exports = app => {
         }
 
         async getAuditGroupList(tid, type, status, group_id = 0) {
+            const idSql = isNaN(tid) ? 'sp.spid = ?' : 'sp.tid = ?';
             const sql = 'SELECT sp.id, sp.audit_id, sp.audit_type, sp.audit_order, sp.sp_group, sp.audit_group, sp.audit_group_order, sp.audit_group_limit, sp.audit_checkno_valid, sp.audit_group_need, ' +
                 '  pa.name FROM ?? AS sp LEFT JOIN ?? AS pa ON sp.audit_id = pa.id' +
-                ' WHERE sp.tid = ? AND sp.sp_type = ? AND sp.sp_status = ? AND sp.sp_group = ? ORDER BY sp.audit_order ASC, sp.id ASC';
+                ` WHERE ${idSql} AND sp.sp_type = ? AND sp.sp_status = ? AND sp.sp_group = ? ORDER BY sp.audit_order ASC, sp.id ASC`;
             const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, tid, type, status, group_id];
             const audits = await this.db.query(sql, sqlParam);
             const result = [];
@@ -117,6 +121,7 @@ module.exports = app => {
 
         async addAudit(data, tid = this.ctx.tender.id) {
             let result;
+            const condition = isNaN(tid) ? { spid: tid } : { tid };
             const transaction = await this.db.beginTransaction();
             try {
                 if (parseInt(data.code) === shenpiConst.sp_type.stage && parseInt(data.status) === shenpiConst.sp_status.gdspl) {
@@ -133,12 +138,12 @@ module.exports = app => {
                 }
                 const group = await this.ctx.service.shenpiGroup.getGroupBySelect(tid, data.code);
                 const insertData = {
-                    tid,
                     sp_type: data.code,
                     sp_status: data.status,
                     audit_id: data.audit_id,
                     sp_group: group ? group.id : 0,
                 };
+                this._.assign(insertData, condition);
                 if (data.audit_type) insertData.audit_type = data.audit_type;
                 if (data.audit_order) insertData.audit_order = data.audit_order;
                 result = await transaction.insert(this.tableName, insertData);
@@ -155,17 +160,19 @@ module.exports = app => {
             return await this.db.queryOne(sql, sqlParam);
         }
 
-        async removeAudit(data) {
-            const group = await this.ctx.service.shenpiGroup.getGroupBySelect(this.ctx.tender.id, data.code);
+        async removeAudit(data, tid = this.ctx.tender.id) {
+            const tidCondition = isNaN(tid) ? { spid: tid } : { tid };
+            const group = await this.ctx.service.shenpiGroup.getGroupBySelect(tid, data.code);
             const delData = {
-                tid: this.ctx.tender.id,
                 sp_type: data.code,
                 sp_status: data.status,
                 audit_id: data.audit_id,
                 sp_group: group ? group.id : 0,
             };
+            this._.assign(delData, tidCondition);
             const audit = await this.getDataByCondition(delData);
-            const conditon = { tid: this.ctx.tender.id, sp_type: data.code, sp_status: data.status };
+            const conditon = { sp_type: data.code, sp_status: data.status };
+            this._.assign(conditon, tidCondition);
             if (group) conditon.sp_group = group.id;
             const allAudit = await this.getAllDataByCondition({ where: conditon });
             const sameOrder = allAudit.filter(x => { return x.audit_order === audit.audit_order; });
@@ -182,7 +189,7 @@ module.exports = app => {
                 if (parseInt(data.code) === shenpiConst.sp_type.stage && parseInt(data.status) === shenpiConst.sp_status.gdspl) {
                     const options = {
                         where: {
-                            tid: this.ctx.tender.id,
+                            tid,
                             user_id: data.audit_id,
                         },
                     };
@@ -372,18 +379,19 @@ module.exports = app => {
             }
         }
 
-        async setAuditType(data) {
+        async setAuditType(data, tid = this.ctx.tender.id) {
+            const tidCondition = isNaN(tid) ? { spid: tid } : { tid };
             const conn = await this.db.beginTransaction();
             try {
                 const updateData = { audit_type: data.audit_type };
-                const group = await this.ctx.service.shenpiGroup.getGroupBySelect(this.ctx.tender.id, data.code);
+                const group = await this.ctx.service.shenpiGroup.getGroupBySelect(tid, data.code);
                 const condition = {
-                    tid: this.ctx.tender.id,
                     sp_type: data.code,
                     sp_status: data.status,
                     audit_id: data.audit_id,
                     sp_group: group ? group.id : 0,
                 };
+                this._.assign(condition, tidCondition);
                 await conn.update(this.tableName, updateData, { where: condition });
                 await conn.commit();
             } catch (err) {
@@ -394,6 +402,7 @@ module.exports = app => {
 
         // 更新审批流程
         async updateAuditList(transaction, tenderId, sp_status, sp_type, ids) {
+            const tidCondition = isNaN(tenderId) ? { spid: tenderId } : { tid: tenderId };
             if (sp_status === shenpiConst.sp_status.gdspl) {
                 const auditList = await this.getAuditList(tenderId, sp_type, sp_status);
                 const oldIds = this._.map(auditList, 'audit_id');
@@ -404,13 +413,14 @@ module.exports = app => {
                 await transaction.delete(this.tableName, { tid: tenderId, sp_type, sp_status });
                 const insertDatas = [];
                 for (const [i, id] of ids.entries()) {
-                    insertDatas.push({
-                        tid: tenderId,
+                    const oneInsertData = {
                         sp_type,
                         sp_status,
                         audit_id: id,
                         audit_order: i + 1,
-                    });
+                    };
+                    this._.assign(oneInsertData, tidCondition);
+                    insertDatas.push(oneInsertData);
                 }
                 await transaction.insert(this.tableName, insertDatas);
                 if (sp_type === shenpiConst.sp_type.stage) {
@@ -445,15 +455,24 @@ module.exports = app => {
             } else if (sp_status === shenpiConst.sp_status.gdzs) {
                 const audit = await this.getAudit(tenderId, sp_type, sp_status);
                 if (audit && audit.audit_id !== ids[ids.length - 1]) {
+                    const condition = {
+                        sp_type, sp_status,
+                    };
+                    this._.assign(condition, tidCondition);
                     // 更换终审
-                    await transaction.update(this.tableName, { audit_id: ids[ids.length - 1] }, { where: { tid: tenderId, sp_type, sp_status } });
+                    await transaction.update(this.tableName, { audit_id: ids[ids.length - 1] }, { where: condition });
                 } else if (!audit) {
-                    await transaction.insert(this.tableName, { tid: tenderId, sp_type, sp_status, audit_id: ids[ids.length - 1] });
+                    const condition = {
+                        sp_type, sp_status, audit_id: ids[ids.length - 1],
+                    };
+                    this._.assign(condition, tidCondition);
+                    await transaction.insert(this.tableName, condition);
                 }
             }
         }
 
         async updateAuditListWithAuditType(transaction, tenderId, sp_status, sp_type, auditGroup, sp_group = 0) {
+            const tidCondition = isNaN(tenderId) ? { spid: tenderId } : { tid: tenderId };
             const auditList = await this.getAuditList(tenderId, sp_type, sp_status, sp_group);
             const newAuditList = [];
             for (const group of auditGroup) {
@@ -472,11 +491,14 @@ module.exports = app => {
             if (sameAudit) return;
 
             // 更新固定审批流
-            await transaction.delete(this.tableName, { tid: tenderId, sp_type, sp_status, sp_group });
+            const condition = {
+                sp_type, sp_status, sp_group,
+            };
+            this._.assign(condition, tidCondition);
+            await transaction.delete(this.tableName, condition);
             const insertDatas = [];
             for (const a of newAuditList) {
-                insertDatas.push({
-                    tid: tenderId,
+                const oneInsertData = {
                     sp_type,
                     sp_status,
                     audit_id: a.aid || a.uid || a.audit_id,
@@ -489,7 +511,9 @@ module.exports = app => {
                     audit_group_need: a.audit_group_need || 1,
                     audit_ledger_id: a.audit_ledger_id || '',
                     sp_group,
-                });
+                };
+                this._.assign(oneInsertData, tidCondition);
+                insertDatas.push(oneInsertData);
             }
             await transaction.insert(this.tableName, insertDatas);
         }

+ 20 - 13
app/service/shenpi_group.js

@@ -25,17 +25,20 @@ module.exports = app => {
         }
 
         async getGroupList(tid, type, transaction = null) {
-            return transaction ? await transaction.select(this.tableName, { where: { tid, sp_type: type } }) : await this.getAllDataByCondition({ where: { tid, sp_type: type } });
+            // 判断tid是字符还是数字,如果是数字就是tid,不是则是spid
+            const condition = isNaN(tid) ? { spid: tid, sp_type: type } : { tid, sp_type: type };
+            return transaction ? await transaction.select(this.tableName, { where: condition }) : await this.getAllDataByCondition({ where: condition });
         }
 
         async getOneGroup(id) {
             const group = await this.getDataById(id);
             if (group.change_type) group.change_type = JSON.parse(group.change_type);
-            group.auditGroupList = await this.ctx.service.shenpiAudit.getAuditGroupList(group.tid, group.sp_type, shenpiConst.sp_status.gdspl, group.id);
+            group.auditGroupList = await this.ctx.service.shenpiAudit.getAuditGroupList(group.tid === null ? group.spid : group.tid, group.sp_type, shenpiConst.sp_status.gdspl, group.id);
             return group;
         }
 
         async saveGroup(tid, data) {
+            const condition = isNaN(tid) ? { spid: tid } : { tid };
             const transaction = await this.db.beginTransaction();
             try {
                 const sp_type = shenpiConst.sp_type[data.code];
@@ -54,16 +57,16 @@ module.exports = app => {
                         id: info.id,
                         name: data.name,
                     };
-                    if (data.code === 'change' && data.change_type) {
+                    if ((data.code === 'change' || data.code === 'contract') && data.change_type) {
                         updateData.change_type = JSON.stringify(data.change_type);
                     }
                     await transaction.update(this.tableName, updateData);
                 } else {
-                    const insertData = {
-                        tid, sp_type, name: data.name,
-                        is_select: 1, change_type: data.code === 'change' && data.change_type ? JSON.stringify(data.change_type) : null,
+                    const insertData = { sp_type, name: data.name,
+                        is_select: 1, change_type: (data.code === 'change' || data.code === 'contract') && data.change_type ? JSON.stringify(data.change_type) : null,
                         create_time: new Date(),
                     };
+                    this._.assign(insertData, condition);
                     const updateGroupData = this._.map(groupList, function(item) {
                         return {
                             id: item.id,
@@ -98,7 +101,7 @@ module.exports = app => {
             if (!info) {
                 throw '该审批组不存在';
             }
-            const groupList = await this.getGroupList(info.tid, info.sp_type);
+            const groupList = await this.getGroupList(info.tid === null ? info.spid : info.tid, info.sp_type);
             const updateGroupData = this._.map(groupList, function(item) {
                 return {
                     id: item.id,
@@ -117,7 +120,7 @@ module.exports = app => {
                 await transaction.delete(this.ctx.service.shenpiAudit.tableName, { sp_group: group.id });
                 await transaction.delete(this.tableName, { id: group.id });
                 // 第一个审批组设置为is_select=1
-                const groupList = await this.getGroupList(group.tid, group.sp_type, transaction);
+                const groupList = await this.getGroupList(group.tid === null ? group.spid : group.tid, group.sp_type, transaction);
                 if (groupList && groupList.length > 0) {
                     const updateGroupData = this._.map(groupList, function(item) {
                         return {
@@ -136,13 +139,16 @@ module.exports = app => {
         }
 
         async getGroupBySelect(tid, sp_type) {
-            return await this.getDataByCondition({ tid, sp_type, is_select: 1 });
+            const condition = isNaN(tid) ? { spid: tid, sp_type, is_select: 1 } : { tid, sp_type, is_select: 1 };
+            return await this.getDataByCondition(condition);
         }
 
         async getGroupListByStage(tid, sp_type) {
-            const sql = 'select * from ?? where tid= ? and sp_type= ? order by id asc';
-            const sqlParam = [this.tableName, tid, sp_type];
-            return await this.db.query(sql, sqlParam);
+            const condition = isNaN(tid) ? { spid: tid, sp_type } : { tid, sp_type };
+            return await this.getAllDataByCondition({ where: condition, order: [['id', 'asc']] });
+            // const sql = 'select * from ?? where tid= ? and sp_type= ? order by id asc';
+            // const sqlParam = [this.tableName, tid, sp_type];
+            // return await this.db.query(sql, sqlParam);
         }
 
         async getSelectGroupByStageType(tid, sp_type, group_id = 0) {
@@ -166,7 +172,8 @@ module.exports = app => {
         }
 
         async getGroupListByChangeType(tid, sp_type, change_type) {
-            const sql = 'select * from ?? where tid= ? and sp_type= ? and JSON_CONTAINS(change_type, json_object(?, true)) order by id asc';
+            const idSql = isNaN(tid) ? 'spid= ?' : 'tid= ?';
+            const sql = `select * from ?? where ${idSql} and sp_type= ? and JSON_CONTAINS(change_type, json_object(?, true)) order by id asc`;
             const sqlParam = [this.tableName, tid, sp_type, change_type];
             return await this.db.query(sql, sqlParam);
         }

+ 30 - 4
app/service/sub_project.js

@@ -24,6 +24,9 @@ const defaultFunRela = {
 const funSet = require('../const/fun_set');
 const defaultFunSet = funSet.defaultInfo;
 const pageShowConst = require('../const/sp_page_show').defaultSetting;
+const shenpiConst = require('../const/sp_shenpi');
+const defaultShenpi = shenpiConst.defaultInfo.shenpi;
+const arrayInfo = ['shenpi'];
 
 class DragTree {
     /**
@@ -701,10 +704,18 @@ module.exports = app => {
             return info;
         }
 
-        async updatePageshow(id) {
-            const result = await this.db.update(this.tableName, {
-                id, page_show: JSON.stringify(this.ctx.subProject.page_show),
-            });
+        getShenpi(shenpi) {
+            const info = shenpi ? JSON.parse(shenpi) : {};
+            for (const pi in defaultShenpi) {
+                info[pi] = info[pi] === undefined ? defaultShenpi[pi] : parseInt(info[pi]);
+                this.ctx.helper._.defaultsDeep(info[pi], defaultShenpi[pi]);
+            }
+            return info;
+        }
+
+        async updatePageshow(id, page_show = this.ctx.subProject.page_show, transaction = null) {
+            const condition = { id, page_show: JSON.stringify(page_show) };
+            const result = transaction ? await transaction.update(this.tableName, condition) : await this.db.update(this.tableName, condition);
             return result.affectedRows === 1;
         }
 
@@ -870,6 +881,21 @@ module.exports = app => {
             await this.db.update(this.ctx.service.subProjPermission.tableName, { id: permission.id, tp_cache: JSON.stringify(tp_cache)});
             return tp_cache;
         }
+
+        /**
+         * 保存标段相关信息
+         *
+         * @param data
+         * @return {Promise<void>}
+         */
+        async saveInfo(spid, data) {
+            for (const di in data) {
+                if (arrayInfo.indexOf(di) >= 0) {
+                    data[di] = JSON.stringify(data[di]);
+                }
+            }
+            await this.db.update(this.tableName, data, { where: { id: spid } });
+        }
     }
 
     return SubProject;

+ 24 - 2
app/view/contract/detail.ejs

@@ -92,6 +92,11 @@
                                 <a href="javascript:void(0);" id="cancel_contract_btn" style="display: none" class="btn btn-secondary btn-sm pull-right mr-2">取消</a>
                                 <a href="javascript:void(0);" id="save_contract_btn" style="display: none" class="btn btn-primary btn-sm pull-right mr-2">确定</a>
                                 <a href="javascript:void(0);" id="edit_contract_btn" style="display: none" class="btn btn-primary btn-sm pull-right mr-2">编辑合同</a>
+                                <% if (shenpi_status) { %>
+                                <span class="pull-right mr-2" id="shenpi_btn">
+                                    <a href="#sub-sp" data-toggle="modal" data-target="#sub-sp" class="btn btn-primary btn-sm">待上报</a>
+                                </span>
+                                <% } %>
                             </li>
                         </ul>
                     </div>
@@ -171,9 +176,17 @@
                                     <thead>
                                     <tr class="text-center">
                                         <% if (ctx.contract_type === contractConst.type.income) { %>
-                                            <th width="5%">序号</th><th width="5%">回款日期</th><th width="10%">回款金额</th><th width="10%">扣款金额</th><th width="10%">应回金额</th><th width="10%">实回金额</th><th width="5%">回款方式</th><th width="5%">创建人</th><th width="10%">创建时间</th><th width="15%">备注</th><th width="5%">附件</th><th width="10%">操作</th>
+                                            <th width="4%">序号</th><th width="5%">回款日期</th><th width="8%">回款金额</th><th width="8%">扣款金额</th><th width="8%">应回金额</th><th width="8%">实回金额</th><th width="5%">回款方式</th><th width="5%">创建人</th><th width="9%">创建时间</th><th width="10%">备注</th><th width="5%">附件</th>
+                                            <% if (pay_shenpi_status) { %>
+                                            <th width="15%">审批进度</th>
+                                            <% } %>
+                                            <th width="10%">操作</th>
                                         <% } else if (ctx.contract_type === contractConst.type.expenses) { %>
-                                            <th width="5%">序号</th><th width="5%">支付日期</th><th width="10%">付款金额</th><th width="10%">扣款金额</th><th width="10%">应付金额</th><th width="10%">实付金额</th><th width="5%">支付方式</th><th width="5%">创建人</th><th width="10%">创建时间</th><th width="15%">备注</th><th width="5%">附件</th><th width="10%">操作</th>
+                                            <th width="4%">序号</th><th width="5%">支付日期</th><th width="8%">付款金额</th><th width="8%">扣款金额</th><th width="8%">应付金额</th><th width="8%">实付金额</th><th width="5%">支付方式</th><th width="5%">创建人</th><th width="9%">创建时间</th><th width="10%">备注</th><th width="5%">附件</th>
+                                            <% if (pay_shenpi_status) { %>
+                                            <th width="15%">审批进度</th>
+                                            <% } %>
+                                            <th width="10%">操作</th>
                                         <% } %>
                                     </tr>
                                     </thead>
@@ -222,10 +235,19 @@
     const contractConst = JSON.parse(unescape('<%- escape(JSON.stringify(contractConst)) %>'));
     let contractTreeAudits = JSON.parse(unescape('<%- escape(JSON.stringify(contractTreeAudits)) %>'));
     const colSet = JSON.parse(unescape('<%- escape(JSON.stringify(colSet)) %>'));
+    const auditConst = JSON.parse(unescape('<%- escape(JSON.stringify(auditConst)) %>'));
+    const shenpi_status = JSON.parse(unescape('<%- escape(JSON.stringify(shenpi_status)) %>'));
+    const pay_shenpi_status = JSON.parse(unescape('<%- escape(JSON.stringify(pay_shenpi_status)) %>'));
+    const shenpi = JSON.parse(unescape('<%- escape(JSON.stringify(shenpi)) %>'));
+    const shenpiConst = JSON.parse(unescape('<%- escape(JSON.stringify(shenpiConst)) %>'));
+    const auditType = JSON.parse(unescape('<%- escape(JSON.stringify(auditType)) %>'));
+    const accountGroup3 = JSON.parse(unescape('<%- escape(JSON.stringify(accountGroup)) %>'));
+    const accountList3 = JSON.parse(unescape('<%- escape(JSON.stringify(accountList)) %>'));
 
     const thisUrl = JSON.parse(unescape('<%- escape(JSON.stringify(thisUrl)) %>'));
     const stdChapters = JSON.parse(unescape('<%- escape(JSON.stringify(stdChapters)) %>'));
     let types = JSON.parse(unescape('<%- escape(JSON.stringify(types)) %>'));
     types.push('');
     let contractPays = [];
+</script>)
 </script>

+ 115 - 2
app/view/contract/detail_modal.ejs

@@ -331,5 +331,118 @@
     </div>
 </div>
 <% } %>
-
-
+<% if (shenpi_status || pay_shenpi_status) { %>
+<!--上报审批-->
+<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 (shenpi.contract !== shenpiConst.sp_status.gdspl) { %>
+                        <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-search3" autocomplete="off"></div>
+                            <dl class="list-unstyled book-list search-user-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>
+                    <% } else if(spGroupList.length > 0 || paySpGroupList.length > 0) { %>
+                        <% if (spGroupList.length > 0) { %>
+                        <div class="row" id="contract-add-group" style="display: none">
+                            <div class="col-7"></div>
+                            <div class="col-5">
+                                <select class="form-control form-control-sm" id="change-sp-group">
+                                    <% for (const g of spGroupList) { %>
+                                        <option value="<%= g.id %>"><%= g.name %></option>
+                                    <% } %>
+                                </select>
+                            </div>
+                        </div>
+                        <% } %>
+                        <% if (paySpGroupList.length > 0) { %>
+                        <div class="row" id="pay-add-group" style="display: none">
+                            <div class="col-7"></div>
+                            <div class="col-5">
+                                <select class="form-control form-control-sm" id="change-pay-sp-group">
+                                    <% for (const g of paySpGroupList) { %>
+                                        <option value="<%= g.id %>"><%= g.name %></option>
+                                    <% } %>
+                                </select>
+                            </div>
+                        </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">
+                        </ul>
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                <input type="hidden" id="start-sp-cid" />
+                <input type="hidden" id="start-sp-cpid" />
+                <button class="btn btn-primary btn-sm" type="button" id="start-sp-btn">确认上报</button>
+            </div>
+        </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">审批流程</h5>
+            </div>
+            <div class="modal-body">
+                <div class="row">
+                    <div class="col-4">
+<!--                            <a class="sp-list-item" href="#sub-sp" data-toggle="modal" data-target="#sub-sp"-->
+<!--                               id="hideSp">修改审批流程</a>-->
+<!--                            <a class="sp-list-item" href="#sub-sp2" data-toggle="modal" data-target="#sub-sp2"-->
+<!--                               id="hideSp">修改审批流程</a>-->
+                        <div class="card modal-height-500 mt-3" style="overflow: auto">
+                            <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>
+                <input type="hidden" id="check-sp-cid" />
+                <input type="hidden" id="check-sp-cpid" />
+                <input type="hidden" id="check-operate" />
+                <span id="check-sp-btn-html">
+                </span>
+            </div>
+        </div>
+    </div>
+</div>
+<% } %>

+ 4 - 4
app/view/contract/setting.ejs

@@ -76,12 +76,12 @@
                                 <label class="mb-2"><i class="fa fa-list-ul"></i> 项目合同</label>
                                 <div class="form-group mb-1 ml-3">
                                     <div class="form-check form-check-inline mr-3">
-                                        <input class="form-check-input" type="checkbox" id="openContractSubPorjectShenpi" <% if (ctx.subProject.page_show.openContractSubPorjectShenpi) { %>checked<% } %>>
-                                        <label class="form-check-label" for="openContractSubPorjectShenpi">开启「合同审批」功能</label>
+                                        <input class="form-check-input" type="checkbox" id="openContractSubProjectShenpi" <% if (ctx.subProject.page_show.openContractSubProjectShenpi) { %>checked<% } %>>
+                                        <label class="form-check-label" for="openContractSubProjectShenpi">开启「合同审批」功能</label>
                                     </div>
                                     <div class="form-check form-check-inline ml-3">
-                                        <input class="form-check-input" type="checkbox" id="openContractPaySubPorjectShenpi" <% if (ctx.subProject.page_show.openContractPaySubPorjectShenpi) { %>checked<% } %>>
-                                        <label class="form-check-label" for="openContractPaySubPorjectShenpi">开启「支付/回款审批」功能</label>
+                                        <input class="form-check-input" type="checkbox" id="openContractPaySubProjectShenpi" <% if (ctx.subProject.page_show.openContractPaySubProjectShenpi) { %>checked<% } %>>
+                                        <label class="form-check-label" for="openContractPaySubProjectShenpi">开启「支付/回款审批」功能</label>
                                     </div>
                                 </div>
                                 <label class="my-2"><i class="fa fa-list-ul"></i> 标段合同</label>

+ 245 - 0
app/view/contract/shenpi.ejs

@@ -0,0 +1,245 @@
+<% include ./sub_menu.ejs %>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main d-flex">
+            <h2>审批流程设置</h2>
+        </div>
+    </div>
+    <div class="content-wrap">
+        <div class="c-body">
+            <div class="sjs-height-0">
+                <div class="row m-0 mt-3">
+                    <div class="col-7">
+                        <% for (const sp of shenpi.sp_lc) { %>
+                            <div class="card mb-3">
+                                <div class="card-body <%- sp.code %>_div">
+<!--                                    <a class="pull-right set-otherTender" data-name="<%- sp.name %>" data-code="<%- sp.code %>" href="#batch" data-toggle="modal" data-target="#batch">设置其他标段</a>-->
+<!--                                    <a class="pull-right set-otherShenpi mr-3" data-name="<%- sp.name %>" data-code="<%- sp.code %>" href="javascript: void(0);">设置其他流程</a>-->
+                                    <h5 class="card-title"><%- sp.name %></h5>
+                                    <div class="form-group">
+                                        <div class="form-group form-check">
+                                            <% for (const st in shenpi.sp_status_list) { %>
+                                            <% if (shenpi.sp_status_list[st]) { %>
+                                                <div class="custom-control custom-checkbox custom-control-inline">
+                                                    <input type="radio" class="custom-control-input" data-code="<%- sp.code %>" value="<%- shenpi.sp_status_list[st].status %>" name="<%- sp.code %>" id="<%- sp.code %>_<%- shenpi.sp_status_list[st].status %>" <%if (sp.status === shenpi.sp_status_list[st].status) { %>checked<% } %>>
+                                                    <label class="custom-control-label" for="<%- sp.code %>_<%- shenpi.sp_status_list[st].status %>"><%- shenpi.sp_status_list[st].name %></label>
+                                                </div>
+                                            <% } %>
+                                            <% } %>
+                                        </div>
+                                    </div>
+                                    <div class="alert alert-warning"><%- shenpi.sp_status_list[sp.status].name %>:<%- shenpi.sp_status_list[sp.status].msg %></div>
+                                    <div class="lc-show">
+                                    <% if (sp.status === shenpi.sp_status.gdspl) { %>
+                                    <% if (sp.groupList && sp.groupList.length > 0) { %>
+                                        <!-- 切换 编辑 新建审批组-->
+                                        <div class="d-flex justify-content-start align-items-center mb-3">
+                                            <span class="col-auto">当前审批组:</span>
+                                            <span style="width: 200px;">
+                                                <select class="form-control form-control-sm group-list">
+                                                    <% for (const group of sp.groupList) { %>
+                                                        <option value="<%- group.id %>" <% if (group.is_select === 1) { %>selected<% } %>><%- group.name %></option>
+                                                    <% } %>
+                                                </select>
+                                            </span>
+                                            <span class="pl-3"><a href="javascript:void(0);" class="show-spzsave edit-spzsave" data-group="<%- sp.groupList.find(x => { return x.is_select === 1 }).id %>" data-code="<%- sp.code %>"><i class="fa fa-edit"></i> 编辑审批组</a></span>
+                                            <span class="pl-3"><a href="javascript:void(0);" class="show-spzsave" data-code="<%- sp.code %>"><i class="fa fa-plus"></i> 添加审批组</a></span>
+                                        </div>
+                                    <% } %>
+                                    <ul class="list-unstyled">
+                                        <% if (sp.auditGroupList.length > 0) { %>
+                                        <% for (const [i, auditGroup] of sp.auditGroupList.entries()) { %>
+                                        <li class="d-flex justify-content-start mb-3 align-items-center">
+                                            <span class="col-auto"><%- ctx.helper.transFormToChinese(i+1) %>审</span>
+                                            <span class="col-7 spr-span">
+                                                <span class="d-inline-block">
+                                                    <select class="form-control form-control-sm audit-type-key" data-type="<%- auditGroup[0].audit_type %>">
+                                                        <% for (const at of auditType.types) { %>
+                                                        <% if (at.valid && at.valid.indexOf(sp.code) < 0) continue; %>
+                                                        <option value="<%- at.value %>" <% if (auditGroup[0].audit_type === at.value) { %>selected<%} %>><%- at.name %></option>
+                                                        <% } %>
+                                                    </select>
+                                                </span>
+                                                <% for (const audit of auditGroup) { %>
+                                                <span class="d-inline-block"><span class="badge badge-light"><%- audit.name %> <span class="dropdown">
+                                                            <a href="javascript:void(0);" class="btn-sm text-danger px-1" title="移除" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><i class="fa fa-remove"></i></a>
+                                                            <div class="dropdown-menu">
+                                                                <a class="dropdown-item" href="javascript:void(0);">确认移除审批人?</a>
+                                                                <div class="dropdown-divider"></div>
+                                                                <div class="px-2 py-1 text-center">
+                                                                    <button class="remove-audit btn btn-sm btn-danger" data-id="<%- audit.audit_id %>">移除</button>
+                                                                    <button class="btn btn-sm btn-secondary">取消</button>
+                                                                </div>
+                                                            </div>
+                                                </span></span></span>
+                                                <% } %>
+                                                <% if (auditGroup[0].audit_type !== auditType.key.common) { %>
+                                                <span class="d-inline-block">
+                                                <div class="dropdown text-right">
+                                                    <button class="btn btn-outline-primary btn-sm dropdown-toggle" type="button" id="<%- sp.code %>_dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+                                                        选择审批人
+                                                    </button>
+                                                    <div class="dropdown-menu dropdown-menu-right" id="<%- sp.code %>_dropdownMenu" aria-labelledby="<%- sp.code %>_dropdownMenuButton" style="width:220px">
+                                                        <div class="mb-2 p-2"><input class="form-control form-control-sm gr-search"
+                                                                                     placeholder="姓名/手机 检索" autocomplete="off" data-code="<%- sp.code %>"></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>
+                                                </span>
+                                                <% } %>
+                                                <% if (auditGroup[0].audit_type === auditType.key.union) { %>
+                                                <button class="btn btn-outline-primary btn-sm" sp_type="<%- sp.code %>" audit_order="<%- auditGroup[0].audit_order %>" name="union-set">协同设置</button>
+                                                <% } %>
+                                                <% if (auditGroup[0].audit_type === auditType.key.and && auditType.info[auditType.key.and].setValid.indexOf(sp.code) >= 0) { %>
+                                                <button class="btn btn-outline-primary btn-sm" sp_type="<%- sp.code %>" audit_order="<%- auditGroup[0].audit_order %>" name="and-set">会签设置</button>
+                                                <% } %>
+                                                <% if (auditGroup[0].audit_type === auditType.key.or && auditType.info[auditType.key.or].setValid.indexOf(sp.code) >= 0) { %>
+                                                <button class="btn btn-outline-primary btn-sm" sp_type="<%- sp.code %>" audit_order="<%- auditGroup[0].audit_order %>" name="or-set">或签设置</button>
+                                                <% } %>
+                                                <% if (auditGroup[0].audit_type === auditType.key.multi) { %>
+                                                <button class="btn btn-outline-primary btn-sm" sp_type="<%- sp.code %>" audit_order="<%- auditGroup[0].audit_order %>" name="multi-set">分组设置</button>
+                                                <% } %>
+                                            </span>
+                                        </li>
+                                        <% } %>
+                                        <li>
+                                            <span class="pl-3"><a href="javascript:void(0);" class="add-audit" ><i class="fa fa-plus"></i> 添加流程</a></span>
+                                            <% if (sp.code === 'contract' && (!sp.groupList || (sp.groupList && sp.groupList.length === 0))) { %>
+                                            <span class="pl-3"><a href="javascript:void(0);" class="show-spzsave" data-code="<%- sp.code %>"><i class="fa fa-save"></i> 存为审批组</a></span>
+                                            <% } %>
+                                        </li>
+                                        <% } else { %>
+                                        <li class="d-flex justify-content-start mb-3 align-items-center">
+                                            <span class="col-auto">一审</span>
+                                            <span class="col-7 spr-span">
+                                                <span class="d-inline-block">
+                                                    <select class="form-control form-control-sm audit-type-key" data-type="<%- auditType.key.common %>">
+                                                        <% for (const at of auditType.types) { %>
+                                                        <% if (at.valid && at.valid.indexOf(sp.code) < 0) continue; %>
+                                                        <option value="<%- at.value %>" <% if (auditType.key.common === at.value) { %>selected<%} %>><%- at.name %></option>
+                                                        <% } %>
+                                                    </select>
+                                                </span>
+                                            <span class="d-inline-block">
+                                                <div class="dropdown text-right">
+                                                    <button class="btn btn-outline-primary btn-sm dropdown-toggle" type="button" id="<%- sp.code %>_dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+                                                        选择审批人
+                                                    </button>
+                                                    <div class="dropdown-menu dropdown-menu-right" id="<%- sp.code %>_dropdownMenu" aria-labelledby="<%- sp.code %>_dropdownMenuButton" style="width:220px">
+                                                        <div class="mb-2 p-2"><input class="form-control form-control-sm gr-search"
+                                                                                     placeholder="姓名/手机 检索" autocomplete="off" data-code="<%- sp.code %>"></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>
+                                            </span>
+                                        </span>
+                                        </li>
+                                        <% } %>
+                                    </ul>
+                                    <% } else if (sp.status === shenpi.sp_status.gdzs) { %>
+                                    <ul class="list-unstyled">
+                                        <li class="d-flex justify-content-start mb-3">
+                                            <span class="col-auto">授权审批人</span>
+                                            <span class="col-7">
+                                                <span class="d-inline-block"></span>
+                                            </span>
+                                        </li>
+                                        <% if (sp.audit) { %>
+                                        <li class="d-flex justify-content-start mb-3 align-items-center">
+                                            <span class="col-auto">终审</span>
+                                            <span class="col-7 spr-span">
+                                            <span class="d-inline-block"></span>
+                                            <span class="d-inline-block"><span class="badge badge-light"><%- sp.audit.name %> <span class="dropdown">
+                                                            <a href="javascript:void(0);" class="btn-sm text-danger px-1" title="移除" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><i class="fa fa-remove"></i></a>
+                                                            <div class="dropdown-menu">
+                                                                <a class="dropdown-item" href="javascript:void(0);">确认移除审批人?</a>
+                                                                <div class="dropdown-divider"></div>
+                                                                <div class="px-2 py-1 text-center">
+                                                                    <button class="remove-audit btn btn-sm btn-danger" data-id="<%- sp.audit.audit_id %>">移除</button>
+                                                                    <button class="btn btn-sm btn-secondary">取消</button>
+                                                                </div>
+                                                            </div>
+                                                        </span>
+                                                    <!--<a href="javascript:void(0);" class="remove-audit btn-sm text-danger px-1" title="移除" data-id=""><i class="fa fa-remove"></i></a></span> </span>-->
+                                            </span></span></span>
+                                        </li>
+                                        <% } else { %>
+                                        <li class="d-flex justify-content-start mb-3 align-items-center">
+                                            <span class="col-auto">终审</span>
+                                            <span class="col-7 spr-span">
+                                                <span class="d-inline-block">
+                                                    <div class="dropdown text-right">
+                                                        <button class="btn btn-outline-primary btn-sm dropdown-toggle" type="button" id="<%- sp.code %>_dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+                                                            选择审批人
+                                                        </button>
+                                                        <div class="dropdown-menu dropdown-menu-right" id="<%- sp.code %>_dropdownMenu" aria-labelledby="<%- sp.code %>_dropdownMenuButton" style="width:220px">
+                                                            <div class="mb-2 p-2"><input class="form-control form-control-sm gr-search"
+                                                                                         placeholder="姓名/手机 检索" autocomplete="off" data-code="<%- sp.code %>"></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>
+                                                </span>
+                                            </span>
+                                        </li>
+                                        <% } %>
+                                    </ul>
+                                    <% } %>
+                                    </div>
+                                </div>
+                            </div>
+                        <% } %>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+<script>
+    const sp_lc = JSON.parse(unescape('<%- escape(JSON.stringify(shenpi.sp_lc)) %>'));
+    const sp_type = JSON.parse('<%- JSON.stringify(shenpi.sp_type) %>');
+    const sp_status = JSON.parse('<%- JSON.stringify(shenpi.sp_status) %>');
+    const sp_status_list = JSON.parse('<%- JSON.stringify(shenpi.sp_status_list) %>');
+    const accountGroup = JSON.parse(unescape('<%- escape(JSON.stringify(accountGroup)) %>'));
+    const accountList = JSON.parse(unescape('<%- escape(JSON.stringify(accountList)) %>'));
+    const auditType = JSON.parse(unescape('<%- escape(JSON.stringify(auditType)) %>'));
+    const cur_uid = JSON.parse('<%- JSON.stringify(ctx.subProject.user_id) %>');
+</script>

+ 204 - 0
app/view/contract/shenpi_modal.ejs

@@ -0,0 +1,204 @@
+<!--设置其他标段-->
+<div class="modal fade" id="batch" 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="alert alert-warning">将「<span id="shenpi-name"></span>」设置至其他标段</div>
+                <div class="input-group input-group-sm mb-2">
+                    <input class="form-control" placeholder="输入审批人名称搜索" type="text" name="audit-name" id="search-audit">
+                    <div class="input-group-append">
+                        <span class="input-group-text" id="search-result">0/0</span>
+                        <button class="btn btn-outline-secondary" id="up-search" disabled><i class="fa fa-arrow-up" aria-hidden="true"></i></button>
+                        <button class="btn btn-outline-secondary" id="down-search" disabled><i class="fa fa-arrow-down" aria-hidden="true"></i></button>
+                    </div>
+                </div>
+                <div class="modal-height-300" id="tender-list">
+                </div>
+            </div>
+            <div class="modal-footer">
+                <input type="hidden" id="shenpi_auditors" value="">
+                <input type="hidden" id="shenpi_code" value="">
+                <input type="hidden" id="shenpi_status" value="">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
+                <button type="button" class="btn btn-sm btn-primary" id="save-other-tender">确认</button>
+            </div>
+        </div>
+    </div>
+</div>
+<!--设置其他流程-->
+<div class="modal fade" id="batch2" 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="alert alert-warning">将「<span id="shenpi-name2"></span>」设置至其他流程</div>
+                <div class="modal-height-300" id="shenpi-list">
+                    <table class="table table-hover table-bordered">
+                        <thead>
+                        <th>名称</th>
+                        <th width="100">审批流程</th>
+                        <th width="40">选择</th>
+                        </thead>
+                        <tr>
+                            <td>预付款审批</td>
+                            <td>固定流程<i class="fa fa-question-circle text-primary" data-container="body" data-toggle="tooltip" data-placement="bottom" data-original-title="王五-张三-李四"></i></td>
+                            <td></td>
+                        </tr>
+                        <tr>
+                            <td>台账审批</td>
+                            <td>固定流程<i class="fa fa-question-circle text-primary" data-container="body" data-toggle="tooltip" data-placement="bottom" data-original-title="王五-张三-李四"></i></td>
+                            <td><input type="checkbox"></td>
+                        </tr>
+                        <tr>
+                            <td>台账修订</td>
+                            <td>固定流程<i class="fa fa-question-circle text-primary" data-container="body" data-toggle="tooltip" data-placement="bottom" data-original-title="王五-张三-李四"></i></td>
+                            <td><input type="checkbox"></td>
+                        </tr>
+                        <tr>
+                            <td>计量期审批</td>
+                            <td>固定流程<i class="fa fa-question-circle text-primary" data-container="body" data-toggle="tooltip" data-placement="bottom" data-original-title="王五-张三-李四"></i></td>
+                            <td><input type="checkbox"></td>
+                        </tr>
+                        <tr>
+                            <td>工程变更审批</td>
+                            <td>固定流程<i class="fa fa-question-circle text-primary" data-container="body" data-toggle="tooltip" data-placement="bottom" data-original-title="王五-张三-李四"></i></td>
+                            <td><input type="checkbox"></td>
+                        </tr>
+                        <tr>
+                            <td>材料调差审批</td>
+                            <td>固定流程<i class="fa fa-question-circle text-primary" data-container="body" data-toggle="tooltip" data-placement="bottom" data-original-title="王五-张三-李四"></i></td>
+                            <td><input type="checkbox"></td>
+                        </tr>
+                    </table>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <input type="hidden" id="shenpi_auditors2" value="">
+                <input type="hidden" id="shenpi_code2" value="">
+                <input type="hidden" id="shenpi_status2" value="">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
+                <button type="button" class="btn btn-sm btn-primary" id="save-other-shenpi">确认</button>
+            </div>
+        </div>
+    </div>
+</div>
+<div class="modal fade" id="union" data-backdrop="static">
+    <div class="modal-dialog modal-xl" 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-6">
+                        <div class="modal-height-500" style="overflow: auto;">
+                            <table class="table table-hover table-bordered">
+                                <thead class="text-center" >
+                                <tr><th>姓名</th><th>单位</th><th>协同处理</th></tr>
+                                </thead>
+                                <tbody id="union_table">
+                                </tbody>
+                            </table>
+                        </div>
+                    </div>
+                    <div class="col-6">
+                        <div class="modal-height-500" style="overflow: auto;" id="union-spread">
+                        </div>
+                    </div>
+                </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="union-ok">保存</button>
+            </div>
+        </div>
+    </div>
+</div>
+<div class="modal fade" id="multi" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title" id="multi-title">分组审批</h5>
+            </div>
+            <div class="modal-body">
+                <div class="modal-height-500" style="overflow: auto;" id="multi-spread">
+                </div>
+                <div name="set-hint" htype="<%- auditType.key.multi %>">
+                    顺序:从1开始,务必连续,某审批人审批通过时,将会跳过其前面所有的审批人。<br/>
+                    受限:受限时,前面所有的审批人都审批通过后,方可审批。
+                </div>
+                <div name="set-hint" htype="<%- auditType.key.and %>">
+                    顺序:从1开始,务必连续,某审批人审批通过时,将会跳过其前面所有的审批人。
+                </div>
+                <div name="set-hint" htype="<%- auditType.key.or %>">
+                    最少审批:该审批人审批通过时,审批通过人数满足则结束本流程。
+                </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="multi-ok">保存</button>
+            </div>
+        </div>
+    </div>
+</div>
+<!--存为审批组-->
+<div class="modal fade" id="spzsave" 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-2">
+                    <label>审批组名称<b class="text-danger">*</b></label>
+                    <input class="form-control form-control-sm" name="group_name" placeholder="输入名称" type="text">
+                </div>
+                <div class="form-group" id="show-contract-type" style="display: none">
+                    <label>显示模块</label>
+                    <div>
+                        <% for (const c of contract_type_list) { %>
+                            <div class="form-check form-check-inline">
+                                <input class="form-check-input" type="checkbox" data-type="<%- c.code %>" name="contract_type[]" id="<%- c.code %>_checkbox" disabled>
+                                <label class="form-check-label" for="<%- c.code %>_checkbox"><%- c.name %></label>
+                            </div>
+                        <% } %>
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <input type="hidden" id="save-code">
+                <input type="hidden" id="save-group-id">
+                <button type="button" id="show-delete-group-btn" style="display: none;" class="btn btn-sm btn-danger mr-auto">删除审批组</button>
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                <button type="button" class="btn btn-sm btn-primary" id="save-group-btn">确定</button>
+            </div>
+        </div>
+    </div>
+</div>
+<!--删除审批组-->
+<div class="modal fade" id="spzdelete" 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-2">
+                    <h5>确认删除<span id="delete-group-name"></span>?</h5>
+                    <h5>删除后,该审批组已添加的人员也将一同移除,请谨慎操作。</h5>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <input type="hidden" id="delete-code">
+                <input type="hidden" id="delete-group-id">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                <button type="button" class="btn btn-sm btn-danger" id="delete-group-btn">确定删除</button>
+            </div>
+        </div>
+    </div>
+</div>

+ 11 - 0
app/view/sp_setting/manage_modal.ejs

@@ -330,6 +330,17 @@
                         <% } %>
                     </div>
                 </div>
+                <div class="form-group" id="show-contract-type" style="display: none">
+                    <label>显示模块</label>
+                    <div>
+                        <% for (const c of contract_type_list) { %>
+                            <div class="form-check form-check-inline">
+                                <input class="form-check-input" type="checkbox" data-type="<%- c.code %>" name="contract_type[]" id="<%- c.code %>_checkbox" disabled>
+                                <label class="form-check-label" for="<%- c.code %>_checkbox"><%- c.name %></label>
+                            </div>
+                        <% } %>
+                    </div>
+                </div>
             </div>
             <div class="modal-footer">
                 <input type="hidden" id="save-code">

+ 1 - 1
app/view/tender/shenpi.ejs

@@ -123,7 +123,7 @@
                                         <% } %>
                                         <li>
                                             <span class="pl-3"><a href="javascript:void(0);" class="add-audit" ><i class="fa fa-plus"></i> 添加流程</a></span>
-                                            <% if (sp.code === 'change' && (!sp.groupList || (sp.groupList && sp.groupList.length === 0))) { %>
+                                            <% if ((sp.code === 'change' || sp.code === 'contract') && (!sp.groupList || (sp.groupList && sp.groupList.length === 0))) { %>
                                             <span class="pl-3"><a href="javascript:void(0);" class="show-spzsave" data-code="<%- sp.code %>"><i class="fa fa-save"></i> 存为审批组</a></span>
                                             <% } else if (sp.code === 'stage' && (!sp.groupList || sp.groupList.length === 0)) { %>
                                             <span class="pl-3"><a href="javascript:void(0);" class="show-spzsave" data-code="<%- sp.code %>"><i class="fa fa-save"></i> 存为审批组</a></span>

+ 11 - 0
app/view/tender/shenpi_modal.ejs

@@ -250,6 +250,17 @@
                         <% } %>
                     </div>
                 </div>
+                <div class="form-group" id="show-contract-type" style="display: none">
+                    <label>显示模块</label>
+                    <div>
+                        <% for (const c of contract_type_list) { %>
+                            <div class="form-check form-check-inline">
+                                <input class="form-check-input" type="checkbox" data-type="<%- c.code %>" name="contract_type[]" id="<%- c.code %>_checkbox" disabled>
+                                <label class="form-check-label" for="<%- c.code %>_checkbox"><%- c.name %></label>
+                            </div>
+                        <% } %>
+                    </div>
+                </div>
             </div>
             <div class="modal-footer">
                 <input type="hidden" id="save-code">

+ 18 - 0
config/web.js

@@ -1907,6 +1907,24 @@ const JsFiles = {
                 ],
                 mergeFile: 'contract_setting',
             },
+            shenpi: {
+                files: [
+                    '/public/js/spreadjs/sheets/v11/gc.spread.sheets.all.11.2.2.min.js',
+                    '/public/js/decimal.min.js',
+                ],
+                mergeFiles: [
+                    '/public/js/sub_menu.js',
+                    '/public/js/div_resizer.js',
+                    '/public/js/spreadjs_rela/spreadjs_zh.js',
+                    '/public/js/shares/sjs_setting.js',
+                    '/public/js/zh_calc.js',
+                    '/public/js/path_tree.js',
+                    '/public/js/sp_shenpi.js',
+                    // '/public/js/tender_showhide.js',
+                    // '/public/js/shares/show_level.js',
+                ],
+                mergeFile: 'contract_shenpi',
+            },
         },
         financial: {
             index: {

+ 40 - 6
sql/update.sql

@@ -8,17 +8,24 @@
 ------------------------------------
 
 ALTER TABLE `zh_contract`
-ADD COLUMN `type` varchar(255) NULL DEFAULT '' COMMENT '合同类型(筛选的字段)' AFTER `name`,
+ADD COLUMN `need_shenpi` tinyint(1) NULL DEFAULT 0 COMMENT '是否需要审批' AFTER `name`,
+ADD COLUMN `status` tinyint(2) NULL DEFAULT 1 COMMENT '审批状态' AFTER `need_shenpi`,
+ADD COLUMN `times` tinyint(3) NULL DEFAULT 1 COMMENT '审批次数' AFTER `status`,
+ADD COLUMN `sp_group` int(11) NULL DEFAULT 0 COMMENT '固定审批组id' AFTER `times`,
+ADD COLUMN `final_auditor_str` varchar(50) NOT NULL DEFAULT '' COMMENT '终审人相关(cache)' AFTER `sp_group`,
+ADD COLUMN `type` varchar(255) NULL DEFAULT '' COMMENT '合同类型(筛选的字段)' AFTER `final_auditor_str`,
 ADD COLUMN `remark1` varchar(1000) NULL DEFAULT '' COMMENT '备注1' AFTER `remark`;
 
+ALTER TABLE `zh_contract_pay`
+ADD COLUMN `need_shenpi` tinyint(1) NULL COMMENT '是否需要审批' AFTER `fpcid`,
+ADD COLUMN `status` tinyint(2) NULL DEFAULT 1 COMMENT '审批状态' AFTER `need_shenpi`,
+ADD COLUMN `times` tinyint(3) NULL DEFAULT 1 COMMENT '审批次数' AFTER `status`,
+ADD COLUMN `sp_group` int(11) NULL DEFAULT 0 COMMENT '固定审批组id' AFTER `times`,
+ADD COLUMN `final_auditor_str` varchar(50) NOT NULL DEFAULT '' COMMENT '终审人相关(cache)' AFTER `sp_group`;
+
 ALTER TABLE `zh_contract_tree`
 ADD COLUMN `remark1` varchar(1000) NULL DEFAULT '' COMMENT '备注1' AFTER `remark`;
 
-ALTER TABLE `zh_contract`
-
-ADD COLUMN `calc` decimal(30, 6) NULL DEFAULT NULL COMMENT '计算1' AFTER `remark2`,
-ADD COLUMN `calc2` decimal(30, 6) NULL DEFAULT NULL COMMENT '计算2' AFTER `calc`;
-
 ALTER TABLE `zh_shenpi_audit`
 ADD COLUMN `audit_group` varchar(20) NOT NULL DEFAULT '' COMMENT '审批分组' AFTER `audit_ledger_id`,
 ADD COLUMN `audit_group_order` tinyint(4) NOT NULL DEFAULT 0 COMMENT '组内审批顺序' AFTER `audit_group`,
@@ -440,6 +447,33 @@ ADD COLUMN `ctrl_chapter_id` varchar(255) CHARACTER SET ascii COLLATE ascii_gene
 ADD COLUMN `ctrl_template_id` varchar(255) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL DEFAULT '' COMMENT '控制目标-新建模板-id列表(\',\'分隔)' AFTER `ctrl_chapter_id`,
 ADD COLUMN `ctrl_bills_id` varchar(255) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL DEFAULT '' COMMENT '控制目标-工程量清单-id列表(\',\'分隔)' AFTER `ctrl_template_id`;
 
+ALTER TABLE `zh_sub_project` ADD `shenpi` VARCHAR(1000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '审批流程设置' AFTER `payment_setting`;
+
+ALTER TABLE `zh_shenpi_group`
+ADD COLUMN `spid` varchar(255) NULL DEFAULT NULL COMMENT '项目id' AFTER `id`;
+
+ALTER TABLE `zh_shenpi_audit`
+ADD COLUMN `spid` varchar(255) NULL DEFAULT NULL COMMENT '项目id' AFTER `id`,
+MODIFY COLUMN `tid` int(11) NULL DEFAULT NULL COMMENT '标段id' AFTER `spid`;
+
+CREATE TABLE `zh_contract_sp_audit` (
+  `id` int NOT NULL AUTO_INCREMENT,
+  `spid` varchar(255) NULL DEFAULT NULL COMMENT '项目id',
+  `tid` int NULL DEFAULT NULL COMMENT '标段id',
+  `cid` varchar(100) NULL DEFAULT NULL COMMENT '合同详情id',
+  `cpid` int NULL DEFAULT NULL COMMENT '合同支付id',
+  `aid` int NOT NULL COMMENT '审批人id',
+  `order` int NOT NULL COMMENT '审批顺序',
+  `times` int NOT NULL COMMENT '审批次数',
+  `status` tinyint(1) NOT NULL COMMENT '审批状态',
+  `begin_time` datetime NULL DEFAULT NULL COMMENT '开始审批时间',
+  `end_time` datetime NULL DEFAULT NULL COMMENT '结束审批时间',
+  `opinion` varchar(1000) NULL DEFAULT NULL COMMENT '审批意见',
+  `audit_type` tinyint(4) NOT NULL DEFAULT 1 COMMENT '审批类型(1个人,2会签,3或签)',
+  `audit_order` tinyint(4) NOT NULL DEFAULT 0 COMMENT '审批顺序',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT = '合同详情和合同支付,回款审批人表';
+
 ------------------------------------
 -- 表数据
 ------------------------------------