浏览代码

质量巡检单功能提交

ellisran 1 天之前
父节点
当前提交
cc2ce4b0f6
共有 38 个文件被更改,包括 6154 次插入1418 次删除
  1. 138 0
      app/const/audit.js
  2. 3 0
      app/const/code_rule.js
  3. 2 0
      app/const/shenpi.js
  4. 2 0
      app/const/sp_page_show.js
  5. 1 0
      app/const/tender_info.js
  6. 25 6
      app/controller/change_controller.js
  7. 17 0
      app/controller/dashboard_controller.js
  8. 510 3
      app/controller/quality_controller.js
  9. 9 5
      app/controller/sub_proj_setting_controller.js
  10. 126 0
      app/middleware/inspection_check.js
  11. 6 0
      app/public/css/main.css
  12. 6 0
      app/public/css/wap/main.css
  13. 3 3
      app/public/js/material_checklist.js
  14. 349 0
      app/public/js/quality_inspection.js
  15. 631 0
      app/public/js/quality_inspection_information.js
  16. 2 2
      app/public/js/quality_tender.js
  17. 82 3
      app/public/js/setting_manage.js
  18. 11 1
      app/router.js
  19. 315 0
      app/service/quality_inspection.js
  20. 82 0
      app/service/quality_inspection_att.js
  21. 1185 0
      app/service/quality_inspection_audit.js
  22. 86 2
      app/service/tender_permission.js
  23. 33 1
      app/view/dashboard/index.ejs
  24. 23 0
      app/view/dashboard/workspace.ejs
  25. 2 2
      app/view/quality/flaw.ejs
  26. 2 2
      app/view/quality/info.ejs
  27. 111 0
      app/view/quality/inspection.ejs
  28. 468 0
      app/view/quality/inspection_information.ejs
  29. 248 0
      app/view/quality/inspection_information_modal.ejs
  30. 166 0
      app/view/quality/inspection_modal.ejs
  31. 2 2
      app/view/quality/lab.ejs
  32. 2 1
      app/view/quality/sub_memu_list.ejs
  33. 0 0
      app/view/quality/sub_menu.ejs
  34. 3 9
      app/view/quality/tender.ejs
  35. 30 0
      app/view/sp_setting/manage.ejs
  36. 34 0
      config/web.js
  37. 48 1376
      sql/update.sql
  38. 1391 0
      sql/update20250928.sql

+ 138 - 0
app/const/audit.js

@@ -1379,6 +1379,142 @@ const financial = (function() {
     return { status, statusString, statusClass, auditString, auditStringClass, auditProgress, auditProgressClass, filter, statusButton, statusButtonClass };
 })();
 
+// 质量巡检
+const inspection = (function() {
+    const status = {
+        uncheck: 1, // 待审批
+        checking: 2, // 审批中或者原报人待上报或者原报上报修订中
+        checked: 3, // 审批通过或者原报人上报完成
+        checkStop: 4, // 审批终止
+        checkNo: 5, // 退回到原报人重新上报
+        checkNoPre: 6, // 退回到上一个审批人
+        // checkAgain: 7, // 重新审批
+        checkSkip: 8, // 跳过
+        // revise: 9, // 修订变更
+        // cancelRevise: 10, // 撤销修订
+        // checkCancel: 11, // 撤回 // 该状态为上一审批人可发起,回到它到审批阶段,并同时新增一条新的审批中记录
+        rectification: 12, // 整改中
+    };
+    const statusString = [];
+    statusString[status.uncheck] = '未提交';
+    statusString[status.checking] = '审批中';
+    statusString[status.checked] = '完成';
+    statusString[status.checkStop] = '关闭';
+    statusString[status.checkNo] = '退回';
+    statusString[status.checkNoPre] = '退回';
+    // statusString[status.checkAgain] = '重新审批';
+    // statusString[status.revise] = '修订';
+    // statusString[status.cancelRevise] = '撤销修订';
+    // statusString[status.checkCancel] = '撤回';
+    statusString[status.rectification] = '整改中';
+
+    const statusClass = [];
+    statusClass[status.uncheck] = '';
+    statusClass[status.checking] = 'text-warning';
+    statusClass[status.checked] = 'text-success';
+    statusClass[status.checkStop] = 'text-danger';
+    statusClass[status.checkNo] = 'text-warning';
+    statusClass[status.checkNoPre] = 'text-warning';
+    // statusClass[status.checkAgain] = 'text-warning';
+    // statusClass[status.revise] = 'text-warning';
+    // statusClass[status.cancelRevise] = 'text-success';
+    // statusClass[status.checkCancel] = 'text-warning';
+    statusClass[status.rectification] = 'text-warning';
+
+    // 标段概况页
+    // 描述文本
+    const auditString = [];
+    auditString[status.uncheck] = '未提交';
+    auditString[status.checking] = '审批中';
+    auditString[status.checked] = '审批通过';
+    auditString[status.checkStop] = '关闭';
+    auditString[status.checkNo] = '审批退回';
+    auditString[status.checkNoPre] = '审批退回';
+    // auditString[status.checkAgain] = '重新审批';
+    // auditString[status.revise] = '修订';
+    // auditString[status.cancelRevise] = '撤销修订';
+    // auditString[status.checkCancel] = '撤回';
+    auditString[status.checkSkip] = '审批通过';
+    auditString[status.rectification] = '整改中';
+    // 文字样式
+    const auditStringClass = [];
+    auditStringClass[status.uncheck] = '';
+    auditStringClass[status.checking] = 'text-warning';
+    auditStringClass[status.checked] = 'text-success';
+    auditStringClass[status.checkStop] = 'text-danger';
+    auditStringClass[status.checkNo] = 'text-warning';
+    auditStringClass[status.checkNoPre] = 'text-warning';
+    // auditStringClass[status.checkAgain] = 'text-warning';
+    // auditStringClass[status.revise] = 'text-warning';
+    // auditStringClass[status.cancelRevise] = 'text-success';
+    // auditStringClass[status.checkCancel] = 'text-warning';
+    auditStringClass[status.checkSkip] = 'text-success';
+    auditStringClass[status.rectification] = 'text-warning';
+    // 描述文本
+    const auditProgress = [];
+    auditProgress[status.uncheck] = '待上报';
+    auditProgress[status.checking] = '审批中';
+    auditProgress[status.checked] = '审批通过';
+    auditProgress[status.checkStop] = '关闭';
+    auditProgress[status.checkNo] = '审批退回';
+    auditProgress[status.checkNoPre] = '审批退回';
+    // auditProgress[status.checkAgain] = '重新审批';
+    // auditProgress[status.revise] = '修订中';
+    // auditProgress[status.cancelRevise] = '撤销修订';
+    // auditProgress[status.checkCancel] = '撤回';
+    auditProgress[status.checkSkip] = '审批通过';
+    auditProgress[status.rectification] = '整改中';
+    // 样式
+    const auditProgressClass = [];
+    auditProgressClass[status.uncheck] = '';
+    auditProgressClass[status.checking] = 'text-warning';
+    auditProgressClass[status.checked] = 'text-success';
+    auditProgressClass[status.checkStop] = 'text-danger';
+    auditProgressClass[status.checkNo] = 'text-warning';
+    auditProgressClass[status.checkNoPre] = 'text-warning';
+    // auditProgressClass[status.checkAgain] = 'text-warning';
+    // auditProgressClass[status.revise] = 'text-warning';
+    // auditProgressClass[status.cancelRevise] = 'text-success';
+    // auditProgressClass[status.checkCancel] = 'text-warning';
+    auditProgressClass[status.checkSkip] = 'text-success';
+    auditProgressClass[status.rectification] = 'text-warning';
+
+    const filter = {
+        status: {
+            pending: 1,
+            uncheck: 5,
+            checking: 2,
+            rectification: 12,
+            checked: 3,
+            checkStop: 4,
+        },
+        statusString: [],
+    };
+    filter.statusString[filter.status.pending] = '待处理';
+    filter.statusString[filter.status.uncheck] = '待提交';
+    filter.statusString[filter.status.checking] = '审批中';
+    filter.statusString[filter.status.rectification] = '整改中';
+    filter.statusString[filter.status.checked] = '完成';
+    filter.statusString[filter.status.checkStop] = '关闭';
+
+    // 按钮
+    const statusButton = [];
+    statusButton[status.uncheck] = '上报';
+    statusButton[status.checking] = '审批';
+    statusButton[status.checked] = '';
+    statusButton[status.checkNo] = '重新上报';
+    // statusButton[status.revise] = '修订';
+
+    // 按钮样式
+    const statusButtonClass = [];
+    statusButtonClass[status.uncheck] = 'btn-primary';
+    statusButtonClass[status.checking] = 'btn-success';
+    statusButtonClass[status.checked] = '';
+    statusButtonClass[status.checkNo] = 'btn-warning';
+    // statusButtonClass[status.revise] = 'btn-warning';
+    return { status, statusString, statusClass, auditString, auditStringClass, auditProgress, auditProgressClass, filter, statusButton, statusButtonClass };
+})();
+
 // 推送类型
 const pushType = {
     material: 1,
@@ -1393,6 +1529,7 @@ const pushType = {
     settle: 10,
     financial: 11,
     phasePay: 12,
+    inspection: 13,
 };
 
 module.exports = {
@@ -1423,4 +1560,5 @@ module.exports = {
     changeApply,
     changePlan,
     financial,
+    inspection,
 };

+ 3 - 0
app/const/code_rule.js

@@ -15,6 +15,7 @@ const ruleType = {
     will: 4,
     apply: 5,
     plan: 6,
+    inspection: 7,
 };
 const ruleField = [];
 ruleField[ruleType.measure] = 'm_rule';
@@ -23,6 +24,7 @@ ruleField[ruleType.suggestion] = 'suggestion';
 ruleField[ruleType.will] = 'will';
 ruleField[ruleType.apply] = 'apply';
 ruleField[ruleType.plan] = 'plan';
+ruleField[ruleType.inspection] = 'inspection';
 const ruleString = [];
 ruleString[ruleType.measure] = 'measure';
 ruleString[ruleType.change] = 'change';
@@ -30,6 +32,7 @@ ruleString[ruleType.suggestion] = 'suggestion';
 ruleString[ruleType.will] = 'will';
 ruleString[ruleType.apply] = 'apply';
 ruleString[ruleType.plan] = 'plan';
+ruleString[ruleType.inspection] = 'inspection';
 
 
 // 中间计量编号规则

+ 2 - 0
app/const/shenpi.js

@@ -18,6 +18,7 @@ const sp_type = {
     material: 6,
     // financial: 8, // 资金支付审批流程设置不出现在这里,但请别用8这个类型控制审批流程,因为数据库我用了8来表示资金支付固定审批流
     phasePay: 9,
+    inspection: 10,
 };
 const sp_other_type = {
     financial: 8,
@@ -40,6 +41,7 @@ const sp_lc = [
     { code: 'material', type: sp_type.material, name: '材料调差审批' },
     // { code: 'financial', type: sp_type.financial, name: '资金支付审批' },
     { code: 'phasePay', type: sp_type.phasePay, name: '合同支付审批' },
+    { code: 'inspection', type: sp_type.inspection, name: '质量巡检审批' },
 ];
 
 const sp_status = {

+ 2 - 0
app/const/sp_page_show.js

@@ -33,6 +33,7 @@ const tenderPageControl = [
     { title: '过程结算', name: 'openSettle', value: pageStatus.show, type: 'checkbox' },
     { title: '施工日志', name: 'openConstruction', value: pageStatus.show, type: 'checkbox' },
     { title: '合同管理', name: 'openTenderContract', value: pageStatus.show, type: 'checkbox' },
+    { title: '质量管理', name: 'quality', value: pageStatus.show, type: 'checkbox' },
 ];
 // 报表相关开关
 const reportPageControl = [
@@ -108,6 +109,7 @@ const defaultSetting = {
     openChangeState: 0,
     openInfo: 1,
     correctCalcContractTp: 0,
+    quality: 1,
 };
 
 module.exports = {

+ 1 - 0
app/const/tender_info.js

@@ -193,6 +193,7 @@ const defaultInfo = {
         change: 1,
         material: 1,
         phasePay: 1,
+        inspection: 1,
     },
     ledger_check: {
         same_code: true,

+ 25 - 6
app/controller/change_controller.js

@@ -239,13 +239,32 @@ module.exports = app => {
                     const c_code_rules = tenderData.c_code_rules !== null ? JSON.parse(tenderData.c_code_rules) : null;
                     cCodeRule = c_code_rules && c_code_rules[data.type + '_rule'] ? c_code_rules[data.type + '_rule'] : [];
                     cConnector = c_code_rules && c_code_rules[data.type + '_connector'] ? c_code_rules[data.type + '_connector'] : null;
-                    if (data.type === codeRuleConst.ruleField[codeRuleConst.ruleType.apply]) {
-                        changeCount = await ctx.service.changeApply.count({ tid: tenderId });
-                    } else if (data.type === codeRuleConst.ruleField[codeRuleConst.ruleType.plan]) {
-                        changeCount = await ctx.service.changePlan.count({ tid: tenderId });
-                    } else {
-                        changeCount = await ctx.service.changeProject.count({ tid: tenderId, type: codeRuleConst.ruleType[data.type] });
+                    switch (data.type) {
+                        case codeRuleConst.ruleField[codeRuleConst.ruleType.apply]:
+                            changeCount = await ctx.service.changeApply.count({ tid: tenderId });
+                            break;
+                        case codeRuleConst.ruleField[codeRuleConst.ruleType.plan]:
+                            changeCount = await ctx.service.changePlan.count({ tid: tenderId });
+                            break;
+                        case codeRuleConst.ruleField[codeRuleConst.ruleType.suggestion]:
+                        case codeRuleConst.ruleField[codeRuleConst.ruleType.will]:
+                            changeCount = await ctx.service.changeProject.count({ tid: tenderId, type: codeRuleConst.ruleType[data.type] });
+                            break;
+                        case codeRuleConst.ruleField[codeRuleConst.ruleType.inspection]:
+                            changeCount = await ctx.service.qualityInspection.count({ tid: tenderId });
+                            break;
+                        default:
+                            break;
                     }
+                    // if (data.type === codeRuleConst.ruleField[codeRuleConst.ruleType.apply]) {
+                    //     changeCount = await ctx.service.changeApply.count({ tid: tenderId });
+                    // } else if (data.type === codeRuleConst.ruleField[codeRuleConst.ruleType.plan]) {
+                    //     changeCount = await ctx.service.changePlan.count({ tid: tenderId });
+                    // } else if (data.type === codeRuleConst.ruleField[codeRuleConst.ruleType.project]) {
+                    //     changeCount = await ctx.service.changeProject.count({ tid: tenderId, type: codeRuleConst.ruleType[data.type] });
+                    // } else if (data.type === codeRuleConst.ruleField[codeRuleConst.ruleType.inspection]) {
+                    //     changeCount = await ctx.service.qualityInspection.count({ tid: tenderId });
+                    // }
                 } else {
                     cCodeRule = tenderData.c_rule !== null ? JSON.parse(tenderData.c_rule) : [];
                     cConnector = tenderData.c_connector;

+ 17 - 0
app/controller/dashboard_controller.js

@@ -40,6 +40,7 @@ module.exports = app => {
             const allAuditPayments = await ctx.service.paymentDetailAudit.getAuditPayment(ctx.session.sessionUser.accountId);
             const allAuditStageAss = await ctx.service.stageAuditAss.getAuditStageAss(ctx.session.sessionUser.accountId);
             const allAuditFinancials = await ctx.service.financialPayAudit.getAuditFinancial(ctx.session.sessionUser.accountId);
+            const allAuditInspections = await ctx.service.qualityInspectionAudit.getAuditInspection(ctx.session.sessionUser.accountId);
             const pa = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
             const auditShenpiTenders = await ctx.service.ledgerAudit.getDonesByAudit(ctx.session.sessionUser.accountId);
             const auditShenpiStages = await ctx.service.stageAudit.getDonesByAudit(ctx.session.sessionUser.accountId);
@@ -52,6 +53,7 @@ module.exports = app => {
             const auditShenpiChangePlan = await ctx.service.changePlanAudit.getDonesByAudit(ctx.session.sessionUser.accountId);
             const auditShenpiPayment = await ctx.service.paymentDetailAudit.getDonesByAudit(ctx.session.sessionUser.accountId);
             const auditShenpiFinancial = await ctx.service.financialPayAudit.getDonesByAudit(ctx.session.sessionUser.accountId);
+            const auditShenpiInspection = await ctx.service.qualityInspectionAudit.getDonesByAudit(ctx.session.sessionUser.accountId);
             const dashboardStatus = {
                 all: 0,
                 dashboard: 0,
@@ -69,6 +71,7 @@ module.exports = app => {
                     changePlan: 0,
                     payment: 0,
                     financial: 0,
+                    inspection: 0,
                 },
             };
             let noticeList = [];
@@ -98,6 +101,8 @@ module.exports = app => {
                 noticeList.push(...ctx.helper.addKeyValue4ObjArray(spPayment, 'shenpi_type', 'payment'));
                 const spFinancial = subProject.page_show.openFinancial ? ctx.helper._.filter(auditShenpiFinancial, { spid: subProject.id }) : [];
                 noticeList.push(...ctx.helper.addKeyValue4ObjArray(spFinancial, 'shenpi_type', 'financial'));
+                const spInspection = subProject.page_show.quality ? ctx.helper._.filter(auditShenpiInspection, { spid: subProject.id }) : [];
+                noticeList.push(...ctx.helper.addKeyValue4ObjArray(spInspection, 'shenpi_type', 'inspection'));
             }
             const noticeDayList = [];
             noticeList = ctx.helper._.orderBy(noticeList, ['shenpi_time'], ['desc']);
@@ -131,6 +136,8 @@ module.exports = app => {
             dashboardShenpis.push(...auditStageAss);
             const auditFinancials = await this.auditSet(ctx, allAuditFinancials, subProjects, dashboardStatus, 'financial');
             dashboardShenpis.push(...auditFinancials);
+            const auditInspections = await this.auditSet(ctx, allAuditInspections, subProjects, dashboardStatus, 'inspection');
+            dashboardShenpis.push(...auditInspections);
             // console.log(ctx.helper._.orderBy(dashboardShenpis, ['start_audit', 'shenpi_time'], ['desc', 'desc']));
             const projectData = await ctx.service.project.getDataById(ctx.session.sessionProject.id);
             // 获取销售人员数据
@@ -161,6 +168,7 @@ module.exports = app => {
                 acChangeApply: auditConst.changeApply,
                 acChangePlan: auditConst.changeApply,
                 acFinancial: auditConst.financial,
+                acInspection: auditConst.inspection,
                 noticeList,
                 noticeDayList,
                 pushType: auditConst.pushType,
@@ -263,6 +271,10 @@ module.exports = app => {
                             if (!sp.page_show.openFinancial) closeType = true;
                             calcTime = t.fpcstatus !== auditConst[type].status.checkNo ? t.begin_time : t.end_time;
                             break;
+                        case 'inspection':
+                            if (!sp.page_show.quality) closeType = true;
+                            calcTime = t.status !== auditConst[type].status.checkNo ? t.begin_time : t.end_time;
+                            break;
                         default:
                             closeType = true;
                     }
@@ -308,6 +320,7 @@ module.exports = app => {
             const auditPayments = ctx.subProject.page_show.openPayment ? await ctx.service.paymentDetailAudit.getAuditPayment(ctx.session.sessionUser.accountId, ctx.subProject.id) : [];
             const auditStageAss = await ctx.service.stageAuditAss.getAuditStageAss(ctx.session.sessionUser.accountId, ctx.subProject.id);
             const auditFinancials = ctx.subProject.page_show.openFinancial ? await ctx.service.financialPayAudit.getAuditFinancial(ctx.session.sessionUser.accountId, ctx.subProject.id) : [];
+            const auditInspections = ctx.subProject.page_show.quality ? await ctx.service.qualityInspectionAudit.getAuditInspection(ctx.session.sessionUser.accountId, ctx.subProject.id) : [];
             const pa = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
             const noticeList = await ctx.service.noticePush.getNotice(ctx.session.sessionProject.id, pa.id, ctx.subProject.id);
             const projectData = await ctx.service.project.getDataById(ctx.session.sessionProject.id);
@@ -337,6 +350,7 @@ module.exports = app => {
             if (ctx.subProject.page_show.openChangePlan) shenpi_count.push({ count: await ctx.service.changePlanAudit.getCountByChecked(ctx.session.sessionUser.accountId, ctx.subProject.id), name: '变更方案' });
             if (ctx.subProject.page_show.openMaterial) shenpi_count.push({ count: await ctx.service.materialAudit.getCountByChecked(ctx.session.sessionUser.accountId, ctx.subProject.id), name: '材料调差' });
             if (ctx.subProject.page_show.openFinancial) shenpi_count.push({ count: await ctx.service.financialPayAudit.getCountByChecked(ctx.session.sessionUser.accountId, ctx.subProject.id), name: '资金支付' });
+            if (ctx.subProject.page_show.quality) shenpi_count.push({ count: await ctx.service.qualityInspectionAudit.getCountByChecked(ctx.session.sessionUser.accountId, ctx.subProject.id), name: '质量巡检' });
             // shenpi_count.push({ count: await ctx.service.advanceAudit.getCountByChecked(ctx.session.sessionUser.accountId), name: '预付款' });
             const total_count = ctx.app._.sumBy(shenpi_count, 'count');
             const shenpi_lastime = [
@@ -350,6 +364,7 @@ module.exports = app => {
                 ctx.subProject.page_show.openChangePlan ? await ctx.service.changePlanAudit.getLastEndTimeByChecked(ctx.session.sessionUser.accountId, ctx.subProject.id) : null,
                 ctx.subProject.page_show.openMaterial ? await ctx.service.materialAudit.getLastEndTimeByChecked(ctx.session.sessionUser.accountId, ctx.subProject.id) : null,
                 ctx.subProject.page_show.openFinancial ? await ctx.service.financialPayAudit.getLastEndTimeByChecked(ctx.session.sessionUser.accountId, ctx.subProject.id) : null,
+                ctx.subProject.page_show.quality ? await ctx.service.qualityInspectionAudit.getLastEndTimeByChecked(ctx.session.sessionUser.accountId, ctx.subProject.id) : null,
             ];
             const last_time = ctx.app._.max(shenpi_lastime);
             // console.log(ctx.app._.max(shenpi_lastime), ctx.helper.calcDayNum(last_time));
@@ -366,6 +381,7 @@ module.exports = app => {
                 auditPayments,
                 auditStageAss,
                 auditFinancials,
+                auditInspections,
                 shenpi_count,
                 total_count,
                 last_day: ctx.helper.calcDayNum(last_time),
@@ -381,6 +397,7 @@ module.exports = app => {
                 acChangeApply: auditConst.changeApply,
                 acChangePlan: auditConst.changeApply,
                 acFinancial: auditConst.financial,
+                acInspection: auditConst.inspection,
                 noticeList,
                 pushType: auditConst.pushType,
                 projectData,

+ 510 - 3
app/controller/quality_controller.js

@@ -9,11 +9,15 @@
  */
 
 const auditConst = require('../const/audit');
+const auditType = require('../const/audit').auditType;
+const shenpiConst = require('../const/shenpi');
+const codeRuleConst = require('../const/code_rule');
 const contractConst = require('../const/contract');
 const moment = require('moment');
 const sendToWormhole = require('stream-wormhole');
 const fs = require('fs');
 const path = require('path');
+const PermissionCheck = require('../const/account_permission').PermissionCheck;
 
 module.exports = app => {
     class QualityController extends app.BaseController {
@@ -45,11 +49,17 @@ module.exports = app => {
                 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 accountGroupList = unitList.map(item => {
                     const groupList = accountList.filter(item1 => item1.company === item.name);
                     return { groupName: item.name, groupList };
-                });
-                renderData.accountInfo = await this.ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
+                }).filter(x => { return x.groupList.length > 0; });
+                // 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 };
+                // });
+                renderData.accountGroup = accountGroupList;
+                renderData.accountInfo = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
                 renderData.tenderList = await ctx.service.tender.getSpecList(ctx.service.tenderPermission, 'quality', ctx.session.sessionUser.is_admin ? 'all' : '');
                 renderData.categoryData = await this.ctx.service.category.getAllCategory(this.ctx.subProject);
                 renderData.selfCategoryLevel = this.ctx.subProject.permission.self_category_level;
@@ -85,6 +95,78 @@ module.exports = app => {
             }
         }
 
+        async auditSave(ctx) {
+            try {
+                if (ctx.session.sessionUser.is_admin === 0) throw '没有设置权限';
+                const tid = parseInt(ctx.params.tid);
+                const responseData = {
+                    err: 0, msg: '', data: null,
+                };
+                const tenderInfo = await ctx.service.tender.getDataById(tid);
+                if (!tenderInfo) throw '标段不存在';
+
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.type) {
+                    throw '提交数据错误';
+                }
+                let uids;
+                let auditList = [];
+                switch (data.type) {
+                    case 'add-audit':
+                        // // 判断用户是单个还是数组
+                        uids = data.id instanceof Array ? data.id : [data.id];
+                        // // 判断该用户的组是否已加入到表中,已加入则提示无需添加
+                        auditList = await ctx.service.tenderPermission.getPartsPermission(tenderInfo.id, ['quality']);
+                        const addAidList = ctx.helper._.difference(uids, ctx.helper._.map(auditList, 'uid'));
+                        if (addAidList.length === 0) {
+                            throw '用户已存在成员管理中,无需重复添加';
+                        }
+                        const accountList = await ctx.service.projectAccount.getAllDataByCondition({ where: { id: addAidList } });
+                        const insert_members = [];
+                        for (const account of accountList) {
+                            insert_members.push({
+                                uid: account.id,
+                                quality: ['1'],
+                            });
+                        }
+                        await ctx.service.tenderPermission.saveOnePermission(tenderInfo.id, ctx.helper._.map(accountList, 'id'), insert_members, ['quality']);
+                        responseData.data = await ctx.service.tenderPermission.getPartsPermission(tenderInfo.id, ['quality']);
+                        break;
+                    case 'del-audit':
+                        uids = data.id instanceof Array ? data.id : [data.id];
+                        if (uids.length === 0) throw '没有选择要移除的用户';
+                        auditList = await ctx.service.tenderPermission.getPartsPermission(tenderInfo.id, ['quality']);
+                        // 判断uids和auditList中是否有相同的uid
+                        const commonUids = ctx.helper._.intersection(uids, ctx.helper._.map(auditList, 'uid'));
+                        if (commonUids.length === 0) {
+                            throw '该用户已不在成员管理中,移除失败';
+                        }
+                        const del_members = [];
+                        for (const uid of uids) {
+                            del_members.push({
+                                uid,
+                                quality: [],
+                            });
+                        }
+                        await ctx.service.tenderPermission.saveOnePermission(tenderInfo.id, uids, del_members, ['quality']);
+                        responseData.data = await ctx.service.tenderPermission.getPartsPermission(tenderInfo.id, ['quality']);
+                        break;
+                    case 'save-permission':
+                        uids = data.uid instanceof Array ? data.uid : [data.uid];
+                        await ctx.service.tenderPermission.saveOnePermission(tenderInfo.id, uids, data.members, ['quality']);
+                        break;
+                    case 'list':
+                        responseData.data = await ctx.service.tenderPermission.getPartsPermission(tenderInfo.id, ['quality']);
+                        break;
+                    default: throw '参数有误';
+                }
+                ctx.body = responseData;
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+        }
+
         async info(ctx) {
             try {
                 if (!ctx.subProject.page_show.quality) throw '该功能已关闭';
@@ -408,6 +490,431 @@ module.exports = app => {
                 ctx.ajaxErrorBody(err, '推送失败');
             }
         }
+
+        /**
+         * 变更管理 页面 (Get)
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async inspection(ctx) {
+            try {
+                if (!ctx.subProject.page_show.quality) throw '该功能已关闭';
+                const status = parseInt(ctx.query.status) || 0;
+                await this._filterInspection(ctx, status);
+            } catch (err) {
+                ctx.log(err);
+                ctx.postError(err, '无法查看质量管理数据');
+                ctx.redirect(`/sp/${ctx.subProject.id}/quality/tender`);
+            }
+        }
+
+        // 质量巡检单功能
+        async _filterInspection(ctx, status = 0) {
+            try {
+                ctx.session.sessionUser.tenderId = ctx.tender.id;
+                const sorts = ctx.query.sort ? ctx.query.sort : 0;
+                const orders = ctx.query.order ? ctx.query.order : 0;
+                const filter = JSON.parse(JSON.stringify(auditConst.inspection.filter));
+                filter.count = [];
+                filter.count[filter.status.pending] = await ctx.service.qualityInspection.getCountByStatus(ctx.tender.id, filter.status.pending);
+                filter.count[filter.status.uncheck] = await ctx.service.qualityInspection.getCountByStatus(ctx.tender.id, filter.status.uncheck);
+                filter.count[filter.status.checking] = await ctx.service.qualityInspection.getCountByStatus(ctx.tender.id, filter.status.checking);
+                filter.count[filter.status.rectification] = await ctx.service.qualityInspection.getCountByStatus(ctx.tender.id, filter.status.rectification);
+                filter.count[filter.status.checked] = await ctx.service.qualityInspection.getCountByStatus(ctx.tender.id, filter.status.checked);
+                filter.count[filter.status.checkStop] = await ctx.service.qualityInspection.getCountByStatus(ctx.tender.id, filter.status.checkStop);// await ctx.service.change.pendingDatas(tender.id, ctx.session.sessionUser.accountId);
+                const inspectionList = await ctx.service.qualityInspection.getListByStatus(ctx.tender.id, status, 1, sorts, orders);
+                const total = await ctx.service.qualityInspection.getCountByStatus(ctx.tender.id, status);
+                const allAttList = inspectionList.length > 0 ? await ctx.service.qualityInspectionAtt.getAllAtt(ctx.tender.id, ctx.helper._.map(inspectionList, 'id')) : [];
+                for (const item of inspectionList) {
+                    item.attList = ctx.helper._.filter(allAttList, { qiid: item.id });
+                }
+                // 分页相关
+                const page = ctx.page;
+                const pageSize = ctx.pageSize;
+                const pageInfo = {
+                    page,
+                    pageSizeSelect: 1,
+                    pageSize,
+                    total_num: total,
+                    total: Math.ceil(total / pageSize),
+                    queryData: JSON.stringify(ctx.urlInfo.query),
+                };
+                let codeRule = [];
+                let c_connector = '1';
+                let c_rule_first = 1;
+                const rule_type = 'inspection';
+                const tender = await this.service.tender.getDataById(ctx.tender.id);
+                if (tender.c_code_rules) {
+                    const c_code_rules = JSON.parse(tender.c_code_rules);
+                    codeRule = c_code_rules[rule_type + '_rule'] !== undefined ? c_code_rules[rule_type + '_rule'] : [];
+                    c_connector = c_code_rules[rule_type + '_connector'] !== undefined ? c_code_rules[rule_type + '_connector'] : '1';
+                    c_rule_first = c_code_rules[rule_type + '_rule_first'] !== undefined ? c_code_rules[rule_type + '_rule_first'] : 1;
+                }
+                for (const rule of codeRule) {
+                    switch (rule.rule_type) {
+                        case codeRuleConst.measure.ruleType.dealCode:
+                            rule.preview = ctx.tender.info.deal_info.dealCode;
+                            break;
+                        case codeRuleConst.measure.ruleType.tenderName:
+                            rule.preview = tender.name;
+                            break;
+                        case codeRuleConst.measure.ruleType.inDate:
+                            rule.preview = moment().format('YYYY');
+                            break;
+                        case codeRuleConst.measure.ruleType.text:
+                            rule.preview = rule.text;
+                            break;
+                        case codeRuleConst.measure.ruleType.addNo:
+                            const s = '0000000000';
+                            rule.preview = s.substr(s.length - rule.format);
+                            break;
+                        default: break;
+                    }
+                }
+                const renderData = {
+                    moment,
+                    tender,
+                    permission: ctx.permission.quality,
+                    rule_type,
+                    codeRule,
+                    dealCode: ctx.tender.info.deal_info.dealCode,
+                    c_connector,
+                    c_rule_first,
+                    ruleType: codeRuleConst.ruleType[rule_type],
+                    ruleConst: codeRuleConst.measure,
+                    filter,
+                    inspectionList,
+                    auditType,
+                    auditConst: auditConst.inspection,
+                    status,
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.quality.inspection),
+                    pageInfo,
+                };
+                await this.layout('quality/inspection.ejs', renderData, 'quality/inspection_modal.ejs');
+            } catch (err) {
+                ctx.log(err);
+                ctx.postError(err, '无法查看质量管理数据');
+                ctx.redirect(`/sp/${ctx.subProject.id}/quality/tender`);
+            }
+        }
+
+        /**
+         * 新增变更申请 (Post)
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async inspectionSave(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const reponseData = {
+                    err: 0, msg: '', data: {},
+                };
+                switch (data.type) {
+                    case 'add':
+                        if (!data.code || data.code === '') {
+                            throw '编号不能为空';
+                        }
+                        if (!data.check_item || !data.check_date) {
+                            throw '请填写检查项和日期';
+                        }
+                        reponseData.data = await ctx.service.qualityInspection.add(ctx.tender.id, ctx.session.sessionUser.accountId, data.code, data.check_item, data.check_date);
+                        break;
+                    default:throw '参数有误';
+                }
+                ctx.body = reponseData;
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString() };
+            }
+        }
+
+        /**
+         * 获取审批界面所需的 原报、审批人数据等
+         * @param ctx
+         * @return {Promise<void>}
+         * @private
+         */
+        async _getInspectionAuditViewData(ctx) {
+            await ctx.service.qualityInspection.loadAuditViewData(ctx.inspection);
+        }
+
+        async inspectionInformation(ctx) {
+            try {
+                const whiteList = this.ctx.app.config.multipart.whitelist;
+                const tender = await ctx.service.tender.getDataById(ctx.tender.id);
+                await this._getInspectionAuditViewData(ctx);
+                // 获取附件列表
+                const fileList = await ctx.service.qualityInspectionAtt.getAllAtt(ctx.tender.id, ctx.inspection.id);
+                // 获取用户人验证手机号
+                const renderData = {
+                    moment,
+                    tender,
+                    inspection: ctx.inspection,
+                    auditConst: auditConst.inspection,
+                    fileList,
+                    whiteList,
+                    auditType,
+                    shenpiConst,
+                    deleteFilePermission: PermissionCheck.delFile(this.ctx.session.sessionUser.permission),
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.quality.inspection_information),
+                    preUrl: `/sp/${ctx.subProject.id}/quality/tender/${ctx.tender.id}/inspection/${ctx.inspection.id}/information`,
+                };
+                // data.accountGroup = accountGroup;
+                // 获取所有项目参与者
+                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('quality/inspection_information.ejs', renderData, 'quality/inspection_information_modal.ejs');
+            } catch (err) {
+                this.log(err);
+                ctx.redirect(`/sp/${ctx.subProject.id}/quality/tender/${ctx.tender.id}/inspection`);
+            }
+        }
+
+        async inspectionInformationSave(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const reponseData = {
+                    err: 0, msg: '', data: {},
+                };
+                switch (data.type) {
+                    case 'update-field':
+                        if (!(!ctx.inspection.readOnly || ctx.inspection.rectificationPower)) {
+                            throw '当前状态不可修改';
+                        }
+                        if (data.update.check_item !== undefined && data.update.check_item === '') {
+                            throw '检查项不能为空';
+                        }
+                        if (data.update.check_date !== undefined && data.update.check_date === '') {
+                            throw '请填写检查日期';
+                        }
+                        if (data.update.rectification_item !== undefined && data.update.rectification_item === '') {
+                            throw '整改情况不能为空';
+                        }
+                        if (data.update.rectification_date !== undefined && data.update.rectification_date === '') {
+                            throw '请填写整改日期';
+                        }
+                        const fields = ['id', 'check_item', 'check_situation', 'action', 'check_date', 'inspector', 'rectification_item', 'rectification_date'];
+                        if (!this.checkFieldExists(data.update, fields)) {
+                            throw '参数有误';
+                        }
+                        reponseData.data = await ctx.service.qualityInspection.defaultUpdate(data.update);
+                        break;
+                    case 'add-audit':
+                        const id = this.app._.toInteger(data.auditorId);
+                        if (isNaN(id) || id <= 0) {
+                            throw '参数错误';
+                        }
+                        // 检查权限等
+                        if (ctx.inspection.uid !== ctx.session.sessionUser.accountId) {
+                            throw '您无权添加审核人';
+                        }
+                        if (ctx.inspection.status !== auditConst.inspection.status.uncheck && ctx.inspection.status !== auditConst.inspection.status.checkNo) {
+                            throw '当前不允许添加审核人';
+                        }
+
+                        ctx.inspection.auditorList = await ctx.service.qualityInspectionAudit.getAuditors(ctx.inspection.id, ctx.inspection.times);
+                        // 检查审核人是否已存在
+                        const exist = this.app._.find(ctx.inspection.auditorList, { aid: id });
+                        if (exist) {
+                            throw '该审核人已存在,请勿重复添加';
+                        }
+                        const result = await ctx.service.qualityInspectionAudit.addAuditor(ctx.inspection.id, id, ctx.inspection.times);
+                        if (!result) {
+                            throw '添加审核人失败';
+                        }
+                        reponseData.data = await ctx.service.qualityInspectionAudit.getUserGroup(ctx.inspection.id, ctx.inspection.times);
+                        break;
+                    case 'del-audit':
+                        const id2 = data.auditorId instanceof Number ? data.auditorId : this.app._.toNumber(data.auditorId);
+                        if (isNaN(id2) || id2 <= 0) {
+                            throw '参数错误';
+                        }
+                        const result2 = await ctx.service.qualityInspectionAudit.deleteAuditor(ctx.inspection.id, id2, ctx.inspection.times);
+                        if (!result2) {
+                            throw '移除审核人失败';
+                        }
+                        reponseData.data = await ctx.service.qualityInspectionAudit.getAuditors(ctx.inspection.id, ctx.inspection.times);
+                        break;
+                    case 'start-inspection':
+                        if (ctx.inspection.readOnly) {
+                            throw '当前状态不可提交';
+                        }
+                        await ctx.service.qualityInspectionAudit.start(ctx.inspection.id, ctx.inspection.times);
+                        break;
+                    case 'del-inspection':
+                        if (ctx.inspection.readOnly) {
+                            throw '当前状态不可删除';
+                        }
+                        await ctx.service.qualityInspection.delInspection(ctx.inspection.id);
+                        break;
+                    case 'check':
+                        if (!ctx.inspection || !(ctx.inspection.status === auditConst.inspection.status.checking || ctx.inspection.status === auditConst.inspection.status.checkNoPre)) {
+                            throw '当前质量巡检数据有误';
+                        }
+                        if (ctx.inspection.curAuditorIds.length === 0 || ctx.inspection.curAuditorIds.indexOf(ctx.session.sessionUser.accountId) === -1) {
+                            throw '您无权进行该操作';
+                        }
+                        await ctx.service.qualityInspectionAudit.check(ctx.inspection, data);
+                        break;
+                    case 'rectification':
+                        if (!ctx.inspection || ctx.inspection.status !== auditConst.inspection.status.rectification) {
+                            throw '当前质量巡检数据有误';
+                        }
+                        if (ctx.inspection.curAuditorIds.length === 0 || ctx.inspection.curAuditorIds.indexOf(ctx.session.sessionUser.accountId) === -1) {
+                            throw '您无权进行该操作';
+                        }
+                        await ctx.service.qualityInspectionAudit.rectification(ctx.inspection, data);
+                        break;
+                    default:throw '参数有误';
+                }
+                ctx.body = reponseData;
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString() };
+            }
+        }
+
+        checkFieldExists(update, fields) {
+            for (const field of Object.keys(update)) {
+                if (!fields.includes(field)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        /**
+         * 上传附件
+         * @param {*} ctx 上下文
+         */
+        async uploadInspectionFile(ctx) {
+            let stream;
+            try {
+                // this._checkAdvanceFileCanModify(ctx);
+                const parts = this.ctx.multipart({
+                    autoFields: true,
+                });
+                const files = [];
+                const create_time = Date.parse(new Date()) / 1000;
+                let idx = 0;
+                const extra_upload = ctx.inspection.status === auditConst.inspection.status.checked;
+                while ((stream = await parts()) !== undefined) {
+                    if (!stream.filename) {
+                        // 如果没有传入直接返回
+                        return;
+                    }
+                    const fileInfo = path.parse(stream.filename);
+                    const filepath = `app/public/upload/${this.ctx.tender.id.toString()}/quality_inspection/fujian_${create_time + idx.toString() + fileInfo.ext}`;
+                    // await ctx.helper.saveStreamFile(stream, path.resolve(this.app.baseDir, 'app', filepath));
+                    await ctx.app.fujianOss.put(ctx.app.config.fujianOssFolder + filepath, stream);
+                    files.push({ filepath, name: stream.filename, ext: fileInfo.ext });
+                    ++idx;
+                    stream && (await sendToWormhole(stream));
+                }
+                const in_time = new Date();
+                const payload = files.map(file => {
+                    let idx;
+                    if (Array.isArray(parts.field.name)) {
+                        idx = parts.field.name.findIndex(name => name === file.name);
+                    } else {
+                        idx = 'isString';
+                    }
+                    const newFile = {
+                        tid: ctx.tender.id,
+                        qiid: ctx.inspection.id,
+                        uid: ctx.session.sessionUser.accountId,
+                        filename: file.name,
+                        fileext: file.ext,
+                        filesize: ctx.helper.bytesToSize(idx === 'isString' ? parts.field.size : parts.field.size[idx]),
+                        filepath: file.filepath,
+                        upload_time: in_time,
+                        extra_upload,
+                    };
+                    return newFile;
+                });
+                // 执行文件信息写入数据库
+                await ctx.service.qualityInspectionAtt.saveFileMsgToDb(payload);
+                // 将最新的当前标段的所有文件信息返回
+                const data = await ctx.service.qualityInspectionAtt.getAllAtt(ctx.tender.id, ctx.inspection.id);
+                ctx.body = { err: 0, msg: '', data };
+            } catch (err) {
+                stream && (await sendToWormhole(stream));
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+        }
+
+        /**
+         * 删除附件
+         * @param {Ojbect} ctx 上下文
+         */
+        async deleteInspectionFile(ctx) {
+            try {
+                const { id } = JSON.parse(ctx.request.body.data);
+                const fileInfo = await ctx.service.qualityInspectionAtt.getDataById(id);
+                if (fileInfo || Object.keys(fileInfo).length) {
+                    // 先删除文件
+                    // await fs.unlinkSync(path.resolve(this.app.baseDir, './app', fileInfo.filepath));
+                    await ctx.app.fujianOss.delete(ctx.app.config.fujianOssFolder + fileInfo.filepath);
+                    // 再删除数据库
+                    await ctx.service.qualityInspectionAtt.delete(id);
+                } else {
+                    throw '不存在该文件';
+                }
+                const data = await ctx.service.qualityInspectionAtt.getAllAtt(ctx.tender.id, ctx.inspection.id);
+                ctx.body = { err: 0, msg: '请求成功', data };
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+        }
+
+        /**
+         * 下载附件
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async downloadInspectionFile(ctx) {
+            const id = ctx.params.fid;
+            if (id) {
+                try {
+                    const fileInfo = await ctx.service.qualityInspectionAtt.getDataById(id);
+                    if (fileInfo !== undefined && fileInfo !== '') {
+                        // const fileName = path.join(__dirname, '../', fileInfo.filepath);
+                        // 解决中文无法下载问题
+                        const userAgent = (ctx.request.header['user-agent'] || '').toLowerCase();
+                        let disposition = '';
+                        if (userAgent.indexOf('msie') >= 0 || userAgent.indexOf('chrome') >= 0) {
+                            disposition = 'attachment; filename=' + encodeURIComponent(fileInfo.filename);
+                        } else if (userAgent.indexOf('firefox') >= 0) {
+                            disposition = 'attachment; filename*="utf8\'\'' + encodeURIComponent(fileInfo.filename) + '"';
+                        } else {
+                            /* safari等其他非主流浏览器只能自求多福了 */
+                            disposition = 'attachment; filename=' + new Buffer(fileInfo.filename).toString('binary');
+                        }
+                        ctx.response.set({
+                            'Content-Type': 'application/octet-stream',
+                            'Content-Disposition': disposition,
+                            'Content-Length': fileInfo.filesize,
+                        });
+                        // ctx.body = await fs.createReadStream(fileName);
+                        ctx.body = await ctx.helper.ossFileGet(fileInfo.filepath);
+                    } else {
+                        throw '不存在该文件';
+                    }
+                } catch (err) {
+                    this.log(err);
+                    this.setMessage(err.toString(), this.messageType.ERROR);
+                }
+            }
+        }
     }
 
     return QualityController;

+ 9 - 5
app/controller/sub_proj_setting_controller.js

@@ -542,7 +542,6 @@ module.exports = app => {
                 const projectData = await ctx.service.project.getDataById(projectId);
                 if (projectData === null) throw '没有对应的项目数据';
                 if (ctx.session.sessionUser.is_admin === 0) throw '没有访问权限';
-
                 const tenderList = await ctx.service.tender.getList('', null, 1);
                 const removeTenders = await ctx.service.shenpiAudit.getRemoveTenders(tenderList);
                 if (removeTenders.length > 0) {
@@ -550,10 +549,10 @@ module.exports = app => {
                         return removeTenders.indexOf(n.id) !== -1;
                     });
                 }
-                for (const t of tenderList) {
-                    t.visitor = (await this.ctx.service.tenderTourist.getTourists(t.id)).map(x => { return x.user_name; });
-                    await this.ctx.service.tenderCache.loadTenderCache(t, this.ctx.session.sessionUser.accountId);
-                }
+                // for (const t of tenderList) {
+                //     t.visitor = (await this.ctx.service.tenderTourist.getTourists(t.id)).map(x => { return x.user_name; });
+                //     await this.ctx.service.tenderCache.loadTenderCache(t, this.ctx.session.sessionUser.accountId);
+                // }
                 // todo 自定义分类移动到子项目内部后,在整个项目上没有分类了
                 const categoryData = await ctx.service.category.getAllCategory(ctx.subProject);
                 // const categoryData = []; // await this.ctx.service.category.getAllCategory(this.ctx.session.sessionProject.id);
@@ -588,6 +587,8 @@ module.exports = app => {
                     subProjects,
                 };
                 renderData.selfCategoryLevel = await this.ctx.service.projectAccount.getSelfCategoryLevel(this.ctx.session.sessionUser.accountId);
+                renderData.permissionConst = ctx.service.tenderPermission.partPermissionConst('quality');
+                renderData.permissionBlock = ctx.service.tenderPermission.partPermissionBlock('quality');
                 await this.layout('sp_setting/manage.ejs', renderData, 'sp_setting/manage_modal.ejs');
             } catch (error) {
                 ctx.log(error);
@@ -640,6 +641,7 @@ module.exports = app => {
                         responseData.data.scheduleAuditList = await ctx.service.scheduleAudit.getAllDataByCondition({ where: { tid: tender.id } });
                         responseData.data.contractAuditList = await ctx.service.contractAudit.getList({ tid: tender.id });
                         responseData.data.constructionAuditList = await ctx.service.constructionAudit.getList(tender.id);
+                        responseData.data.qualityAuditList = await ctx.service.tenderPermission.getPartsPermission(tender.id, ['quality']);
                         break;
                     case 'copy2otu':
                         if (data.userType === 'tourist') {
@@ -650,6 +652,8 @@ module.exports = app => {
                             await ctx.service.contractAudit.setOtherTender(data.tidList, data.auditList);
                         } else if (data.userType === 'construction') {
                             await ctx.service.constructionAudit.setOtherTender(data.tidList, data.auditList);
+                        } else if (data.userType === 'quality') {
+                            await ctx.service.tenderPermission.setOtherTender(data.tidList, data.auditList, ['quality']);
                         } else {
                             throw '参数有误';
                         }

+ 126 - 0
app/middleware/inspection_check.js

@@ -0,0 +1,126 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Ellisran
+ * @date 2020/10/15
+ * @version
+ */
+
+const status = require('../const/audit').inspection.status;
+const shenpiConst = require('../const/shenpi');
+const _ = require('lodash');
+
+module.exports = options => {
+    /**
+     * 标段校验 中间件
+     * 1. 读取标段数据(包括属性)
+     * 2. 检验用户是否可见标段(不校验具体权限)
+     *
+     * @param {function} next - 中间件继续执行的方法
+     * @return {void}
+     */
+    return function* InspectionCheck(next) {
+        try {
+            // 获取revise
+            if (!this.subProject.page_show.quality) {
+                throw '该功能已关闭';
+            }
+            const qiid = this.params.qiid || this.request.body.qiid;
+            if (!qiid) {
+                throw '您访问的质量巡检不存在';
+            }
+            const inspection = yield this.service.qualityInspection.getDataById(qiid);
+            if (!inspection) throw '质量巡检数据有误';
+            // 读取原报、审核人数据
+            yield this.service.qualityInspection.loadUser(inspection);
+            // 权限相关
+            // todo 校验权限 (标段参与人、分享)
+            const accountId = this.session.sessionUser.accountId,
+                auditorIds = _.map(inspection.auditors, 'aid'),
+                shareIds = [];
+            const permission = this.session.sessionUser.permission;
+            if (accountId === inspection.uid) { // 原报
+                inspection.filePermission = true;
+            } else if (auditorIds.indexOf(accountId) !== -1) { // 审批人
+                if (inspection.status === status.uncheck) {
+                    throw '您无权查看该数据';
+                }
+                inspection.filePermission = true;
+            } else if (inspection.status === status.checkNo && inspection.uid !== accountId) {
+                const preAuditors = yield this.service.qualityInspectionAudit.getAuditors(inspection.id, inspection.times - 1);
+                const preAuditorIds = _.map(preAuditors, 'aid');
+                if (preAuditorIds.indexOf(accountId) === -1) {
+                    throw '您无权查看该数据';
+                }
+                inspection.filePermission = true;
+            } else if (this.tender.isTourist || this.session.sessionUser.is_admin) {
+                inspection.filePermission = this.tender.touristPermission.file || auditorIds.indexOf(accountId) !== -1;
+            } else if (shareIds.indexOf(accountId) !== -1 || (permission !== null && permission.tender !== undefined && permission.tender.indexOf('2') !== -1)) { // 分享人
+                if (inspection.status === status.uncheck) {
+                    throw '您无权查看该数据';
+                }
+                inspection.filePermission = false;
+            } else { // 其他不可见
+                throw '您无权查看该数据';
+            }
+            // 调差的readOnly 指表格和页面只能看不能改,和审批无关
+            inspection.readOnly = !((inspection.status === status.uncheck || inspection.status === status.checkNo) && accountId === inspection.uid);
+            inspection.rectificationPower = inspection.status === status.rectification && inspection.curAuditorIds.indexOf(accountId) !== -1;
+            inspection.shenpiPower = (inspection.status === status.checking || inspection.status === status.checkNoPre) && inspection.curAuditorIds.indexOf(accountId) !== -1;
+            this.inspection = inspection;
+            // 根据状态判断是否需要更新审批人列表
+            if ((inspection.status === status.uncheck || inspection.status === status.checkNo) && this.tender.info.shenpi.inspection !== shenpiConst.sp_status.sqspr) {
+                const shenpi_status = this.tender.info.shenpi.inspection;
+                // 进一步比较审批流是否与审批流程设置的相同,不同则替换为固定审批流或固定的终审
+                const auditList = yield this.service.qualityInspectionAudit.getAllDataByCondition({ where: { qiid: inspection.id, times: inspection.times, is_rectification: 0 }, orders: [['order', 'asc']] });
+                if (shenpi_status === shenpiConst.sp_status.gdspl) {
+                    const shenpiList = yield this.service.shenpiAudit.getAllDataByCondition({ where: { tid: inspection.tid, sp_type: shenpiConst.sp_type.inspection, sp_status: shenpi_status } });
+                    // 判断2个id数组是否相同,不同则删除原审批流,切换成固定的审批流
+                    let sameAudit = auditList.length === shenpiList.length;
+                    if (sameAudit) {
+                        for (const audit of auditList) {
+                            const shenpi = shenpiList.find(x => { return x.audit_id === audit.aid; });
+                            if (!shenpi || shenpi.audit_order !== audit.audit_order || shenpi.audit_type !== audit.audit_type) {
+                                sameAudit = false;
+                                break;
+                            }
+                        }
+                    }
+                    if (!sameAudit) {
+                        yield this.service.qualityInspectionAudit.updateNewAuditList(inspection, shenpiList);
+                        yield this.service.qualityInspection.loadUser(inspection);
+                    }
+                } else if (shenpi_status === shenpiConst.sp_status.gdzs) {
+                    const shenpiInfo = yield this.service.shenpiAudit.getDataByCondition({ tid: inspection.tid, sp_type: shenpiConst.sp_type.inspection, sp_status: shenpi_status });
+                    // 判断最后一个id是否与固定终审id相同,不同则删除原审批流中如果存在的id和添加终审
+                    const lastAuditors = auditList.filter(x => { x.order === auditList[auditList.length - 1].order; });
+                    if (shenpiInfo && (lastAuditors.length === 0 || (lastAuditors.length > 1 || shenpiInfo.audit_id !== lastAuditors[0].aid))) {
+                        yield this.service.qualityInspectionAudit.updateLastAudit(inspection, auditList, shenpiInfo.audit_id);
+                        yield this.service.qualityInspection.loadUser(inspection);
+                    } else if (!shenpiInfo) {
+                        // 不存在终审人的状态下这里恢复为授权审批人
+                        this.tender.info.shenpi.inspection = shenpiConst.sp_status.sqspr;
+                    }
+                }
+            }
+            yield next;
+        } catch (err) {
+            console.log(err);
+            // 输出错误到日志
+            if (err.stack) {
+                this.logger.error(err);
+            } else {
+                this.getLogger('fail').info(JSON.stringify({
+                    error: err,
+                    project: this.session.sessionProject,
+                    user: this.session.sessionUser,
+                    body: this.session.body,
+                }));
+            }
+            // 重定向值标段管理
+            this.redirect(this.request.headers.referer);
+        }
+    };
+};

+ 6 - 0
app/public/css/main.css

@@ -1910,6 +1910,9 @@ overflow-y: auto;
 .bg-new-financial{
     background: rgba(58, 88, 50, 0.08) !important;
 }
+.bg-new-inspection{
+    background: rgba(158, 88, 80, 0.08) !important;
+}
 .text-new-advance{
   color: rgba(241, 82, 91, 1) !important;
 }
@@ -1943,6 +1946,9 @@ overflow-y: auto;
 .text-new-financial{
     color: rgba(58, 88, 50, 1) !important;
 }
+.text-new-inspection{
+    color: rgba(158, 58, 80, 1) !important;
+}
 .text-width{
   width: 66px;
   text-align: center;

+ 6 - 0
app/public/css/wap/main.css

@@ -59,6 +59,9 @@ input.form-control[readonly],textarea.form-control[readonly] {
 .bg-new-financial{
     background: rgba(58, 88, 50, 0.08) !important;
 }
+.bg-new-inspection{
+    background: rgba(158, 88, 80, 0.08) !important;
+}
 .text-new-advance{
     color: rgba(241, 82, 91, 1) !important;
 }
@@ -92,3 +95,6 @@ input.form-control[readonly],textarea.form-control[readonly] {
 .text-new-financial{
     color: rgba(58, 88, 50, 1) !important;
 }
+.text-new-inspection{
+    color: rgba(158, 58, 80, 1) !important;
+}

+ 3 - 3
app/public/js/material_checklist.js

@@ -181,7 +181,7 @@ $(document).ready(() => {
                 // const newOrder = _.indexOf(gclGatherData, gcl);
                 // console.log(newOrder);
                 if (!mc && _.findIndex(pushData, { b_code: gcl.b_code, name: gcl.name, unit: gcl.unit, unit_price: gcl.unit_price }) === -1) {
-                    pushData.push({ b_code: gcl.b_code, name: gcl.name, unit: gcl.unit, unit_price: gcl.unit_price, quantity: (gcl.quantity ? gcl.quantity : null), total_price: (gcl.total_price ? gcl.total_price : null), had_bills: 1 });
+                    pushData.push({ b_code: gcl.b_code, name: gcl.name, unit: gcl.unit, unit_price: gcl.unit_price, quantity: (gcl.quantity ? gcl.quantity : 0), total_price: (gcl.total_price ? gcl.total_price : 0), had_bills: 1 });
                 }
             }
         }
@@ -353,8 +353,8 @@ $(document).ready(() => {
                     name: gclGatherData[order].name,
                     unit: gclGatherData[order].unit,
                     unit_price: gclGatherData[order].unit_price,
-                    quantity: gclGatherData[order].quantity ? gclGatherData[order].quantity : null,
-                    total_price: gclGatherData[order].total_price ? gclGatherData[order].total_price : null,
+                    quantity: gclGatherData[order].quantity ? gclGatherData[order].quantity : 0,
+                    total_price: gclGatherData[order].total_price ? gclGatherData[order].total_price : 0,
                     had_bills: 0,
                 })
             }

+ 349 - 0
app/public/js/quality_inspection.js

@@ -0,0 +1,349 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2018/8/21
+ * @version
+ */
+// 向后端请求中间计量号
+function getNewCode() {
+    postData('/tender/'+ tenderId +'/change/newCode', { type: rulesType }, function (code) {
+        if (code !== '') {
+            $('#bj-code').val(code);
+        }
+    });
+}
+
+class codeRuleSet {
+    constructor (obj) {
+        this.body = obj;
+        // 切换规则组件类型
+        $('.rule-change', obj).change(function () {
+            const codeType = this.selectedIndex-1;
+            if (codeType === ruleConst.ruleType.addNo) {
+                $('#format', obj).show();
+                $('#text', obj).show();
+                $('#text>label', obj).text('起始编号');
+                $('#text>input', obj).val('001');
+                const s = '0000000000' + 1;
+                $('#text>input', obj).val(s.substr(s.length - $('#format>input', obj).val()));
+            } else if (codeType === ruleConst.ruleType.text) {
+                $('#format', obj).hide();
+                $('#text', obj).show();
+                $('#text>label', obj).text('文本');
+                $('#text>input', obj).val('').attr('placeholder', '请在这里输入需要的文本');
+            } else {
+                $('#format', obj).hide();
+                $('#text', obj).hide();
+            }
+        });
+        // 修改编号位数
+        $('#format>input', obj).change(function () {
+            const s = '0000000000' + parseInt($('#text>input', obj).val());
+            $('#text>input', obj).val(s.substr(s.length - $(this).val()));
+        });
+
+        // 修改连接符
+        $('.connector-change', obj).change(function () {
+            const connectorType = this.options[this.selectedIndex].text;
+            const rules = $('span>span', obj), ruleText = [];
+            for (const r of rules) {
+                ruleText.push($.trim(r.innerText));
+            }
+            if (connectorType === '无') {
+                $('#preview', obj).text(ruleText.join(''));
+            } else {
+                $('#preview', obj).text(ruleText.join(connectorType));
+            }
+            connectorRule = this.options[this.selectedIndex].value;
+        });
+
+        // 新增规则组件
+        $('#addRule', obj).click(function () {
+            const codeType = $('select', obj)[1].selectedIndex-1;
+            const rule = {rule_type: codeType}, html = [];
+            let preview;
+            switch (codeType) {
+                case ruleConst.ruleType.dealCode: {
+                    if (dealCode === '') {
+                        toastr.error('当前标段合同编号为空,请选择其他组件。');
+                        return false;
+                    }
+                    preview = dealCode;
+                    break;
+                }
+                case ruleConst.ruleType.tenderName: {
+                    preview = tenderName;
+                    break;
+                }
+                case ruleConst.ruleType.text: {
+                    rule.text = $('#text>input', obj).val();
+                    if (rule.text === '') {
+                        toastr.error('文本内容不允许为空。');
+                        return false;
+                    }
+                    preview = rule.text;
+                    break;
+                }
+                case ruleConst.ruleType.inDate: {
+                    preview = moment().format('YYYY');
+                    break;
+                }
+                case ruleConst.ruleType.addNo: {
+                    rule.format = parseInt($('#format>input', obj).val());
+                    rule.start = parseInt($('#text>input', obj).val());
+                    if ($('#text>input', obj).val().length !== rule.format) {
+                        toastr.error('起始编号位数和自动编号位数不一致。');
+                        return false;
+                    }
+                    const s = '0000000000';
+                    preview = s.substr(s.length - rule.format);
+                    break;
+                }
+                default: {
+                    toastr.error('请选择组件再添加');
+                    return false;
+                }
+            }
+            // 更新规则
+            codeRule.push(rule);
+            // 更新规则显示
+            html.push('<span class="badge badge-light" title="' + ruleConst.ruleString[codeType] + '" rule="' + JSON.stringify(rule) + '">');
+            html.push('<span>' + preview + '</span>');
+            html.push('<a href="javascript: void(0);" class="text-danger" title="移除"><i class="fa fa-remove"></i></a>');
+            html.push('</span>');
+            const part = $('#ruleParts', obj).append(html.join(''));
+            // 更新规则预览
+            const connectorType = connectorRule !== '' && parseInt(connectorRule) !== ruleConst.connectorType.nothing ? ruleConst.connectorString[connectorRule] : '';
+            const previewtext = $.trim($('#preview', obj).text()) === '' ? preview : $.trim($('#preview', obj).text()) + connectorType + preview;
+            $('#preview', obj).text(previewtext);
+        });
+        // 删除规则组件
+        $($('#ruleParts', obj)).on('click', 'a', function () {
+            const index = $('a', obj).index(this);
+            codeRule.splice(index-1, 1);
+            $(this).parent().remove();
+            const rules = $('span>span', obj), ruleText = [];
+            for (const r of rules) {
+                ruleText.push($.trim(r.innerText));
+            }
+            const connectorType = connectorRule !== '' && parseInt(connectorRule) !== ruleConst.connectorType.nothing ? ruleConst.connectorString[connectorRule] : '';
+            $('#preview', obj).text(ruleText.join(connectorType));
+        });
+    }
+}
+$(document).ready(() => {
+    // 首次进入设置
+    let showNoNeed = false;
+    if (cRuleFirst) {
+        codeRule = [];
+        showNoNeed = true;
+        $('#setting').modal('show');
+    }
+    // 设置
+    const ruleSet = new codeRuleSet($('div.modal-body', '#setting'));
+    $('#setRule', '#setting').bind('click', function () {
+        const data = {
+            rule: ruleType,
+            type: rulesType,
+            connector: connectorRule,
+            data: JSON.stringify(codeRule),
+        };
+        if (codeRule.length !== 0) {
+            $('#autoCodeShow').show();
+        }
+        postData('/tender/rule', data, function () {
+            if (cRuleFirst && showNoNeed) {
+                $('#changeFirst').click();
+                $('.ml-auto a[href="#add-bj"]').click();
+            } else {
+                $('#setting').modal('hide');
+            }
+        });
+    })
+    $('.ml-auto').on('click', 'a', function () {
+        const content = $(this).attr('href');
+        if (content === '#add-bj') {
+            $('#add-bj-modal').modal('show')
+            getNewCode();
+            if ($('#changeList').children.length === 0) {
+                $('#addCancel').hide();
+            } else {
+                $('#addCancel').show();
+            }
+            $('#bj-code').removeClass('is-invalid');
+        }
+    });
+    // 获取最新可用变更令号
+    $('#autoCode').click(getNewCode);
+    // 新增变更令 确认
+    $('#addOk').click(function () {
+        $(this).attr('disabled', true);
+        if ($('#check_item').val().length === 0) {
+            $('#check_item').addClass('is-invalid');
+            $('#name_error_msg').show();
+            $('#name_error_msg').text('检查项不能为空。');
+            $(this).attr('disabled', false);
+            setTimeout(function () {
+                $('#check_item').removeClass('is-invalid');
+                $('#name_error_msg').hide();
+            }, 2000);
+            return;
+        }
+        if ($('#check_item').val().length > 255) {
+            $('#chek_item').addClass('is-invalid');
+            $('#name_error_msg').show();
+            $('#name_error_msg').text('检查项超过255个字,请缩减。');
+            $(this).attr('disabled', false);
+            setTimeout(function () {
+                $('#check_item').removeClass('is-invalid');
+                $('#name_error_msg').hide();
+            }, 2000);
+            return;
+        }
+        if ($('#check_date').val() === '') {
+            $('#check_date').addClass('is-invalid');
+            $('#check_date').siblings('.invalid-feedback').show();
+            $('#check_date').siblings('.invalid-feedback').text('检查日期不能为空。');
+            $(this).attr('disabled', false);
+            setTimeout(function () {
+                $('#check_date').removeClass('is-invalid');
+                $('#check_date').siblings('.invalid-feedback').hide();
+            }, 2000);
+            return;
+        } else {
+            // 判断日期格式
+            const reg = /^\d{4}-\d{2}-\d{2}$/;
+            if (!reg.test($('#check_date').val())) {
+                $('#check_date').addClass('is-invalid');
+                $('#check_date').siblings('.invalid-feedback').show();
+                $('#check_date').siblings('.invalid-feedback').text('检查日期格式错误,应为YYYY-MM-DD。');
+                $(this).attr('disabled', false);
+                setTimeout(function () {
+                    $('#check_date').removeClass('is-invalid');
+                    $('#check_date').siblings('.invalid-feedback').hide();
+                }, 2000);
+                return;
+            }
+        }
+        const data = {
+            type: 'add',
+            code: $('#bj-code').val(),
+            check_item: $('#check_item').val(),
+            check_date: $('#check_date').val(),
+        };
+        if (data.code || data.code !== '') {
+            postData(`/sp/${spid}/quality/tender/${tenderId}/inspection/save`, data, function (rst) {
+                $('#bj-code').removeClass('is-invalid');
+                $('#add-bj-modal').modal('hide');
+                $(this).attr('disabled', false);
+                window.location.href = `/sp/${spid}/quality/tender/${tenderId}/inspection/${rst.id}/information`;
+            }, function () {
+                $('#bj-code').addClass('is-invalid');
+                $('#bjHint').show();
+                $(this).attr('disabled', false);
+            });
+        }
+    });
+
+    //状态切换
+    $('#status_select a').on('click', function () {
+        const status = $(this).data('val');
+        let url = `/sp/${spid}/quality/tender/${tenderId}/inspection`;
+        const filterString = setChangeFilterData('quality-inspection-'+ tenderId +'-list-order', status !== 0 ? '?status='+ status : '');
+        if (filterString) url = url + filterString;
+        window.location.href = url;
+    });
+    // 不再显示首次使用
+    $('#changeFirst').click(function () {
+        showNoNeed = false;
+        $('#changeFirst').remove();
+        $('#hide_modal').show();
+        $('#setting').modal('hide');
+        postData('/tender/'+ tenderId +'/rule/first', { type: rulesType }, function () {
+        });
+    });
+
+    // 排序初始化
+    let orderSetting = getLocalCache('quality-inspection-'+ tenderId +'-list-order');
+    if (!orderSetting) orderSetting = 'time|desc';
+    const orders = orderSetting.split('|');
+    $("#sort-radio input[value='"+ orders[0] +"']").prop('checked', true);
+    $("#order-radio input[value='"+ orders[1] +"']").prop('checked', true);
+    if (orders[0] === 'time') {
+        $('#bpaixu').text('排序:创建时间');
+    } else {
+        $('#bpaixu').text('排序:编号');
+    }
+    $('#sort-radio input[name="paizhi"]').click(function () {
+        const orderStr = $(this).val() + '|' + $('#order-radio input[name="paixu"]:checked').val();
+        setLocalCache('quality-inspection-'+ tenderId +'-list-order', orderStr);
+        let link = window.location.origin + window.location.pathname;
+        const filterData = [];
+        if ($('#zhankai').data('status') !== '0') {
+            filterData.push('status=' + $('#zhankai').data('status'));
+        }
+        filterData.push('sort='+ $(this).val());
+        filterData.push('order=' + $('#order-radio input[name="paixu"]:checked').val());
+        if (getLocalCache('account-pageSize')) {
+            filterData.push('pageSize=' + getLocalCache('account-pageSize'));
+        }
+        if (filterData.length > 0) {
+            link += '?' + filterData.join('&');
+        }
+        window.location.href = link;
+    });
+    $('#order-radio input[name="paixu"]').click(function () {
+        const orderStr = $('#sort-radio input[name="paizhi"]:checked').val() + '|' + $(this).val();
+        setLocalCache('quality-inspection-'+ tenderId +'-list-order', orderStr);
+        let link = window.location.origin + window.location.pathname;
+        const filterData = [];
+        if ($('#zhankai').data('status') !== '0') {
+            filterData.push('status=' + $('#zhankai').data('status'));
+        }
+        filterData.push('sort='+ $('#sort-radio input[name="paizhi"]:checked').val());
+        filterData.push('order=' + $(this).val());
+        if (getLocalCache('account-pageSize')) {
+            filterData.push('pageSize=' + getLocalCache('account-pageSize'));
+        }
+        if (filterData.length > 0) {
+            link += '?' + filterData.join('&');
+        }
+        window.location.href = link;
+    });
+
+    $('.show-files').on('click', function () {
+        const id = parseInt($(this).data('id'));
+        const info = _.find(inspectionList, { id: id });
+        console.log(info);
+        handleFileList(info.attList || []);
+    });
+
+    function handleFileList(files = []) {
+        $('#file-content').empty();
+        let html = '';
+        files.forEach((file, idx) => {
+            html += `<tr><td><a href="${file.filepath}" target="_blank">${file.filename}</a></td><td>${file.username}</td><td>${moment(file.upload_time).format('YYYY-MM-DD HH:mm:ss')}</td><td><a href="/sp/${spid}/quality/tender/${file.tid}/inspection/${file.qiid}/information/file/${file.id}/download" class="mr-2"><i class="fa fa-download"></i></a></td></tr>`
+        })
+        $('#file-content').append(html);
+    }
+
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        key: 'menu.1.0.0',
+        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            autoFlashHeight();
+        }
+    });
+});

+ 631 - 0
app/public/js/quality_inspection_information.js

@@ -0,0 +1,631 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author lanjianrong
+ * @date 2020/8/7
+ * @version
+ */
+
+$(document).ready(function () {
+    autoFlashHeight();
+
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        key: 'menu.1.0.0',
+        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            autoFlashHeight();
+        }
+    });
+
+    // 展开历史审核记录
+    $('td #fold-btn').click(function () {
+        const type = $(this).data('target')
+        const auditCard = $(this).parent().parent()
+        if (type === 'show') {
+            $(this).data('target', 'hide')
+            auditCard.find('.fold-card').slideDown('swing', () => {
+                auditCard.find('#fold-btn').text('收起历史审核记录')
+            })
+        } else {
+            $(this).data('target', 'show')
+            auditCard.find('.fold-card').slideUp('swing', () => {
+                auditCard.find('#fold-btn').text('展开历史审核记录')
+            })
+        }
+    });
+
+    // 添加审批流程按钮逻辑
+    $('.book-list').on('click', 'dt', function () {
+        const idx = $(this).find('.acc-btn').attr('data-groupid')
+        const type = $(this).find('.acc-btn').attr('data-type')
+        if (type === 'hide') {
+            $(this).parent().find(`div[data-toggleid="${idx}"]`).show(() => {
+                $(this).children().find('i').removeClass('fa-plus-square').addClass('fa-minus-square-o')
+                $(this).find('.acc-btn').attr('data-type', 'show')
+
+            })
+        } else {
+            $(this).parent().find(`div[data-toggleid="${idx}"]`).hide(() => {
+                $(this).children().find('i').removeClass('fa-minus-square-o').addClass('fa-plus-square')
+                $(this).find('.acc-btn').attr('data-type', 'hide')
+            })
+        }
+        return false
+    })
+
+    let timer = null
+    let oldSearchVal = null
+    $('.gr-search').bind('input propertychange', function (e) {
+        oldSearchVal = e.target.value
+        timer && clearTimeout(timer)
+        timer = setTimeout(() => {
+            const newVal = $('#gr-search').val()
+            let html = ''
+            if (newVal && newVal === oldSearchVal) {
+                accountList.filter(item => item && inspection.uid !== item.id && (item.name.indexOf(newVal) !== -1 || (item.mobile && item.mobile.indexOf(newVal) !== -1))).forEach(item => {
+                    html += `<dd class="border-bottom p-2 mb-0 " data-id="${item.id}" >
+                        <p class="mb-0 d-flex"><span class="text-primary">${item.name}</span><span
+                                class="ml-auto">${item.mobile || ''}</span></p>
+                        <span class="text-muted">${item.role || ''}</span>
+                    </dd>`
+                })
+                $('.book-list').empty()
+                $('.book-list').append(html)
+            } else {
+                if (!$('.acc-btn').length) {
+                    accountGroup.forEach((group, idx) => {
+                        if (!group) return
+                        html += `<dt><a href="javascript: void(0);" class="acc-btn" data-groupid="${idx}" data-type="hide"><i class="fa fa-plus-square"></i>
+                        </a> ${group.groupName}</dt>
+                        <div class="dd-content" data-toggleid="${idx}">`
+                        group.groupList.forEach(item => {
+                            if (item.id !== inspection.uid) {
+                                html += `<dd class="border-bottom p-2 mb-0 " data-id="${item.id}" >
+                                    <p class="mb-0 d-flex"><span class="text-primary">${item.name}</span><span
+                                            class="ml-auto">${item.mobile || ''}</span></p>
+                                    <span class="text-muted">${item.role || ''}</span>
+                                </dd>`
+                            }
+                        });
+                        html += '</div>'
+                    })
+                    $('.book-list').empty()
+                    $('.book-list').append(html)
+                }
+            }
+        }, 400);
+    })
+    if (!inspection.readOnly) {
+        const checkDate = $('#check_date').datepicker({
+            autoClose: true,
+            onSelect: function (formattedDate, date, inst) {
+                if (!date && inspection.check_date) {
+                    toastr.error('检查日期不能为空');
+                    checkDate.selectDate(inspection.check_date ? new Date(inspection.check_date) : new Date());
+                    return;
+                }
+                // 判断日期格式
+                const check_date = moment(date).format('YYYY-MM-DD');
+                const reg = /^\d{4}-\d{2}-\d{2}$/;
+                if (!reg.test(check_date)) {
+                    toastr.error('检查日期格式错误,应为YYYY-MM-DD。');
+                    return;
+                }
+                if (check_date !== moment(inspection.check_date).format('YYYY-MM-DD')) {
+                    updateInspection('check_date', check_date);
+                }
+
+            }
+        }).data('datepicker');
+        checkDate.selectDate(inspection.check_date ? new Date(inspection.check_date) : new Date());
+
+        $('#check_table textarea').on('change', function (e) {
+            const value = $(this).val().trim();
+            const key = $(this).data('key');
+            if (value !== inspection[key]) {
+                updateInspection(key, value);
+            }
+        });
+
+        $("#check_table input").on('change', function (e) {
+            const value = $(this).val().trim();
+            const key = $(this).data('key');
+            if (key === 'check_date') {
+                if (!value && inspection.check_date) {
+                    toastr.error('检查日期不能为空');
+                    checkDate.selectDate(inspection.check_date ? new Date(inspection.check_date) : new Date());
+                    return;
+                }
+                // 判断日期格式
+                const reg = /^\d{4}-\d{2}-\d{2}$/;
+                if (!reg.test(value)) {
+                    toastr.error('检查日期格式错误,应为YYYY-MM-DD。');
+                    checkDate.selectDate(inspection.check_date ? new Date(inspection.check_date) : new Date());
+                    return;
+                }
+            }
+            if (value !== inspection[key]) {
+                updateInspection(key, value);
+            }
+        });
+
+        $('#check_table dl').on('click', 'dd', function () {
+            const id = parseInt($(this).data('id'))
+            if (id !== 0) {
+                const user = _.find(accountList, { id });
+                $('#inspector-set').html(`<span class="badge">
+                          ${user.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="btn btn-sm btn-danger remove-btn">移除</button>
+                                <button class="btn btn-sm btn-secondary">取消</button>
+                              </div>
+                            </div>
+                          </span>
+                          </span>`);
+                $('#inspector-set').siblings('.dropdown').attr('style', 'display:none!important;');
+                updateInspection('inspector', user.name);
+            }
+        });
+
+        $('body').on('click', '#check_table .remove-btn', function () {
+            updateInspection('inspector', '');
+            $('#inspector-set').html('');
+            $('#inspector-set').siblings('.dropdown').show();
+        });
+
+        function updateInspection(field, value) {
+            const data = {
+                id: inspection.id,
+            };
+            data[field] = value;
+            postData(`${preUrl}/save`, {type: 'update-field', update: data}, function (result) {
+                inspection[field] = value;
+                if (field === 'check_date') {
+                    checkDate.selectDate(inspection.check_date ? new Date(inspection.check_date) : new Date());
+                }
+            }, function () {
+                if (field === 'check_date') {
+                    checkDate.selectDate(inspection.check_date ? new Date(inspection.check_date) : new Date());
+                } else {
+                    $(`#check_table textarea[data-key=${field}]`).val(inspection[field] || '');
+                }
+            });
+        }
+
+        // 添加到审批流程中
+        $('#shenpi_select dl').on('click', 'dd', function () {
+            const id = parseInt($(this).data('id'))
+            if (id !== 0) {
+                postData(preUrl + '/save', {type: 'add-audit', auditorId: id}, (datas) => {
+                    // <p class="m-0 ml-2"><small class="text-muted">中交第一公路工程局有限公司国道311线满别公路施工一分部</small></p>
+                    const html = [];
+                    // 如果是重新上报,添加到重新上报列表中
+                    const auditorshtml = [];
+                    for (const [index, data] of datas.entries()) {
+                        if (index !== 0) {
+                            html.push('<li class="list-group-item d-flex" auditorId="' + data[0].aid + '">');
+                            html.push(`<div class="col-auto">${index}</div>`);
+                            html.push('<div class="col">');
+                            for (const auditor of data) {
+                                html.push(`<div class="d-inline-block mx-1"><i class="fa fa-user text-muted"></i> ${auditor.name} <small class="text-muted">${auditor.role}</small></div>`);
+                            }
+                            html.push('</div>');
+                            html.push('<div class="col-auto">');
+                            if (data[0].audit_type !== auditType.key.common) {
+                                html.push(`<span class="badge badge-pill badge-${auditType.info[data[0].audit_type].class} badge-bg-small"><small>${auditType.info[data[0].audit_type].long}</small></span>`);
+                            }
+                            if (shenpi_status === shenpiConst.sp_status.sqspr || (shenpi_status === shenpiConst.sp_status.gdzs && index + 1 !== datas.length)) {
+                                html.push('<a href="javascript: void(0)" class="text-danger pull-right ml-1">移除</a>');
+                            }
+                            html.push('</div>');
+                            html.push('</li>');
+                        }
+                        // 添加新审批人流程修改
+                        auditorshtml.push('<li class="list-group-item d-flex justify-content-between align-items-center" data-auditorid="' + data[0].aid + '">');
+                        auditorshtml.push('<span class="mr-1"><i class="fa ' + (index === 0 ? 'fa-play-circle fa-rotate-90' : index + 1 === datas.length ? 'fa-stop-circle' : 'fa-chevron-circle-down') + '"></i></span>');
+                        auditorshtml.push('<span class="text-muted">');
+                        for (const auditor of data) {
+                            auditorshtml.push(`<small class="d-inline-block text-dark mx-1" title="${auditor.role}" data-auditorId="${auditor.uid}">${auditor.name}</small>`);
+                        }
+                        auditorshtml.push('</span>');
+                        auditorshtml.push('<div class="d-flex ml-auto">');
+                        if (data[0].audit_type !== auditType.key.common) {
+                            auditorshtml.push(`<span class="badge badge-pill badge-${auditType.info[data[0].audit_type].class} p-1"><small>${auditType.info[data[0].audit_type].short}</small></span>`);
+                        }
+                        if (index === 0) {
+                            auditorshtml.push('<span class="badge badge-light badge-pill ml-auto"><small>原报</small></span>');
+                        } else if (index + 1 === datas.length) {
+                            auditorshtml.push('<span class="badge badge-light badge-pill"><small>终审</small></span>');
+                        } else {
+                            auditorshtml.push('<span class="badge badge-light badge-pill"><small>' + transFormToChinese(index) + '审</small></span>');
+                        }
+                    }
+                    $('#auditors').html(html.join(''));
+                    $('#auditors2').html(auditorshtml.join(''));
+                });
+            }
+        });
+
+        // 删除审批人
+        $('body').on('click', '#auditors li a', function () {
+            const li = $(this).parents('li');
+            const data = {
+                type: 'del-audit',
+                auditorId: parseInt(li.attr('auditorId')),
+            };
+            postData(preUrl + '/save', data, (result) => {
+                li.remove();
+                for (const rst of result) {
+                    const aLi = $('li[auditorid=' + rst.aid + ']');
+                    $('div:first', aLi).text(rst.order);
+                }
+                // 删除左边审核人
+                $(`#auditors2 li[data-auditorid='${data.auditorId}']`).remove();
+                if ($('#auditors2 li').length !== 0 && !$('#auditors-list li i').hasClass('fa-stop-circle')) {
+                    console.log($('#auditors2 li').length - 1, $('#auditors2 li').eq($('#auditors2 li').length - 1).find('i'));
+                    $('#auditors2 li').eq($('#auditors2 li').length - 1).find('i')
+                        .removeClass('fa-chevron-circle-down').addClass('fa-stop-circle');
+                }
+                for (let i = 0; i < $('#auditors2 li').length; i++) {
+                    $('#auditors2 li').eq(i).find('.badge-pill').children('small').text(i === 0 ? '原报' : (i + 1 === $('#auditors2 li').length ? '终' : transFormToChinese(i)) + '审');
+                }
+            })
+        });
+
+        $('#del-inspection-btn').click(function() {
+            const text = $('#del-inspection-text').val().trim();
+            if (text.length === 0 || text !== '确认删除本次巡检') {
+                toastr.error('请正确输入“确认删除本次巡检”');
+                return;
+            }
+            postData(preUrl + '/save', {type: 'del-inspection' }, function (result) {
+                let link = `/sp/${spid}/quality/tender/${tender_id}/inspection`;
+                let orderSetting = getLocalCache('quality-inspection-'+ tender_id +'-list-order');
+                if (!orderSetting) orderSetting = 'time|desc';
+                const orders = orderSetting.split('|');
+                const filterData = [];
+                filterData.push('sort='+ orders[0]);
+                filterData.push('order=' + orders[1]);
+                if (getLocalCache('account-pageSize')) {
+                    filterData.push('pageSize=' + getLocalCache('account-pageSize'));
+                }
+                if (filterData.length > 0) {
+                    link += '?' + filterData.join('&');
+                }
+                window.location.href = link;
+            });
+        });
+
+        $('#judge-start-btn').click(function () {
+            const flag = !(inspection.code && inspection.check_item && inspection.check_date);
+            if (flag) {
+                toastr.warning('请完善巡检信息再提交');
+                return;
+            }
+            if ($('#auditors li').length === 0) {
+                if(shenpi_status === shenpiConst.sp_status.gdspl) {
+                    toastr.error('请联系管理员添加审批人');
+                } else {
+                    toastr.error('请先选择审批人,再上报数据');
+                }
+                return false;
+            }
+            $('#sp-done').modal('show');
+        });
+
+        $('#start-btn').click(function () {
+            $('#start-btn').prop('disabled', true);
+            postData(preUrl + '/save', { type: 'start-inspection' }, function (result) {
+                window.location.reload();
+            });
+        });
+    } else if (inspection.shenpiPower) {
+        // 添加到审批流程中
+        $('dl').on('click', 'dd', function () {
+            const id = parseInt($(this).data('id'))
+            if (id !== 0) {
+                const user = _.find(accountList, { id });
+                $('#rectification-user-set').html(`<span class="badge">
+                              ${user.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="btn btn-sm btn-danger remove-btn">移除</button>
+                                    <button class="btn btn-sm btn-secondary">取消</button>
+                                  </div>
+                                </div>
+                              </span>
+                              </span>`);
+                $('#rectification-uid').val(user.id);
+                $('#rectification-user-set').siblings('.dropdown').attr('style', 'display:none!important;');
+            }
+        });
+
+        // 删除审批人
+        $('body').on('click', '#rectification-user-set .remove-btn', function () {
+            $('#rectification-user-set').html('');
+            $('#rectification-uid').val('');
+            $('#rectification-user-set').siblings('.dropdown').show();
+        });
+
+        $('#approval-success-btn').click(function () {
+            if (inspection.finalAuditorIds.indexOf(cur_uid) !== -1 && $('#rectification-uid').val() === '') {
+                toastr.warning('请选择整改人');
+                return;
+            }
+            const opinion = $('#sp-done').find('textarea[name="opinion"]').eq(0).val().replace(/\r\n/g, '<br/>').replace(/\n/g, '<br/>').replace(/\s/g, ' ');
+            if (opinion.length === 0) {
+                toastr.warning('请填写审核意见');
+                return;
+            }
+            const data = {
+                type: 'check',
+                checkType: auditConst.status.checked,
+                opinion,
+                rectification_uid: $('#rectification-uid').val(),
+            }
+            postData(preUrl + '/save', data, function (result) {
+                window.location.reload();
+            });
+        });
+
+        $('#approval-back-btn').click(function () {
+            console.log($('#sp-back').find('textarea[name="opinion"]').eq(0).val());
+            const opinion = $('#sp-back').find('textarea[name="opinion"]').eq(0).val().replace(/\r\n/g, '<br/>').replace(/\n/g, '<br/>').replace(/\s/g, ' ');
+            if (opinion.length === 0) {
+                toastr.warning('请填写审核意见');
+                return;
+            }
+            const inlineRadio1 = $('#inlineRadio1:checked').val();
+            const inlineRadio2 = $('#inlineRadio2:checked').val();
+            if (!inlineRadio1 && !inlineRadio2) {
+                if (!$('#warning-text').length) {
+                    $('#reject-process').prepend('<p id="warning-text" style="color: red; margin: 0;">请选择退回流程</p>');
+                }
+                return;
+            }
+            if ($('#warning-text').length) $('#warning-text').remove()
+            const data = {
+                type: 'check',
+                checkType: parseInt(inlineRadio1 ? inlineRadio1 : inlineRadio2),
+                opinion,
+            }
+            postData(preUrl + '/save', data, function (result) {
+                window.location.reload();
+            });
+        });
+
+        $('#approval-stop-btn').click(function () {
+            const opinion = $('#sp-close').find('textarea[name="opinion"]').eq(0).val().replace(/\r\n/g, '<br/>').replace(/\n/g, '<br/>').replace(/\s/g, ' ');
+            if (opinion.length === 0) {
+                toastr.warning('请填写关闭原因');
+                return;
+            }
+            const data = {
+                type: 'check',
+                checkType: auditConst.status.checkStop,
+                opinion,
+            }
+            postData(preUrl + '/save', data, function (result) {
+                window.location.reload();
+            });
+        });
+    } else if (inspection.rectificationPower) {
+        $('#judge-success-btn').click(function () {
+            const flag = !(inspection.rectification_item && inspection.rectification_date);
+            if (flag) {
+                toastr.warning('请完善整改单再提交');
+                return;
+            }
+            $('#sp-done').modal('show');
+        });
+        // 整改完成
+        $('#rectification-success-btn').click(function () {
+            const opinion = $('#sp-done').find('textarea[name="opinion"]').eq(0).val().replace(/\r\n/g, '<br/>').replace(/\n/g, '<br/>').replace(/\s/g, ' ');
+            if (opinion.length === 0) {
+                toastr.warning('请填写审核意见');
+                return;
+            }
+            const data = {
+                type: 'rectification',
+                checkType: auditConst.status.checked,
+                opinion,
+            }
+            postData(preUrl + '/save', data, function (result) {
+                window.location.reload();
+            });
+        });
+
+        $('#rectification-back-btn').click(function () {
+            const opinion = $('#sp-back').find('textarea[name="opinion"]').eq(0).val().replace(/\r\n/g, '<br/>').replace(/\n/g, '<br/>').replace(/\s/g, ' ');
+            if (opinion.length === 0) {
+                toastr.warning('请填写审核意见');
+                return;
+            }
+            const data = {
+                type: 'rectification',
+                checkType: auditConst.status.checkNoPre,
+                opinion,
+            }
+            postData(preUrl + '/save', data, function (result) {
+                window.location.reload();
+            });
+        });
+
+        const rectificationDate = $('#rectification_date').datepicker({
+            autoClose: true,
+            onSelect: function (formattedDate, date, inst) {
+                if (!date && inspection.rectification_date) {
+                    toastr.error('检查日期不能为空');
+                    rectificationDate.selectDate(inspection.rectification_date ? new Date(inspection.rectification_date) : '');
+                    return;
+                }
+                // 判断日期格式
+                const rectification_date = moment(date).format('YYYY-MM-DD');
+                const reg = /^\d{4}-\d{2}-\d{2}$/;
+                if (!reg.test(rectification_date)) {
+                    toastr.error('整改日期格式错误,应为YYYY-MM-DD。');
+                    return;
+                }
+                if (rectification_date !== moment(inspection.rectification_date).format('YYYY-MM-DD')) {
+                    updateInspection('rectification_date', rectification_date);
+                }
+
+            }
+        }).data('datepicker');
+        rectificationDate.selectDate(inspection.rectification_date ? new Date(inspection.rectification_date) : '');
+
+        $('#rectification_table textarea').on('change', function (e) {
+            const value = $(this).val().trim();
+            const key = $(this).data('key');
+            if (value !== inspection[key]) {
+                updateInspection(key, value);
+            }
+        });
+
+        $("#rectification_table input").on('change', function (e) {
+            const value = $(this).val().trim();
+            const key = $(this).data('key');
+            if (key === 'check_date') {
+                if (!value && inspection.rectification_date) {
+                    toastr.error('检查日期不能为空');
+                    rectificationDate.selectDate(inspection.rectification_date ? new Date(inspection.rectification_date) : '');
+                    return;
+                }
+                // 判断日期格式
+                const reg = /^\d{4}-\d{2}-\d{2}$/;
+                if (!reg.test(value)) {
+                    toastr.error('检查日期格式错误,应为YYYY-MM-DD。');
+                    rectificationDate.selectDate(inspection.rectification_date ? new Date(inspection.rectification_date) : '');
+                    return;
+                }
+            }
+            if (value !== inspection[key]) {
+                updateInspection(key, value);
+            }
+        });
+
+        function updateInspection(field, value) {
+            const data = {
+                id: inspection.id,
+            };
+            data[field] = value;
+            console.log(data);
+            postData(`${preUrl}/save`, {type: 'update-field', update: data}, function (result) {
+                inspection[field] = value;
+                if (field === 'rectification_date') {
+                    rectificationDate.selectDate(inspection.rectification_date ? new Date(inspection.rectification_date) : '');
+                }
+            }, function () {
+                if (field === 'rectification_date') {
+                    rectificationDate.selectDate(inspection.rectification_date ? new Date(inspection.rectification_date) : '');
+                } else {
+                    $(`#rectification_table textarea[data-key=${field}]`).val(inspection[field] || '');
+                }
+            });
+        }
+    }
+
+    handleFileList(fileList);
+
+    $('#file-ok').click(function () {
+        const files = Array.from($('#file-modal')[0].files)
+        const valiData = files.map(v => {
+            const ext = v.name.substring(v.name.lastIndexOf('.') + 1)
+            return {
+                size: v.size,
+                ext
+            }
+        });
+        if (validateFiles(valiData)) {
+            if (files.length) {
+                const formData = new FormData();
+                files.forEach(file => {
+                    formData.append('name', file.name);
+                    formData.append('size', file.size);
+                    formData.append('file', file);
+                })
+                postDataWithFile(`${preUrl}/file/upload`, formData, function (result) {
+                    handleFileList(result);
+                    $('#file-modal').val('');
+                    $('#file-cancel').click();
+                });
+            }
+        }
+    })
+    function handleFileList(files = []) {
+        $('#file-content').empty();
+        const newFiles = files.map(file => {
+            let showDel = false;
+            if (file.uid === cur_uid) {
+                if (inspection.status === auditConst.status.checked) {
+                    showDel = Boolean(file.extra_upload ) || deleteFilePermission
+                } else {
+                    showDel = true
+                }
+            }
+            return {...file, showDel}
+        })
+        let html = inspection.filePermission ? `<tr><td colspan="5"><a href="#addfujian" data-toggle="modal" class="btn btn-sm btn-light text-primary" data-placement="bottom" title="" data-original-title="上传附件"><i class="fa fa-cloud-upload" aria-hidden="true"></i> 上传附件</a></td></tr>` : '';
+        newFiles.forEach((file, idx) => {
+            if (file.showDel) {
+                html += `<tr><td>${idx + 1}</td><td><a href="${file.filepath}" target="_blank">${file.filename}</a></td><td>${file.username}</td><td>${moment(file.upload_time).format('YYYY-MM-DD HH:mm:ss')}</td><td><a href="${preUrl}/file/${file.id}/download" class="mr-2"><i class="fa fa-download"></i></a><a href="javascript: void(0);" class="text-danger file-del" data-id="${file.id}"><i class="fa fa-remove"></i></a></td></tr>`
+            } else {
+                html += `<tr><td width="70">${idx + 1}</td><td><a href="${file.filepath}" target="_blank">${file.filename}</a></td><td>${file.username}</td><td>${moment(file.upload_time).format('YYYY-MM-DD HH:mm:ss')}</td><td><a href="${preUrl}/file/${file.id}/download" class="mr-2"><i class="fa fa-download"></i></a></td></tr>`
+            }
+        })
+        $('#file-content').append(html);
+    }
+
+    $('#file-content').on('click', 'a', function () {
+        if ($(this).hasClass('file-del')) {
+            const id = $(this).data('id');
+            postData(`${preUrl}/file/delete`, {id}, (result) => {
+                handleFileList(result);
+            })
+        }
+    });
+})
+
+
+/**
+ * 校验文件大小、格式
+ * @param {Array} files 文件数组
+ */
+function validateFiles(files) {
+    if (files.length > 10) {
+        toastr.error('至多同时上传10个文件');
+        return false
+    }
+    return files.every(file => {
+        if (file.size > 1024 * 1024 * 50) {
+            toastr.error('文件大小限制为50MB');
+            return false
+        }
+        if (whiteList.indexOf('.' + file.ext) === -1) {
+            toastr.error('请上传正确的格式文件');
+            return false
+        }
+        return true
+    })
+}
+

+ 2 - 2
app/public/js/quality_tender.js

@@ -53,6 +53,6 @@ $(document).ready(() => {
     const memberPermission = MemberPermission();
     $('.member-manage').click(function(){
         const tid = this.getAttribute('data-tid');
-        memberPermission.show({ data: { tid }, loadUrl: '/quality/member', saveUrl: '/quality/memberSave'});
+        memberPermission.show({ data: { tid }, loadUrl: `/sp/${spid}/quality/member`, saveUrl: `/sp/${spid}/quality/memberSave`});
     });
-});
+});

+ 82 - 3
app/public/js/setting_manage.js

@@ -163,7 +163,6 @@ function getFilterTenderTreeHtml() {
 }
 
 $(document).ready(() => {
-    console.log('hello');
     autoFlashHeight();
     function getObjHeight(select) {
         return select.length > 0 ? select.height() : 0;
@@ -176,8 +175,9 @@ $(document).ready(() => {
         schedule: '投资进度',
         contract: '合同管理',
         construction: '施工日志',
+        quality: '质量管理',
     };
-    const tabTypeKeys = ['tourist', 'schedule', 'contract', 'construction'];
+    const tabTypeKeys = ['tourist', 'schedule', 'contract', 'construction', 'quality'];
 
     $('body').on('click', '.c-body .tender-info', function () {
         $('.c-body .tender-info').removeClass('table-warning');
@@ -200,6 +200,7 @@ $(document).ready(() => {
             setScheduleHtml(result.scheduleAuditList);
             setContractHtml(result.contractAuditList);
             setConstructionHtml(result.constructionAuditList);
+            setQualityHtml(result.qualityAuditList);
             resetAddUserHtml();
         });
     });
@@ -224,6 +225,8 @@ $(document).ready(() => {
                 $('#contract-tip').show();
             } else if ($(this).attr('href') === '#sgrz') {
                 $('#add_user_dropdownMenuButton').attr('data-type', 'construction');
+            } else if ($(this).attr('href') === '#zlgl') {
+                $('#add_user_dropdownMenuButton').attr('data-type', 'quality');
             }
         }
     });
@@ -305,6 +308,28 @@ $(document).ready(() => {
         });
     });
 
+    // 权限更改
+    $('body').on('click', '#quality-users input[type="checkbox"]', function () {
+        const uid = parseInt($(this).parents('tr').data('uid'));
+        const member = {
+            uid,
+            quality: [1],
+        };
+        $(this).parents('tr').find('input[type="checkbox"]').each(function () {
+            if ($(this).is(':checked')) {
+                member.quality.push($(this).data('value'));
+            }
+        });
+        const prop = {
+            type: 'save-permission',
+            uid,
+            members: [member],
+        };
+        const _self = $(this);
+        postData('/sp/' + spid + '/quality/' + cur_tenderid + '/audit/save', prop, function (data) {
+        });
+    });
+
     for (const key of tabTypeKeys) {
         $('body').on('click', `#${key}-users .remove-${key}-user`, function () {
             $('#remove_user_type').val(key);
@@ -326,10 +351,15 @@ $(document).ready(() => {
                 $('#remove-user').modal('hide');
             });
         } else if (type === 'construction') {
-            postData('/sp/' + spid + '/construction/' + cur_tenderid + '/audit/save', { type: 'del-audit', id }, function (data) {
+            postData('/sp/' + spid + '/' + type + '/' + cur_tenderid + '/audit/save', { type: 'del-audit', id }, function (data) {
                 $('#'+ type + '-users').find('tr[data-id="'+ id +'"]').remove();
                 $('#remove-user').modal('hide');
             });
+        } else if (type === 'quality') {
+            postData('/sp/' + spid + '/' + type + '/' + cur_tenderid + '/audit/save', { type: 'del-audit', id }, function (data) {
+                $('#'+ type + '-users').find('tr[data-uid="'+ id +'"]').remove();
+                $('#remove-user').modal('hide');
+            });
         } else {
             const prop = {
                 id: id,
@@ -534,6 +564,26 @@ $(document).ready(() => {
                 postData('/sp/' + spid + '/construction/' + cur_tenderid + '/audit/save', prop, function (datas) {
                     setConstructionHtml(datas);
                 });
+            } else if (type === 'quality') {
+                const user = _.find(accountList, function (item) {
+                    return item.id === id;
+                });
+                const saIdList = [];
+                for (let i = 0; i < $('#quality-users tr').length; i++) {
+                    saIdList.push(parseInt($('#quality-users tr').eq(i).data('uid')));
+                }
+                if (_.includes(saIdList, id)) {
+                    toastr.error('该用户已存在列表中,无需重复添加');
+                    return;
+                }
+
+                const prop = {
+                    id: id,
+                    type: 'add-audit',
+                };
+                postData('/sp/' + spid + '/quality/' + cur_tenderid + '/audit/save', prop, function (datas) {
+                    setQualityHtml(datas);
+                });
             }
         }
     });
@@ -682,6 +732,11 @@ $(document).ready(() => {
                 }
             } else if (userType === 'construction') {
                 userData.is_report = $('#construction-users tr').eq(i).find('input[type="checkbox"]').eq(0).is(':checked') ? 1 : 0;
+            } else if (userType === 'quality') {
+                userData.member = [1];
+                if ($('#quality-users tr').eq(i).find('input[data-block="upload"]').eq(0).is(':checked')) userData.member.push(2);
+                if ($('#quality-users tr').eq(i).find('input[data-block="add"]').eq(0).is(':checked')) userData.member.push(3);
+                if ($('#quality-users tr').eq(i).find('input[data-block="add_inspection"]').eq(0).is(':checked')) userData.member.push(4);
             }
             saIdList.push(userData);
         }
@@ -1018,3 +1073,27 @@ function setConstructionHtml(constructionAuditList) {
     }
     $('#construction-users').html(html);
 }
+
+function setQualityHtml(qualityAuditList) {
+    const html = [];
+    for (const m of qualityAuditList) {
+        html.push(getUserPermissionHtml(m));
+    }
+    $('#quality-users').html(html.join(''));
+}
+
+const getUserPermissionHtml = function(user) {
+    const html = [];
+    html.push(`<tr data-uid="${user.uid}" data-id="${user.id}">`);
+    html.push(`<td>${user.name}</td>`, `<td>${user.role}</td>`);
+    for (const block of permissionBlock) {
+        for (const p of block.permission) {
+            if (p.isDefault) continue;
+            const checked = user[block.key] ? (user[block.key].indexOf(p.value) >= 0 ? 'checked' : '') : '';
+            html.push(`<td class="text-center"><input type="checkbox" data-block="${p.key}" data-value="${p.value}" ${checked}></td>`);
+        }
+    }
+    html.push(`<td class="text-center"><a href="#remove-user1" data-id="${user.uid}" data-toggle="modal" data-target="#remove-user" class="btn btn-sm btn-outline-danger remove-quality-user">移除</a></td>`);
+    html.push('</tr>');
+    return html.join('');
+};

+ 11 - 1
app/router.js

@@ -59,6 +59,8 @@ module.exports = app => {
     const financialCheck = app.middlewares.financialCheck();
     const financialPayCheck = app.middlewares.financialPayCheck();
     const financialPayAuditCheck = app.middlewares.financialPayAuditCheck();
+    // 质量巡检中间件
+    const inspectionCheck = app.middlewares.inspectionCheck();
     // 登入登出相关
     app.get('/login', 'loginController.index');
     app.get('/login/:code', 'loginController.index');
@@ -468,6 +470,7 @@ module.exports = app => {
     app.get('/sp/:id/quality/tender', sessionAuth, subProjectCheck, 'qualityController.tender');
     app.post('/sp/:id/quality/member', sessionAuth, subProjectCheck, projectManagerCheck, 'qualityController.member');
     app.post('/sp/:id/quality/memberSave', sessionAuth, subProjectCheck, projectManagerCheck, 'qualityController.memberSave');
+    app.post('/sp/:id/quality/:tid/audit/save', sessionAuth, subProjectCheck, 'qualityController.auditSave');
     app.get('/sp/:id/quality/tender/:tid/info', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, 'qualityController.info');
     app.get('/sp/:id/quality/tender/:tid/flaw', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, 'qualityController.flaw');
     app.get('/sp/:id/quality/tender/:tid/lab', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, 'qualityController.lab');
@@ -480,6 +483,13 @@ module.exports = app => {
     app.get('/sp/:id/quality/rule', sessionAuth, subProjectCheck, projectManagerCheck, 'qualityController.rule');
     app.post('/sp/:id/quality/rule/save', sessionAuth, subProjectCheck, projectManagerCheck, 'qualityController.ruleSave');
     app.post('/sp/:id/quality/tender/:tid/rule/save', sessionAuth, subProjectCheck, tenderCheck, projectManagerCheck, 'qualityController.ruleSave');
+    app.get('/sp/:id/quality/tender/:tid/inspection', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, 'qualityController.inspection');
+    app.post('/sp/:id/quality/tender/:tid/inspection/save', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, 'qualityController.inspectionSave');
+    app.get('/sp/:id/quality/tender/:tid/inspection/:qiid/information', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, inspectionCheck, 'qualityController.inspectionInformation');
+    app.post('/sp/:id/quality/tender/:tid/inspection/:qiid/information/save', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, inspectionCheck, 'qualityController.inspectionInformationSave');
+    app.post('/sp/:id/quality/tender/:tid/inspection/:qiid/information/file/upload', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, inspectionCheck, 'qualityController.uploadInspectionFile');
+    app.post('/sp/:id/quality/tender/:tid/inspection/:qiid/information/file/delete', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, inspectionCheck, 'qualityController.deleteInspectionFile');
+    app.get('/sp/:id/quality/tender/:tid/inspection/:qiid/information/file/:fid/download', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, inspectionCheck, 'qualityController.downloadInspectionFile');
 
     // ------------------------- 项目内部相关 -----------------------------
 
@@ -879,7 +889,7 @@ module.exports = app => {
     app.get('/tender/:id/change', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, 'changeController.index');
     app.get('/tender/:id/change/status/:status', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, 'changeController.status');
     app.post('/tender/:id/change/auditors', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, 'changeController.changeAuditors');
-    app.post('/tender/:id/change/newCode', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, 'changeController.newCode');
+    app.post('/tender/:id/change/newCode', sessionAuth, tenderCheck, subProjectCheck, 'changeController.newCode');
     app.post('/tender/:id/change/add', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, tenderBuildCheck, 'changeController.add');
     app.post('/tender/:id/change/defaultBills', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, 'changeController.defaultBills');
     app.post('/tender/:id/change/:cid/info/file/upload', sessionAuth, 'changeController.uploadFile');

+ 315 - 0
app/service/quality_inspection.js

@@ -0,0 +1,315 @@
+'use strict';
+
+/**
+ * 质量管理 - 巡检单
+ *
+ * @author Mai
+ * @date 2024/7/22
+ * @version
+ */
+const auditConst = require('../const/audit').inspection;
+const auditType = require('../const/audit').auditType;
+module.exports = app => {
+
+    class QualityInspection extends app.BaseService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'quality_inspection';
+        }
+
+        async loadUser(inspection) {
+            const status = auditConst.status;
+            const accountId = this.ctx.session.sessionUser.accountId;
+
+            inspection.user = await this.ctx.service.projectAccount.getAccountInfoById(inspection.uid);
+            if (inspection.rectification_uid) {
+                inspection.rectification_user = await this.ctx.service.projectAccount.getAccountInfoById(inspection.rectification_uid);
+            }
+            inspection.auditors = await this.ctx.service.qualityInspectionAudit.getAuditors(inspection.id, inspection.times); // 全部参与的审批人
+            inspection.auditorIds = this._.map(inspection.auditors, 'aid');
+            inspection.curAuditors = inspection.auditors.filter(x => { return x.status === status.checking || x.status === status.rectification; }); // 当前流程中审批中的审批人
+            inspection.curAuditorIds = this._.map(inspection.curAuditors, 'aid');
+            inspection.flowAuditors = inspection.curAuditors.length > 0 ? inspection.auditors.filter(x => { return x.order === inspection.curAuditors[0].order; }) : []; // 当前流程中参与的审批人(包含会签时,审批通过的人)
+            inspection.flowAuditorIds = this._.map(inspection.flowAuditors, 'aid');
+            inspection.nextAuditors = inspection.curAuditors.length > 0 ? inspection.auditors.filter(x => { return x.order === inspection.curAuditors[0].order + 1; }) : [];
+            inspection.nextAuditorIds = this._.map(inspection.nextAuditors, 'aid');
+            const newAuditors = inspection.auditors.filter(x => { return x.is_old === 0 && x.is_rectification === 0; });
+            inspection.auditorGroups = this.ctx.helper.groupAuditors(newAuditors);
+            inspection.userGroups = this.ctx.helper.groupAuditorsUniq(inspection.auditorGroups);
+            inspection.userGroups.unshift([{
+                aid: inspection.user.id, order: 0, times: inspection.times, audit_order: 0, audit_type: auditType.key.common,
+                name: inspection.user.name, role: inspection.user.role, company: inspection.user.company,
+            }]);
+            inspection.finalAuditorIds = inspection.userGroups[inspection.userGroups.length - 1].map(x => { return x.aid; });
+        }
+
+        async loadAuditViewData(inspection) {
+            const times = inspection.status === auditConst.status.checkNo ? inspection.times - 1 : inspection.times;
+
+            if (!inspection.user) inspection.user = await this.ctx.service.projectAccount.getAccountInfoById(inspection.uid);
+            inspection.auditHistory = await this.ctx.service.qualityInspectionAudit.getAuditorHistory(inspection.id, times);
+            // 获取审批流程中左边列表
+            if (inspection.status === auditConst.status.checkNo && inspection.uid !== this.ctx.session.sessionUser.accountId) {
+                const auditors = await this.ctx.service.qualityInspectionAudit.getAuditors(inspection.id, times); // 全部参与的审批人
+                const newAuditors = auditors.filter(x => { return x.is_old === 0 && x.is_rectification === 0; });
+                const auditorGroups = this.ctx.helper.groupAuditors(newAuditors);
+                inspection.auditors2 = this.ctx.helper.groupAuditorsUniq(auditorGroups);
+                inspection.auditors2.unshift([{
+                    aid: inspection.user.id, order: 0, times: inspection.times - 1, audit_order: 0, audit_type: auditType.key.common,
+                    name: inspection.user.name, role: inspection.user.role, company: inspection.user.company,
+                }]);
+            } else {
+                inspection.auditors2 = inspection.userGroups;
+            }
+            if (inspection.status === auditConst.status.uncheck || inspection.status === auditConst.status.checkNo) {
+                inspection.auditorList = await this.ctx.service.qualityInspectionAudit.getAuditors(inspection.id, inspection.times);
+            }
+        }
+
+        async add(tenderId, userId, code, check_item, check_date) {
+            const sql = 'SELECT COUNT(*) as count FROM ?? WHERE `tid` = ? AND `code` = ?';
+            const sqlParam = [this.tableName, tenderId, code];
+            const codeCount = await this.db.queryOne(sql, sqlParam);
+            const count = codeCount.count;
+            if (count > 0) {
+                throw '编号重复';
+            }
+
+            // 初始化事务
+            const transaction = await this.db.beginTransaction();
+            let result = false;
+            try {
+                const inspection = {
+                    tid: tenderId,
+                    uid: userId,
+                    status: auditConst.status.uncheck,
+                    times: 1,
+                    code,
+                    check_item,
+                    check_date,
+                    inspector: this.ctx.session.sessionUser.name,
+                    create_time: new Date(),
+                };
+                const operate = await transaction.insert(this.tableName, inspection);
+
+                if (operate.affectedRows <= 0) {
+                    throw '新建质量巡检数据失败';
+                }
+                inspection.id = operate.insertId;
+                // 先找出标段最近存在的变更令审批人的变更令info
+                const preChangeInfo = await this.getHaveAuditLastInfo(tenderId);
+                if (preChangeInfo) {
+                    // 并把之前存在的变更令审批人添加到zh_change_audit
+                    const auditResult = await this.ctx.service.qualityInspectionAudit.copyPreAuditors(transaction, preChangeInfo, inspection);
+                    if (!auditResult) {
+                        throw '复制上一次审批流程失败';
+                    }
+                }
+                result = inspection;
+                await transaction.commit();
+            } catch (error) {
+                console.log(error);
+                // 回滚
+                await transaction.rollback();
+            }
+            return result;
+        }
+
+        async delInspection(id) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 删除预付款记录
+                await transaction.delete(this.tableName, { id });
+                // 删除附件
+                const fileInfo = await this.db.select(this.ctx.service.qualityInspectionAtt.tableName, { where: { qiid: id } });
+                await transaction.delete(this.ctx.service.qualityInspectionAtt.tableName, { qiid: id });
+                await this.ctx.helper.delFiles(fileInfo);
+                // 先删除文件
+                // for (let i = 0; i < fileInfo.length; i++) {
+                //     const file = fileInfo[i];
+                //     if (fs.existsSync(path.resolve(this.app.baseDir, './app', file.filepath))) {
+                //         fs.unlinkSync(path.resolve(this.app.baseDir, './app', file.filepath));
+                //         // fs.unlinkSync(path.resolve(this.app.baseDir, zipPath));
+                //     }
+                //     await this.ctx.app.fujianOss.delete(file.filepath);
+                // }
+                // 删除审批记录
+                await transaction.delete(this.ctx.service.qualityInspectionAudit.tableName, { qiid: id });
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        async getHaveAuditLastInfo(tenderId) {
+            const sql = 'SELECT a.* FROM ?? as a LEFT JOIN ?? as b ON a.`id` = b.`qiid` WHERE a.`tid` = ? ORDER BY a.`create_time` DESC';
+            const sqlParam = [this.tableName, this.ctx.service.qualityInspectionAudit.tableName, tenderId];
+            return await this.db.queryOne(sql, sqlParam);
+        }
+
+        async getListByStatus(tid, status, hadlimit = 0, sortBy = '', orderBy = '') {
+            let sql = '';
+            let sqlParam = '';
+            if ((this.ctx.tender.isTourist || this.ctx.session.sessionUser.is_admin) && status === 0) {
+                sql = 'SELECT a.* FROM ?? As a WHERE a.tid = ?';
+                sqlParam = [this.tableName, tid];
+            } else {
+                switch (status) {
+                    case 0: // 所有
+                        sql =
+                            'SELECT a.* FROM ?? AS a WHERE a.tid = ? AND' +
+                            ' (a.uid = ? OR (a.status != ? AND a.id IN (SELECT b.qiid FROM ?? AS b WHERE b.aid = ? GROUP BY b.qiid))' +
+                            ' OR a.status = ?)';
+                        sqlParam = [
+                            this.tableName,
+                            tid,
+                            this.ctx.session.sessionUser.accountId,
+                            auditConst.status.uncheck,
+                            this.ctx.service.qualityInspectionAudit.tableName,
+                            this.ctx.session.sessionUser.accountId,
+                            auditConst.status.checked,
+                        ];
+                        break;
+                    case auditConst.filter.status.pending: // 待处理(你的)
+                        sql = 'SELECT a.* FROM ?? as a WHERE a.tid = ? AND (a.id in(SELECT b.qiid FROM ?? as b WHERE b.tid = ? AND b.aid = ? AND (b.status = ? OR b.status = ?)) OR (a.uid = ? AND (a.status = ? OR a.status = ?)))';
+                        sqlParam = [this.tableName, tid, this.ctx.service.qualityInspectionAudit.tableName, tid, this.ctx.session.sessionUser.accountId, auditConst.status.checking, auditConst.status.rectification, this.ctx.session.sessionUser.accountId, auditConst.status.uncheck, auditConst.status.checkNo];
+                        break;
+                    case auditConst.filter.status.uncheck: // 待上报(所有的)PS:取未上报,退回,修订的变更令
+                        sql =
+                            'SELECT a.* FROM ?? AS a WHERE ' +
+                            'a.uid = ? AND a.tid = ? AND (a.status = ? OR a.status = ?)';
+                        sqlParam = [
+                            this.tableName,
+                            this.ctx.session.sessionUser.accountId,
+                            tid,
+                            auditConst.status.uncheck,
+                            auditConst.status.checkNo,
+                        ];
+                        break;
+                    case auditConst.filter.status.checking: // 进行中(所有的)
+                        sql =
+                            'SELECT a.* FROM ?? AS a WHERE ' +
+                            '(a.status = ? OR a.status = ?) AND a.tid = ?' +
+                            (this.ctx.session.sessionUser.is_admin ? '' : ' AND a.id IN (SELECT b.qiid FROM ?? AS b WHERE b.aid = ? GROUP BY b.qiid)');
+                        sqlParam = [this.tableName, status, auditConst.status.checkNoPre, tid, this.ctx.service.qualityInspectionAudit.tableName, this.ctx.session.sessionUser.accountId];
+                        break;
+                    case auditConst.filter.status.rectification: // 整改中(所有的)
+                    case auditConst.filter.status.checkStop: // 终止(所有的)
+                        sql =
+                            'SELECT a.* FROM ?? AS a WHERE ' +
+                            'a.status = ? AND a.tid = ?' +
+                            (this.ctx.session.sessionUser.is_admin ? '' : ' AND a.id IN (SELECT b.qiid FROM ?? AS b WHERE b.aid = ? GROUP BY b.qiid)');
+                        sqlParam = [this.tableName, status, tid, this.ctx.service.qualityInspectionAudit.tableName, this.ctx.session.sessionUser.accountId];
+                        break;
+                    case auditConst.filter.status.checked: // 已完成(所有的)
+                        sql = 'SELECT a.* FROM ?? as a WHERE a.status = ? AND a.tid = ?';
+                        sqlParam = [this.tableName, status, tid];
+                        break;
+                    default:
+                        break;
+                }
+            }
+            if (sortBy && orderBy) {
+                if (sortBy === 'code') {
+                    sql += ' ORDER BY CHAR_LENGTH(a.code) ' + orderBy + ',convert(a.code using gbk) ' + orderBy;
+                } else {
+                    sql += ' ORDER BY a.create_time ' + orderBy;
+                }
+            } else {
+                sql += ' ORDER BY a.create_time DESC';
+            }
+            if (hadlimit) {
+                const limit = this.ctx.pageSize ? this.ctx.pageSize : this.app.config.pageSize;
+                const offset = limit * (this.ctx.page - 1);
+                const limitString = offset >= 0 ? offset + ',' + limit : limit;
+                sql += ' LIMIT ' + limitString;
+            }
+            const list = await this.db.query(sql, sqlParam);
+            return list;
+        }
+
+        /**
+         * 获取支付个数
+         * @param {int} spid - 项目id
+         * @param {int} status - 状态
+         * @return {void}
+         */
+        async getCountByStatus(tid, status = 0) {
+            if ((this.ctx.tender.isTourist || this.ctx.session.sessionUser.is_admin) && status === 0) {
+                const sql5 = 'SELECT count(*) AS count FROM ?? AS a WHERE a.tid = ?';
+                const sqlParam5 = [this.tableName, tid];
+                const result5 = await this.db.query(sql5, sqlParam5);
+                return result5[0].count;
+            }
+            switch (status) {
+                case 0: // 所有
+                    const sql =
+                        'SELECT count(*) AS count FROM ?? AS a WHERE a.tid = ? AND ' +
+                        '(a.uid = ? OR (a.status != ? AND a.id IN (SELECT b.qiid FROM ?? AS b WHERE b.aid = ? GROUP BY b.qiid)) OR a.status = ?)';
+                    const sqlParam = [
+                        this.tableName,
+                        tid,
+                        this.ctx.session.sessionUser.accountId,
+                        auditConst.status.uncheck,
+                        this.ctx.service.qualityInspectionAudit.tableName,
+                        this.ctx.session.sessionUser.accountId,
+                        auditConst.status.checked,
+                    ];
+                    const result = await this.db.query(sql, sqlParam);
+                    return result[0].count;
+                case auditConst.filter.status.pending: // 待处理(你的)
+                    const sql6 = 'SELECT count(*) AS count FROM ?? as a WHERE a.tid = ? AND (a.id in(SELECT b.qiid FROM ?? as b WHERE b.tid = ? AND b.aid = ? AND (b.status = ? OR b.status = ?)) OR (a.uid = ? AND (a.status = ? OR a.status = ?)))';
+                    const sqlParam6 = [this.tableName, tid, this.ctx.service.qualityInspectionAudit.tableName, tid, this.ctx.session.sessionUser.accountId, auditConst.status.checking, auditConst.status.rectification, this.ctx.session.sessionUser.accountId, auditConst.status.uncheck, auditConst.status.checkNo];
+                    const result6 = await this.db.query(sql6, sqlParam6);
+                    return result6[0].count;
+                case auditConst.filter.status.uncheck: // 待上报(所有的)PS:取未上报,退回的变更立项
+                    const sql2 =
+                        'SELECT count(*) AS count FROM ?? AS a WHERE ' +
+                        'a.uid = ? AND a.tid = ? AND (a.status = ? OR a.status = ?)';
+                    const sqlParam2 = [
+                        this.tableName,
+                        this.ctx.session.sessionUser.accountId,
+                        tid,
+                        auditConst.status.uncheck,
+                        auditConst.status.checkNo,
+                    ];
+                    const result2 = await this.db.query(sql2, sqlParam2);
+                    return result2[0].count;
+                case auditConst.filter.status.checking: // 进行中(所有的)
+                    const sql7 =
+                        'SELECT count(*) AS count FROM ?? as a WHERE ' +
+                        '(a.status = ? OR a.status = ?) AND a.tid = ?' +
+                        (this.ctx.session.sessionUser.is_admin ? '' : ' AND a.id IN (SELECT b.qiid FROM ?? AS b WHERE b.aid = ? GROUP BY b.qiid)');
+                    const sqlParam7 = [this.tableName, status, auditConst.status.checkNoPre, tid, this.ctx.service.qualityInspectionAudit.tableName, this.ctx.session.sessionUser.accountId];
+                    const result7 = await this.db.query(sql7, sqlParam7);
+                    return result7[0].count;
+                case auditConst.filter.status.rectification: // 整改中(所有的)
+                case auditConst.filter.status.checkStop: // 终止(所有的)
+                    const sql3 =
+                        'SELECT count(*) AS count FROM ?? as a WHERE ' +
+                        'a.status = ? AND a.tid = ?' +
+                        (this.ctx.session.sessionUser.is_admin ? '' : ' AND a.id IN (SELECT b.qiid FROM ?? AS b WHERE b.aid = ? GROUP BY b.qiid)');
+                    const sqlParam3 = [this.tableName, status, tid, this.ctx.service.qualityInspectionAudit.tableName, this.ctx.session.sessionUser.accountId];
+                    const result3 = await this.db.query(sql3, sqlParam3);
+                    return result3[0].count;
+                case auditConst.filter.status.checked: // 已完成(所有的)
+                    const sql4 = 'SELECT count(*) AS count FROM ?? as a WHERE a.status = ? AND a.tid = ?';
+                    const sqlParam4 = [this.tableName, status, tid];
+                    const result4 = await this.db.query(sql4, sqlParam4);
+                    return result4[0].count;
+                default:
+                    break;
+            }
+        }
+    }
+
+    return QualityInspection;
+};

+ 82 - 0
app/service/quality_inspection_att.js

@@ -0,0 +1,82 @@
+'use strict';
+const archiver = require('archiver');
+const path = require('path');
+const fs = require('fs');
+/**
+ * 附件表 数据模型
+ * @author LanJianRong
+ * @date 2020/6/30
+ * @version
+ */
+
+module.exports = app => {
+    class QualityInspectionAtt extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'quality_inspection_attachment';
+        }
+
+        /**
+         * 获取当前标段(期)所有上传的附件
+         * @param {Number} tid 标段id
+         * @param {Number?} mid 期id
+         * @return {Promise<void>} 数据库查询实例
+         */
+        async getAllAtt(tid, qiid) {
+            const { ctx } = this;
+            // qiid 如果qiid只有一个就转成数组
+            if (!tid || !qiid) {
+                return [];
+            }
+            qiid = qiid instanceof Array ? qiid : [qiid];
+            const sql = 'SELECT a.*,b.name as username FROM ?? as a LEFT JOIN ?? as b ON a.uid = b.id WHERE a.tid = ? AND a.qiid in (' + ctx.helper.getInArrStrSqlFilter(qiid) + ') ORDER BY upload_time DESC';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, tid];
+            const result = await this.db.query(sql, sqlParam);
+            return result.map(item => {
+                item.orginpath = this.ctx.app.config.fujianOssPath + item.filepath;
+                if (!ctx.helper.canPreview(item.fileext)) {
+                    item.filepath = `/tender/${ctx.tender.id}/change/plan/${item.cpid}/information/file/${item.id}/download`;
+                } else {
+                    item.filepath = this.ctx.app.config.fujianOssPath + item.filepath;
+                }
+                return item;
+            });
+        }
+
+
+        /**
+         * 存储上传的文件信息至数据库
+         * @param {Array} payload 载荷
+         * @return {Promise<void>} 数据库插入执行实例
+         */
+        async saveFileMsgToDb(payload) {
+            return await this.db.insert(this.tableName, payload);
+        }
+
+        /**
+         * 获取单个文件信息
+         * @param {Number} id 文件id
+         * @return {Promise<void>} 数据库查询实例
+         */
+        async getMaterialFileById(id) {
+            return await this.getDataByCondition({ id });
+        }
+
+        /**
+         * 删除附件
+         * @param {Number} id - 附件id
+         * @return {void}
+         */
+        async delete(id) {
+            return await this.deleteById(id);
+        }
+    }
+    return QualityInspectionAtt;
+};
+

文件差异内容过多而无法显示
+ 1185 - 0
app/service/quality_inspection_audit.js


+ 86 - 2
app/service/tender_permission.js

@@ -26,6 +26,7 @@ module.exports = app => {
                     view: { title: '查看', value: 1, isDefault: 1 },
                     upload: { title: '上传文件', value: 2 },
                     add: { title: '新增功能', value: 3 },
+                    add_inspection: { title: '添加质量巡检', value: 4 },
                 },
             };
             this.PermissionBlock = [
@@ -110,7 +111,7 @@ module.exports = app => {
             if (!parts || parts.length === 0) return [];
 
             const partSql = parts.map(x => { return `${x} <> ''`}).join(' OR ');
-            const sql = `SELECT qp.*, pa.name, pa.role FROM ${this.tableName} qp LEFT JOIN ${this.ctx.service.projectAccount.tableName} pa ON qp.uid = pa.id WHERE qp.tid = ? AND (${partSql})`;
+            const sql = `SELECT qp.*, pa.name, pa.role FROM ${this.tableName} qp LEFT JOIN ${this.ctx.service.projectAccount.tableName} pa ON qp.uid = pa.id WHERE qp.tid = ? AND (${partSql}) ORDER BY qp.create_time DESC`;
             const result = await this.db.query(sql, [tid]);
             this.parsePermission(result);
             return result;
@@ -138,7 +139,7 @@ module.exports = app => {
                 }
             }
             for (const m of member) {
-                const im = { id: this.uuid.v4(), pid: this.ctx.session.sessionProject.id, tid, uid: m.uid };
+                const im = { id: this.uuid.v4(), pid: this.ctx.session.sessionProject.id, spid: this.ctx.subProject.id, tid, uid: m.uid };
                 for (const p in this.PermissionConst) {
                     if (m[p]) im[p] = m[p].join(',');
                 }
@@ -155,6 +156,89 @@ module.exports = app => {
             }
         }
 
+        async saveOnePermission(tid, uids, member, permissionBlock, transaction = null) {
+            const orgMember = await this.getAllDataByCondition({ where: { tid, uid: uids } });
+            const updateMember = [], insertMember = [];
+            for (const om of orgMember) {
+                const nmi = member.findIndex(x => { return om.uid == x.uid; });
+                if (nmi < 0) {
+                    const um = { id: om.id };
+                    for (const p of permissionBlock) {
+                        um[p] = '';
+                    }
+                    updateMember.push(um);
+                } else {
+                    const nm = member[nmi];
+                    const um = { id: om.id };
+                    for (const p in this.PermissionConst) {
+                        if (nm[p]) um[p] = nm[p].join(',');
+                    }
+                    updateMember.push(um);
+                    member.splice(nmi, 1);
+                }
+            }
+            for (const m of member) {
+                const im = { id: this.uuid.v4(), pid: this.ctx.session.sessionProject.id, spid: this.ctx.subProject.id, tid, uid: m.uid };
+                for (const p in this.PermissionConst) {
+                    if (m[p]) im[p] = m[p].join(',');
+                }
+                insertMember.push(im);
+            }
+            if (!transaction) {
+                const conn = await this.db.beginTransaction();
+                try {
+                    if (updateMember.length > 0) await conn.updateRows(this.tableName, updateMember);
+                    if (insertMember.length > 0) await conn.insert(this.tableName, insertMember);
+                    await conn.commit();
+                } catch (err) {
+                    await conn.rollback();
+                    throw err;
+                }
+            } else {
+                if (updateMember.length > 0) await transaction.updateRows(this.tableName, updateMember);
+                if (insertMember.length > 0) await transaction.insert(this.tableName, insertMember);
+            }
+        }
+
+        async setOtherTender(tidList, userList, permissionBlock) {
+            // 根据标段找出创建人去除,已存在的先删除再插入
+            const transaction = await this.db.beginTransaction();
+            try {
+                const tenderList = await this.ctx.service.tender.getAllDataByCondition({
+                    columns: ['id', 'user_id'],
+                    where: { id: tidList.split(',') },
+                });
+                const oldTouristList = await this.getAllDataByCondition({ where: { tid: tidList.split(',') } });
+                const insertData = [];
+                const updateData = [];
+                for (const user of userList) {
+                    for (const t of tenderList) {
+                        const updateInfo = this._.find(oldTouristList, { tid: t.id, uid: user.uid });
+                        // if (delId) deleteIdData.push(delId.id);
+                        if (updateInfo) {
+                            const um = { id: updateInfo.id };
+                            for (const p of permissionBlock) {
+                                um[p] = user.member.join(',');
+                            }
+                            updateData.push(um);
+                        } else if (user.uid !== t.user_id) {
+                            const um = { id: this.uuid.v4(), pid: this.ctx.session.sessionProject.id, spid: this.ctx.subProject.id, tid: t.id, uid: user.uid };
+                            for (const p of permissionBlock) {
+                                um[p] = user.member.join(',');
+                            }
+                            insertData.push(um);
+                        }
+                    }
+                }
+                if (updateData.length > 0) await transaction.updateRows(this.tableName, updateData);
+                if (insertData.length > 0) await transaction.insert(this.tableName, insertData);
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
     }
 
     return tenderPermission;

+ 33 - 1
app/view/dashboard/index.ejs

@@ -72,12 +72,15 @@
                                     <% if (ctx.subProject.page_show.openFinancial && auditFinancials.length !== 0) { %>
                                         <option value="11">资金支付(<%- auditFinancials.length %>)</option>
                                     <% } %>
+                                    <% if (ctx.subProject.page_show.quality && auditInspections.length !== 0) { %>
+                                        <option value="12">质量巡检(<%- auditInspections.length %>)</option>
+                                    <% } %>
                                 </select>
                             </div>
                         </div>
                         <div class="card-body p-0">
                             <div class="contant-height-one">
-                                <% if (auditTenders.length !== 0 || auditRevise.length !== 0 || auditStages.length !== 0 || auditChanges.length !== 0 || auditMaterial.length !== 0 || auditAdvance.length !== 0 || auditChangeProject.length !== 0 || auditChangeApply.length !== 0 || auditChangePlan.length !== 0 || auditPayments.length !== 0 || auditStageAss.length !== 0 || auditFinancials.length !== 0) { %>
+                                <% if (auditTenders.length !== 0 || auditRevise.length !== 0 || auditStages.length !== 0 || auditChanges.length !== 0 || auditMaterial.length !== 0 || auditAdvance.length !== 0 || auditChangeProject.length !== 0 || auditChangeApply.length !== 0 || auditChangePlan.length !== 0 || auditPayments.length !== 0 || auditStageAss.length !== 0 || auditFinancials.length !== 0 || auditInspections.length !== 0) { %>
                                 <style>
                                     #doing-list td {
                                         word-wrap:break-word;
@@ -307,6 +310,19 @@
                                                 <td><a href="/sp/<%- af.spid %>/financial/pay/<%- af.fpid %>/detail" class="btn btn-sm btn-table <% if (af.fpstatus === acFinancial.status.checkNo) { %>btn-outline-warning text-warning<% } else { %>btn-outline-primary<% } %>" role="button"><% if (af.fpstatus !== acFinancial.status.checkNo) { %>审批<% } else { %>重新上报<% } %></a></td>
                                             </tr>
                                         <% } %>
+                                        <% for (const acp of auditInspections) { %>
+                                            <tr data-type="9">
+                                                <td><span class="bg-new-inspection text-new-inspection badge text-width">质量巡检</span></td>
+                                                <td><a href="/sp/<%- acp.spid %>/quality/tender/<%- acp.tid %>/info"><%- acp.name %></a> <a href="/sp/<%- acp.spid %>/quality/tender/<%- acp.tid %>/inspection/<%- acp.qiid %>/information"><%- acp.mcode %></a></td>
+                                                <td>巡检</td>
+                                                <td><%- (
+                                                            acp.mstatus !== acInspection.status.checkNo
+                                                                    ? (acp.begin_time ? ctx.moment(acp.begin_time).format('YYYY/MM/DD HH:mm') : '')
+                                                                    : (acp.end_time ? ctx.moment(acp.end_time).format('YYYY/MM/DD HH:mm') : '')
+                                                    ) %></td>
+                                                <td><a href="/sp/<%- acp.spid %>/quality/tender/<%- acp.tid %>/inspection/<%- acp.qiid %>/information" class="btn btn-sm btn-table <% if (acp.mstatus === acInspection.status.checkNo) { %>btn-outline-warning text-warning<% } else { %>btn-outline-primary<% } %>" role="button"><% if (acp.mstatus !== acInspection.status.checkNo) { %>审批<% } else { %>重新上报<% } %></a></td>
+                                            </tr>
+                                        <% } %>
                                     </tbody>
                                 </table>
                                 <% } else { %>
@@ -407,6 +423,9 @@
                                     <% if (ctx.subProject.page_show.openFinancial) { %>
                                         <option value="11">资金支付</option>
                                     <% } %>
+                                    <% if (ctx.subProject.page_show.quality) { %>
+                                        <option value="13">质量巡检</option>
+                                    <% } %>
                                 </select>
                             </div>
                         </div>
@@ -533,6 +552,16 @@
                                                     <td class="<%- acFinancial.statusClass[notice.status] %>"><%- acFinancial.statusString[notice.status] %></td>
                                                     <td><%- notice.opinion ? notice.opinion : '' %></td>
                                                 </tr>
+                                            <% } else if(notice.type === pushType.inspection && ctx.subProject.page_show.quality) { %>
+                                                <tr data-type="13">
+                                                    <td><span class="bg-new-inspection text-new-inspection badge text-width">质量巡检</span></td>
+                                                    <td><a href="/tender/<%- notice.tid %>"><%- notice.name %></a> <a href="/sp/<%- notice.spid %>/quality/tender/<%- notice.tid %>/inspection/<%- notice.qiid %>/information"><%- notice.c_code %></a></td>
+                                                    <td><%- notice.su_name %><%- (notice.su_role ? '-' + notice.su_role : '') %></td>
+                                                    <td><%- ctx.helper.dateTran(notice.create_time, 'YYYY/MM/DD HH:mm') %></td>
+                                                    <td>巡检</td>
+                                                    <td class="<%- acInspection.statusClass[notice.status] %>"><%- acInspection.statusString[notice.status] %></td>
+                                                    <td><%- notice.opinion ? notice.opinion : '' %></td>
+                                                </tr>
                                             <% } %>
                                         <% } %>
                                     </tbody>
@@ -670,6 +699,9 @@
             <% if (ctx.subProject.page_show.openFinancial) { %>
             'rgba(58, 88, 50,'+ transparentCount +')',
             <% } %>
+            <% if (ctx.subProject.page_show.quality) { %>
+            'rgba(158, 58, 80,'+ transparentCount +')',
+            <% } %>
         ],
         tooltip: {
             trigger: 'item'

+ 23 - 0
app/view/dashboard/workspace.ejs

@@ -133,6 +133,9 @@
                                             <% if (dashboardStatus.shenpi.financial !== 0) { %>
                                                 <option value="financial">资金支付(<%- dashboardStatus.shenpi.financial %>)</option>
                                             <% } %>
+                                            <% if (dashboardStatus.shenpi.inspection !== 0) { %>
+                                                <option value="inspection">质量巡检(<%- dashboardStatus.shenpi.inspection %>)</option>
+                                            <% } %>
                                         </select>
                                     </div>
                                 </div>
@@ -408,6 +411,21 @@
                                                                 ) %></td>
                                                             <td><a href="/sp/<%- af.spid %>/financial/pay/<%- af.fpid %>/detail" class="btn btn-sm btn-table <% if (af.fpstatus === acFinancial.status.checkNo) { %>btn-outline-warning text-warning<% } else { %>btn-outline-primary<% } %>" role="button"><% if (af.fpstatus !== acFinancial.status.checkNo) { %>审批<% } else { %>重新上报<% } %></a></td>
                                                         </tr>
+                                                    <% } else if (db.shenpi_type === 'inspection') { %>
+                                                        <% const am = db; %>
+                                                        <tr data-type="material">
+                                                            <% if (am.start_audit === 2) { %><td class="text-center text-danger"><i class="fa fa-exclamation-triangle"></i></td><% } else if (am.start_audit === 1) { %><td class="text-center text-warning"><i class="fa fa-bell"></i></td><% } else { %><td></td><% } %>
+                                                            <td><span class="bg-new-inspection text-new-inspection badge text-width">质量巡检</span></td>
+                                                            <td><a href="/sp/<%- am.spid %>/dashboard"><%- am.sp_name %></a></td>
+                                                            <td><a href="/sp/<%- am.spid %>/quality/tender/<%- am.tid %>/info"><%- am.name %></a> <a href="/sp/<%- am.spid %>/quality/tender/<%- am.tid %>/inspection/<%- am.qiid %>/information"><%- am.mcode %></a></td>
+                                                            <td>巡检</td>
+                                                            <td><%- (
+                                                                        am.mstatus !== acInspection.status.checkNo
+                                                                                ? (am.begin_time ? ctx.moment(am.begin_time).format('YYYY/MM/DD HH:mm') : '')
+                                                                                : (am.end_time ? ctx.moment(am.end_time).format('YYYY/MM/DD HH:mm') : '')
+                                                                ) %></td>
+                                                            <td><a href="/sp/<%- am.spid %>/quality/tender/<%- am.tid %>/inspection/<%- am.qiid %>/information" class="btn btn-sm btn-table <% if (am.mstatus === acInspection.status.checkNo) { %>btn-outline-warning text-warning<% } else { %>btn-outline-primary<% } %>" role="button"><% if (am.mstatus !== acInspection.status.checkNo) { %>审批<% } else { %>重新上报<% } %></a></td>
+                                                        </tr>
                                                     <% } %>
                                                 <% } %>
                                                 </tbody>
@@ -578,6 +596,7 @@
     const acChangeApply = JSON.parse(unescape('<%- escape(JSON.stringify(acChangeApply)) %>'));
     const acChangePlan = JSON.parse(unescape('<%- escape(JSON.stringify(acChangePlan)) %>'));
     const acFinancial = JSON.parse(unescape('<%- escape(JSON.stringify(acFinancial)) %>'));
+    const acInspection = JSON.parse(unescape('<%- escape(JSON.stringify(acInspection)) %>'));
     const typeColMap = JSON.parse(unescape('<%- escape(JSON.stringify(typeColMap)) %>'));
     const userMsgPermission = false;
 
@@ -831,6 +850,10 @@
                                         html.push(`<li class="list-group-item">
                                             <span class="${acFinancial.auditStringClass[n.status]}">${acFinancial.auditString[n.status]}</span> <a href="/sp/${n.spid}/financial/pay/${n.fpid}/detail" target="_blank">${n.name}</a> 资金监管 ${n.code}<span class="float-right">${moment(new Date(n.shenpi_time)).format('HH:mm:ss')}</span></li>`);
                                         break;
+                                    case 'inspection':
+                                        html.push(`<li class="list-group-item">
+                                            <span class="${acInspection.auditStringClass[n.status]}">${acInspection.auditString[n.status]}</span> <a href="/sp/${n.spid}/quality/tender/${n.id}/inspection/${n.qiid}/information" target="_blank">${n.name}</a> 质量巡检 ${n.code}<span class="float-right">${moment(new Date(n.shenpi_time)).format('HH:mm:ss')}</span></li>`);
+                                        break;
                                     default: continue;
                                 }
                             }

+ 2 - 2
app/view/quality/flaw.ejs

@@ -1,4 +1,4 @@
-<% include ./sub_memu.ejs %>
+<% include ./sub_menu.ejs %>
 <div class="panel-content">
     <div class="panel-title">
         <div class="title-main d-flex">
@@ -10,4 +10,4 @@
     </div>
     <div class="content-wrap">
     </div>
-</div>
+</div>

+ 2 - 2
app/view/quality/info.ejs

@@ -1,4 +1,4 @@
-<% include ./sub_memu.ejs %>
+<% include ./sub_menu.ejs %>
 <div class="panel-content">
     <div class="panel-title">
         <div class="title-main d-flex">
@@ -103,4 +103,4 @@
     const thirdParty = JSON.parse('<%- JSON.stringify(thirdParty) %>');
     const permission = JSON.parse('<%- JSON.stringify(ctx.permission.quality) %>');
     const canDelete = false;
-</script>
+</script>

+ 111 - 0
app/view/quality/inspection.ejs

@@ -0,0 +1,111 @@
+<% include ./sub_menu.ejs %>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main d-flex">
+            <% include ./sub_mini_menu.ejs %>
+            <div>
+                <div class="d-inline-block">
+                    <div class="btn-group" id="sort-dropdown">
+                        <button type="button" class="btn btn-sm btn-light text-primary dropdown-toggle" data-toggle="dropdown" id="bpaixu">排序:创建时间</button>
+                        <div class="dropdown-menu" aria-labelledby="bpaixu">
+                            <ul class="list-unstyled px-3 mb-0" id="sort-radio">
+                                <li class="mb-2">
+                                    <div class="custom-control custom-radio">
+                                        <input type="radio" class="custom-control-input" id="pai1" name="paizhi" value="time" checked="">
+                                        <label class="custom-control-label" for="pai1">创建时间</label>
+                                    </div>
+                                </li>
+                                <li class="mb-2">
+                                    <div class="custom-control custom-radio">
+                                        <input type="radio" class="custom-control-input" id="pai3" name="paizhi" value="code">
+                                        <label class="custom-control-label" for="pai3">编号</label>
+                                    </div>
+                                </li>
+                            </ul>
+                            <ul class="list-unstyled px-3 pt-2 mb-0 border-top" id="order-radio">
+                                <li class="mb-2">
+                                    <div class="custom-control custom-radio">
+                                        <input type="radio" class="custom-control-input" id="pdown" name="paixu" value="desc" checked="">
+                                        <label class="custom-control-label" for="pdown">降序</label>
+                                    </div>
+                                </li>
+                                <li class="mb-2">
+                                    <div class="custom-control custom-radio">
+                                        <input type="radio" class="custom-control-input" id="pup" name="paixu" value="asc">
+                                        <label class="custom-control-label" for="pup">升序</label>
+                                    </div>
+                                </li>
+                            </ul>
+                        </div>
+                    </div>
+                </div>
+                <div class="d-inline-block">
+                    <div class="btn-group">
+                        <button type="button" class="btn btn-sm btn-light text-primary dropdown-toggle" data-toggle="dropdown" data-status="<%- status %>" id="zhankai"><% if (status !== 0) { %><%- filter.statusString[status] %>(<%- filter.count[status] %>)<% } else { %>全部<% } %></button>
+                        <div class="dropdown-menu" aria-labelledby="zhankai" id="status_select">
+                            <% if (status !== 0) { %><a class="dropdown-item" data-val="0" href="javascript:void(0);">全部</a><% } %>
+                            <% for (const fs in filter.status) { %>
+                                <% const f = filter.status[fs]; %>
+                                <% if (f !== status) { %><a class="dropdown-item" data-val="<%- f %>" href="javascript:void(0);"><%- filter.statusString[f] %>(<%- filter.count[f] %>)</a><% } %>
+                            <% } %>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="ml-auto">
+                <% if (permission.add_inspection) { %>
+                <a href="#add-bj" data-toggle="modal" data-target="#add-bj" class="btn btn-sm btn-primary pull-right ml-1">新建巡检</a>
+                <a href="#setting" data-toggle="modal" data-target="#setting" class="btn btn-sm btn-outline-primary pull-right ml-1"><i class="fa fa-cog"></i></a>
+                <% } %>
+            </div>
+        </div>
+    </div>
+    <div class="content-wrap">
+        <div class="c-body">
+            <div class="sjs-height-0">
+                <table class="table table-bordered">
+                    <thead class="text-center">
+                    <tr>
+                        <th width="13%" id="sort_change">编号</th><th width="5%">部位</th>
+                        <th width="10%">检查项目</th><th width="25%">检查情况</th><th width="13%">处理要求及措施</th>
+                        <th width="10%">检查日期</th><th width="7%">附件</th><th width="13%">状态</th>
+                    </tr>
+                    </thead>
+                    <tbody id="changeList">
+                    <% for (const c of inspectionList) { %>
+                        <tr><td><a href="/sp/<%- ctx.subProject.id %>/quality/tender/<%- ctx.tender.id %>/inspection/<%- c.id %>/information"><%- c.code %></a></td><td></td>
+                            <td><%- c.check_item %></td>
+                            <td><%- c.check_situation %></td>
+                            <td><%- c.action %></td>
+                            <td><%- c.check_date ? moment(c.check_date).format('YYYY-MM-DD') : '' %></td>
+                            <td class="text-center">
+                                <a class="show-files" href="#file" data-toggle="modal" data-target="#file" data-id="<%- c.id %>"><i class="fa fa-paperclip"></i> <%- c.attList.length > 0 ? c.attList.length : '' %></a>
+                            </td>
+                            <td class="text-center">
+                                <span class="<%- auditConst.auditStringClass[c.status] %>"><% if (c.status !== auditConst.status.uncheck) { %><i class="fa fa-circle <%- auditConst.auditStringClass[c.status] %>"></i> <% } %><%- auditConst.auditString[c.status] %></span>
+                            </td>
+                        </tr>
+                    <% } %>
+                    </tbody>
+                </table>
+                <!--翻页-->
+                <% include ../layout/page.ejs %>
+            </div>
+        </div>
+    </div>
+</div>
+<script>
+    autoFlashHeight();
+    const tenderId = parseInt('<%- ctx.tender.id %>');
+    const tenderName = JSON.parse(unescape('<%- escape(JSON.stringify(tender.name)) %>'));
+    const dealCode = JSON.parse(unescape('<%- escape(JSON.stringify(dealCode)) %>'));
+    const ruleConst = JSON.parse(unescape('<%- escape(JSON.stringify(ruleConst)) %>'));
+    let codeRule = JSON.parse(unescape('<%- escape(JSON.stringify(codeRule)) %>'));
+    let connectorRule = '<%- c_connector %>';
+    const cRuleFirst = parseInt('<%- c_rule_first %>');
+    const ruleType = parseInt('<%- ruleType %>');
+    const rulesType = '<%- rule_type %>';
+    const auditType = JSON.parse(unescape('<%- escape(JSON.stringify(auditType)) %>'));
+    const auditConst = JSON.parse(unescape('<%- escape(JSON.stringify(auditConst)) %>'));
+    const inspectionList = JSON.parse(unescape('<%- escape(JSON.stringify(inspectionList)) %>'));
+</script>

文件差异内容过多而无法显示
+ 468 - 0
app/view/quality/inspection_information.ejs


+ 248 - 0
app/view/quality/inspection_information_modal.ejs

@@ -0,0 +1,248 @@
+<!--添加附件-->
+<div class="modal fade" id="addfujian">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title" id="myModalLabel">上传附件</h5>
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">&times;</span>
+                </button>
+            </div>
+            <div class="modal-body">
+                <div class="form-group">
+                    <label for="file-modal">单个文件大小限制:50MB,支持office等文档格式、图片格式、压缩包格式</label>
+                    <!-- <p><a href="javascript: void(0);" class="btn btn-primary" id="file-modal-target">选择文件</a></p> -->
+                    <input type="file" id="file-modal" multiple="multiple">
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button id="file-cancel" type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">取消</button>
+                <button id="file-ok" type="button" class="btn btn-primary btn-sm">添加</button>
+            </div>
+        </div>
+    </div>
+</div>
+<% if (!inspection.readOnly) { %>
+<!--删除巡检-->
+<div class="modal fade" id="del" 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">
+                <p class="mb-2">删除后,数据无法恢复,请谨慎操作。</p>
+                <p class="mb-2">请在下方文本框输入文本「<span class="text-danger">确认删除本次巡检</span>」,以此确认删除操作。</p>
+                <p class="mb-2"><input type="text" class="form-control form-control-sm" id="del-inspection-text" placeholder="输入文本,确认删除"></p>
+            </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-danger" id="del-inspection-btn">确定删除</button>
+            </div>
+        </div>
+    </div>
+</div>
+<!--提交审批-->
+<div class="modal fade" id="sp-done" 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>
+            <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-success" id="start-btn">确认提交</button>
+            </div>
+        </div>
+    </div>
+</div>
+<% } %>
+<% if (inspection.shenpiPower) { %>
+    <div class="modal fade" id="sp-back" data-backdrop="static">
+        <div class="modal-dialog" role="document">
+            <div class="modal-content">
+                <div class="modal-header">
+                    <h5 class="modal-title">审批退回</h5>
+                </div>
+                <div class="modal-body">
+                    <div class="form-group">
+                        <label>审批意见</label>
+                        <textarea class="form-control form-control-sm" name="opinion" rows="5">不同意</textarea>
+                    </div>
+                    <!--退回至上一审批人-->
+                    <% if (inspection.curAuditorIds.indexOf(ctx.session.sessionUser.accountId) >= 0) { %>
+                        <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">退回原报
+                                    <%- inspection.user.name %></label>
+                            </div>
+                            <% if (inspection.curAuditors[0].audit_order > 1) { %>
+                                <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">退回上一审批人
+                                        <% const pre = inspection.auditHistory[inspection.auditHistory.length - 1].find(x => { return x.audit_order === inspection.curAuditors[0].audit_order - 1}); %>
+                                        <%- (pre.audit_type === auditType.key.common ? pre.auditors[0].name : `${pre.audit_order}审`)%></label>
+                                    </label>
+                                </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-warning" id="approval-back-btn">确认退回</button>
+                </div>
+            </div>
+        </div>
+    </div>
+    <div class="modal fade" id="sp-done" data-backdrop="static">
+        <div class="modal-dialog" role="document">
+            <div class="modal-content">
+                <div class="modal-header">
+                    <h5 class="modal-title">审批通过</h5>
+                </div>
+                <div class="modal-body">
+                    <div class="form-group">
+                        <label>审批意见</label>
+                        <textarea class="form-control form-control-sm" name="opinion" rows="5">同意</textarea>
+                    </div>
+                    <% if (inspection.finalAuditorIds.indexOf(ctx.session.sessionUser.accountId) !== -1) { %>
+                    <div class="alert alert-success">审批通过并指派人员整改:
+                        <input type="hidden" id="rectification-uid" value="<%- inspection.rectification_uid ? inspection.rectification_uid : '' %>">
+                        <span class="d-inline-block" id="rectification-user-set">
+                            <% if (inspection.rectification_uid && inspection.rectification_user) { %>
+                            <span class="badge">
+                              <%- inspection.rectification_user.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="btn btn-sm btn-danger remove-btn">移除</button>
+                                    <button class="btn btn-sm btn-secondary">取消</button>
+                                  </div>
+                                </div>
+                              </span>
+                              </span>
+                            <% } %>
+                        </span>
+                        <div class="d-inline-block dropdown" <% if (inspection.rectification_user) { %>style="display: none!important;" <% } %>>
+                            <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 gr-search"
+                                                             placeholder="姓名/手机 检索" autocomplete="off"></div>
+                                <dl class="list-unstyled book-list">
+                                    <% accountGroup.forEach((group, idx) => { %>
+                                        <dt><a href="javascript: void(0);" class="acc-btn" data-groupid="<%- idx %>" data-type="hide"><i class="fa fa-plus-square"></i></a> <%- group.groupName %></dt>
+                                        <div class="dd-content" data-toggleid="<%- idx %>">
+                                            <% group.groupList.forEach(item => { %>
+                                                <% if (item.id !== inspection.uid) { %>
+                                                <dd class="border-bottom p-2 mb-0 " data-id="<%- item.id %>" >
+                                                    <p class="mb-0 d-flex">
+                                                        <span class="text-primary"><%- item.name %></span>
+                                                        <span class="ml-auto"><%- item.mobile %></span></p>
+                                                    <span class="text-muted"><%- item.role %></span>
+                                                </dd>
+                                                <% } %>
+                                            <% });%>
+                                        </div>
+                                    <% }) %>
+                                </dl>
+                            </div>
+                        </div>
+                    </div>
+                    <% } else if (inspection.nextAuditors.length > 0) { %>
+                    <div class="alert alert-success">下一个审批人:
+                        <% const next = inspection.auditHistory[inspection.auditHistory.length - 1].find(x => { return x.audit_order === inspection.curAuditors[0].audit_order + 1}); %>
+                        <%- (next.audit_type === auditType.key.common ? next.auditors[0].name : `${next.audit_order}审`)%></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-success" id="approval-success-btn">确认通过</button>
+                </div>
+            </div>
+        </div>
+    </div>
+    <div class="modal fade" id="sp-close" data-backdrop="static">
+        <div class="modal-dialog" role="document">
+            <div class="modal-content">
+                <div class="modal-header">
+                    <h5 class="modal-title">审批关闭</h5>
+                </div>
+                <div class="modal-body">
+                    <div class="form-group">
+                        <label>关闭原因</label>
+                        <textarea class="form-control form-control-sm" name="opinion" rows="5"></textarea>
+                    </div>
+                    <div class="alert alert-danger">审批关闭,将直接停止该巡检流程。</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-danger" id="approval-stop-btn">确认关闭</button>
+                </div>
+            </div>
+        </div>
+    </div>
+<% } %>
+<% if (inspection.rectificationPower) { %>
+    <div class="modal fade" id="sp-back" data-backdrop="static">
+        <div class="modal-dialog" role="document">
+            <div class="modal-content">
+                <div class="modal-header">
+                    <h5 class="modal-title">审批退回</h5>
+                </div>
+                <div class="modal-body">
+                    <div class="form-group">
+                        <label>审批意见</label>
+                        <textarea class="form-control form-control-sm" name="opinion" rows="5">不同意</textarea>
+                    </div>
+                    <!--退回至上一审批人-->
+                    <% if (inspection.curAuditorIds.indexOf(ctx.session.sessionUser.accountId) >= 0) { %>
+                        <div id="reject-process" class="alert alert-warning"
+                             style="margin-top: 15px;">
+                                退回上一审批人
+                                <% const pre = inspection.auditHistory[inspection.auditHistory.length - 1].find(x => { return x.audit_order === inspection.curAuditors[0].audit_order - 1}); %>
+                                <%- (pre.audit_type === auditType.key.common ? pre.auditors[0].name : `${pre.audit_order}审`)%>
+                        </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-warning" id="rectification-back-btn">确认退回</button>
+                </div>
+            </div>
+        </div>
+    </div>
+    <div class="modal fade" id="sp-done" data-backdrop="static">
+        <div class="modal-dialog" role="document">
+            <div class="modal-content">
+                <div class="modal-header">
+                    <h5 class="modal-title">整改完成</h5>
+                </div>
+                <div class="modal-body">
+                    <div class="form-group">
+                        <label>整改意见</label>
+                        <textarea class="form-control form-control-sm" name="opinion" rows="5">已整改</textarea>
+                    </div>
+                </div>
+                <div class="modal-footer">
+                    <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
+                    <button type="button" class="btn btn-sm btn-success" id="rectification-success-btn">确认通过</button>
+                </div>
+            </div>
+        </div>
+    </div>
+<% } %>

+ 166 - 0
app/view/quality/inspection_modal.ejs

@@ -0,0 +1,166 @@
+<!--审批流程/结果-->
+<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 modal-height-500" style="overflow: auto">
+                        <div class="card mt-3">
+                            <ul class="list-group list-group-flush" id="auditor-list">
+                            </ul>
+                        </div>
+                    </div>
+                    <div class="col-8 modal-height-500" style="overflow: auto" id="audit-list">
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+            </div>
+        </div>
+    </div>
+</div>
+<!--附件-->
+<div class="modal fade" id="file" 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="modal-height-500" style="overflow: auto;">
+                    <table class="table table-sm table-bordered">
+                        <thead>
+                        <tr><th>文件名</th><th>上传人</th><th>上传时间</th><th>操作</th></tr>
+                        </thead>
+                        <tbody id="file-content"></tbody>
+                    </table>
+                </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-primary">确定</button> -->
+            </div>
+        </div>
+    </div>
+</div>
+<% if (permission.add_inspection) { %>
+    <!--弹出添加变更令-->
+    <div class="modal fade" id="add-bj-modal" data-backdrop="static">
+        <div class="modal-dialog" role="document">
+            <div class="modal-content">
+                <div class="modal-header">
+                    <h5 class="modal-title">新建巡检</h5>
+                </div>
+                <div class="modal-body">
+                    <div class="form-group">
+                        <label>编号<b class="text-danger">*</b></label>
+                        <div class="input-group">
+                            <input type="text" class="form-control form-control-sm is-invalid" placeholder="请输入编号" value="变更申请编号" id="bj-code">
+                            <div class="input-group-append" id="autoCodeShow" <% if (codeRule.length === 0) { %>style="display: none"<% } %>>
+                                <button class="btn btn-sm btn-outline-secondary" type="button" title="自动编号" id="autoCode"><i class="fa fa-repeat"></i></button>
+                            </div>
+                            <div class="invalid-feedback" style="display: none" id="bjHint">您输入的编号已存在。</div>
+                        </div>
+                    </div>
+                    <div class="form-group">
+                        <label>检查项<b class="text-danger">*</b></label>
+                        <input class="form-control form-control-sm" value="" type="text" id="check_item">
+                        <div class="invalid-feedback" style="display: none" id="name_error_msg">超过255个字,请缩减名称。</div>
+                    </div>
+                    <div class="form-group">
+                        <label>日期<b class="text-danger">*</b></label>
+                        <input id="check_date" class="datepicker-here form-control form-control-sm" placeholder="请选择检查日期" data-date-format="yyyy-MM-dd" data-language="zh" type="text">
+                        <div class="invalid-feedback" style="display: none">请输入日期</div>
+                    </div>
+                </div>
+                <div class="modal-footer">
+                    <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal" id="addCancel">关闭</button>
+                    <a href="javascript: void(0)" class="btn btn-primary btn-sm" id="addOk">确认新建</a>
+                </div>
+            </div>
+        </div>
+    </div>
+    <!--设置-->
+    <div class="modal fade" id="setting" 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">
+                    <ul class="nav nav-tabs mb-3" role="tablist">
+                        <li class="nav-item">
+                            <a class="nav-link active" data-toggle="tab" href="#bianhao" role="tab" aria-controls="home" aria-selected="true">编号规则</a>
+                        </li>
+                    </ul>
+                    <div class="tab-content">
+                        <div class="tab-pane active" id="bianhao">
+                            <h5>
+                                当前规则:
+                                <span id="preview">
+                                    <% if (codeRule && codeRule instanceof Array) { %>
+                                        <% const preview = []; %>
+                                        <% for (const rule of codeRule) { %>
+                                            <% preview.push(rule.preview); %>
+                                        <% } %>
+                                        <%- preview.join(c_connector !== null && c_connector !== '3' ? ruleConst.connectorString[c_connector] : ''); %>
+                                    <% } %>
+                                </span>
+                            </h5>
+                            <h5 id="ruleParts">
+                                <% if (codeRule && codeRule instanceof Array) { %>
+                                    <% for (const rule of codeRule) { %>
+                                        <span class="badge badge-light" title="<%- ruleConst.ruleString[rule.rule_type] %>">
+                                    <span>
+                                        <%- rule.preview %>
+                                    </span>
+                                    <a href="javascript: void(0);" class="text-danger" title="移除"><i class="fa fa-remove"></i></a>
+                                </span>
+                                    <% } %>
+                                <% } %>
+                            </h5>
+                            <h5 class="my-3">连接符</h5>
+                            <div class="form-group">
+                                <select class="form-control form-control-sm connector-change">
+                                    <option disabled selected>请选择</option>
+                                    <% for (const index in ruleConst.connectorString) { %>
+                                        <option value="<%- index %>" <% if (c_connector !== null && parseInt(c_connector) === parseInt(index)) { %>selected<% } %>><%- ruleConst.connectorString[index] %></option>
+                                    <% } %>
+                                </select>
+                            </div>
+                            <h5 class="my-3">添加新规则组件</h5>
+                            <div class="form-group">
+                                <select class="form-control form-control-sm rule-change">
+                                    <option disabled selected>请选择组件</option>
+                                    <% for (const index in ruleConst.ruleString) { %>
+                                        <option value="<%- index %>"><%- ruleConst.ruleString[index] %></option>
+                                    <% } %>
+                                </select>
+                            </div>
+                            <div class="form-group" id="format" style="display: none">
+                                <label>自动编号位数</label>
+                                <input min="3" class="form-control form-control-sm" step="1" max="6" value="3" type="number">
+                            </div>
+                            <div class="form-group" id="text" style="display: none">
+                                <label>起始编号</label>
+                                <input class="form-control form-control-sm" value="001" type="text">
+                            </div>
+                            <button class="btn btn-sm btn-outline-primary" id="addRule">添加组件</button>
+                        </div>
+                    </div>
+                </div>
+                <div class="modal-footer">
+                    <% if (c_rule_first) { %><button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal" id="changeFirst">暂时不需要</button><% } %>
+                    <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal" id="hide_modal" <% if (c_rule_first) { %>style="display: none"<% } %>>关闭</button>
+                    <button type="button" class="btn btn-primary btn-sm" id="setRule">确定添加</button>
+                </div>
+            </div>
+        </div>
+    </div>
+<% } %>
+
+

+ 2 - 2
app/view/quality/lab.ejs

@@ -1,4 +1,4 @@
-<% include ./sub_memu.ejs %>
+<% include ./sub_menu.ejs %>
 <div class="panel-content">
     <div class="panel-title">
         <div class="title-main d-flex">
@@ -10,4 +10,4 @@
     </div>
     <div class="content-wrap">
     </div>
-</div>
+</div>

+ 2 - 1
app/view/quality/sub_memu_list.ejs

@@ -1,4 +1,5 @@
 <nav-menu title="返回" url="/sp/<%- ctx.subProject.id %>/quality/tender" tclass="text-primary" ml="1" icon="fa-chevron-left"></nav-menu>
-<nav-menu title="工程资料" url="/sp/<%- ctx.subProject.id %>/quality/tender/<%= ctx.tender.id %>/info%>" ml="3" active="<%= ctx.url.indexOf('/info') %>"></nav-menu>
+<nav-menu title="工程资料" url="/sp/<%- ctx.subProject.id %>/quality/tender/<%= ctx.tender.id %>/info%>" ml="3" active="<%- (ctx.url.indexOf('/info') !== -1 && ctx.url.indexOf('/information') === -1 ? 1 : -1) %>"></nav-menu>
 <!--<nav-menu title="缺陷管理" url="/sp/<%- ctx.subProject.id %>/quality/tender/<%= ctx.tender.id %>/flaw%>" ml="3" active="<%= ctx.url.indexOf('/flaw') %>"></nav-menu>-->
 <!--<nav-menu title="试验报告" url="/sp/<%- ctx.subProject.id %>/quality/tender/<%= ctx.tender.id %>/lab%>" ml="3" active="<%= ctx.url.indexOf('/lab') %>"></nav-menu>-->
+<nav-menu title="质量巡检" url="/sp/<%- ctx.subProject.id %>/quality/tender/<%= ctx.tender.id %>/inspection %>" ml="3" active="<%= ctx.url.indexOf('/inspection') %>"></nav-menu>

app/view/quality/sub_memu.ejs → app/view/quality/sub_menu.ejs


+ 3 - 9
app/view/quality/tender.ejs

@@ -1,15 +1,9 @@
 <% include ../tender/list_sub_menu.ejs %>
 <div class="panel-content">
     <div class="panel-title fluid">
-        <div class="title-main  d-flex justify-content-between">
+        <div class="title-main  d-flex">
             <% include ../tender/list_sub_mini_menu.ejs %>
-            <div class="d-inline-block mr-2">
-                <button type="button" class="btn btn-sm btn-light dropdown-toggle text-primary" data-toggle="dropdown">展开/收起</button>
-                <div class="dropdown-menu">
-                    <a class="dropdown-item tree-toggle" href="javascript:void(0);" data-item="open">展开所有</a>
-                    <a class="dropdown-item tree-toggle" href="javascript:void(0);" data-item="hide">收起所有</a>
-                </div>
-            </div>
+            <div class="d-inline-block" id="show-level"></div>
             <div class="ml-auto">
                 <% if (ctx.session.sessionUser.is_admin) { %>
                 <a class="btn btn-sm btn-primary mr-2" href="/sp/<%- ctx.subProject.id %>/quality/rule">设置状态规则</a>
@@ -51,4 +45,4 @@
             autoFlashHeight();
         }
     });
-</script>
+</script>

+ 30 - 0
app/view/sp_setting/manage.ejs

@@ -24,6 +24,7 @@
                     <a class="nav-item nav-link" data-toggle="tab" href="#tzpro" role="tab">投资进度</a>
                     <a class="nav-item nav-link" data-toggle="tab" href="#htgl" role="tab">合同管理</a>
                     <a class="nav-item nav-link" data-toggle="tab" href="#sgrz" role="tab">施工日志</a>
+                    <a class="nav-item nav-link" data-toggle="tab" href="#zlgl" role="tab">质量管理</a>
 <!--                    <a class="nav-item nav-link" data-toggle="tab" href="#subadmin" role="tab">标段管理员</a>-->
                     <div class="ml-auto" id="user-set" style="display: none">
                         <div class="row">
@@ -221,6 +222,33 @@
                             </table>
                         </div>
                     </div>
+                    <!--质量巡检 -->
+                    <div id="zlgl" class="tab-pane">
+                        <div class="col-8" style="max-width: 800px">
+                            <table class="table table-hover table-bordered table-sm">
+                                <thead class="text-center">
+                                <tr>
+                                    <th class="align-middle" rowspan="2">成员名称</th>
+                                    <th class="align-middle" rowspan="2">角色/职位</th>
+                                    <% for (const pb of permissionBlock) { %>
+                                        <th colspan="<%- pb.permission.filter(x => { return !x.isDefault; }).length %>"><%- pb.name %></th>
+                                    <% } %>
+                                    <th class="align-middle" rowspan="2">操作</th>
+                                </tr>
+                                <tr>
+                                    <% for (const pb of permissionBlock) { %>
+                                        <% for (const p of pb.permission) { %>
+                                            <% if (p.isDefault) continue; %>
+                                            <th><%- p.title %></th>
+                                        <% } %>
+                                    <% } %>
+                                </tr>
+                                </thead>
+                                <tbody id="quality-users">
+                                </tbody>
+                            </table>
+                        </div>
+                    </div>
                     <!--标段管理员 -->
                     <div id="subadmin" class="tab-pane">
                         <div class="col-6">
@@ -359,4 +387,6 @@
     const pid = '<%- pid %>';
     const uphlname = 'user_' + uid + '_pro_' + pid + '_tender_manage_list';
     let sp_lc, sp_type, sp_status, sp_status_list, cur_tenderid, cur_uid;
+    const permissionConst = JSON.parse('<%- JSON.stringify(permissionConst) %>');
+    const permissionBlock = JSON.parse('<%- JSON.stringify(permissionBlock) %>');
 </script>

+ 34 - 0
config/web.js

@@ -2241,6 +2241,40 @@ const JsFiles = {
                 ],
                 mergeFile: 'quality_lab',
             },
+            inspection: {
+                files: [
+                    '/public/js/decimal.min.js',
+                    '/public/js/math.min.js',
+                    '/public/js/component/menu.js',
+                    '/public/js/moment/moment.min.js',
+                ],
+                mergeFiles: [
+                    '/public/js/sub_menu.js',
+                    '/public/js/div_resizer.js',
+                    '/public/js/zh_calc.js',
+                    '/public/js/datepicker/datepicker.min.js',
+                    '/public/js/datepicker/datepicker.zh.js',
+                    '/public/js/quality_inspection.js',
+                ],
+                mergeFile: 'quality_inspection',
+            },
+            inspection_information: {
+                files: [
+                    '/public/js/decimal.min.js',
+                    '/public/js/math.min.js',
+                    '/public/js/component/menu.js',
+                    '/public/js/moment/moment.min.js',
+                ],
+                mergeFiles: [
+                    '/public/js/sub_menu.js',
+                    '/public/js/div_resizer.js',
+                    '/public/js/zh_calc.js',
+                    '/public/js/datepicker/datepicker.min.js',
+                    '/public/js/datepicker/datepicker.zh.js',
+                    '/public/js/quality_inspection_information.js',
+                ],
+                mergeFile: 'quality_inspection_information',
+            },
             rule: {
                 files: [
                     '/public/js/moment/moment.min.js',

文件差异内容过多而无法显示
+ 48 - 1376
sql/update.sql


文件差异内容过多而无法显示
+ 1391 - 0
sql/update20250928.sql