Просмотр исходного кода

Merge branch 'dev' of http://192.168.1.41:3000/maixinrong/Calculation into dev

Tony Kang 11 часов назад
Родитель
Сommit
6ad7caccf7
48 измененных файлов с 5105 добавлено и 132 удалено
  1. 122 3
      app/const/audit.js
  2. 4 0
      app/const/contract.js
  3. 8 0
      app/const/shenpi.js
  4. 4 0
      app/const/sp_page_show.js
  5. 51 0
      app/const/sp_shenpi.js
  6. 1 0
      app/const/tender_info.js
  7. 261 10
      app/controller/contract_controller.js
  8. 82 0
      app/controller/sub_proj_controller.js
  9. 1 0
      app/controller/sub_proj_setting_controller.js
  10. 1 0
      app/controller/tender_controller.js
  11. 5 1
      app/middleware/contract_check.js
  12. 1 0
      app/middleware/sub_project_check.js
  13. 622 32
      app/public/js/contract_detail.js
  14. 10 0
      app/public/js/contract_setting.js
  15. 388 0
      app/public/js/ledger.js
  16. 185 1
      app/public/js/path_tree.js
  17. 40 3
      app/public/js/shenpi.js
  18. 1211 0
      app/public/js/sp_shenpi.js
  19. 2 8
      app/public/js/stage.js
  20. 9 1
      app/router.js
  21. 4 2
      app/service/calc_tmpl.js
  22. 49 2
      app/service/contract.js
  23. 2 0
      app/service/contract_audit.js
  24. 6 2
      app/service/contract_col_set.js
  25. 20 0
      app/service/contract_pay.js
  26. 1192 0
      app/service/contract_sp_audit.js
  27. 4 0
      app/service/contract_tree.js
  28. 1 1
      app/service/phase_pay_file.js
  29. 23 6
      app/service/pos_calc_detail.js
  30. 1 1
      app/service/quality_inspection_audit.js
  31. 1 1
      app/service/safe_inspection_audit.js
  32. 46 22
      app/service/shenpi_audit.js
  33. 20 13
      app/service/shenpi_group.js
  34. 2 0
      app/service/stage_att.js
  35. 5 1
      app/service/stage_bonus.js
  36. 30 4
      app/service/sub_project.js
  37. 2 0
      app/view/contract/col_set.ejs
  38. 24 2
      app/view/contract/detail.ejs
  39. 115 2
      app/view/contract/detail_modal.ejs
  40. 5 5
      app/view/contract/setting.ejs
  41. 245 0
      app/view/contract/shenpi.ejs
  42. 204 0
      app/view/contract/shenpi_modal.ejs
  43. 6 2
      app/view/ledger/explode.ejs
  44. 11 0
      app/view/sp_setting/manage_modal.ejs
  45. 1 1
      app/view/tender/shenpi.ejs
  46. 11 0
      app/view/tender/shenpi_modal.ejs
  47. 18 0
      config/web.js
  48. 49 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,
 };

+ 4 - 0
app/const/contract.js

@@ -25,6 +25,7 @@ const typeName = {
 
 const colSet = {
     [type.expenses]: [
+        { name: '合同类型', field: 'type', fixed: ['alias'], gd: true },
         { name: '累计应付', field: 'yf_price', fixed: ['alias'] },
         { name: '应付进度', field: 'stackedBar', fixed: ['alias'] },
         { name: '累计实付', field: 'sf_price', fixed: ['alias'] },
@@ -35,6 +36,7 @@ const colSet = {
         { name: '计算2', field: 'calc2', fixed: [] },
     ],
     [type.income]: [
+        { name: '合同类型', field: 'type', fixed: ['alias'], gd: true },
         { name: '累计应回', field: 'yf_price', fixed: ['alias'] },
         { name: '应回进度', field: 'stackedBar', fixed: ['alias'] },
         { name: '累计实回', field: 'sf_price', fixed: ['alias'] },
@@ -48,6 +50,7 @@ const colSet = {
 
 const defaultColSet = {
     [type.expenses]: [
+        { field: 'type', show: 1, gd: true },
         { field: 'yf_price', show: 1 },
         { field: 'stackedBar', show: 1 },
         { field: 'sf_price', show: 0 },
@@ -58,6 +61,7 @@ const defaultColSet = {
         { field: 'calc2', show: 0, alias: '计算2' },
     ],
     [type.income]: [
+        { field: 'type', show: 1, gd: true },
         { field: 'yf_price', show: 1 },
         { field: 'stackedBar', show: 1 },
         { field: 'sf_price', show: 0 },

+ 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;

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

@@ -72,6 +72,18 @@ $(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 gdColField = ['type'];
     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},
@@ -88,6 +100,13 @@ $(document).ready(function() {
         if (colInfo && col.show) {
             contractSpreadSetting.cols.push(Object.assign({}, colInfo, { title: (col && col.alias) || col.name }));
         }
+        if (col.gd) {
+            const gdColInfo = gdColField.includes(col.field);
+            if (gdColInfo && !col.show) {
+                const index = contractSpreadSetting.cols.findIndex(c => c.field === col.field);
+                if (index !== -1) contractSpreadSetting.cols.splice(index, 1);
+            }
+        }
     });
 
 
@@ -310,7 +329,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 +355,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 +363,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 +385,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 +509,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 +530,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 +555,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 +573,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 +991,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 +1609,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 +1760,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 +2272,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 +2298,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 +2505,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();
+            // }
         });
     });
 
+    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;
+    }
+
+    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(''));
+        }
+
+        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) {
+        });
+    });
 });

+ 388 - 0
app/public/js/ledger.js

@@ -205,6 +205,7 @@ $(document).ready(function() {
                     for (let i = 1; i <= addCount; i++) {
                         data.add.push({ lid: node.lid, pid: node.id, pcd_order: detailRange.length + i });
                     }
+                    if (first) data.select = first.id;
                 } else if (type === 'delete') {
                     data.del = [];
                     for (let iRow = 0; iRow < count; iRow++) {
@@ -504,6 +505,376 @@ $(document).ready(function() {
 
         return { detail, spread, sheet, refresh, loadCurDetailData, reloadCurDetailData }
     })();
+    const ancGclDetail = (function() {
+        const detail = createAncGclDetail({ id: 'id', masterId: 'ag_id', firstId: 'lid', secondId: 'pid', sort: [['agd_order', 'asc']]});
+        const spread = SpreadJsObj.createNewSpread($('#anc-gcl-detail-spread')[0]);
+        const sheet = spread.getActiveSheet();
+        const emptySetting = {
+            cols: [],
+            emptyRows: 3,
+            headRows: 1,
+            headRowHeight: [32],
+            defaultRowHeight: 21,
+            headerFont: '12px 微软雅黑',
+            font: '12px 微软雅黑',
+            frozenLineColor: '#93b5e4',
+            readOnly: readOnly,
+        };
+        const refresh = function() {
+            if (spread) spread.refresh();
+        };
+        let template;
+        const reloadCurDetailData = function() {
+            const curAncGcl = SpreadJsObj.getSelectObject(ancGclSheet);
+            const detailData = curAncGcl ? detail.getPartData(curAncGcl.id) || [] : [];
+            SpreadJsObj.loadSheetData(sheet, SpreadJsObj.DataType.Data, detailData);
+        };
+        const loadCurDetailData = function() {
+            const curAncGcl = SpreadJsObj.getSelectObject(ancGclSheet);
+            template = node ? posCalcTemplate.find(x => { return x.id === node.calc_template }) : null;
+            if (template) {
+                const specCol = template.spread_cache.cols.find(x => { return x.field === 'spec'; });
+                if (specCol) {
+                    specCol.comboItems = template.specValue.map(x => { return x.spec; });
+                    specCol.cellType = 'customizeCombo';
+                    specCol.cellTypeKey = 'specSelect';
+                    specCol.maxDrop = 10;
+                }
+                template.spread_cache.forceLoadEmpty = true;
+                template.spread_cache.readOnly = readOnly;
+                SpreadJsObj.initSheet(sheet, template.spread_cache);
+            } else {
+                SpreadJsObj.initSheet(sheet, emptySetting);
+            }
+            reloadCurDetailData();
+        };
+        const ctrlObj = {
+            afterPostData: function(result) {
+                detail.updateDatas(result.detail);
+                reloadCurDetailData();
+                if (result.ancGcl) {
+                    ancGcl.updateDatas(result.ancGcl);
+                    // todo 刷新附属工程量
+                }
+            },
+            baseOpr: function (type, addCount = 1) {
+                const data = {};
+
+                const detailRange = sheet.zh_data;
+                if (type !== 'insert' && (!detailRange || detailRange.length === 0)) return;
+
+                const sel = sheet.getSelections();
+                if (!sel[0]) return;
+
+                const row = sel[0].row, count = sel[0].rowCount;
+                const first = detailRange[row];
+                if (type === 'insert') {
+                    const node = SpreadJsObj.getSelectObject(posSheet);
+                    data.add = [];
+                    for (let i = 1; i <= addCount; i++) {
+                        data.add.push({ lid: node.lid, pid: node.id, pcd_order: detailRange.length + i });
+                    }
+                    if (first) data.select = first.id;
+                } else if (type === 'delete') {
+                    data.del = [];
+                    for (let iRow = 0; iRow < count; iRow++) {
+                        const detailData = detailRange[row + iRow];
+                        if (!detailData) continue;
+                        data.del.push(detailData.id);
+                    }
+
+                    if (data.del.length === 0) return;
+                } else if (type === 'up-move') {
+                    data.update = [];
+                    const pre = detailRange[row - 1];
+                    if (!pre) return;
+
+                    const preUpdate = { id: pre.id };
+                    for (let iRow = 0; iRow < count; iRow++) {
+                        const detailData = detailRange[iRow + row];
+                        if (!detailData) continue;
+                        data.update.push({ id: detailData.id, pcd_order: detailRange[iRow + row - 1].pcd_order });
+                        preUpdate.pcd_order = detailData.pcd_order;
+                    }
+                    data.update.push(preUpdate);
+
+                    if (data.update <= 1) return;
+                } else if (type === 'down-move') {
+                    data.update = [];
+                    const next = detailRange[row + count];
+                    if (!next) return;
+
+                    const nextUpdate = { id: next.id };
+                    for (let iRow = count - 1; iRow >= 0; iRow--) {
+                        const detailData = detailRange[iRow + row];
+                        if (!detailData) continue;
+
+                        data.update.push({ id: detailData.id, pcd_order: detailRange[iRow + row + 1].pcd_order});
+                        nextUpdate.pcd_order = detailData.pcd_order;
+                    }
+                    data.update.push(nextUpdate);
+
+                    if (data.update <= 1) return;
+                }
+
+                postData('/tender/' + getTenderId() + '/anc-gcl-calc/update', data, function(result) {
+                    ctrlObj.afterPostData(result);
+                    if (type !== 'delete') SpreadJsObj.locateData(sheet, first);
+                });
+            },
+            editStarting: function (e, info) {
+                ctrlObj.ledgerTreeNode = SpreadJsObj.getSelectObject(ledgerSheet);
+                ctrlObj.posNode = SpreadJsObj.getSelectObject(posSheet);
+                ctrlObj.ancGclNode = SpreadJsObj.getSelectObject(ancGclSheet);
+            },
+            editEnded: function (e, info) {
+                const setting = info.sheet.zh_setting;
+                if (!setting) return;
+                const detailData = SpreadJsObj.getSelectObject(info.sheet);
+
+                const col = setting.cols[info.col];
+                const orgText = detailData ? detailData[col.field] : '', newText = trimInvalidChar(info.editingText);
+                if (orgText === newText || (!orgText && !newText)) return;
+
+                const pos = ctrlObj.posNode;
+                if (!pos) {
+                    toastr.error('数据错误,请选择计量单元后再试');
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    return;
+                }
+
+                const data = {};
+                if (detailData) {
+                    const updateData = { id: detailData.id };
+                    if (col.type === 'Number') {
+                        const num = _.toNumber(newText);
+                        if (!_.isFinite(num)) {
+                            try {
+                                updateData[col.field] = ZhCalc.mathCalcExpr(transExpr(newText));
+                            } catch (err) {
+                                toastr.error('输入的表达式非法');
+                                SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                                return;
+                            }
+                        } else {
+                            updateData[col.field] = num;
+                        }
+                    } else {
+                        updateData[col.field] = newText;
+                    }
+                    data.update = [ updateData ];
+                } else {
+                    const sortData = info.sheet.zh_data;
+                    const order = (!sortData || sortData.length === 0) ? 1 : Math.max(sortData[sortData.length - 1].pcd_order + 1, sortData.length + 1);
+                    const addData = { lid: pos.lid, pid: pos.id, pcd_order: order };
+                    if (col.type === 'Number') {
+                        const num = _.toNumber(newText);
+                        if (!_.isFinite(num)) {
+                            try {
+                                addData[col.field] = ZhCalc.mathCalcExpr(transExpr(newText));
+                            } catch (err) {
+                                toastr.error('输入的表达式非法');
+                                SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                                return;
+                            }
+                        } else {
+                            addData[col.field] = num;
+                        }
+                    } else {
+                        addData[col.field] = newText;
+                    }
+                    data.add = [addData];
+                }
+                postData('/tender/' + getTenderId() + '/pos-calc/update', data, function (result) {
+                    ctrlObj.afterPostData(result);
+                }, function () {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                });
+            },
+            deletePress: function (sheet) {
+                const setting = sheet.zh_setting;
+                if (!setting) return;
+
+                const sortData = sheet.zh_data;
+                if (!sortData || sortData.length === 0) return;
+
+                const sel = sheet.getSelections()[0];
+                const data = { update: [] };
+                for (let iRow = sel.row; iRow < sel.row + sel.rowCount; iRow++) {
+                    let bDel = false;
+                    const node = sortData[iRow];
+                    if (!node) continue;
+
+                    const updateData = { id: node.id };
+                    for (let iCol = sel.col; iCol < sel.col + sel.colCount; iCol++) {
+                        const style = sheet.getStyle(iRow, iCol);
+                        if (style.locked) continue;
+
+                        const col = setting.cols[iCol];
+                        updateData[col.field] = col.type === 'Number' ? 0 : '';
+                        bDel = true;
+                    }
+                    if (bDel) data.update.push(updateData);
+                }
+                if (data.update.length === 0) return;
+
+                postData('/tender/' + getTenderId() + '/pos-calc/update', data, function (result) {
+                    ctrlObj.afterPostData(result);
+                }, function () {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                });
+            },
+            clipboardPasting: function(e, info) {
+                info.cancel = true;
+                const relaPos = SpreadJsObj.getSelectObject(posSheet);
+                if (!relaPos) {
+                    toastr.error('数据错误,请选择计量单元后再试');
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    return;
+                }
+
+                const hint = {
+                    num: {type: 'warning', msg: '输入的 数字 非法,已过滤'},
+                };
+                const setting = info.sheet.zh_setting;
+                const sortData = info.sheet.zh_data || [];
+                const pasteData = SpreadJsObj.analysisPasteText(info.pasteData.text);
+                const data = {};
+                const analysisData = function(pasteRow, targetData) {
+                    pasteRow.forEach((value, iCol) => {
+                        const col = setting.cols[info.cellRange.col + iCol];
+                        if (col.type === 'Number') {
+                            const num = _.toNumber(value);
+                            if (!_.isFinite(num)) {
+                                try {
+                                    targetData[col.field] = ZhCalc.mathCalcExpr(transExpr(value));
+                                } catch (err) {
+                                    toastr.error('输入的表达式非法');
+                                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                                    return;
+                                }
+                            } else {
+                                targetData[col.field] = num;
+                            }
+                        } else {
+                            targetData[col.field] = value;
+                        }
+                    });
+                };
+                for (let iRow = 0; iRow < pasteData.length; iRow++) {
+                    const curRow = iRow + info.cellRange.row;
+                    const detailData = sortData[curRow];
+                    if (detailData) {
+                        if (!data.update) data.update = [];
+                        const updateData = { id: detailData.id };
+                        analysisData(pasteData[iRow], updateData);
+                        data.update.push(updateData);
+                    } else {
+                        if (!data.add) data.add = [];
+                        const addData = { lid: relaPos.lid, pid: relaPos.id, pcd_order: curRow + 1};
+                        analysisData(pasteData[iRow], addData);
+                        data.add.push(addData);
+                    }
+                }
+                if ((!data.update || data.update.length === 0) && (!data.add || data.add.length === 0)) return;
+
+                postData('/tender/' + getTenderId() + '/pos-calc/update', data, function (result) {
+                    ctrlObj.afterPostData(result);
+                }, function () {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                });
+            },
+        };
+        if (!readOnly) {
+            SpreadJsObj.addDeleteBind(spread, ctrlObj.deletePress);
+            spread.bind(spreadNS.Events.EditStarting, ctrlObj.editStarting);
+            spread.bind(spreadNS.Events.EditEnded, ctrlObj.editEnded);
+            spread.bind(spreadNS.Events.ClipboardPasting, ctrlObj.clipboardPasting);
+            $.contextMenu({
+                selector: '#pos-detail-spread',
+                build: function ($trigger, e) {
+                    const target = SpreadJsObj.safeRightClickSelection($trigger, e, spread);
+                    return target.hitTestType === GC.Spread.Sheets.SheetArea.viewport || target.hitTestType === GC.Spread.Sheets.SheetArea.rowHeader;
+                },
+                items: {
+                    'insert': {
+                        name: '插入',
+                        icon: 'fa-plus',
+                        disabled: function (key, opt) {
+                            const pos = SpreadJsObj.getSelectObject(posSheet);
+                            return !pos;
+                        },
+                        callback: function (key, opt) {
+                            ctrlObj.baseOpr('insert');
+                        }
+                    },
+                    'batchInsert': {
+                        name: '批量插入',
+                        type: 'batchInsert',
+                        value: '2',
+                        icon: 'fa-plus',
+                        disabled: function (key, opt) {
+                            const pos = SpreadJsObj.getSelectObject(posSheet);
+                            return !pos;
+                        },
+                        batchInsert: function (obj, root) {
+                            if (_.toNumber(obj.value) > _.toNumber(obj.max)) {
+                                obj.value = obj.max;
+                                toastr.warning('批量插入不可多于' + obj.max);
+                            } else if(_.toNumber(obj.value) < _.toNumber(obj.min)) {
+                                obj.value = obj.min;
+                                toastr.warning('批量插入不可少于' + obj.min);
+                            } else {
+                                ctrlObj.baseOpr('insert', parseInt(obj.value));
+                                root.$menu.trigger('contextmenu:hide');
+                            }
+                        },
+                    },
+                    'delete': {
+                        name: '删除',
+                        icon: 'fa-remove',
+                        disabled: function (key, opt) {
+                            const detailData = SpreadJsObj.getSelectObject(sheet);
+                            return !detailData;
+                        },
+                        callback: function (key, opt) {
+                            ctrlObj.baseOpr('delete');
+                        }
+                    },
+                    'down-move': {
+                        name: '下移',
+                        icon: 'fa-arrow-down',
+                        disabled: function(key, opt) {
+                            const sel = sheet.getSelections()[0];
+                            const row = sel ? sel.row : -1;
+                            const first = sheet.zh_data[row];
+                            const next = sheet.zh_data[sel.row + sel.rowCount];
+                            return !first || !next;
+                        },
+                        callback: function(key, opt) {
+                            ctrlObj.baseOpr('down-move');
+                        }
+                    },
+                    'up-move': {
+                        name: '上移',
+                        icon: 'fa-arrow-up',
+                        disabled: function(key, opt) {
+                            const sel = sheet.getSelections()[0];
+                            const row = sel ? sel.row : -1;
+                            const first = sheet.zh_data[row];
+                            const preNode = sheet.zh_data[row - 1];
+                            return !first || !preNode;
+                        },
+                        callback: function(key, opt) {
+                            ctrlObj.baseOpr('up-move');
+                        }
+                    },
+                }
+            });
+        }
+
+        return { detail, spread, sheet, refresh, loadCurDetailData, reloadCurDetailData }
+    })();
 
     const billsTag = $.billsTag({
         relaTree: ledgerTree,
@@ -528,6 +899,7 @@ $(document).ready(function() {
             ledgerSpread.refresh();
             if (posSpread) posSpread.refresh();
             if (ancGclSpread) ancGclSpread.refresh();
+            if (ancGclDetail) ancGclDetail.refresh();
             if (posCalcDetail) posCalcDetail.refresh();
         },
     });
@@ -544,6 +916,7 @@ $(document).ready(function() {
             ledgerSpread.refresh();
             if (posSpread) posSpread.refresh();
             if (ancGclSpread) ancGclSpread.refresh();
+            if (ancGclDetail) ancGclDetail.refresh();
             if (posCalcDetail) posCalcDetail.refresh();
         },
     });
@@ -562,6 +935,7 @@ $(document).ready(function() {
             ledgerSpread.refresh();
             if (posSpread) posSpread.refresh();
             if (ancGclSpread) ancGclSpread.refresh();
+            if (ancGclDetail) ancGclDetail.refresh();
             if (posCalcDetail) posCalcDetail.refresh();
         },
     });
@@ -1604,6 +1978,7 @@ $(document).ready(function() {
             ledgerSpread.refresh();
             if (posSpread) posSpread.refresh();
             if (ancGclSpread) ancGclSpread.refresh();
+            if (ancGclDetail) ancGclDetail.refresh();
             if (posCalcDetail) posCalcDetail.refresh();
             if (stdXmj) stdXmj.spread.refresh();
             if (stdGcl) stdGcl.spread.refresh();
@@ -2450,6 +2825,7 @@ $(document).ready(function() {
                 $(".sp-wrap").height(bcontent-30);
                 posSpread.refresh();
                 if (ancGclSpread) ancGclSpread.refresh();
+                if (ancGclDetail) ancGclDetail.refresh();
                 if (posCalcDetail) posCalcDetail.refresh();
             }
         });
@@ -2465,6 +2841,7 @@ $(document).ready(function() {
             ledgerSpread.refresh();
             posSpread.refresh();
             if (ancGclSpread) ancGclSpread.refresh();
+            if (ancGclDetail) ancGclDetail.refresh();
             if (posCalcDetail) posCalcDetail.refresh();
         };
         const reloadPosDisplayCache = function () {
@@ -3643,6 +4020,7 @@ $(document).ready(function() {
             ledgerSpread.refresh();
             if (posSpread) posSpread.refresh();
             if (ancGclSpread) ancGclSpread.refresh();
+            if (ancGclDetail) ancGclDetail.refresh();
             if (posCalcDetail) posCalcDetail.refresh();
             if (stdXmj) stdXmj.spread.refresh();
             if (stdGcl) stdGcl.spread.refresh();
@@ -3894,6 +4272,7 @@ $(document).ready(function() {
             posSpread.refresh();
         }
         if (ancGclSpread) ancGclSpread.refresh();
+        if (ancGclDetail) ancGclDetail.refresh();
         if (posCalcDetail) posCalcDetail.refresh();
     });
     class DealBills {
@@ -4887,6 +5266,7 @@ $(document).ready(function() {
         }
         if (posSpread) posSpread.refresh();
         if (ancGclSpread) ancGclSpread.refresh();
+        if (ancGclDetail) ancGclDetail.refresh();
         if (posCalcDetail) posCalcDetail.refresh();
     });
     $.divResizer({
@@ -4894,9 +5274,17 @@ $(document).ready(function() {
         callback: function () {
             if (posSpread) posSpread.refresh();
             if (ancGclSpread) ancGclSpread.refresh();
+            if (ancGclDetail) ancGclDetail.refresh();
             if (posCalcDetail) posCalcDetail.refresh();
         }
     });
+    $.divResizer({
+        select: '#anc-gcl-spr',
+        callback: function () {
+            if (ancGclSpread) ancGclSpread.refresh();
+            if (ancGclDetail) ancGclDetail.refresh();
+        }
+    });
 
     // $('#searchAccount').click(() => {
     //     const data = {

+ 185 - 1
app/public/js/path_tree.js

@@ -2475,7 +2475,191 @@ const createPosCalcDetail = function (setting) {
             if (topRange) {
                 delete this.topIndex[topKey];
                 for (const tr of topRange) {
-                    this.removeDatasByTopId(tr);
+                    this.removeDatasByMasterId(tr);
+                }
+            }
+        }
+
+        getItem(id) {
+            return this.items[this.itemPre + id];
+        }
+
+        getPartData(mid) {
+            return this.masterIndex[this.itemPre + mid];
+        }
+
+        set sort(sort) {
+            this.setting.sort = sort;
+            for (const key in this.masterIndex) {
+                this.resortPart(this.masterIndex[key]);
+            }
+        }
+    }
+
+    return new PosCalcDetail(setting);
+};
+
+const createAncGclDetail = function (setting) {
+    class PosCalcDetail {
+        constructor(setting) {
+            this.itemPre = 'id_';
+            // 无索引
+            this.datas = [];
+            // 以key为索引
+            this.items = {};
+            // 以分类id为索引的有序
+            this.masterIndex = {}; // posCalcDetail ---ag_id---> ancGcl
+            this.secondIndex = {}; // ag_id --- pid ---> pos
+            this.firstIndex = {}; // ag_id --- pid --- lid ---> bills
+            // 设置
+            this.setting = setting;
+        }
+
+        resortPart(partData) {
+            const sortRule = this.setting.sort || [['agd_order', 'asc']];
+            if (partData instanceof Array) {
+                partData.sort(function (a, b) {
+                    for (const sr of sortRule) {
+                        const iSort = sr[1] === 'asc' ? a[sr[0]] - b[sr[0]] : b[sr[0]] - a[sr[0]];
+                        if (iSort) return iSort;
+                    }
+                })
+            }
+        }
+
+        /**
+         * 加载数据
+         * @param datas
+         */
+        loadDatas(datas) {
+            this.datas = datas;
+            this.items = {};
+            this.masterIndex = {};
+            this.secondIndex = {};
+            this.firstIndex = {};
+            for (const data of this.datas) {
+                const key = this.itemPre + data[this.setting.id];
+                this.items[key] = data;
+
+                const masterKey = this.itemPre + data[this.setting.masterId];
+                if (!this.masterIndex[masterKey]) {
+                    this.masterIndex[masterKey] = [];
+                }
+                this.masterIndex[masterKey].push(data);
+
+                const secondKey = this.itemPre + data[this.setting.secondId];
+                if (!this.secondIndex[secondKey]) this.secondIndex[secondKey] = [];
+                if (this.secondIndex[secondKey].indexOf(data[this.setting.masterId]) < 0) this.secondIndex[secondKey].push(data[this.setting.masterId]);
+
+                const firstKey = this.itemPre + data[this.setting.firstId];
+                if (!this.firstIndex[firstKey]) this.firstIndex[firstKey] = [];
+                if (this.firstIndex[firstKey].indexOf(data[this.setting.secondId]) < 0) this.firstIndex[firstKey].push(data[this.setting.secondId]);
+            }
+            for (const prop in this.masterIndex) {
+                this.resortPart(this.masterIndex[prop]);
+            }
+        }
+
+        _addDatas(data, resort) {
+            const datas = data instanceof Array ? data : [data];
+            for (const d of datas) {
+                const key = this.itemPre + d[this.setting.id];
+                this.items[key] = d;
+
+                const masterKey = this.itemPre + d[this.setting.masterId];
+                if (!this.masterIndex[masterKey]) this.masterIndex[masterKey] = [];
+                this.masterIndex[masterKey].push(d);
+                if (resort.indexOf(masterKey) < 0) resort.push(masterKey);
+
+                const secondKey = this.itemPre + data[this.setting.secondId];
+                if (!this.secondIndex[secondKey]) this.secondIndex[secondKey] = [];
+                if (this.secondIndex[secondKey].indexOf(data[this.setting.masterId]) < 0) this.secondIndex[secondKey].push(data[this.setting.masterId]);
+
+                const firstKey = this.itemPre + data[this.setting.firstId];
+                if (!this.firstIndex[firstKey]) this.firstIndex[firstKey] = [];
+                if (this.firstIndex[firstKey].indexOf(data[this.setting.secondId]) < 0) this.firstIndex[firstKey].push(data[this.setting.secondId]);
+            }
+        }
+
+        /**
+         * 更新数据
+         * @param datas
+         */
+        _updateDatas(data, resort) {
+            const datas = data instanceof Array ? data : [data];
+            for (const d of datas) {
+                const item = this.getItem(d[this.setting.id]);
+                if (!item) continue;
+                for (const prop in d) {
+                    item[prop] = d[prop];
+                }
+                const masterKey = this.itemPre + item[this.setting.masterId];
+                if (resort.indexOf(masterKey) < 0) resort.push(masterKey);
+            }
+
+        }
+
+        /**
+         * 移除数据
+         * @param datas
+         */
+        _removeDatas(data, resort) {
+            if (!data) { return; }
+            const datas = data instanceof Array ? data : [data];
+            for (let i = datas.length - 1; i >= 0; i--) {
+                const id = datas[i];
+                const d = this.getItem(id);
+                this.datas.splice(this.datas.indexOf(d), 1);
+                const key = this.itemPre + d[this.setting.id];
+                delete this.items[key];
+                const range = this.getPartData(d[this.setting.masterId]);
+                range.splice(range.indexOf(d), 1);
+            }
+        }
+
+        updateDatas(data) {
+            const resort = [];
+            if (data.add) this._addDatas(data.add, resort);
+            if (data.del) this._removeDatas(data.del, resort);
+            if (data.update) this._updateDatas(data.update, resort);
+            for (const s of resort) {
+                this.resortPart(this.masterIndex[s]);
+            }
+        }
+
+        /**
+         * 移除数据 - 根据分类id
+         * @param mid
+         */
+        removeDatasByMasterId(mid) {
+            const masterKey = this.itemPre + mid;
+            const range = this.masterIndex[masterKey];
+            if (range) {
+                delete this.masterIndex[masterKey];
+                for (const r of range) {
+                    this.datas.splice(this.datas.indexOf(r), 1);
+                    const key = this.itemPre + r[this.setting.id];
+                    delete this.items[key];
+                }
+            }
+        }
+        removeDatasBySecondId(secondId) {
+            const secondKey = this.itemPre + secondId;
+            const secondRange = this.secondIndex[secondKey];
+            if (secondRange) {
+                delete this.secondIndex[secondKey];
+                for (const tr of secondRange) {
+                    this.removeDatasByMasterId(tr);
+                }
+            }
+        }
+        removeDatasByFirstId(firstId) {
+            const firstKey = this.itemPre + firstId;
+            const firstRange = this.secondIndex[firstKey];
+            if (firstRange) {
+                delete this.secondIndex[firstKey];
+                for (const tr of firstRange) {
+                    this.removeDatasBySecondId(tr);
                 }
             }
         }

+ 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


+ 2 - 8
app/public/js/stage.js

@@ -5196,14 +5196,8 @@ $(document).ready(() => {
             }, `确认删除附件「${att.filename}」?`);
 
         } else if (content === 'view') {
-            const data = {id: fid};
-            postData('/tender/' + tender.id + '/measure/stage/' + stage.order + '/check/file', data, function (result) {
-                const { filepath } = result
-                $('#load-file').attr('href', filepath);
-                $('#load-file')[0].click();
-                // $('#show-att tr').eq(2).find('a').attr('href', filepath)
-              // $('#show-att tr').eq(2).find('a').children('span').eq(0).trigger('click')
-            });
+            const att = attData.find(item => item.id === parseInt(fid));
+            window.open(att.viewpath || att.orginpath);
         } else if (content === 'location') {
             const att = attData.find(item => item.id === parseInt(fid));
             if (Object.keys(att).length) {

+ 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');
 
 
     // 资料归集-列表

+ 4 - 2
app/service/calc_tmpl.js

@@ -35,9 +35,11 @@ const PosCalc = (function(){
     const ValidColInfo = [
         { key: 'str', name: '文本', fields: ['str1', 'str2', 'str3', 'str4'], valid: ['title', 'width'], def: { title: '文字', width: 80, type: 'str'} },
         {
-            key: 'num', name: '数值',
+            key: 'num', name: '数值/计算',
             fields: ['num_a', 'num_b', 'num_c', 'num_d', 'num_e', 'num_f', 'num_g', 'num_h', 'num_i', 'num_j', 'num_k', 'num_l', 'num_m', 'num_n', 'num_o', 'num_p', 'num_q', 'num_r', 'num_s', 'num_t', 'num_u'], // 'num_v', 'num_w', 'num_x', 'num_y', 'num_z'],
-            valid: ['title', 'width', 'calc_code', 'decimal', 'unit'], def: { title: '数值', width: 80, decimal: 2, type: 'num', unit: ''} },
+            valid: ['title', 'width', 'calc_code', 'decimal', 'unit', 'expr'],
+            def: { title: '数值', width: 80, decimal: 2, type: 'num', unit: ''}
+        },
         { key: 'spec', name: '规格', fields: ['spec'], valid: ['title', 'width', 'rela_col', 'spec_set'], def: { title: '规格', width: 80, rela_col: '', type: 'spec'} },
         { key: 'qty', name: '数量', fields: ['qty'], valid: ['title', 'width', 'expr', 'decimal'], def: { title: '数量', width: 80, decimal: 2, expr: '', type: 'qty' } },
     ];

+ 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,

+ 6 - 2
app/service/contract_col_set.js

@@ -82,10 +82,14 @@ module.exports = app => {
                 const field = csd.field;
                 if (!colSetFieldSet.has(field)) { // 未在 colSet 中找到的字段
                     const dcs = defaultColSet.find(x => x.field === field); // 取默认配置
-                    result.push({ ...dcs, ...csd }); 
+                    if (dcs.gd) { // 补充到结果头中
+                        result.unshift({ ...dcs, ...csd });
+                    } else {
+                        result.push({ ...dcs, ...csd });
+                    }
                 }
             }
-            
+
             return result;
         }
     }

+ 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/phase_pay_file.js

@@ -26,8 +26,8 @@ module.exports = app => {
             const userId = this.ctx.session.sessionUser.accountId;
             const ossPath = this.ctx.app.config.fujianOssPath;
             files.forEach(x => {
-                x.viewpath = helper.canPreview(x.fileext) ? ossPath + x.filepath : '';
                 x.filepath = ossPath + x.filepath;
+                x.viewpath = this.ctx.helper.getPreviewPath(x.fileext, x.filepath);
                 x.fileext_str = helper.fileExtStr(x.fileext);
                 x.canEdit = x.user_id === userId;
             });

+ 23 - 6
app/service/pos_calc_detail.js

@@ -136,7 +136,7 @@ module.exports = app => {
             }
         }
 
-        async _addDatas(data, calcMaster) {
+        async _addDatas(data, selectId) {
             const user_id = this.ctx.session.sessionUser.accountId;
 
             const datas = data instanceof Array ? data : [data];
@@ -144,24 +144,39 @@ module.exports = app => {
             if (!le.calc_template) throw '未定义计算模板,请先在清单处选择计算模板';
             const calcTemplate = await this.ctx.service.calcTmpl.getTemplate(le.calc_template);
             if (!calcTemplate) throw '计算模板不存在';
+            const posDetail = await this.getAllDataByCondition({ where: { tid: this.ctx.tender.id, pid: datas[0].pid }, orders: [['pcd_order', 'ASC']] });
+            const selectIndex = selectId ? posDetail.findIndex(x => { return x.id === selectId; }) : -1;
+            if (selectId && selectIndex < 0) throw '选择的数据不存在';
+            const startOrder = selectId
+                ? (selectIndex > 0 ? posDetail[selectIndex - 1].pcd_order : 0)
+                : (posDetail.length > 0 ? posDetail[posDetail.length - 1].pcd_order : 0);
 
-            const insertData = [];
+            const insertData = [], updateData = [];
             let isCalc = false;
-            for (const d of datas) {
+            for (const [i, d] of datas.entries()) {
                 if (!d.lid || !d.pid || !d.pcd_order) throw '新增其他数据,提交的数据错误';
                 const nd = {
                     id: this.uuid.v4(), tid: this.ctx.tender.id,
                     create_user_id: user_id, update_user_id: user_id,
-                    lid: d.lid, pid: d.pid, pcd_order: d.pcd_order,
+                    lid: d.lid, pid: d.pid, pcd_order: startOrder + i + 1,
                 };
                 if (this._loadDataAndCalc(nd, d, {}, calcTemplate)) isCalc = true;
                 insertData.push(nd);
             }
             const [posUpdate, billsUpdate] = await this._getBillsPosUpdateData(insertData, insertData[0].pid, insertData[0].lid, isCalc);
+            if (selectIndex >= 0) {
+                const maxOrder = insertData[insertData.length - 1].pcd_order;
+                for (const p of posDetail) {
+                    if (p.pcd_order > startOrder) {
+                        updateData.push({ id: p.id, pcd_order: maxOrder + updateData.length + 1 });
+                    }
+                }
+            }
 
             const conn = await this.db.beginTransaction();
             try {
                 await conn.insert(this.tableName, insertData);
+                if (updateData.length > 0) await conn.updateRows(this.tableName, updateData);
                 if (posUpdate) await conn.update(this.ctx.service.pos.tableName, posUpdate);
                 if (billsUpdate) await conn.update(this.ctx.service.ledger.tableName, billsUpdate);
                 await conn.commit();
@@ -174,7 +189,7 @@ module.exports = app => {
             });
             const posData = isCalc ? await this.ctx.service.pos.getDataById(addData[0].pid) : null;
             const ledgerData = isCalc ? await this.ctx.service.ledger.getDataById(addData[0].lid) : null;
-            return [addData, posData, ledgerData]
+            return [addData, updateData, posData, ledgerData]
         }
         async _delDatas (data) {
             if (!data || data.length === 0) throw '提交数据错误';
@@ -259,10 +274,12 @@ module.exports = app => {
             const result = { detail: {add: [], del: [], update: []}, pos: null, bills: null };
             try {
                 if (data.add) {
-                    [result.detail.add, result.pos, result.bills] = await this._addDatas(data.add);
+                    [result.detail.add, result.detail.update, result.pos, result.bills] = await this._addDatas(data.add, data.select);
                 }
                 if (data.update) {
+                    const orgUpdate = result.detail.update;
                     [result.detail.update, result.pos, result.bills] = await this._updateDatas(data.update);
+                    if (orgUpdate.length > 0) result.detail.update.push(...orgUpdate);
                 }
                 if (data.del) {
                     [result.detail.del, result.detail.update, result.pos, result.bills] = await this._delDatas(data.del);

+ 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);
         }

+ 2 - 0
app/service/stage_att.js

@@ -74,6 +74,8 @@ module.exports = app => {
             const result = await this.db.query(sql, sqlParam);
             return result.map(item => {
                 item.orginpath = this.ctx.app.config.fujianOssPath + item.filepath;
+                item.filepath = this.ctx.app.config.fujianOssPath + item.filepath;
+                item.viewpath = this.ctx.helper.getPreviewPath(item.fileext, item.filepath);
                 delete item.filepath;
                 // if (!this.ctx.helper.canPreview(item.fileext)) {
                 //     item.filepath = `/tender/${this.ctx.tender.id}/measure/stage/${this.ctx.params.order}/download/file/${item.id}`;

+ 5 - 1
app/service/stage_bonus.js

@@ -9,6 +9,8 @@
  */
 
 const auditConst = require('../const/audit').stage;
+const path = require('path');
+
 module.exports = app => {
     class StageBonus extends app.BaseService {
         /**
@@ -30,7 +32,9 @@ module.exports = app => {
                 if (d.proof_file) {
                     d.proof_file = JSON.parse(d.proof_file);
                     d.proof_file.forEach(f => {
-                        f.viewpath = helper.getPreviewPath(f.fileext, f.filepath.substr(3, f.filepath.length - 3));
+                        if (helper.canPreview(f.fileext)){
+                            f.viewpath = f.filepath.substr(3, f.filepath.length - 3);
+                        }
                     });
                 } else {
                     d.proof_file = [];

+ 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;

+ 2 - 0
app/view/contract/col_set.ejs

@@ -38,8 +38,10 @@
                           <td><input type="text" class="form-control form-control-sm" value="<%- cs.alias %>"></td>
                           <% } %>
                           <td>
+                              <% if (!cs.gd) { %>
                               <a href="javascript:;" class="move-up text-primary mr-2" style="text-decoration: none;">上移</a>
                               <a href="javascript:;" class="move-down text-primary" style="text-decoration: none;">下移</a>
+                              <% } %>
                           </td>
                       </tr>
                       <% } %>

+ 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>
+<% } %>

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

@@ -12,7 +12,7 @@
                     <div class="col-8">
                         <div class="card mb-3">
                             <div class="card-header">
-                                <div class="float-left">合同设置</div>
+                                <div class="float-left">合同类型</div>
                                 <div class="float-right" id="show-type-btn" style="display: none">
                                     <button id="cancel-type-btn" class="btn btn-sm btn-secondary">取消</button>
                                     <button id="set-type-btn" class="btn btn-sm btn-success">保存</button>
@@ -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>

+ 6 - 2
app/view/ledger/explode.ejs

@@ -103,7 +103,7 @@
             <div class="c-body" id="left-view" style="width: 100%">
                 <div id="ledger-spread" class="sjs-height-1"></div>
                 <% if (tender.measure_type === measureType.tz.value) { %>
-                <div class="bcontent-wrap" id="main-bottom">
+                <div class="bcontent-wrap" id="main-bottom" <% if (tenderInfo.display.ledger.ancillaryGcl) {%>style="height: 500px;"<% } %>>
                     <div id="main-resize" class="resize-y" id="top-spr" r-Type="height" div1=".sjs-height-1" div2=".bcontent-wrap" r-parent="div2" title="调整大小"><!--调整上下高度条--></div>
                     <div class="bc-bar mb-1 d-flex">
                         <div class="d-inline-block">
@@ -159,7 +159,11 @@
                             <div class="resize-x" id="pos-right-spr" r-Type="width" div1="#pos-spread" div2="#pos-right" title="调整大小" a-type="percent"><!--调整左右高度条--></div>
                             <div class="tab-content sp-wrap">
                                 <div id="anc-gcl" class="tab-pane table-select-show">
-                                    <div class="sp-wrap" id="anc-gcl-spread">
+                                    <div id="anc-gcl-spread" style="height: 235px;">
+                                    </div>
+                                    <div id="anc-gcl-detail" style="height: 235px;">
+                                        <div id="anc-gcl-spr" class="resize-y" r-Type="height" div1="#anc-gcl-spread" div2="#anc-gcl-detail" r-parent="div2" title="调整大小"><!--调整上下高度条--></div>
+                                        <div id="anc-gcl-detail-spread" class="w-100 h-100"></div>
                                     </div>
                                 </div>
                                 <div id="pos-detail" class="tab-pane table-select-show">

+ 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: {

+ 49 - 6
sql/update.sql

@@ -1,4 +1,8 @@
+-- sql提交须知:
+-- 1. 请按如下分类提交sql!!!
+-- 2. 表名前不可有库名!!!
+-- 3. 表结构部分,新添加的表,不可先创建后修改!!!
+--
 -- Version V3.5.51.0145
 -- uat 2026-01-21
 -- prod 2026-01-22
@@ -8,17 +12,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 +451,37 @@ 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 = '合同详情和合同支付,回款审批人表';
+
+ALTER TABLE `zh_ancillary_gcl`
+ADD COLUMN `pid` varchar(36) NOT NULL DEFAULT '' COMMENT '计量单元id' AFTER `lid`,
+ADD COLUMN `calc_template` varchar(36) NOT NULL DEFAULT '' COMMENT '明细计算模板' AFTER `update_time`;
+
 ------------------------------------
 -- 表数据
 ------------------------------------