Browse Source

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

Tony Kang 8 tháng trước cách đây
mục cha
commit
ff22ee1957
100 tập tin đã thay đổi với 5960 bổ sung405 xóa
  1. 2 1
      app/base/base_budget_service.js
  2. 25 5
      app/base/base_controller.js
  3. 4 1
      app/const/audit.js
  4. 3 0
      app/const/code_rule.js
  5. 4 0
      app/const/shenpi.js
  6. 11 11
      app/const/source_type.js
  7. 6 1
      app/const/sp_page_show.js
  8. 2 0
      app/const/tender_info.js
  9. 7 3
      app/controller/budget_controller.js
  10. 3 0
      app/controller/change_controller.js
  11. 20 5
      app/controller/dashboard_controller.js
  12. 1 1
      app/controller/ledger_controller.js
  13. 1 1
      app/controller/profile_controller.js
  14. 72 25
      app/controller/quality_controller.js
  15. 2 1
      app/controller/report_archive_controller.js
  16. 544 0
      app/controller/safe_controller.js
  17. 138 25
      app/controller/spss_controller.js
  18. 16 0
      app/controller/stage_controller.js
  19. 22 15
      app/controller/sub_proj_setting_controller.js
  20. 37 0
      app/controller/wap_controller.js
  21. 25 7
      app/lib/budget_final.js
  22. 3 2
      app/lib/dsk.js
  23. 68 0
      app/lib/ledger.js
  24. 84 1
      app/lib/nls_token.js
  25. 2 2
      app/lib/rm/change.js
  26. 138 0
      app/lib/rm/spss.js
  27. 71 0
      app/lib/rm/tender.js
  28. 2 1
      app/lib/rm/tender_budget.js
  29. 35 4
      app/lib/ybp_tree.js
  30. 2 1
      app/middleware/change_check.js
  31. 3 1
      app/middleware/inspection_check.js
  32. 128 0
      app/middleware/safe_inspection_check.js
  33. 11 4
      app/middleware/stage_check.js
  34. 30 24
      app/public/js/budget_compare.js
  35. 11 10
      app/public/js/budget_detail.js
  36. 3 3
      app/public/js/change_information.js
  37. 34 7
      app/public/js/change_revise.js
  38. 19 12
      app/public/js/file_detail.js
  39. 1 1
      app/public/js/global.js
  40. 4 2
      app/public/js/ledger_bwtz.js
  41. 5 4
      app/public/js/path_tree.js
  42. 4 1
      app/public/js/payment_safe.js
  43. 45 0
      app/public/js/quality_info.js
  44. 49 1
      app/public/js/quality_inspection_information.js
  45. 2 2
      app/public/js/quality_tender.js
  46. 4 2
      app/public/js/revise.js
  47. 349 0
      app/public/js/safe_inspection.js
  48. 679 0
      app/public/js/safe_inspection_information.js
  49. 58 0
      app/public/js/safe_tender.js
  50. 0 5
      app/public/js/se_bonus.js
  51. 9 0
      app/public/js/setting.js
  52. 323 78
      app/public/js/setting_manage.js
  53. 5 0
      app/public/js/shares/ali_oss.js
  54. 15 2
      app/public/js/shares/db2full_code.js
  55. 2 1
      app/public/js/shares/dsk.js
  56. 1 0
      app/public/js/shares/export_excel.js
  57. 49 0
      app/public/js/shares/spss_stash.js
  58. 37 4
      app/public/js/shares/tender_permission.js
  59. 4 3
      app/public/js/shares/tender_select_multi.js
  60. 7 1
      app/public/js/shares/tenders2tree.js
  61. 2 2
      app/public/js/shenpi.js
  62. 48 10
      app/public/js/spss_compare_ledger.js
  63. 52 9
      app/public/js/spss_compare_stage.js
  64. 41 1
      app/public/js/spss_gather_ledger.js
  65. 43 1
      app/public/js/spss_gather_stage.js
  66. 59 0
      app/public/js/spss_gather_stage_extra.js
  67. 36 1
      app/public/js/spss_gather_stage_info.js
  68. 198 0
      app/public/js/spss_gather_stage_pay.js
  69. 73 3
      app/public/js/stage.js
  70. 67 0
      app/public/js/stage_audit.js
  71. 2 4
      app/public/js/stage_bwtz.js
  72. 3 2
      app/public/js/stage_pay.js
  73. 1 1
      app/public/js/tender_list_manage.js
  74. 10 0
      app/public/js/wap/global.js
  75. 31 7
      app/router.js
  76. 76 20
      app/service/change.js
  77. 3 0
      app/service/change_plan.js
  78. 2 1
      app/service/ledger.js
  79. 2 1
      app/service/pay_att.js
  80. 4 1
      app/service/payment_safe_bills.js
  81. 1 1
      app/service/phase_pay.js
  82. 2 0
      app/service/phase_pay_audit.js
  83. 2 1
      app/service/phase_pay_detail.js
  84. 14 15
      app/service/quality_inspection.js
  85. 36 0
      app/service/quality_inspection_audit.js
  86. 72 2
      app/service/report.js
  87. 18 2
      app/service/rpt_gather_memory.js
  88. 314 0
      app/service/safe_inspection.js
  89. 82 0
      app/service/safe_inspection_att.js
  90. 1221 0
      app/service/safe_inspection_audit.js
  91. 26 0
      app/service/shenpi_group.js
  92. 76 0
      app/service/spss_stash.js
  93. 20 0
      app/service/stage_audit.js
  94. 3 3
      app/service/stage_pos.js
  95. 18 10
      app/service/sub_proj_permission.js
  96. 16 4
      app/service/tender_permission.js
  97. 20 7
      app/view/budget/compare_modal.ejs
  98. 24 14
      app/view/change/apply_information_modal.ejs
  99. 1 1
      app/view/change/apply_modal.ejs
  100. 0 0
      app/view/change/modal.ejs

+ 2 - 1
app/base/base_budget_service.js

@@ -492,9 +492,10 @@ class BaseBudget extends TreeService {
         for (const subject of ybpData.subjects) {
             if (!subject.bills || subject.bills.length === 0) continue;
 
+            const parents = YbpTrees.YbpUtils.getSubjectParentPath(subject, ybpData.subjects);
             const ybpTree = new YbpTrees.YbpTree(ybpTreeSetting);
             ybpTree.loadDatas(subject.bills);
-            gatherTree.importTree(ybpTree, subject.project.name, null, needGcl ? null : filterGcl);
+            gatherTree.importTree(ybpTree, subject.project.name, parents, null, needGcl ? null : filterGcl);
         }
         gatherTree.sort(false);
         gatherTree.calculateAll();

+ 25 - 5
app/base/base_controller.js

@@ -11,7 +11,7 @@ const moment = require('moment');
 const messageType = require('../const/message_type');
 const Controller = require('egg').Controller;
 const maintainConst = require('../const/maintain');
-const otherProjectController = ['setting', 'file', 'profile'];
+const otherProjectController = ['setting', 'file', 'profile', 'devTest'];
 
 class BaseController extends Controller {
 
@@ -37,11 +37,17 @@ class BaseController extends Controller {
             menuList.datacollect.display = ctx.subProject.showDataCollect || false;
             menuList.file.display = ctx.subProject.page_show.openFile || false;
             menuList.contract.display = ctx.subProject.page_show.openContract || ctx.subProject.page_show.openTenderContract || false;
+            menuList.contract.children.find(item => item.msg === 'subproj').display = ctx.subProject.page_show.openContract || false;
+            menuList.contract.children.find(item => item.msg === 'tender').display = ctx.subProject.page_show.openTenderContract || false;
             menuList.financial.display = ctx.subProject.page_show.openFinancial || false;
             menuList.budget.display = ctx.subProject.page_show.openBudget || false;
             menuList.payment.display = ctx.subProject.page_show.openPayment || false;
-            menuList.quality.display = ctx.subProject.page_show.quality || false;
-            menuList.inspection.display = ctx.subProject.page_show.quality || false;
+            menuList.quality.display = ctx.subProject.page_show.quality || ctx.subProject.page_show.qualityInspection || false;
+            menuList.quality.children.find(item => item.msg === 'quality').display = ctx.subProject.page_show.quality || false;
+            menuList.quality.children.find(item => item.msg === 'inspection').display = ctx.subProject.page_show.qualityInspection || false;
+            menuList.safe.display = ctx.subProject.page_show.safePayment || ctx.subProject.page_show.safeInspection || false;
+            menuList.safe.children.find(item => item.msg === 'payment').display = ctx.subProject.page_show.safePayment || false;
+            menuList.safe.children.find(item => item.msg === 'inspection').display = ctx.subProject.page_show.safeInspection || false;
             for (const index in menuList) {
                 const im = menuList[index];
                 if (!im.url) {
@@ -55,10 +61,24 @@ class BaseController extends Controller {
                                 child.url = `/sp/${ctx.subProject.id}/contract/tender`;
                             }
                         }
+                    } else if (index === 'quality') {
+                        for (const child of im.children) {
+                            if (child.msg === 'quality') {
+                                child.url = `/sp/${ctx.subProject.id}/quality`;
+                            } else if (child.msg === 'inspection') {
+                                child.url = `/sp/${ctx.subProject.id}/quality/inspection`;
+                            }
+                        }
+                    } else if (index === 'safe') {
+                        for (const child of im.children) {
+                            if (child.msg === 'payment') {
+                                child.url = `/sp/${ctx.subProject.id}/safe`;
+                            } else if (child.msg === 'inspection') {
+                                child.url = `/sp/${ctx.subProject.id}/safe/inspection`;
+                            }
+                        }
                     } else if (index === 'financial') {
                         im.url = `/sp/${ctx.subProject.id}/${im.controller}/${ctx.subProject.financialToUrl}`;
-                    } else if (index === 'inspection') {
-                        im.url = `/sp/${ctx.subProject.id}/quality/inspection`;
                     } else {
                         im.url = `/sp/${ctx.subProject.id}/${im.controller}`;
                     }

+ 4 - 1
app/const/audit.js

@@ -1379,7 +1379,7 @@ const financial = (function() {
     return { status, statusString, statusClass, auditString, auditStringClass, auditProgress, auditProgressClass, filter, statusButton, statusButtonClass };
 })();
 
-// 质量巡检
+// 质量巡检 & 安全巡检
 const inspection = (function() {
     const status = {
         uncheck: 1, // 待审批
@@ -1503,6 +1503,7 @@ const inspection = (function() {
     statusButton[status.checking] = '审批';
     statusButton[status.checked] = '';
     statusButton[status.checkNo] = '重新上报';
+    statusButton[status.rectification] = '整改';
     // statusButton[status.revise] = '修订';
 
     // 按钮样式
@@ -1511,6 +1512,7 @@ const inspection = (function() {
     statusButtonClass[status.checking] = 'btn-success';
     statusButtonClass[status.checked] = '';
     statusButtonClass[status.checkNo] = 'btn-warning';
+    statusButtonClass[status.rectification] = 'btn-success';
     // statusButtonClass[status.revise] = 'btn-warning';
     return { status, statusString, statusClass, auditString, auditStringClass, auditProgress, auditProgressClass, filter, statusButton, statusButtonClass };
 })();
@@ -1530,6 +1532,7 @@ const pushType = {
     financial: 11,
     phasePay: 12,
     inspection: 13,
+    safeInspection: 14,
 };
 
 module.exports = {

+ 3 - 0
app/const/code_rule.js

@@ -16,6 +16,7 @@ const ruleType = {
     apply: 5,
     plan: 6,
     inspection: 7,
+    safe_inspection: 8,
 };
 const ruleField = [];
 ruleField[ruleType.measure] = 'm_rule';
@@ -25,6 +26,7 @@ ruleField[ruleType.will] = 'will';
 ruleField[ruleType.apply] = 'apply';
 ruleField[ruleType.plan] = 'plan';
 ruleField[ruleType.inspection] = 'inspection';
+ruleField[ruleType.safe_inspection] = 'safe_inspection';
 const ruleString = [];
 ruleString[ruleType.measure] = 'measure';
 ruleString[ruleType.change] = 'change';
@@ -33,6 +35,7 @@ ruleString[ruleType.will] = 'will';
 ruleString[ruleType.apply] = 'apply';
 ruleString[ruleType.plan] = 'plan';
 ruleString[ruleType.inspection] = 'inspection';
+ruleString[ruleType.safe_inspection] = 'safe_inspection';
 
 
 // 中间计量编号规则

+ 4 - 0
app/const/shenpi.js

@@ -19,6 +19,8 @@ const sp_type = {
     // financial: 8, // 资金支付审批流程设置不出现在这里,但请别用8这个类型控制审批流程,因为数据库我用了8来表示资金支付固定审批流
     phasePay: 9,
     inspection: 10,
+    safe_inspection: 11,
+    safe_payment: 12,
 };
 const sp_other_type = {
     financial: 8,
@@ -42,6 +44,8 @@ const sp_lc = [
     // { code: 'financial', type: sp_type.financial, name: '资金支付审批' },
     { code: 'phasePay', type: sp_type.phasePay, name: '合同支付审批' },
     { code: 'inspection', type: sp_type.inspection, name: '质量巡检审批' },
+    { code: 'safe_payment', type: sp_type.payment, name: '安全计量审批' },
+    { code: 'safe_inspection', type: sp_type.inspection, name: '安全巡检审批' },
 ];
 
 const sp_status = {

+ 11 - 11
app/const/source_type.js

@@ -1,17 +1,17 @@
 'use strict';
 
 const sourceTypeData = [
-    { id: 1, name: '标段&期', key: 'tender' },
-    { id: 10, name: '预付款', key: 'advance' },
-    { id: 20, name: '变更令', key: 'change' },
-    { id: 21, name: '变更方案', key: 'change_plan' },
-    { id: 22, name: '变更立项', key: 'change_project' },
-    { id: 23, name: '变更申请', key: 'change_apply' },
-    { id: 30, name: '材料调差', key: 'material' },
-    { id: 100, name: '支付审批', key: 'payment' },
-    { id: 101, name: '安全生产费', key: 'payment_safe' },
-    { id: 200, name: '动态投资', key: 'budget' },
-    { id: 300, name: '合同管理', key: 'contract_management' },
+    { id: 1, name: '标段&期', key: 'tender', params: { tender_id: 1, stage_id: 1 } },
+    { id: 10, name: '预付款', key: 'advance', params: { advance_id: 1 } },
+    { id: 20, name: '变更令', key: 'change', params: { change_id: 'uuid' } },
+    { id: 21, name: '变更方案', key: 'change_plan', params: { change_plan_id: 1 } },
+    { id: 22, name: '变更立项', key: 'change_project', params: { change_project_id: 1 } },
+    { id: 23, name: '变更申请', key: 'change_apply', params: { change_apply_id: 1 } },
+    { id: 30, name: '材料调差', key: 'material', params: { material_id: 1 } },
+    { id: 100, name: '支付审批', key: 'payment', params: { tender_id: 1, detail_id: 1 } },
+    { id: 101, name: '安全生产费', key: 'payment_safe', params: { tender_id: 1, detail_id: 1 } },
+    { id: 200, name: '动态投资', key: 'budget', params: { sp_id: 'uuid', budget_id: 1 } },
+    { id: 300, name: '合同管理', key: 'contract_management', params: {} },
     // { id: 301, name: '标段合同', key: 'tender_contract' },
 ];
 

+ 6 - 1
app/const/sp_page_show.js

@@ -17,7 +17,7 @@ const pageStatus = {
 const managerPageControl = [
     { title: '项目概况', name: 'openInfo', value: pageStatus.show, type: 'checkbox' },
     { title: '决策大屏', name: 'openDataCollect', value: pageStatus.show, type: 'checkbox', tip: '开启后,前台配置用户权限后方可显示' },
-    { title: '合同管理', name: 'openContract', value: pageStatus.show, type: 'checkbox' },
+    { title: '项目合同', name: 'openContract', value: pageStatus.show, type: 'checkbox' },
     { title: '资料管理', name: 'openFile', value: pageStatus.show, type: 'checkbox' },
     { title: '动态投资', name: 'openBudget', value: pageStatus.show, type: 'checkbox', tip: '开启后,前台配置用户权限后方可显示' },
     { title: '支付审批', name: 'openPayment', value: pageStatus.show, type: 'checkbox', tip: '开启后,前台配置用户权限后方可显示' },
@@ -34,6 +34,9 @@ const tenderPageControl = [
     { title: '施工日志', name: 'openConstruction', value: pageStatus.show, type: 'checkbox' },
     { title: '合同管理', name: 'openTenderContract', value: pageStatus.show, type: 'checkbox' },
     { title: '质量管理', name: 'quality', value: pageStatus.show, type: 'checkbox' },
+    { title: '质量巡检', name: 'qualityInspection', value: pageStatus.show, type: 'checkbox' },
+    { title: '安全计量', name: 'safePayment', value: pageStatus.show, type: 'checkbox' },
+    { title: '安全巡检', name: 'safeInspection', value: pageStatus.show, type: 'checkbox' },
 ];
 // 报表相关开关
 const reportPageControl = [
@@ -111,6 +114,8 @@ const defaultSetting = {
     correctCalcContractTp: 0,
     quality: 1,
     qualityInspection: 1,
+    safePayment: 1,
+    safeInspection: 1,
 };
 
 module.exports = {

+ 2 - 0
app/const/tender_info.js

@@ -195,6 +195,8 @@ const defaultInfo = {
         material: 1,
         phasePay: 1,
         inspection: 1,
+        safe_payment: 1,
+        safe_inspection: 1,
     },
     ledger_check: {
         same_code: true,

+ 7 - 3
app/controller/budget_controller.js

@@ -131,6 +131,10 @@ module.exports = app => {
         async compareFinal(ctx) {
             try {
                 const data = JSON.parse(ctx.request.body.data);
+                if (data.final_type !== ctx.budget.final_type) {
+                    await ctx.service.budget.defaultUpdate({ id: ctx.budget.id, final_type: data.final_type });
+                    ctx.budget.final_type = data.final_type;
+                }
                 if (ctx.budget.final_id && data.final_id !== ctx.budget.final_id) {
                     const final = await ctx.service.budgetFinal.getAllDataByCondition({ where: { final_id: ctx.budget.final_id } });
                     const finalInfo = await ctx.service.budgetFinalList.getFinal(ctx.budget.final_id);
@@ -162,7 +166,7 @@ module.exports = app => {
                     {title: '图册号', colSpan: '1', rowSpan: '2', field: 'drawing_code', hAlign: 0, width: 100, formatter: '@'},
                     {title: '备注', colSpan: '1', rowSpan: '2', field: 'memo', hAlign: 0, width: 100, formatter: '@'},
                 ],
-                emptyRows: 3,
+                emptyRows: 0,
                 headRows: 2,
                 headRowHeight: [25, 25],
                 defaultRowHeight: 21,
@@ -242,7 +246,7 @@ module.exports = app => {
             }
         }
         async _addStd(relaService, data) {
-            if ((isNaN(data.id) || data.id <= 0) || !data.stdType || !data.stdNode) throw '参数错误';
+            if (!data.stdType || !data.stdNode) throw '参数错误';
 
             let stdLib, addType;
             switch (data.stdType) {
@@ -373,7 +377,7 @@ module.exports = app => {
                 const ybpData = { header: {}, subjects: [] };
                 const dsk = new DSK(ctx);
                 for (const s of data.subjects) {
-                    const subject = { project: { name: s.name, compilationID: s.compilationId }, rations: [], };
+                    const subject = { project: { ID: s.ID, parentID: s.parentID, type: s.type, name: s.name, compilationID: s.compilationId }, rations: [], };
                     subject.bills = await dsk.getProjectBills(s.compilationId, s.subjectId);
                     // 拼凑fees.marketCommon.totalFee,fees.marketLabour.totalFee防止出错
                     subject.bills.forEach(b => {

+ 3 - 0
app/controller/change_controller.js

@@ -253,6 +253,9 @@ module.exports = app => {
                         case codeRuleConst.ruleField[codeRuleConst.ruleType.inspection]:
                             changeCount = await ctx.service.qualityInspection.count({ tid: tenderId });
                             break;
+                        case codeRuleConst.ruleField[codeRuleConst.ruleType.safe_inspection]:
+                            changeCount = await ctx.service.safeInspection.count({ tid: tenderId });
+                            break;
                         default:
                             break;
                     }

+ 20 - 5
app/controller/dashboard_controller.js

@@ -41,6 +41,7 @@ module.exports = app => {
             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 allAuditSafeInspections = await ctx.service.safeInspectionAudit.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);
@@ -54,6 +55,7 @@ module.exports = app => {
             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 auditShenpiSafeInspection = await ctx.service.safeInspectionAudit.getDonesByAudit(ctx.session.sessionUser.accountId);
             const dashboardStatus = {
                 all: 0,
                 dashboard: 0,
@@ -72,6 +74,7 @@ module.exports = app => {
                     payment: 0,
                     financial: 0,
                     inspection: 0,
+                    safeInspection: 0,
                 },
             };
             let noticeList = [];
@@ -101,8 +104,10 @@ 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 }) : [];
+                const spInspection = subProject.page_show.qualityInspection ? ctx.helper._.filter(auditShenpiInspection, { spid: subProject.id }) : [];
                 noticeList.push(...ctx.helper.addKeyValue4ObjArray(spInspection, 'shenpi_type', 'inspection'));
+                const spSafeInspection = subProject.page_show.safeInspection ? ctx.helper._.filter(auditShenpiSafeInspection, { spid: subProject.id }) : [];
+                noticeList.push(...ctx.helper.addKeyValue4ObjArray(spSafeInspection, 'shenpi_type', 'safeInspection'));
             }
             const noticeDayList = [];
             noticeList = ctx.helper._.orderBy(noticeList, ['shenpi_time'], ['desc']);
@@ -138,6 +143,8 @@ module.exports = app => {
             dashboardShenpis.push(...auditFinancials);
             const auditInspections = await this.auditSet(ctx, allAuditInspections, subProjects, dashboardStatus, 'inspection');
             dashboardShenpis.push(...auditInspections);
+            const auditSafeInspections = await this.auditSet(ctx, allAuditSafeInspections, subProjects, dashboardStatus, 'safeInspection');
+            dashboardShenpis.push(...auditSafeInspections);
             // console.log(ctx.helper._.orderBy(dashboardShenpis, ['start_audit', 'shenpi_time'], ['desc', 'desc']));
             const projectData = await ctx.service.project.getDataById(ctx.session.sessionProject.id);
             // 获取销售人员数据
@@ -272,9 +279,13 @@ module.exports = app => {
                             calcTime = t.fpcstatus !== auditConst[type].status.checkNo ? t.begin_time : t.end_time;
                             break;
                         case 'inspection':
-                            if (!sp.page_show.quality) closeType = true;
+                            if (!sp.page_show.qualityInspection) closeType = true;
                             calcTime = t.status !== auditConst[type].status.checkNo ? t.begin_time : t.end_time;
                             break;
+                        case 'safeInspection':
+                            if (!sp.page_show.safeInspection) closeType = true;
+                            calcTime = t.status !== auditConst.inspection.status.checkNo ? t.begin_time : t.end_time;
+                            break;
                         default:
                             closeType = true;
                     }
@@ -320,7 +331,8 @@ 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 auditInspections = ctx.subProject.page_show.qualityInspection ? await ctx.service.qualityInspectionAudit.getAuditInspection(ctx.session.sessionUser.accountId, ctx.subProject.id) : [];
+            const auditSafeInspections = ctx.subProject.page_show.safeInspection ? await ctx.service.safeInspectionAudit.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);
@@ -350,7 +362,8 @@ 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: '质量巡检' });
+            if (ctx.subProject.page_show.qualityInspection) shenpi_count.push({ count: await ctx.service.qualityInspectionAudit.getCountByChecked(ctx.session.sessionUser.accountId, ctx.subProject.id), name: '质量巡检' });
+            if (ctx.subProject.page_show.safeInspection) shenpi_count.push({ count: await ctx.service.safeInspectionAudit.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 = [
@@ -364,7 +377,8 @@ 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,
+                ctx.subProject.page_show.qualityInspection ? await ctx.service.qualityInspectionAudit.getLastEndTimeByChecked(ctx.session.sessionUser.accountId, ctx.subProject.id) : null,
+                ctx.subProject.page_show.safeInspection ? await ctx.service.safeInspectionAudit.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));
@@ -382,6 +396,7 @@ module.exports = app => {
                 auditStageAss,
                 auditFinancials,
                 auditInspections,
+                auditSafeInspections,
                 shenpi_count,
                 total_count,
                 last_day: ctx.helper.calcDayNum(last_time),

+ 1 - 1
app/controller/ledger_controller.js

@@ -571,7 +571,7 @@ module.exports = app => {
                 const dsk = new DSK(ctx);
                 for (const s of data.subjects) {
                     console.log(s);
-                    const subject = { project: { name: s.name, compilationID: s.compilationId }, rations: [], };
+                    const subject = { project: { ID: s.ID, parentID: s.parentID, type: s.type, name: s.name, compilationID: s.compilationId }, rations: [], };
                     subject.bills = await dsk.getProjectBills(s.compilationId, s.subjectId);
                     // 拼凑fees.marketCommon.totalFee,fees.marketLabour.totalFee防止出错
                     subject.bills.forEach(b => {

+ 1 - 1
app/controller/profile_controller.js

@@ -380,7 +380,7 @@ module.exports = app => {
                         if (!(/^1[3456789]\d{9}$/.test(data.mobile))) {
                             throw '手机号码格式有误';
                         }
-                        await dsk.sendSms(data.mobile);
+                        await dsk.sendSms(data.mobile, data.captchaVerifyParam);
                         break;
                     case 'bind':
                         if (dskAccount.dsk_account) {

+ 72 - 25
app/controller/quality_controller.js

@@ -70,7 +70,7 @@ module.exports = app => {
             } catch (err) {
                 ctx.log(err);
                 ctx.postError(err, '无法查看质量管理数据');
-                ctx.redirect('/dashboard');
+                ctx.redirect(`/sp/${ctx.subProject.id}/dashboard`);
             }
         }
 
@@ -106,7 +106,7 @@ module.exports = app => {
             } catch (err) {
                 ctx.log(err);
                 ctx.postError(err, '无法查看质量巡检数据');
-                ctx.redirect('/dashboard');
+                ctx.redirect(`/sp/${ctx.subProject.id}/dashboard`);
             }
         }
 
@@ -148,13 +148,19 @@ module.exports = app => {
                 }
                 let uids;
                 let auditList = [];
+                const tenderPermissionKeys = ['quality', 'inspection', 'safe_inspection', 'safe_payment'];
+                const tPsKeys = {
+                    quality: ['quality', 'inspection'],
+                    safe: ['safe_inspection', 'safe_payment'],
+                };
                 switch (data.type) {
                     case 'add-audit':
-                        if (!data.key || !['quality', 'inspection'].includes(data.key)) throw '参数有误';
+                        if (!data.key || !tenderPermissionKeys.includes(data.key) || !Object.keys(tPsKeys).includes(data.key)) throw '参数有误';
                         // // 判断用户是单个还是数组
                         uids = data.id instanceof Array ? data.id : [data.id];
                         // // 判断该用户的组是否已加入到表中,已加入则提示无需添加
-                        auditList = await ctx.service.tenderPermission.getPartsPermission(tenderInfo.id, [data.key]);
+                        const insertKeys = data.together ? tPsKeys[data.key] : [data.key];
+                        auditList = await ctx.service.tenderPermission.getPartsPermission(tenderInfo.id, insertKeys);
                         const addAidList = ctx.helper._.difference(uids, ctx.helper._.map(auditList, 'uid'));
                         if (addAidList.length === 0) {
                             throw '用户已存在成员管理中,无需重复添加';
@@ -163,17 +169,20 @@ module.exports = app => {
                         const insert_members = [];
                         for (const account of accountList) {
                             const insert_member = { uid: account.id };
-                            insert_member[data.key] = ['1'];
+                            for (const key of insertKeys) {
+                                insert_member[key] = ['1'];
+                            }
                             insert_members.push(insert_member);
                         }
-                        await ctx.service.tenderPermission.saveOnePermission(tenderInfo.id, ctx.helper._.map(accountList, 'id'), insert_members, [data.key]);
-                        responseData.data = await ctx.service.tenderPermission.getPartsPermission(tenderInfo.id, [data.key]);
+                        await ctx.service.tenderPermission.saveOnePermission(tenderInfo.id, ctx.helper._.map(accountList, 'id'), insert_members, insertKeys);
+                        responseData.data = await ctx.service.tenderPermission.getPartsPermission(tenderInfo.id, insertKeys);
                         break;
                     case 'del-audit':
-                        if (!data.key || !['quality', 'inspection'].includes(data.key)) throw '参数有误';
+                        if (!data.key || !tenderPermissionKeys.includes(data.key) || !Object.keys(tPsKeys).includes(data.key)) throw '参数有误';
                         uids = data.id instanceof Array ? data.id : [data.id];
                         if (uids.length === 0) throw '没有选择要移除的用户';
-                        auditList = await ctx.service.tenderPermission.getPartsPermission(tenderInfo.id, [data.key]);
+                        const deleteKeys = data.together ? tPsKeys[data.key] : [data.key];
+                        auditList = await ctx.service.tenderPermission.getPartsPermission(tenderInfo.id, deleteKeys);
                         // 判断uids和auditList中是否有相同的uid
                         const commonUids = ctx.helper._.intersection(uids, ctx.helper._.map(auditList, 'uid'));
                         if (commonUids.length === 0) {
@@ -182,19 +191,21 @@ module.exports = app => {
                         const del_members = [];
                         for (const uid of uids) {
                             const del_member = { uid };
-                            del_member[data.key] = [];
+                            for (const key of deleteKeys) {
+                                del_member[key] = [];
+                            }
                             del_members.push(del_member);
                         }
-                        await ctx.service.tenderPermission.saveOnePermission(tenderInfo.id, uids, del_members, [data.key]);
-                        responseData.data = await ctx.service.tenderPermission.getPartsPermission(tenderInfo.id, [data.key]);
+                        await ctx.service.tenderPermission.saveOnePermission(tenderInfo.id, uids, del_members, deleteKeys);
+                        responseData.data = await ctx.service.tenderPermission.getPartsPermission(tenderInfo.id, deleteKeys);
                         break;
                     case 'save-permission':
-                        if (!data.key || !['quality', 'inspection'].includes(data.key)) throw '参数有误';
+                        if (!data.key || !tenderPermissionKeys.includes(data.key)) throw '参数有误';
                         uids = data.uid instanceof Array ? data.uid : [data.uid];
                         await ctx.service.tenderPermission.saveOnePermission(tenderInfo.id, uids, data.members, [data.key]);
                         break;
                     case 'list':
-                        if (!data.key || !['quality', 'inspection'].includes(data.key)) throw '参数有误';
+                        if (!data.key || !tenderPermissionKeys.includes(data.key)) throw '参数有误';
                         responseData.data = await ctx.service.tenderPermission.getPartsPermission(tenderInfo.id, [data.key]);
                         break;
                     default: throw '参数有误';
@@ -306,15 +317,19 @@ module.exports = app => {
                 default: throw '未知数据类型';
             }
         }
+        async _load(data) {
+            if (!data.filter) throw '参数错误';
+            const filter = data.filter.split(';');
+            const result = {};
+            for (const f of filter) {
+                result[f] = await this._loadData(f, data, result);
+            }
+            return result;
+        }
         async load(ctx) {
             try {
                 const data = JSON.parse(ctx.request.body.data);
-                if (!data.filter) throw '参数错误';
-                const filter = data.filter.split(';');
-                const result = {};
-                for (const f of filter) {
-                    result[f] = await this._loadData(f, data, result);
-                }
+                const result = await this._load(data);
                 ctx.body = { err: 0, msg: '', data: result };
             } catch (err) {
                 ctx.log(err);
@@ -530,6 +545,34 @@ module.exports = app => {
             }
         }
 
+
+        async _forceRefreshStatus() {
+            const qualities = await this.ctx.service.quality.getAllDataByCondition({ where: { tid: this.ctx.tender.id } });
+            for (const q of qualities) {
+                await this.ctx.service.quality.loadQualityDetail(q);
+                await this.ctx.service.quality.checkQualityStatusAndSave(q);
+            }
+        }
+        async force(ctx) {
+            try {
+                if (!ctx.subProject.page_show.quality) throw '该功能已关闭';
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.type) throw '参数错误';
+                switch (data.type) {
+                    case 'all':
+                        await this._forceRefreshStatus();
+                        break;
+                    default: throw '参数错误';
+                }
+                const result = data.load ? this._load(data.load) : {};
+                ctx.body = { err: 0, msg: '', data: result };
+            } catch (err) {
+                console.log(err);
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '操作失败');
+            }
+        }
+
         /**
          * 变更管理 页面 (Get)
          *
@@ -565,8 +608,12 @@ module.exports = app => {
                 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 });
+                for (const c of inspectionList) {
+                    c.attList = ctx.helper._.filter(allAttList, { qiid: c.id });
+                    c.curAuditors = await ctx.service.qualityInspectionAudit.getAuditorsByStatus(c.id, c.status, c.times);
+                    if (c.status === auditConst.inspection.status.checkNoPre) {
+                        c.curAuditors2 = await ctx.service.qualityInspectionAudit.getAuditorsByStatus(c.id, auditConst.inspection.status.checking, c.times);
+                    }
                 }
                 // 分页相关
                 const page = ctx.page;
@@ -614,7 +661,7 @@ module.exports = app => {
                 const renderData = {
                     moment,
                     tender,
-                    permission: ctx.permission.quality,
+                    permission: ctx.permission.inspection,
                     rule_type,
                     codeRule,
                     dealCode: ctx.tender.info.deal_info.dealCode,
@@ -633,8 +680,8 @@ module.exports = app => {
                 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`);
+                ctx.postError(err, '无法查看质量巡检数据');
+                ctx.redirect(`/sp/${ctx.subProject.id}/quality/inspection`);
             }
         }
 

+ 2 - 1
app/controller/report_archive_controller.js

@@ -918,6 +918,7 @@ module.exports = app => {
                 if (!(oss_result && oss_result.url && oss_result.res.status === 200)) {
                     throw '上传文件失败';
                 }
+                if (stream) await sendToWormhole(stream);
                 const versionId = oss_result.res.headers['x-oss-version-id'];
                 // 记录签名和保存
                 await ctx.service.netcasignLog.add(uuid, role, ctx.session.sessionUser.accountId, versionId);
@@ -925,7 +926,7 @@ module.exports = app => {
                 ctx.body = { err: 0, msg: '', data: signLogList };
             } catch (err) {
                 // 必须将上传的文件流消费掉,要不然浏览器响应会卡死
-                await sendToWormhole(stream);
+                if (stream) await sendToWormhole(stream);
                 this.log(err);
                 ctx.body = { err: 1, msg: err.toString(), data: null };
             }

+ 544 - 0
app/controller/safe_controller.js

@@ -0,0 +1,544 @@
+'use strict';
+
+/**
+ * 标段管理控制器
+ *
+ * @author Mai
+ * @date 2025/7/17
+ * @version
+ */
+
+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 SafeController extends app.BaseController {
+        constructor(ctx) {
+            super(ctx);
+            ctx.showProject = true;
+            // ctx.showTitle = true;
+        }
+
+        loadMenu(ctx) {
+            super.loadMenu(ctx);
+            // 虚拟menu,以保证标题显示正确
+            ctx.menu = {
+                name: '安全管理',
+                display: false,
+                caption: '安全管理',
+                controller: 'safe',
+            };
+        }
+
+        async tender(ctx) {
+            try {
+                if (!ctx.subProject.page_show.safePayment) throw '该功能已关闭';
+
+                const renderData = {
+                    is_inspection: ctx.url.includes('inspection') ? 1 : 0,
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.safe.tender),
+                };
+
+                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 } });
+                const accountGroupList = unitList.map(item => {
+                    const groupList = accountList.filter(item1 => item1.company === item.name);
+                    return { groupName: item.name, groupList };
+                }).filter(x => { return x.groupList.length > 0; });
+                // 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, 'safe_payment', 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;
+                renderData.permissionConst = ctx.service.tenderPermission.partPermissionConst('safe_payment');
+                renderData.permissionBlock = ctx.service.tenderPermission.partPermissionBlock('safe_payment');
+                await this.layout('safe/tender.ejs', renderData, 'safe/tender_modal.ejs');
+            } catch (err) {
+                ctx.log(err);
+                ctx.postError(err, '无法查看安全计量数据');
+                ctx.redirect(`/sp/${ctx.subProject.id}/dashboard`);
+            }
+        }
+
+        async inspectionTender(ctx) {
+            try {
+                if (!ctx.subProject.page_show.safeInspection) throw '该功能已关闭';
+
+                const renderData = {
+                    is_inspection: ctx.url.includes('inspection') ? 1 : 0,
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.safe.tender),
+                };
+
+                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 } });
+                const accountGroupList = unitList.map(item => {
+                    const groupList = accountList.filter(item1 => item1.company === item.name);
+                    return { groupName: item.name, groupList };
+                }).filter(x => { return x.groupList.length > 0; });
+                // 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, 'safe_inspection', 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;
+                renderData.permissionConst = ctx.service.tenderPermission.partPermissionConst('safe_inspection');
+                renderData.permissionBlock = ctx.service.tenderPermission.partPermissionBlock('safe_inspection');
+                await this.layout('safe/tender.ejs', renderData, 'safe/tender_modal.ejs');
+            } catch (err) {
+                ctx.log(err);
+                ctx.postError(err, '无法查看安全巡检数据');
+                ctx.redirect(`/sp/${ctx.subProject.id}/dashboard`);
+            }
+        }
+
+        /**
+         * 变更管理 页面 (Get)
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async inspection(ctx) {
+            try {
+                if (!ctx.subProject.page_show.safeInspection) 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}/safe/inspection`);
+            }
+        }
+
+        // 质量巡检单功能
+        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.safeInspection.getCountByStatus(ctx.tender.id, filter.status.pending);
+                filter.count[filter.status.uncheck] = await ctx.service.safeInspection.getCountByStatus(ctx.tender.id, filter.status.uncheck);
+                filter.count[filter.status.checking] = await ctx.service.safeInspection.getCountByStatus(ctx.tender.id, filter.status.checking);
+                filter.count[filter.status.rectification] = await ctx.service.safeInspection.getCountByStatus(ctx.tender.id, filter.status.rectification);
+                filter.count[filter.status.checked] = await ctx.service.safeInspection.getCountByStatus(ctx.tender.id, filter.status.checked);
+                filter.count[filter.status.checkStop] = await ctx.service.safeInspection.getCountByStatus(ctx.tender.id, filter.status.checkStop);// await ctx.service.change.pendingDatas(tender.id, ctx.session.sessionUser.accountId);
+                const inspectionList = await ctx.service.safeInspection.getListByStatus(ctx.tender.id, status, 1, sorts, orders);
+                const total = await ctx.service.safeInspection.getCountByStatus(ctx.tender.id, status);
+                const allAttList = inspectionList.length > 0 ? await ctx.service.safeInspectionAtt.getAllAtt(ctx.tender.id, ctx.helper._.map(inspectionList, 'id')) : [];
+                for (const c of inspectionList) {
+                    c.attList = ctx.helper._.filter(allAttList, { qiid: c.id });
+                    c.curAuditors = await ctx.service.safeInspectionAudit.getAuditorsByStatus(c.id, c.status, c.times);
+                    if (c.status === auditConst.inspection.status.checkNoPre) {
+                        c.curAuditors2 = await ctx.service.safeInspectionAudit.getAuditorsByStatus(c.id, auditConst.inspection.status.checking, c.times);
+                    }
+                }
+                // 分页相关
+                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 = 'safe_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.safe_inspection,
+                    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.safe.inspection),
+                    pageInfo,
+                };
+                await this.layout('safe/inspection.ejs', renderData, 'safe/inspection_modal.ejs');
+            } catch (err) {
+                ctx.log(err);
+                ctx.postError(err, '无法查看安全巡检数据');
+                ctx.redirect(`/sp/${ctx.subProject.id}/safe/inspection`);
+            }
+        }
+
+        /**
+         * 新增变更申请 (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.safeInspection.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.safeInspection.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.safeInspectionAtt.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.safe.inspection_information),
+                    preUrl: `/sp/${ctx.subProject.id}/safe/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('safe/inspection_information.ejs', renderData, 'safe/inspection_information_modal.ejs');
+            } catch (err) {
+                this.log(err);
+                ctx.redirect(`/sp/${ctx.subProject.id}/safe/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.safeInspection.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.safeInspectionAudit.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.safeInspectionAudit.addAuditor(ctx.inspection.id, id, ctx.inspection.times);
+                        if (!result) {
+                            throw '添加审核人失败';
+                        }
+                        reponseData.data = await ctx.service.safeInspectionAudit.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.safeInspectionAudit.deleteAuditor(ctx.inspection.id, id2, ctx.inspection.times);
+                        if (!result2) {
+                            throw '移除审核人失败';
+                        }
+                        reponseData.data = await ctx.service.safeInspectionAudit.getAuditors(ctx.inspection.id, ctx.inspection.times);
+                        break;
+                    case 'start-inspection':
+                        if (ctx.inspection.readOnly) {
+                            throw '当前状态不可提交';
+                        }
+                        await ctx.service.safeInspectionAudit.start(ctx.inspection.id, ctx.inspection.times);
+                        break;
+                    case 'del-inspection':
+                        if (ctx.inspection.readOnly) {
+                            throw '当前状态不可删除';
+                        }
+                        await ctx.service.safeInspection.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.safeInspectionAudit.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.safeInspectionAudit.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()}/safe_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.safeInspectionAtt.saveFileMsgToDb(payload);
+                // 将最新的当前标段的所有文件信息返回
+                const data = await ctx.service.safeInspectionAtt.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.safeInspectionAtt.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.safeInspectionAtt.delete(id);
+                } else {
+                    throw '不存在该文件';
+                }
+                const data = await ctx.service.safeInspectionAtt.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.safeInspectionAtt.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 SafeController;
+};

+ 138 - 25
app/controller/spss_controller.js

@@ -30,8 +30,10 @@ module.exports = app => {
         async info(ctx) {
             try {
                 const categoryData = await this.ctx.service.category.getAllCategory(this.ctx.subProject);
+                const stashList = await this.ctx.service.spssStash.getSpssStashList(this.ctx.subProject.id, 'gather_info');
                 const renderData = {
                     categoryData,
+                    stashList,
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.spss.gatherInfo)
                 };
                 await this.layout('spss/gather_info.ejs', renderData, 'spss/gather_info_modal.ejs');
@@ -43,11 +45,13 @@ module.exports = app => {
         async gatherLedger(ctx) {
             try {
                 const categoryData = await this.ctx.service.category.getAllCategory(this.ctx.subProject);
+                const stashList = await this.ctx.service.spssStash.getSpssStashList(this.ctx.subProject.id, 'gather_ledger');
                 const renderData = {
                     categoryData,
+                    stashList,
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.spss.gatherLedger)
                 };
-                await this.layout('spss/gather_ledger.ejs', renderData, 'spss/spss_select_modal.ejs');
+                await this.layout('spss/gather_ledger.ejs', renderData, 'spss/spss_modal.ejs');
             } catch (err) {
                 ctx.helper.log(err);
             }
@@ -56,11 +60,13 @@ module.exports = app => {
         async gatherStage(ctx) {
             try {
                 const categoryData = await this.ctx.service.category.getAllCategory(this.ctx.subProject);
+                const stashList = await this.ctx.service.spssStash.getSpssStashList(this.ctx.subProject.id, 'gather_stage');
                 const renderData = {
                     categoryData,
+                    stashList,
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.spss.gatherStage)
                 };
-                await this.layout('spss/gather_stage.ejs', renderData, 'spss/spss_select_modal.ejs');
+                await this.layout('spss/gather_stage.ejs', renderData, 'spss/spss_modal.ejs');
             } catch (err) {
                 ctx.helper.log(err);
             }
@@ -69,24 +75,43 @@ module.exports = app => {
         async gatherStageExtra(ctx) {
             try {
                 const categoryData = await this.ctx.service.category.getAllCategory(this.ctx.subProject);
+                const stashList = await this.ctx.service.spssStash.getSpssStashList(this.ctx.subProject.id, 'gather_stage_extra');
                 const renderData = {
                     categoryData,
+                    stashList,
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.spss.gatherStageExtra)
                 };
-                await this.layout('spss/gather_stage_extra.ejs', renderData, 'spss/spss_select_modal.ejs');
+                await this.layout('spss/gather_stage_extra.ejs', renderData, 'spss/spss_modal.ejs');
             } catch (err) {
                 ctx.helper.log(err);
             }
         }
 
+        async gatherStagePay(ctx) {
+            try {
+                const categoryData = await this.ctx.service.category.getAllCategory(this.ctx.subProject);
+                const stashList = await this.ctx.service.spssStash.getSpssStashList(this.ctx.subProject.id, 'gather_stage_pay');
+                const renderData = {
+                    categoryData,
+                    stashList,
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.spss.gatherStagePay)
+                };
+                await this.layout('spss/gather_stage_pay.ejs', renderData, 'spss/spss_modal.ejs');
+            } catch(err) {
+                ctx.log(err);
+            }
+        }
+
         async compareLedger(ctx) {
             try {
                 const categoryData = await this.ctx.service.category.getAllCategory(this.ctx.subProject);
+                const stashList = await this.ctx.service.spssStash.getSpssStashList(this.ctx.subProject.id, 'compare_ledger');
                 const renderData = {
                     categoryData,
+                    stashList,
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.spss.compareLedger)
                 };
-                await this.layout('spss/compare_ledger.ejs', renderData, 'spss/spss_select_modal.ejs');
+                await this.layout('spss/compare_ledger.ejs', renderData, 'spss/spss_modal.ejs');
             } catch (err) {
                 ctx.helper.log(err);
             }
@@ -95,11 +120,13 @@ module.exports = app => {
         async compareStage(ctx) {
             try {
                 const categoryData = await this.ctx.service.category.getAllCategory(this.ctx.subProject);
+                const stashList = await this.ctx.service.spssStash.getSpssStashList(this.ctx.subProject.id, 'compare_stage');
                 const renderData = {
                     categoryData,
+                    stashList,
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.spss.compareStage),
                 };
-                await this.layout('spss/compare_stage.ejs', renderData, 'spss/spss_select_modal.ejs');
+                await this.layout('spss/compare_stage.ejs', renderData, 'spss/spss_modal.ejs');
             } catch (err) {
                 ctx.helper.log(err);
             }
@@ -173,41 +200,43 @@ module.exports = app => {
         async _loadDealBillsData(tender) {
 
         }
-        async _getValidStages(tenderId, sort = 'desc') {
+        async _getValidStages(tenderId, sort = 'desc', checked = false) {
             const accountId = this.ctx.session.sessionUser.accountId;
-            const stages = await this.ctx.service.stage.getAllDataByCondition({ where: { tid: tenderId }, orders: [['order', sort]] });
+            const condition = { tid: tenderId };
+            if (checked) condition.status = status.checked;
+            const stages = await this.ctx.service.stage.getAllDataByCondition({ where: condition, orders: [['order', sort]] });
             return stages.filter(s => { return s.status !== status.uncheck || s.user_id === accountId; });
         }
-        async _filterOrder(tender, order) {
-            const stages = await this._getValidStages(tender.id);
+        async _filterOrder(tender, order, checked) {
+            const stages = await this._getValidStages(tender.id, 'desc', checked);
             const stage = this.ctx.helper._.find(stages, { order });
             if (stage) await this.ctx.service.stage.doCheckStage(stage);
             return { type: 'stage', stage, filter: `第${order}期` };
         }
-        async _filterMonth(tender, month) {
-            const stages = await this._getValidStages(tender.id);
+        async _filterMonth(tender, month, checked) {
+            const stages = await this._getValidStages(tender.id, 'desc', checked);
             const stage = this.ctx.helper._.find(stages, { s_time: month });
             if (stage) await this.ctx.service.stage.doCheckStage(stage);
             return { type: 'stage', stage, filter: month };
         }
-        async _filterFinal(tender) {
-            const stages = await this._getValidStages(tender.id);
+        async _filterFinal(tender, checked) {
+            const stages = await this._getValidStages(tender.id, 'desc', checked);
             const stage = stages[0];
             if (stage) await this.ctx.service.stage.doCheckStage(stage);
             return { type: 'stage', stage, filter: `第${stage.order}期` };
         }
-        async _filterCheckedFinal(tender) {
-            const stages = await this._getValidStages(tender.id);
+        async _filterCheckedFinal(tender, checked) {
+            const stages = await this._getValidStages(tender.id, 'desc', checked);
             const checkedStages = stages.filter(x => { return x.status === status.checked; });
             const stage = checkedStages[0];
             if (stage) await this.ctx.service.stage.doCheckStage(stage);
             return { type: 'stage', stage, filter: `第${stage.order}期` };
         }
-        async _filterOrderZone(tender, zone) {
+        async _filterOrderZone(tender, zone, checked) {
             let [iBegin, iEnd] = zone.split(':');
             iBegin = this.ctx.helper._.toInteger(iBegin) || 0;
             iEnd = this.ctx.helper._.toInteger(iEnd) || 0;
-            const stages = await this._getValidStages(tender.id, 'asc'), validStages = [];
+            const stages = await this._getValidStages(tender.id, 'asc', checked), validStages = [];
             let preStage, endStage;
             for (const stage of stages) {
                 if (stage.order < iBegin) {
@@ -220,13 +249,13 @@ module.exports = app => {
             }
             return { type: 'stages', stages: validStages, preStage, endStage, filter: `第${iBegin}期 ~ 第${iEnd}期` };
         }
-        async _filterTimeZone(tender, zone) {
+        async _filterTimeZone(tender, zone, checked) {
             const times = zone.split(' - ');
             if (times.length !== 2) throw '选择的汇总周期无效';
             const beginTime = moment(times[0], 'YYYY-MM');
             const endTime = moment(times[1], 'YYYY-MM');
 
-            const stages = await this._getValidStages(tender.id, 'asc'), validStages = [];
+            const stages = await this._getValidStages(tender.id, 'asc', checked), validStages = [];
             let preStage, endStage;
             for (const stage of stages) {
                 const sTime = moment(stage.s_time, 'YYYY-MM');
@@ -243,17 +272,17 @@ module.exports = app => {
         async _filterStages(tender, stageInfo) {
             switch (stageInfo.type) {
                 case 'stage':
-                    return await this._filterOrder(tender, stageInfo.stage);
+                    return await this._filterOrder(tender, stageInfo.stage, stageInfo.checked);
                 case 'month':
-                    return await this._filterMonth(tender, stageInfo.month);
+                    return await this._filterMonth(tender, stageInfo.month, stageInfo.checked);
                 case 'final':
-                    return await this._filterFinal(tender);
+                    return await this._filterFinal(tender, stageInfo.checked);
                 case 'checked-final':
-                    return await this._filterCheckedFinal(tender);
+                    return await this._filterCheckedFinal(tender, stageInfo.checked);
                 case 'zone':
-                    return await this._filterTimeZone(tender, stageInfo.zone);
+                    return await this._filterTimeZone(tender, stageInfo.zone, stageInfo.checked);
                 case 'stage-zone':
-                    return await this._filterOrderZone(tender, stageInfo.stage_zone);
+                    return await this._filterOrderZone(tender, stageInfo.stage_zone, stageInfo.checked);
                 default:
                     return;
             }
@@ -366,6 +395,28 @@ module.exports = app => {
                 : await this._loadStagesData(tender, filter.stages, filter.preStage, filter.endStage);
         }
 
+        async _loadStagePayData(tender, stage) {
+            if (!stage) return [];
+
+            const dealPay = await this.ctx.service.stagePay.getStagePays(stage);
+            if (!stage.readOnly) {
+                // 计算 本期金额
+                const PayCalculator = require('../lib/pay_calc');
+                const payCalculator = new PayCalculator(this.ctx, stage, tender.info);
+                await payCalculator.calculateAll(dealPay);
+            }
+            return dealPay;
+        }
+        async _loadStagesPayData(tender, stages, preStage, endStage) {
+            // todo
+            return [];
+        }
+        async _loadPayData(tender, filter) {
+            return filter.type === 'stage'
+                ? await this._loadStagePayData(tender, filter.stage)
+                : await this._loadStagesPayData(tender, filter.stage, filter.preStage, filter.endStage);
+        }
+
         async _loadStageJgclData(tender, stage) {
             if (!stage) return [];
 
@@ -531,6 +582,13 @@ module.exports = app => {
                         }
                         [result.bills, result.pos] = await this._loadStageLedgerData(tender, stageFilter);
                         break;
+                    case 'pay':
+                        if (!stageFilter) {
+                            stageFilter = await this._filterStages(tender, stageInfo);
+                            result.stage_filter = stageFilter.filter;
+                        }
+                        result.pay = await this._loadPayData(tender, stageFilter);
+                        break;
                     case 'jgcl':
                         if (!stageFilter) {
                             stageFilter = await this._filterStages(tender, stageInfo);
@@ -660,6 +718,61 @@ module.exports = app => {
                 ctx.body = this.ajaxErrorBody(err, '查询数据错误');
             }
         }
+
+        async stash(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const responseData = { err: 0, msg: '', data: [] };
+                if (!data.type || !data.action) throw '参数错误';
+
+                switch (data.action) {
+                    case 'load':
+                        responseData.data = await this.ctx.service.spssStash.getSpssStash(data.id);
+                        break;
+                    case 'list':
+                        responseData.data = await this.ctx.service.spssStash.getSpssStashList(this.ctx.subProject.id, data.type);
+                        break;
+                    case 'add':
+                        await this.ctx.service.spssStash.addSpssStash(data.type, data.select, data.result);
+                        responseData.data = await this.ctx.service.spssStash.getSpssStashList(this.ctx.subProject.id, data.type);
+                        break;
+                    default: throw '未知操作';
+                }
+                ctx.body = responseData;
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '暂存数据错误');
+            }
+        }
+
+        async test(ctx) {
+            try {
+                const sourceType = require('../const/source_type');
+                await this.layout('spss/dev_test.ejs', { sourceType });
+            } catch (err) {
+                ctx.helper.log(err);
+            }
+        }
+
+        async _testLoadReport(data) {
+            const reportData = await this.ctx.service.report.getReportData(data.source_type, data.params, data.source_filters);
+            this.ctx.body = { err: 0, msg: '', data: reportData };
+        }
+        async testLoad(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.type && !data.detail) throw '参数错误';
+                switch(data.type) {
+                    case 'report':
+                        await this._testLoadReport(data.detail);
+                        break;
+                    default: throw '未知参数';
+                }
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '查询数据错误');
+            }
+        }
     }
 
     return SpssController;

+ 16 - 0
app/controller/stage_controller.js

@@ -189,6 +189,7 @@ module.exports = app => {
                 renderData.categoryData = await ctx.service.category.getAllCategory(ctx.subProject);
                 renderData.settleStatus = ctx.service.settle.settleStatus;
                 renderData.deleteFilePermission = PermissionCheck.delFile(this.ctx.session.sessionUser.permission);
+                renderData.spGroupList = await ctx.service.shenpiGroup.getGroupListByStage(ctx.tender.id, shenpiConst.sp_type.stage);
                 await this.layout('stage/index.ejs', renderData, 'stage/modal.ejs');
             } catch (err) {
                 this.log(err);
@@ -2515,6 +2516,21 @@ module.exports = app => {
             }
         }
 
+        async changeSpGroup(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const result = await ctx.service.stageAudit.stageSpGroup(ctx.stage, data.sp_group);
+                if (!result) {
+                    throw '切换审批组失败';
+                }
+                
+                const auditors = await ctx.service.stageAudit.getUserGroup(ctx.stage.id, ctx.stage.times);
+                ctx.body = { err: 0, msg: '', data: auditors };
+            } catch (err) {
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+        }
+
     }
 
     return StageController;

+ 22 - 15
app/controller/sub_proj_setting_controller.js

@@ -544,9 +544,9 @@ module.exports = app => {
                 const projectData = await ctx.service.project.getDataById(projectId);
                 if (projectData === null) throw '没有对应的项目数据';
                 if (ctx.session.sessionUser.is_admin === 0) throw '没有访问权限';
-                // 默认查询已完工的标
+                // 默认过滤完工的标段即显示build的标
                 const { done = '1' } = ctx.query || {};
-                const buildStatusFilter = ctx.service.tender.db.format(' AND build_status = ?', [tenderConst.buildStatus.status.finish]);
+                const buildStatusFilter = ctx.service.tender.db.format(' AND build_status = ?', [tenderConst.buildStatus.status.build]);
                 const tenderList = await ctx.service.tender.getList('', null, 1, done === '1' ? buildStatusFilter : '');
                 const removeTenders = await ctx.service.shenpiAudit.getRemoveTenders(tenderList);
                 if (removeTenders.length > 0) {
@@ -591,9 +591,10 @@ module.exports = app => {
                     change_type_list: shenpiConst.change_type_list,
                     subProjects,
                 };
+                const permissionKey = ['quality', 'inspection', 'safe_inspection', 'safe_payment'];
                 renderData.selfCategoryLevel = await this.ctx.service.projectAccount.getSelfCategoryLevel(this.ctx.session.sessionUser.accountId);
-                renderData.permissionConst = ctx.service.tenderPermission.partPermissionConst(['quality', 'inspection']);
-                renderData.permissionBlock = ctx.service.tenderPermission.partPermissionBlock(['quality', 'inspection']);
+                renderData.permissionConst = ctx.service.tenderPermission.partPermissionConst(permissionKey);
+                renderData.permissionBlock = ctx.service.tenderPermission.partPermissionBlock(permissionKey);
                 await this.layout('sp_setting/manage.ejs', renderData, 'sp_setting/manage_modal.ejs');
             } catch (error) {
                 ctx.log(error);
@@ -614,14 +615,14 @@ module.exports = app => {
                 if (!data.type) {
                     throw '提交数据错误';
                 }
-                // if (!data.tid) {
-                //     throw '参数有误';
-                // }
-                // const tender = await ctx.service.tender.getDataById(data.tid);
-                // if (!tender) {
-                //     throw '标段不存在';
-                // }
-                // const tenderInfo = await ctx.service.tenderInfo.getTenderInfo(tender.id);
+                // 单个tenderPermission字段用这个
+                // const permissionKey = ['quality', 'inspection', 'safe_inspection', 'safe_payment'];
+                const permissionKey = [];
+                // 多个tenderPermission字段融合处理用这个
+                const tPsKeys = {
+                    quality: ['quality', 'inspection'],
+                    safe: ['safe_inspection', 'safe_payment'],
+                };
                 switch (data.type) {
                     case 'msg':
                         if (!data.tid) {
@@ -646,8 +647,12 @@ 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']);
-                        responseData.data.inspectionAuditList = await ctx.service.tenderPermission.getPartsPermission(tender.id, ['inspection']);
+                        for (const key of permissionKey) {
+                            responseData.data[`${key}AuditList`] = await ctx.service.tenderPermission.getPartsPermission(tender.id, [key]);
+                        }
+                        for (const tpsKey in tPsKeys) {
+                            responseData.data[`${tpsKey}AuditList`] = await ctx.service.tenderPermission.getPartsPermission(tender.id, tPsKeys[tpsKey]);
+                        }
                         break;
                     case 'copy2otu':
                         if (data.userType === 'tourist') {
@@ -658,8 +663,10 @@ 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' || data.userType === 'inspection') {
+                        } else if (permissionKey.includes(data.userType)) {
                             await ctx.service.tenderPermission.setOtherTender(data.tidList, data.auditList, [data.userType]);
+                        } else if (Object.keys(tPsKeys).includes(data.userType)) {
+                            await ctx.service.tenderPermission.setOtherTender(data.tidList, data.auditList, tPsKeys[data.userType]);
                         } else {
                             throw '参数有误';
                         }

+ 37 - 0
app/controller/wap_controller.js

@@ -22,6 +22,7 @@ const auditType = require('../const/audit').auditType;
 const AiInspect = require('../lib/ai_inspect');
 const uuid = require('node-uuid');
 const nlsToken = require('../lib/nls_token');
+const streamToArray = require('stream-to-array');
 
 module.exports = app => {
 
@@ -1235,6 +1236,42 @@ module.exports = app => {
             }
             ctx.body = responseData;
         }
+
+        async voiceOneShot(ctx) {
+            const responseData = {
+                err: 0,
+                msg: '',
+                data: '',
+            };
+            const stream = await ctx.getFileStream();
+            try {
+                // 读取字节流
+                const parts = await streamToArray(stream);
+                // 转化为buffer
+                const buffer = Buffer.concat(parts);
+
+                if (!buffer || buffer.length === 0) {
+                    throw '音频数据为空';
+                }
+
+                // 调用 service 执行一句话识别
+                const nls_token = new nlsToken(this.ctx);
+                const token = await nls_token.getToken();
+                const text = await nls_token.oneShotRecognition(token, buffer);
+                console.log(text);
+                if (stream) {
+                    await sendToWormhole(stream);
+                }
+                responseData.data = text;
+            } catch (error) {
+                if (stream) {
+                    await sendToWormhole(stream);
+                }
+                responseData.err = 1;
+                responseData.msg = error.message ? error.message : error;
+            }
+            ctx.body = responseData;
+        }
     }
 
     return WapController;

+ 25 - 7
app/lib/budget_final.js

@@ -10,22 +10,30 @@
 
 const itemsPre = 'id_';
 const BillsTree = require('./ledger').billsTree;
+const TreeUtils = require('./ledger').treeUtils;
 const auditConst = require('../const/audit');
 
 class FinalTree extends BillsTree {
     constructor(ctx, setting) {
         super(ctx, setting);
         this._newId = 1;
+        this.checkField = ['code', 'name'];
     }
+
     get newId() {
         return this._newId++;
     }
 
     loadNode(node, parent, loadFun) {
         if (node.b_code) return;
+        const checkField = this.checkField;
         const siblings = parent ? parent.children : this.children;
         let cur = siblings.find(function (x) {
-            return x.code === node.code && x.name === node.name;
+            for (const cf of checkField) {
+                if (x[cf] !== node[cf]) return false;
+            }
+            return true;
+            // return x.code === node.code && x.name === node.name;
         });
         if (!cur) {
             cur = { children: [], code: node.code || '', name: node.name || '', unit: node.unit || '' };
@@ -87,7 +95,7 @@ class BudgetFinal {
     constructor (ctx) {
         this.ctx = ctx;
         this.budgetSetting = { id: 'tree_id', pid: 'tree_pid', order: 'order', level: 'level', rootId: -1, calcFields: ['total_price'] };
-        this.tenderSetting = { id: 'ledger_id', pid: 'ledger_pid', order: 'order', level: 'level', rootId: -1, calcFields: ['total_price', 'end_gather_tp', 'end_contract_tp', 'end_qc_tp'] };
+        this.tenderSetting = { id: 'ledger_id', pid: 'ledger_pid', order: 'order', level: 'level', rootId: -1, calcFields: ['total_price', 'end_gather_tp', 'end_contract_tp', 'end_qc_tp', 'tz_qc_tp'] };
         this.finalTree = new FinalTree(this.ctx, { id: 'id', pid: 'pid', order: 'order', level: 'level', fullPath: 'full_path', rootId: -1 });
     }
 
@@ -150,7 +158,7 @@ class BudgetFinal {
     async _loadTender(id) {
         const helper = this.ctx.helper;
         const bills = await this.ctx.service.ledger.getFinalData(id, ['id', 'ledger_id', 'ledger_pid', 'level', 'order', 'full_path', 'is_leaf',
-            'code', 'b_code', 'name', 'unit', 'dgn_qty1', 'dgn_qty2', 'total_price']);
+            'code', 'b_code', 'name', 'unit', 'unit_price', 'dgn_qty1', 'dgn_qty2', 'total_price']);
 
         const dgnData = await this.ctx.service.stageBillsDgn.getDgnData(id);
         // 使用最新一期对比
@@ -192,6 +200,11 @@ class BudgetFinal {
         }
         const tree = new BillsTree(this.ctx, this.tenderSetting);
         tree.loadDatas(bills);
+
+        const tenderInfo = await this.ctx.service.tenderInfo.getTenderInfo(id);
+        const reCalcChange = await this.ctx.service.change.getReCalcChangeData(id, tenderInfo);
+        TreeUtils.loadChangeData(tree, null, reCalcChange, tenderInfo, helper);
+
         tree.calculateAll();
         this.finalTree.loadTree(tree, function (cur, source) {
             cur.total_price = helper.add(cur.total_price, source.total_price);
@@ -206,6 +219,9 @@ class BudgetFinal {
             cur.final_contract_tp = helper.add(cur.final_contract_tp, source.end_contract_tp);
             cur.final_qc_tp = helper.add(cur.final_qc_tp, source.end_qc_tp);
             cur.final_tp = helper.add(cur.final_tp, source.end_gather_tp);
+
+            cur.tz_qc_qty = helper.add(cur.tz_qc_qty, source.tz_qc_qty);
+            cur.tz_qc_tp = helper.add(cur.tz_qc_tp, source.tz_qc_tp);
         });
     }
 
@@ -284,6 +300,7 @@ class BudgetFinal {
                 final_dgn_qty1: x.final_dgn_qty1 || 0, final_dgn_qty2: x.final_dgn_qty2 || 0, final_tp: x.final_tp || 0,
                 final_contract_tp: x.final_contract_tp || 0, final_qc_tp: x.final_qc_tp || 0,
                 final_dgn_price: x.final_dgn_price || 0, final_dgn_qty: x.final_dgn_qty || '',
+                tz_qc_qty: x.tz_qc_qty || 0, tz_qc_tp: x.tz_qc_tp || 0,
                 grow_dgn_qty1: x.grow_dgn_qty1 || 0, grow_dgn_qty2: x.grow_dgn_qty2 || 0, grow_dgn_qty: x.grow_dgn_qty || '', grow_tp: x.grow_tp || 0,
             })
         });
@@ -293,14 +310,15 @@ class BudgetFinal {
     async doFinal(budget, final) {
         this.budget = budget;
         this.final = final;
+        this.finalTree.checkField = budget.final_type.split('_');
 
-        await this._loadGai(budget);
-        await this._loadGu(budget);
-        await this._loadYu(budget);
-        await this._loadZb(budget);
         for (const t of final.tender) {
             await this._loadTender(t);
         }
+        await this._loadZb(budget);
+        await this._loadYu(budget);
+        await this._loadGai(budget);
+        await this._loadGu(budget);
         this._afterLoad();
         return this.getFinalData();
     }

+ 3 - 2
app/lib/dsk.js

@@ -30,10 +30,11 @@ class DSK {
         return jwt.sign({ iat: Date.now() }, privateKey);
     }
 
-    async sendSms(mobile) {
+    async sendSms(mobile, captchaVerifyParam) {
         const url = this.url + 'api/sms/external/getSmsCode';
         const postData = {
             mobile,
+            captchaVerifyParam,
         };
         let result = false;
         try {
@@ -71,7 +72,7 @@ class DSK {
     }
 
     async getCompilation() {
-        const validName = ['全国公路(2018)', '广东公路(2023)', '浙江公路(2025)', '重庆建设(2018)', '广东建设(2018)'];
+        const validName = ['全国公路(2018)', '广东公路(2023)', '浙江公路(2025)', '重庆建设(2018)', '广东建设(2018)', '重庆公路(2018)'];
         const url = this.url + 'api/compilation/external/list';
         const postData = {};
         const result = this.dealWith(await this.ctx.helper.sendRequest(url, postData, 'GET', 'json', true));

+ 68 - 0
app/lib/ledger.js

@@ -1427,6 +1427,73 @@ class reviseTree extends billsTree {
     }
 }
 
+const treeUtils = {
+    loadChangeData: function(tree, pos, changes, tenderInfo, helper) {
+        const findEmptyBills = function(tree, change, changeBills) {
+            const cb = {
+                b_code: changeBills.code || '',
+                name: changeBills.name || '',
+                unit: changeBills.unit || '',
+                unit_price: changeBills.unit_price || 0,
+            };
+            for (const node of tree.nodes) {
+                if (node.children && node.children.length > 0) continue;
+
+                const b = {
+                    b_code: node.b_code || '',
+                    name: node.name || '',
+                    unit: node.unit || '',
+                    unit_price: node.unit_price || 0,
+                };
+                if (helper._.isMatch(cb, b)) return node;
+            }
+            return null;
+        };
+        for (const change of changes) {
+            for (const changeBills of change.bills) {
+                const node = changeBills.gcl_id
+                    ? tree.nodes.find(x => {return x.id === changeBills.gcl_id})
+                    : findEmptyBills(tree, change, changeBills);
+
+                if (!node) continue;
+                if (!node.changeBills) node.changeBills = [];
+                node.tz_qc_qty = helper.add(node.tz_qc_qty, changeBills.checked_amount);
+                if (tenderInfo.calc_type === 'up') {
+                    node.tz_qc_tp = helper.add(node.tz_qc_tp, changeBills.checked_price);
+                } else {
+                    node.tz_qc_tp = helper.add(node.tz_qc_tp, changeBills.tp);
+                }
+
+                const posData = pos ? pos.getLedgerPos(node.id) : undefined;
+                if (posData && posData.length > 0) {
+                    for (const p of changeBills.pos) {
+                        const changePos = p.bwmx ? posData.find(x => { return x.name === p.bwmx; }) : posData[0];
+                        if (changePos) {
+                            changePos.tz_qc_qty = helper.add(changePos.tz_qc_qty, p.checked_amount);
+                            if (!changePos.changeBills) changePos.changeBills = [];
+                            changePos.changeBills.push({
+                                code: change.code || '', name: change.name || '', is_valuation:  p.is_valuation,
+                                qty: p.checked_amount || 0, tp: p.checked_price || 0, pos: changePos.name,
+                            });
+                            node.changeBills.push({
+                                code: change.code || '', name: change.name || '', is_valuation: p.is_valuation,
+                                qty: p.checked_amount || 0, tp: p.checked_price || 0, pos: changePos.name,
+                            });
+                        }
+                    }
+                } else {
+                    for (const p of changeBills.pos) {
+                        node.changeBills.push({
+                            code: change.code || '', name: change.name || '', is_valuation: p.is_valuation,
+                            qty: p.checked_amount || 0, tp: p.checked_price || 0, pos: '',
+                        });
+                    }
+                }
+            }
+        }
+    }
+};
+
 module.exports = {
     baseTree,
     billsTree,
@@ -1437,4 +1504,5 @@ module.exports = {
     gatherPos,
     checkData,
     reviseTree,
+    treeUtils,
 };

+ 84 - 1
app/lib/nls_token.js

@@ -10,7 +10,13 @@
 
 const Core = require('@alicloud/pop-core');
 const smsAli = require('../const/sms_alitemplate.js');
+const Nls = require('alibabacloud-nls');
 class NlsToken {
+    constructor(ctx) {
+        this.ctx = ctx;
+        this.appkey = 'nE0d2Ue08rtPhb9e';
+        this.shotUrl = 'wss://nls-gateway.cn-shanghai.aliyuncs.com/ws/v1';
+    }
 
     async getToken() {
         const client = new Core({
@@ -30,12 +36,89 @@ class NlsToken {
 
         try {
             const result = await client.request('CreateToken', params, requestOption);
-            return result.Token;
+            return result.Token.Id;
         } catch (err) {
             this.ctx.logger.error('获取语音识别Token失败', err);
             throw err;
         }
     }
+
+    /**
+     * audioBuffer: Buffer of wav (16k)
+     */
+    async oneShotRecognition(token, audioBuffer, url = this.shotUrl, appkey = this.appkey) {
+        return new Promise(async (resolve, reject) => {
+            let finalText = '';
+
+            const sr = new Nls.SpeechRecognition({
+                url,
+                appkey,
+                token,
+                enable_punctuation_prediction: false,
+                enable_inverse_text_normalization: true,
+            });
+
+            sr.on('started', (msg) => {
+                try {
+                    const data = JSON.parse(msg);
+                    // console.log('Client recv started:', data);
+                } catch (err) {
+                    console.log('started parse error:', err);
+                }
+            });
+
+            sr.on('changed', (msg) => {
+                try {
+                    const data = JSON.parse(msg);
+                    if (data.payload && data.payload.result) {
+                        finalText = data.payload.result;
+                    }
+                    console.log('changed finalText:', finalText);
+                } catch (err) {
+                    console.log('changed parse error:', err);
+                }
+            });
+
+            sr.on('completed', (msg) => {
+                try {
+                    const data = JSON.parse(msg);
+                    if (data.payload && data.payload.result) {
+                        finalText = data.payload.result;
+                    }
+                    // console.log('completed finalText:', finalText);
+                } catch (err) {
+                    console.log('completed parse error:', err);
+                }
+            });
+
+            sr.on('closed', () => {
+                // console.log('closed finalText:', finalText);
+                resolve(finalText.trim());
+            });
+
+            sr.on('failed', (msg) => {
+                try {
+                    const data = JSON.parse(msg);
+                    reject(new Error(JSON.stringify(data)));
+                } catch (err) {
+                    reject(new Error(msg));
+                }
+            });
+
+            try {
+                // 启动识别
+                await sr.start(sr.defaultStartParams(), true, 6000);
+
+                if (!sr.sendAudio(audioBuffer)) {
+                    throw new Error('send audio failed');
+                }
+                await sr.close();
+            } catch (err) {
+                console.log('error on start:', err);
+                return reject(err);
+            }
+        });
+    }
 }
 
 module.exports = NlsToken;

+ 2 - 2
app/lib/rm/change.js

@@ -112,7 +112,7 @@ class rptMemChange extends RptMemBase {
     async getChangeLedgerBillsData(fields, showLevel) {
         try {
             const billsData = await this.ctx.service.ledger.getData(this.ctx.tender.id);
-            const changeBillsData = await this.ctx.service.changeLedger.getAllDataByCondition({ where: { ccid: this.ctx.change.id } });
+            const changeBillsData = await this.ctx.service.changeLedger.getAllDataByCondition({ where: { tender_id: this.ctx.tender.id } });
             billsData.push(...changeBillsData);
 
             const billsTree = this._getNewBillsTree();
@@ -132,7 +132,7 @@ class rptMemChange extends RptMemBase {
     async getChangeLedgerPosData() {
         try {
             const posData = await this.ctx.service.pos.getAllDataByCondition({ where: {tid: this.ctx.tender.id }});
-            const changePosData = await this.ctx.service.changePos.getAllDataByCondition({ where: { ccid: this.ctx.change.id } });
+            const changePosData = await this.ctx.service.changePos.getAllDataByCondition({ where: { tid: this.ctx.tender.id } });
             posData.push(...changePosData);
 
             const pos = new Ledger.pos({

+ 138 - 0
app/lib/rm/spss.js

@@ -0,0 +1,138 @@
+'use strict';
+
+/**
+ * 要求必须有ctx.subProject
+ * 如果不是子项目内部使用,需要先载入ctx.subProject再使用
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+class reportMemorySpss {
+    constructor(ctx) {
+        this.ctx = ctx;
+    }
+
+    async _loadTypeData(type) {
+        if (!this[type]) this[type] = await this.ctx.service.spssStash.getLatestSpssStash(this.ctx.subProject.id, type);
+    }
+
+    async getTypeTender(type) {
+        await this._loadTypeData(type);
+        return this[type] ? this[type].spss_select : [];
+    }
+
+    async getTyperBills(type) {
+        await this._loadTypeData(type);
+        return this[type] && this[type].spss_result ? this[type].spss_result.bills : [];
+
+    }
+    getBillsPosData(bills) {
+        const pos = [];
+        for (const b of bills) {
+            if (!b.pos || b.pos.length === 0) continue;
+            b.pos.forEach(x => { x.lid === b.ledger_id; });
+            pos.push(...b.pos);
+        }
+        return pos;
+    }
+    async getTypePos(type) {
+        await this._loadTypeData(type);
+        return this[type] && this[type].spss_result ? this.getBillsPosData(this[type].spss_result.bills) : [];
+    }
+
+    async getGatherStageInfoTender() {
+        return await this.getTypeTender('gather_stage_info');
+    }
+    async getGatherStageInfo() {
+        await this._loadTypeData('gather_stage_info');
+        return this.gather_stage_info.spss_result || [];
+    }
+
+    async getGatherLedgerTender() {
+        return await this.getTypeTender('gather_ledger');
+    }
+    async getGatherLedgerBills() {
+        return await this.getTyperBills('gather_ledger');
+    }
+    async getGatherLedgerPos() {
+        return await this.getTypePos('gather_ledger');
+    }
+
+    async getGatherStageTender() {
+        return await this.getTypeTender('gather_stage');
+    }
+    async getGatherStageBills() {
+        return await this.getTyperBills('gather_stage');
+    }
+    async getGatherStagePos() {
+        return await this.getTypePos('gather_stage');
+    }
+
+    async getGatherStageExtraTender() {
+        return await this.getTypeTender('gather_stage_extra');
+    }
+    async getGatherStageJgcl() {
+        const type = 'gather_stage_extra';
+        await this._loadTypeData(type);
+        return this[type] && this[type].spss_result ? this[type].spss_result.jgcl : [];
+    }
+    async getGatherStageYjcl() {
+        const type = 'gather_stage_extra';
+        await this._loadTypeData(type);
+        return this[type] && this[type].spss_result ? this[type].spss_result.yjcl : [];
+    }
+    async getGatherStageBonus() {
+        const type = 'gather_stage_extra';
+        await this._loadTypeData(type);
+        return this[type] && this[type].spss_result ? this[type].spss_result.bonus : [];
+    }
+    async getGatherStageOther() {
+        const type = 'gather_stage_extra';
+        await this._loadTypeData(type);
+        return this[type] && this[type].spss_result ? this[type].spss_result.other : [];
+    }
+    async getGatherStageSafeProd() {
+        const type = 'gather_stage_extra';
+        await this._loadTypeData(type);
+        return this[type] && this[type].spss_result ? this[type].spss_result.safeProd : [];
+    }
+    async getGatherStageTempLand() {
+        const type = 'gather_stage_extra';
+        await this._loadTypeData(type);
+        return this[type] && this[type].spss_result ? this[type].spss_result.tempLand : [];
+    }
+
+    async getGatherStagePayTender() {
+        return await this.getTypeTender('gather_stage_pay');
+    }
+    async getGatherStagePay() {
+        const type = 'gather_stage_pay';
+        await this._loadTypeData(type);
+        return this[type] && this[type].spss_result ? this[type].spss_result.pays : [];
+    }
+
+    async getCompareLedgerTender() {
+        return await this.getTypeTender('compare_ledger');
+    }
+    async getCompareLedgerBills() {
+        return await this.getTyperBills('compare_ledger');
+    }
+    async getCompareLedgerPos() {
+        return await this.getTypePos('compare_ledger');
+    }
+
+    async getCompareStageTender() {
+        return await this.getTypeTender('compare_stage');
+    }
+    async getCompareStageBills() {
+        return await this.getTyperBills('compare_stage');
+    }
+    async getCompareStagePos() {
+        return await this.getTypePos('compare_stage');
+    }
+
+}
+
+module.exports = reportMemorySpss;

+ 71 - 0
app/lib/rm/tender.js

@@ -18,6 +18,7 @@ const BudgetSource = require('./tender_budget');
 const MaterialSource = require('./tender_material');
 const FinancialSource = require('./tender_financial');
 const rptCustomData = require('../rptCustomData');
+const SpssSource = require('./spss');
 
 class rptMemPaymentSafe extends RptMemBase {
     constructor(ctx) {
@@ -217,6 +218,7 @@ class rptMemPaymentSafe extends RptMemBase {
 
     async getFlowData(params, key, fields, customDefine, customSelect) {
         const service = this.ctx.service;
+        const spssSource = new SpssSource(this.ctx);
         switch (filter) {
             case 'mem_custom_select':
                 return customSelect;
@@ -274,6 +276,75 @@ class rptMemPaymentSafe extends RptMemBase {
             //     return await service.rptGatherMemory.getMaterialBills(params.tender_id, params.material_order, memFieldKeys[filter]);
             // case 'mem_material_bills_gl':
             //     return await service.rptGatherMemory.getMaterialBillsGl(params.tender_id, params.material_order, memFieldKeys[filter]);
+            case 'mem_spss_gather_info_tender':
+                return await spssSource.getGatherStageInfoTender();
+                break;
+            case 'mem_spss_gather_info':
+                return await spssSource.getGatherStageInfo();
+                break;
+            case 'mem_spss_gather_ledger_tender':
+                return await spssSource.getGatherLedgerTender();
+                break;
+            case 'mem_spss_gather_ledger_bills':
+                return await spssSource.getGatherLedgerBills();
+                break;
+            case 'mem_spss_gather_ledger_pos':
+                return await spssSource.getGatherLedgerPos();
+                break;
+            case 'mem_spss_gather_stage_tender':
+                return await spssSource.getGatherStageTender();
+                break;
+            case 'mem_spss_gather_stage_bills':
+                return await spssSource.getGatherStageBills();
+                break;
+            case 'mem_spss_gather_stage_pos':
+                return await spssSource.getGatherStagePos();
+                break;
+            case 'mem_spss_gather_stage_extra_tender':
+                return await spssSource.getGatherStageExtraTender();
+                break;
+            case 'mem_spss_gather_stage_jgcl':
+                return await spssSource.getGatherStageJgcl();
+                break;
+            case 'mem_spss_gather_stage_yjcl':
+                return await spssSource.getGatherStageYjcl();
+                break;
+            case 'mem_spss_gather_stage_bonus':
+                return await spssSource.getGatherStageBonus();
+                break;
+            case 'mem_spss_gather_stage_other':
+                return await spssSource.getGatherStageOther();
+                break;
+            case 'mem_spss_gather_stage_safe_prod':
+                return await spssSource.getGatherStageSafeProd();
+                break;
+            case 'mem_spss_gather_stage_temp_land':
+                return await spssSource.getGatherStageTempLand();
+                break;
+            case 'mem_spss_gather_stage_pay_tender':
+                return await spssSource.getGatherStagePayTender();
+                break;
+            case 'mem_spss_gather_stage_pay':
+                return await spssSource.getGatherStagePay();
+                break;
+            case 'mem_spss_compare_ledger_tender':
+                return await spssSource.getCompareLedgerTender();
+                break;
+            case 'mem_spss_compare_ledger_bills':
+                return await spssSource.getCompareLedgerBills();
+                break;
+            case 'mem_spss_compare_ledger_pos':
+                return await spssSource.getCompareLedgerPos();
+                break;
+            case 'mem_spss_compare_stage_tender':
+                return await spssSource.getCompareStageTender();
+                break;
+            case 'mem_spss_compare_stage_bills':
+                return await spssSource.getCompareStageBills();
+                break;
+            case 'mem_spss_compare_stage_pos':
+                return await spssSource.getCompareStagePos();
+                break;
             default:
                 return [];
         }

+ 2 - 1
app/lib/rm/tender_budget.js

@@ -64,7 +64,7 @@ class reportMemoryBudget {
     async _getTenderBudget(tid) {
         if (this.getBudget) return;
 
-        if (this.ctx.subProject) this.budget = await this.ctx.service.budget.getDataById(this.ctx.subProject.id);
+        if (this.ctx.subProject) this.budget = await this.ctx.service.budget.getDataById(this.ctx.subProject.budget_id);
         if (!this.budget) {
             const budgets = await this.ctx.service.budget.getBudget(true);
             this.budget = budgets.find(x => {
@@ -102,6 +102,7 @@ class reportMemoryBudget {
 
     async tenderFinal(tid, filter = false) {
         await this._getTenderBudget(tid);
+        console.log(this.budget);
         return this.budget ? await this.budgetFinal(this.budget.id, filter) : [];
     }
 }

+ 35 - 4
app/lib/ybp_tree.js

@@ -162,7 +162,7 @@ class YbpImportTree {
         //       x.unit_price === data.unit_price;
         // });
     }
-    _importNode(node, parent, loadRelaFun, filter) {
+    _importNode(node, parent, loadRelaFun, filter, forceMerge = false) {
 
         const setting = this.setting;
         const compareData = { kind: node.kind };
@@ -178,7 +178,7 @@ class YbpImportTree {
             : 0;
         if (filter && filter(node, compareData, isXmj)) return;
 
-        let cur = (this.importType === YbpImportType.merge && defaultMerge.indexOf(compareData.kind) >= 0)
+        let cur = forceMerge || (this.importType === YbpImportType.merge && defaultMerge.indexOf(compareData.kind) >= 0)
             ? this._findNode(compareData, parent)
             : null;
         if (!cur) {
@@ -221,8 +221,16 @@ class YbpImportTree {
         }
         return cur;
     }
-    importTree(tree, unitName, loadRelaFun, filter) {
-        const parent = this.importType === YbpImportType.flow ? this._importNode({name: unitName}) : null;
+    _importUnitParent(unitParents) {
+        let cur;
+        for (const up of unitParents) {
+            cur = this._importNode({ name: up }, cur, null, null, true);
+        }
+        return cur;
+    }
+    importTree(tree, unitName, unitParents, loadRelaFun, filter) {
+        const unitTop = this.importType === YbpImportType.flow ? this._importUnitParent(unitParents) : null;
+        const parent = this.importType === YbpImportType.flow ? this._importNode({name: unitName}, unitTop) : null;
         this.unitName = unitName;
         for (const n of tree.children) {
             this._importNode(n, parent, loadRelaFun, filter);
@@ -309,8 +317,31 @@ class YbpImportTree {
     }
 }
 
+const YbpUtils = {
+    /**
+     * (限制3层)在subjects中获取节点的所有父结构节点名称project.name
+     * PS:仅供外部用Utils,实际上与YbpTree内部数据无关,依赖ID,parentID,如果树结构需先转换再使用
+     *
+     * @param node 节点
+     * @param nodes 所有节点Array
+     * @returns {Array} 父节点名称
+     */
+    getSubjectParentPath(node, nodes) {
+        const result = [];
+        let parent = nodes.find(x => { return x.project.ID === node.project.parentID; });
+        let i = 0;
+        while (parent && i < 4 && [3, 4].indexOf(parent.project.type) >= 0) {
+            result.unshift(parent.project.name);
+            parent = nodes.find(y => { return y.project.ID === parent.project.parentID; });
+            i++;
+        }
+        return result;
+    }
+};
+
 module.exports = {
     YbpImportType,
     YbpTree,
     YbpImportTree,
+    YbpUtils,
 };

+ 2 - 1
app/middleware/change_check.js

@@ -84,8 +84,9 @@ module.exports = options => {
             change.readOnly = !((change.status === status.uncheck || change.status === status.checkNo || change.status === status.revise) && accountId === change.uid);
             change.shenpiPower = (change.status === status.checking || change.status === status.checkNoPre) && change.curAuditorIds.indexOf(accountId) !== -1;
             this.change = change;
-            if ((change.status === status.uncheck || change.status === status.checkNo || change.status === status.revise) && change.tp_decimal !== this.tender.info.decimal.tp) {
+            if ((change.status === status.uncheck || change.status === status.checkNo || change.status === status.revise) && (change.tp_decimal !== this.tender.info.decimal.tp || change.up_decimal !== this.tender.info.decimal.up)) {
                 this.change.tp_decimal = this.tender.info.decimal.tp;
+                this.change.up_decimal = this.tender.info.decimal.up;
                 yield this.service.change.updateDecimalAndTp();
             }
             yield this.service.change.doCheckChangeCanCancel(this.change);

+ 3 - 1
app/middleware/inspection_check.js

@@ -21,7 +21,7 @@ module.exports = options => {
      * @param {function} next - 中间件继续执行的方法
      * @return {void}
      */
-    return function* InspectionCheck(next) {
+    return function* QualityInspectionCheck(next) {
         try {
             // 获取revise
             if (!this.subProject.page_show.qualityInspection) {
@@ -43,6 +43,8 @@ module.exports = options => {
             const permission = this.session.sessionUser.permission;
             if (accountId === inspection.uid) { // 原报
                 inspection.filePermission = true;
+            } else if (this.permission.inspection.view_all) {
+                inspection.filePermission = true;
             } else if (auditorIds.indexOf(accountId) !== -1) { // 审批人
                 if (inspection.status === status.uncheck) {
                     throw '您无权查看该数据';

+ 128 - 0
app/middleware/safe_inspection_check.js

@@ -0,0 +1,128 @@
+'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* SafeInspectionCheck(next) {
+        try {
+            // 获取revise
+            if (!this.subProject.page_show.safeInspection) {
+                throw '该功能已关闭';
+            }
+            const qiid = this.params.qiid || this.request.body.qiid;
+            if (!qiid) {
+                throw '您访问的质量巡检不存在';
+            }
+            const inspection = yield this.service.safeInspection.getDataById(qiid);
+            if (!inspection) throw '质量巡检数据有误';
+            // 读取原报、审核人数据
+            yield this.service.safeInspection.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 (this.permission.safe_inspection.view_all) {
+                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.safeInspectionAudit.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.safe_inspection !== shenpiConst.sp_status.sqspr) {
+                const shenpi_status = this.tender.info.shenpi.safe_inspection;
+                // 进一步比较审批流是否与审批流程设置的相同,不同则替换为固定审批流或固定的终审
+                const auditList = yield this.service.safeInspectionAudit.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.safeInspectionAudit.updateNewAuditList(inspection, shenpiList);
+                        yield this.service.safeInspection.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.safeInspectionAudit.updateLastAudit(inspection, auditList, shenpiInfo.audit_id);
+                        yield this.service.safeInspection.loadUser(inspection);
+                    } else if (!shenpiInfo) {
+                        // 不存在终审人的状态下这里恢复为授权审批人
+                        this.tender.info.shenpi.safe_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);
+        }
+    };
+};

+ 11 - 4
app/middleware/stage_check.js

@@ -46,7 +46,7 @@ module.exports = options => {
             if (!stage) {
                 throw '期数据错误';
             }
-
+            
             // 读取原报、审核人数据
             yield this.service.stage.loadStageUser(stage);
             yield this.service.stage.loadPreCheckedStage(stage);
@@ -69,7 +69,7 @@ module.exports = options => {
                 pp.rela_stage = pp.rela_stage ? JSON.parse(pp.rela_stage) : [];
                 return pp.rela_stage.find(x => { return x.stage_id === stage.id; });
             });
-
+            
             // 权限相关
             // todo 校验权限 (标段参与人、分享、游客)
             const accountId = this.session.sessionUser.accountId;
@@ -153,7 +153,14 @@ module.exports = options => {
                 // 进一步比较审批流是否与审批流程设置的相同,不同则替换为固定审批流或固定的终审
                 const auditList = yield this.service.stageAudit.getAllDataByCondition({ where: { sid: stage.id, times: stage.times }, orders: [['order', 'asc']] });
                 if (shenpi_status === shenpiConst.sp_status.gdspl) {
-                    const shenpiList = yield this.service.shenpiAudit.getAllDataByCondition({ where: { tid: stage.tid, sp_type: shenpiConst.sp_type.stage, sp_status: shenpi_status } });
+                    // 判断并获取审批组
+                    const group = yield this.service.shenpiGroup.getSelectGroupByStageType(this.tender.id, shenpiConst.sp_type.stage, stage.sp_group);
+                    if ((group && stage.sp_group !== group.id) || (!group && stage.sp_group !== 0)) {
+                        stage.sp_group = group ? group.id : 0;
+                        yield this.service.stage.defaultUpdate({ sp_group: stage.sp_group }, { where: { id: stage.id } });
+                    }
+                    const condition = { tid: this.tender.id, sp_type: shenpiConst.sp_type.stage, sp_status: shenpi_status, sp_group: stage.sp_group };
+                    const shenpiList = yield this.service.shenpiAudit.getAllDataByCondition({ where: condition, orders: [['audit_order', 'asc']] });
                     // 判断2个id数组是否相同,不同则删除原审批流,切换成固定的审批流
                     let sameAudit = auditList.length === shenpiList.length;
                     if (sameAudit) {
@@ -182,7 +189,7 @@ module.exports = options => {
                     }
                 }
             }
-
+        
             yield next;
         } catch (err) {
             this.helper.log(err);

+ 30 - 24
app/public/js/budget_compare.js

@@ -212,24 +212,17 @@ $(document).ready(() => {
                 rootId: -1,
             });
             const setting = { id: 'tree_id', pid: 'tree_pid', order: 'order', level: 'level', rootId: -1, calcFields: ['total_price'] };
-            const guTree = createNewPathTree('ledger', setting);
-            guTree.loadDatas(result.gu);
-            treeCalc.calculateAll(guTree);
-            compareTree.loadTree(guTree, function (cur, source) {
-                cur.base = true;
-                cur.gu_dgn_qty1 = ZhCalc.add(cur.gu_dgn_qty1, source.dgn_qty1);
-                cur.gu_dgn_qty2 = ZhCalc.add(cur.gu_dgn_qty2, source.dgn_qty2);
-                cur.gu_tp = ZhCalc.add(cur.gu_tp, source.total_price);
-            });
-            const gaiTree = createNewPathTree('ledger', setting);
-            gaiTree.loadDatas(result.gai);
-            treeCalc.calculateAll(gaiTree);
-            compareTree.loadTree(gaiTree, function (cur, source) {
+
+            const zbTree = createNewPathTree('ledger', setting);
+            zbTree.loadDatas(result.zb);
+            treeCalc.calculateAll(zbTree);
+            compareTree.loadTree(zbTree, function (cur, source) {
                 cur.base = true;
-                cur.gai_dgn_qty1 = ZhCalc.add(cur.gai_dgn_qty1, source.dgn_qty1);
-                cur.gai_dgn_qty2 = ZhCalc.add(cur.gai_dgn_qty2, source.dgn_qty2);
-                cur.gai_tp = ZhCalc.add(cur.gai_tp, source.total_price);
+                cur.zb_dgn_qty1 = ZhCalc.add(cur.zb_dgn_qty1, source.dgn_qty1);
+                cur.zb_dgn_qty2 = ZhCalc.add(cur.zb_dgn_qty2, source.dgn_qty2);
+                cur.zb_tp = ZhCalc.add(cur.zb_tp, source.total_price);
             });
+
             const yuTree = createNewPathTree('ledger', setting);
             yuTree.loadDatas(result.yu);
             treeCalc.calculateAll(yuTree);
@@ -239,15 +232,27 @@ $(document).ready(() => {
                 cur.yu_dgn_qty2 = ZhCalc.add(cur.yu_dgn_qty2, source.dgn_qty2);
                 cur.yu_tp = ZhCalc.add(cur.yu_tp, source.total_price);
             });
-            const zbTree = createNewPathTree('ledger', setting);
-            zbTree.loadDatas(result.zb);
-            treeCalc.calculateAll(zbTree);
-            compareTree.loadTree(zbTree, function (cur, source) {
+
+            const gaiTree = createNewPathTree('ledger', setting);
+            gaiTree.loadDatas(result.gai);
+            treeCalc.calculateAll(gaiTree);
+            compareTree.loadTree(gaiTree, function (cur, source) {
                 cur.base = true;
-                cur.zb_dgn_qty1 = ZhCalc.add(cur.zb_dgn_qty1, source.dgn_qty1);
-                cur.zb_dgn_qty2 = ZhCalc.add(cur.zb_dgn_qty2, source.dgn_qty2);
-                cur.zb_tp = ZhCalc.add(cur.zb_tp, source.total_price);
+                cur.gai_dgn_qty1 = ZhCalc.add(cur.gai_dgn_qty1, source.dgn_qty1);
+                cur.gai_dgn_qty2 = ZhCalc.add(cur.gai_dgn_qty2, source.dgn_qty2);
+                cur.gai_tp = ZhCalc.add(cur.gai_tp, source.total_price);
+            });
+
+            const guTree = createNewPathTree('ledger', setting);
+            guTree.loadDatas(result.gu);
+            treeCalc.calculateAll(guTree);
+            compareTree.loadTree(guTree, function (cur, source) {
+                cur.base = true;
+                cur.gu_dgn_qty1 = ZhCalc.add(cur.gu_dgn_qty1, source.dgn_qty1);
+                cur.gu_dgn_qty2 = ZhCalc.add(cur.gu_dgn_qty2, source.dgn_qty2);
+                cur.gu_tp = ZhCalc.add(cur.gu_tp, source.total_price);
             });
+
             compareTree.afterLoad(node => {
                 node.gu_dgn_price = ZhCalc.div(node.gu_tp, node.gu_dgn_qty1, 2);
                 node.gu_dgn_qty = node.gu_dgn_qty1
@@ -488,7 +493,8 @@ $(document).ready(() => {
             $('#select-final-ok').click(() => {
                 const rela = self.getSelects();
                 if (rela.length === 0) return;
-                postData(window.location.pathname + '/final', {final_id: compareObj.curFinalId(), id: rela}, function(result, msg) {
+                const data = { final_id: compareObj.curFinalId(), id: rela, final_type: $('[name=final_type]:checked').val() };
+                postData(window.location.pathname + '/final', data, function(result, msg) {
                     compareObj.loadFinalData(result, msg);
                     $('#select-final').modal('hide');
                 });

+ 11 - 10
app/public/js/budget_detail.js

@@ -146,10 +146,10 @@ $(document).ready(() => {
         },
         getDefaultSelectInfo: function (sheet) {
             const tree = sheet.zh_tree;
-            if (!tree) return;
+            if (!tree) return [null, null, 0];
             const sel = sheet.getSelections()[0];
             const node = sheet.zh_tree.nodes[sel.row];
-            if (!node) return;
+            if (!node) return [tree, null, 0];
             let count = 1;
             if (sel.rowCount > 1) {
                 for (let r = 1; r < sel.rowCount; r++) {
@@ -217,7 +217,7 @@ $(document).ready(() => {
             const valid = !readOnly;
 
             setObjEnable($('a[name=base-opr][type=add]'), valid && first && first.level > 1);
-            setObjEnable($('a[name=base-opr][type=delete]'), valid && first && sameParent && first.level > 1);
+            setObjEnable($('a[name=base-opr][type=delete]'), valid && first && sameParent);
             setObjEnable($('a[name=base-opr][type=up-move]'), valid && first && sameParent && first.level > 1 && preNode);
             setObjEnable($('a[name=base-opr][type=down-move]'), valid && first && sameParent && first.level > 1 && !tree.isLastSibling(last));
             setObjEnable($('a[name=base-opr][type=up-level]'), valid && first && sameParent && tree.getParent(first) && first.level > 2 && tree.isLastSibling(last));
@@ -237,7 +237,8 @@ $(document).ready(() => {
         baseOpr: function (sheet, type, addCount = 1) {
             const self = this;
             const [tree, node, count] = this.getDefaultSelectInfo(sheet);
-            if (!tree || !node || !count) return;
+            if (!tree) return;
+            if (type !== 'add' && (!node || !count)) return;
 
             if (type === 'delete') {
                 deleteAfterHint(function () {
@@ -257,7 +258,7 @@ $(document).ready(() => {
             } else {
                 postData(window.location.pathname + '/update', {
                     postType: type,
-                    postData: { id: node.tree_id, count: type === 'add' ? addCount : count }
+                    postData: { id: node ? node.tree_id : -1, count: type === 'add' ? addCount : count }
                 }, function (result) {
                     const refreshData = tree.loadPostData(result);
                     self.refreshTree(sheet, refreshData);
@@ -656,17 +657,17 @@ $(document).ready(() => {
                 return;
             }
         }
-
-        postData(window.location.pathname + '/update', {
+        const stdUpdateData = {
             postType: 'add-std',
             postData: {
-                id: budgetTree.getNodeKey(mainNode),
-                tender_id: mainNode.tender_id,
                 stdType: updateData.postData.stdType,
                 stdLibId: stdNode.list_id,
                 stdNode: stdTree.getNodeKey(stdNode)
             }
-        }, function (result) {
+        };
+        stdUpdateData.postData.id = mainNode ? budgetTree.getNodeKey(mainNode) : -1;
+
+        postData(window.location.pathname + '/update', stdUpdateData, function (result) {
             const sel = budgetSheet.getSelections()[0];
             const refreshNode = budgetTree.loadPostData(result);
             budgetTreeOpr.refreshTree(budgetSheet, refreshNode);

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

@@ -5801,9 +5801,9 @@ $(document).ready(() => {
             }
         });
         $('#change_form textarea').on('keypress', function () {
-            if(window.event.keyCode === 13) {
-                $(this).blur();
-            }
+            // if(window.event.keyCode === 13) {
+            //     $(this).blur();
+            // }
         });
         $('#change_form input[type="text"]').blur(function () {
             let val_name = $(this).attr('name');

+ 34 - 7
app/public/js/change_revise.js

@@ -54,6 +54,7 @@ const checkOption = {
 
 $(document).ready(() => {
     let stdXmj, stdGcl, searchLedger;
+    let changeGclData = false;
     autoFlashHeight();
     billsSpreadSetting.cols.unshift({ title: '变更清单', colSpan: '1', rowSpan: '2', field: 'is_change', hAlign: 1, width: 60, cellType: 'checkbox', readOnly: 'readOnly.isChangeList', });
     const billsIndex = _.findIndex(billsSpreadSetting.cols, { field: 'drawing_code' }) !== -1 ? _.findIndex(billsSpreadSetting.cols, { field: 'drawing_code' }) : (_.findIndex(billsSpreadSetting.cols, { field: 'ex_tp1' }) !== -1 ? _.findIndex(billsSpreadSetting.cols, { field: 'ex_tp1' }) + 1 : billsSpreadSetting.cols.length - 1);
@@ -666,6 +667,7 @@ $(document).ready(() => {
                             billsTreeSpreadObj.refreshPosData();
                         }
                         self.refreshOperationValid(sheet);
+                        changeGclData = true;
                     });
                 });
             } else {
@@ -694,6 +696,7 @@ $(document).ready(() => {
                         }
                     }
                     self.refreshOperationValid(sheet);
+                    changeGclData = true;
                 });
             }
         },
@@ -1095,6 +1098,7 @@ $(document).ready(() => {
                     }
                     billsTreeSpreadObj.refreshTree(info.sheet, refreshNode);
                     billsTreeSpreadObj.refreshPosData();
+                    changeGclData = true;
                 });
             }
         },
@@ -1348,6 +1352,7 @@ $(document).ready(() => {
                         refreshNode.update = refreshNode.update.concat(filterNodes);
                     }
                     billsTreeSpreadObj.refreshTree(info.sheet, refreshNode);
+                    changeGclData = true;
                 }, function () {
                     SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
                 });
@@ -1505,6 +1510,7 @@ $(document).ready(() => {
                         refreshNode.update = refreshNode.update.concat(filterNodes);
                     }
                     billsTreeSpreadObj.refreshTree(info.sheet, refreshNode);
+                    changeGclData = true;
                 }, function () {
                     SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
                 });
@@ -1595,6 +1601,7 @@ $(document).ready(() => {
                     const refreshNode = sheet.zh_tree.loadPostData(result);
                     billsTreeSpreadObj.refreshTree(sheet, refreshNode);
                     billsTreeSpreadObj.loadExprToInput(sheet);
+                    changeGclData = true;
                 });
             }
         },
@@ -1626,6 +1633,7 @@ $(document).ready(() => {
                 }
                 self.refreshOperationValid(sheet);
                 removeLocalCache(copyBlockTag);
+                changeGclData = true;
             }, null, true);
         },
         topRowChanged: function (e, info) {
@@ -1735,6 +1743,7 @@ $(document).ready(() => {
                     changeList = result.changeList;
                     const refreshNode = sheet.zh_tree.loadPostData(result);
                     billsTreeSpreadObj.refreshTree(sheet, refreshNode);
+                    changeGclData = true;
                 });
             }
         },
@@ -1767,6 +1776,7 @@ $(document).ready(() => {
                     changeList = result.changeList;
                     const refreshNode = tree.loadPostData(result);
                     billsTreeSpreadObj.refreshTree(sheet, refreshNode);
+                    changeGclData = true;
                 })
             }
         },
@@ -2089,6 +2099,7 @@ $(document).ready(() => {
                 changeList = result.changeList;
                 const refreshNode = billsTree.loadPostData(result);
                 billsTreeSpreadObj.refreshTree(billsSheet, refreshNode);
+                changeGclData = true;
             });
         });
 
@@ -2576,6 +2587,7 @@ $(document).ready(() => {
                         }
                         posSpreadObj.loadCurPosData();
                     }
+                    changeGclData = true;
                 });
             }
         },
@@ -3041,6 +3053,7 @@ $(document).ready(() => {
                 const refreshNode = billsTree.loadPostData(loadResult);
                 billsTreeSpreadObj.refreshTree(billsSheet, refreshNode);
                 billsTreeSpreadObj.refreshOperationValid(billsSheet);
+                changeGclData = true;
             }, function () {
                 SpreadJsObj.reLoadRowData(info.sheet, info.row);
             });
@@ -3155,6 +3168,7 @@ $(document).ready(() => {
                     const loadResult = billsTree.loadPostData(result.ledger);
                     billsTreeSpreadObj.refreshTree(billsSheet, loadResult);
                     billsTreeSpreadObj.refreshOperationValid(billsSheet);
+                    changeGclData = true;
                 }, function () {
                     posSpreadObj.loadCurPosData();
                 });
@@ -3214,6 +3228,7 @@ $(document).ready(() => {
                     const refreshNode = billsTree.loadPostData(loadResult);
                     billsTreeSpreadObj.refreshTree(billsSheet, refreshNode);
                     billsTreeSpreadObj.refreshOperationValid(billsSheet);
+                    changeGclData = true;
                 });
             }
         },
@@ -3448,6 +3463,7 @@ $(document).ready(() => {
                 billsTreeSpreadObj.refreshTree(billsSheet, refreshNode);
                 posSpreadObj.loadCurPosData();
                 billsTreeSpreadObj.refreshOperationValid(billsSheet);
+                changeGclData = true;
             }, function () {
                 SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
             });
@@ -3471,6 +3487,7 @@ $(document).ready(() => {
                 pos.updateDatas(result.pos);
                 posSpreadObj.loadCurPosData();
                 billsTreeSpreadObj.refreshOperationValid(billsSheet);
+                changeGclData = true;
             });
         }
     };
@@ -3630,6 +3647,7 @@ $(document).ready(() => {
                 const loadResult = billsTree.loadPostData(result.ledger);
                 billsTreeSpreadObj.refreshTree(billsSheet, loadResult);
                 billsTreeSpreadObj.refreshOperationValid(billsSheet);
+                changeGclData = true;
             });
         });
 
@@ -3702,6 +3720,7 @@ $(document).ready(() => {
         billsTreeSpreadObj.refreshOperationValid(billsSheet);
         billsTreeSpreadObj.loadExprToInput(billsSheet);
         checkList.loadHisCheckData();
+        makeGclGatherData();
         console.log(billsTree);
     }, null);
     $.divResizer({
@@ -3715,13 +3734,16 @@ $(document).ready(() => {
     });
 
     function makeGclGatherData() {
-        gclGatherModel.loadLedgerData(billsTree.datas);
-        gclGatherModel.loadPosData(pos.datas);
-        gclGatherData = gclGatherModel.gatherGclData();
-        gclGatherData = _.filter(gclGatherData, function (item) {
-            return item.leafXmjs && item.leafXmjs.length !== 0;
-        });
-        gclGatherData = gclGatherData.sort(sortByCode);
+        if (changeGclData || !gclGatherData) {
+            gclGatherModel.loadLedgerData(billsTree.datas);
+            gclGatherModel.loadPosData(pos.datas);
+            gclGatherData = gclGatherModel.gatherGclData();
+            gclGatherData = _.filter(gclGatherData, function (item) {
+                return item.leafXmjs && item.leafXmjs.length !== 0;
+            });
+            gclGatherData = gclGatherData.sort(sortByCode);
+            changeGclData = false;
+        }
     }
 
     class DealBills {
@@ -3775,6 +3797,7 @@ $(document).ready(() => {
                         billsTreeSpreadObj.refreshOperationValid(mainSheet);
                         billsSpread.focus();
                         posSpreadObj.loadCurPosData();
+                        changeGclData = true;
                     });
                 });
             }
@@ -3970,6 +3993,7 @@ $(document).ready(() => {
                             SpreadJsObj.reloadRowsBackColor(billsSheet, [sel.row, result.create[0].index]);
                             billsTreeSpreadObj.refreshOperationValid(billsSheet, selection);
                             posSpreadObj.loadCurPosData();
+                            changeGclData = true;
                             self.obj.modal('hide');
                         }, null, true);
                     } else {
@@ -4186,6 +4210,7 @@ $(document).ready(() => {
                         billsTreeSpreadObj.refreshOperationValid(mainSheet);
                         billsSpread.focus();
                         posSpreadObj.loadCurPosData();
+                        changeGclData = true;
                     });
                 });
             }
@@ -4442,6 +4467,7 @@ $(document).ready(() => {
                         billsTreeSpreadObj.refreshOperationValid(mainSheet);
                         billsSpread.focus();
                         posSpreadObj.loadCurPosData();
+                        changeGclData = true;
                     });
                 });
             }
@@ -4761,6 +4787,7 @@ $(document).ready(() => {
             billsTreeSpreadObj.refreshOperationValid(mainSheet);
             billsSpread.focus();
             posSpreadObj.loadCurPosData();
+            changeGclData = true;
         });
     };
     const stdXmjSetting = {

+ 19 - 12
app/public/js/file_detail.js

@@ -74,8 +74,7 @@ $(document).ready(function() {
                     spid: x.spid,
                     tips: x.tips || '',
                 };
-                if (x.is_fixed) result.isParent = true;
-                if (x.is_folder || x.is_fixed) result.open = self.expandCache.indexOf(result.id) >= 0;
+                result.open = self.expandCache.indexOf(result.id) >= 0;
                 return result;
             });
             this.filingTree = $.fn.zTree.init($('#filing'), this.setting, sortNodes);
@@ -94,12 +93,20 @@ $(document).ready(function() {
             this.refreshFileCountHint();
         }
         _getFileNameHtml(file) {
-            const moveHtml = file.canEdit || canEdit ? `<a href="javascript: void(0);" class="mr-1" name="move-file" fid="${file.id}"><i class="fa fa-exchange fa-fw"></i></a>` : '';
-            const editHtml = file.canEdit || canEdit ? `<a href="javascript: void(0);" class="mr-1" name="edit-file" fid="${file.id}"><i class="fa fa-pencil fa-fw"></i></a>` : '';
-            const viewHtml = file.viewpath ? `<a href="${file.viewpath}" class="mr-1" target="_blank"><i class="fa fa-eye fa-fw"></i></a>` : '';
-            const downHtml = `<a href="javascript: void(0);" onclick="AliOss.downloadFile('${file.filepath}', '${file.filename + file.fileext}')" class="mr-1"><i class="fa fa-download fa-fw"></i></a>`;
-            const delHtml = file.canEdit || canEdit ? `<a href="javascript: void(0);" class="mr-1 text-danger" name="del-file" fid="${file.id}"><i class="fa fa-trash-o fa-fw"></i></a>` : '';
-            return `<div class="d-flex justify-content-between align-items-center table-file"><div name="filename" fid="${file.id}">${file.filename}${file.fileext}</div><div class="btn-group-table" style="display: none;">${moveHtml}${editHtml}${viewHtml}${downHtml}${delHtml}</div></div>`;
+            return `<div class="d-flex justify-content-between align-items-center table-file"><div name="filename" fid="${file.id}">${file.filename}${file.fileext}</div></div>`;
+        }
+        _getFileControlHtml(file) {
+            const moveHtml = file.canEdit || canEdit ? `<a href="javascript: void(0);" class="dropdown-item text-primary" name="move-file" fid="${file.id}"><i class="fa fa-exchange fa-fw"></i> 移动</a>` : '';
+            const editHtml = file.canEdit || canEdit ? `<a href="javascript: void(0);" class="dropdown-item text-primary" name="edit-file" fid="${file.id}"><i class="fa fa-pencil fa-fw"></i> 重命名</a>` : '';
+            const viewHtml = file.viewpath ? `<a href="${file.viewpath}" class="dropdown-item text-primary" target="_blank"><i class="fa fa-eye fa-fw"></i> 预览</a>` : '';
+            const downHtml = `<a href="javascript: void(0);" onclick="AliOss.downloadFile('${file.filepath}', '${file.filename + file.fileext}')" class="dropdown-item text-primary"><i class="fa fa-download fa-fw"></i> 下载</a>`;
+            const delHtml = file.canEdit || canEdit ? `<div class="dropdown-divider"></div><a href="javascript: void(0);" class="dropdown-item text-danger" name="del-file" fid="${file.id}"><i class="fa fa-trash-o fa-fw"></i> 删除</a>` : '';
+            return `<div class="dropdown">
+                        <a href="javascript: void(0);" class="dropdown-toggle text-primary" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><i class="fa fa-bars"></i></a>
+                        <div class="dropdown-menu" style="will-change: transform;">
+                            ${editHtml}${viewHtml}${moveHtml}${downHtml}${delHtml}
+                        </div>
+                    </div>`;
         }
         _getEditFileNameHtml(file) {
             const inputHtml = `<input type="text" class="form-control form-control-sm form-control-width" maxlength="100" value="${file.filename + file.fileext}" fid="${file.id}">`;
@@ -114,6 +121,7 @@ $(document).ready(function() {
             html.push(`<td class="text-center">${file.user_name}</td>`);
             html.push(`<td class="text-center">${moment(file.create_time).format('YYYY-MM-DD HH:mm:ss')}</td>`);
             html.push(`<td class="text-center">${file.fileext_str}</td>`);
+            html.push(`<td class="text-center">${this._getFileControlHtml(file)}</td>`);
             html.push('</tr>');
             return html.join('');
         }
@@ -200,7 +208,6 @@ $(document).ready(function() {
                     const path = parent.getPath();
                     for (const p of path) {
                         p.name = p.source_node.name + (p.source_node.total_file_count > 0 ? `(${p.source_node.total_file_count})` : '');
-                        if (p.source_node.is_fixed) p.isParent = true;
                         filingObj.filingTree.updateNode(p);
                     }
                 }
@@ -730,8 +737,9 @@ $(document).ready(function() {
         $(this).children(".btn-group-table").css("display","none");
     });
     $('body').on('click', "a[name=del-file]", function() {
-        const del = [this.getAttribute('fid')];
-        const filename = $('[name=filename]', $(this).closest('td'))[0].innerHTML;
+        const fid = this.getAttribute('fid');
+        const del = [fid];
+        const filename = $(`div[name=filename][fid=${fid}]`)[0].innerHTML;
         filingObj.delFiles(del, null, `确认删除「${filename}」?`);
     });
     $('body').on('click', "a[name=edit-file]", function() {
@@ -851,7 +859,6 @@ $(document).ready(function() {
                     spid: x.spid,
                     tips: x.tips || '',
                 };
-                if (x.is_fixed) result.isParent = true;
                 return result;
             });
             this.moveTree = $.fn.zTree.init($('#moveFile'), this.setting, sortNodes);

+ 1 - 1
app/public/js/global.js

@@ -834,7 +834,7 @@ function setUploadFileProgress(pos) {
  */
 function setLocalCache(key, value) {
     const storage = window.localStorage;
-    if (!storage || key === '' || value === '') {
+    if (!storage || key === '') {
         return;
     }
 

+ 4 - 2
app/public/js/ledger_bwtz.js

@@ -299,7 +299,8 @@ $(document).ready(() => {
                 code: node.code, b_code: node.b_code, name: node.name, unit: node.unit,
                 dgn_qty1: node.dgn_qty1, dgn_qty2: node.dgn_qty2, dgn_price: node.dgn_price,
                 unit_price: node.unit_price, quantity: node.quantity, total_price: node.total_price,
-                drawing_code: node.drawing_code, memo: node.memo
+                drawing_code: node.drawing_code, memo: node.memo,
+                _hidden: !node.visible,
             });
             if (node.unitTree && node.unitTree.nodes.length > 0) {
                 groupData.push({ code: node.code, b_code: node.b_code, start: data.length, count: node.unitTree.nodes.length });
@@ -308,7 +309,8 @@ $(document).ready(() => {
                         code: unitNode.code, b_code: unitNode.b_code,
                         name: unitNode.pos_name ? unitNode.pos_name : unitNode.name, unit: unitNode.unit,
                         unit_price: unitNode.unit_price, quantity: unitNode.quantity, total_price: unitNode.total_price,
-                        drawing_code: unitNode.drawing_code_merge, memo: unitNode.memo_merge
+                        drawing_code: unitNode.drawing_code_merge, memo: unitNode.memo_merge,
+                        _hidden: !node.visible || !unitNode.visible,
                     });
                 }
             }

+ 5 - 4
app/public/js/path_tree.js

@@ -1002,24 +1002,25 @@ const createNewPathTree = function (type, setting) {
             }
         };
 
-        _getDefaultNodeData(node) {
+        _getDefaultNodeData(node, filter) {
             const result = {};
             for (const prop in node) {
                 if (['children', 'visible', 'expanded'].indexOf(prop) >= 0) continue;
+                if (filter && filter(prop)) continue;
                 result[prop] = node[prop];
             }
             return result;
         }
 
-        getDefaultData(node) {
+        getDefaultData(node, filter) {
             if (node instanceof Array) {
                 const arr = [];
                 for (const n of node) {
-                    arr.push(this._getDefaultNodeData(n));
+                    arr.push(this._getDefaultNodeData(n, filter));
                 }
                 return arr;
             } else {
-                this._getDefaultNodeData(node);
+                this._getDefaultNodeData(node, filter);
             }
         }
 

+ 4 - 1
app/public/js/payment_safe.js

@@ -4,6 +4,9 @@ function getTenderId() {
 const invalidFields = {
     parent: ['cur_qty', 'cur_tp', 'unit_price'],
 };
+function transExpr(expr) {
+    return $.trim(expr).replace('\t', '').replace('=', '').replace('%', '/100');
+}
 
 $(document).ready(function() {
     let stdGcl;
@@ -32,7 +35,7 @@ $(document).ready(function() {
                     {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 50, formatter: '@', cellType: 'unit'},
                     {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 80, type: 'Number'},
                     {title: '本期|数量', colSpan: '2|1', rowSpan: '1|1', field: 'cur_qty', hAlign: 2, width: 80, type: 'Number'},
-                    {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'cur_tp', hAlign: 2, width: 80, type: 'Number', readOnly: true},
+                    {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'cur_tp', hAlign: 2, width: 80, type: 'Number'},
                     {title: '截止本期|数量', colSpan: '2|1', rowSpan: '1|1', field: 'end_qty', hAlign: 2, width: 80, type: 'Number'},
                     {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_tp', hAlign: 2, width: 80, type: 'Number', readOnly: true},
                     {title: '发票号', colSpan: '1', rowSpan: '2', field: 'invoice_code', hAlign: 0, width: 100, formatter: '@'},

+ 45 - 0
app/public/js/quality_info.js

@@ -902,6 +902,15 @@ $(document).ready(() => {
             }
             this.refreshQuality();
         }
+        forceRefresh(node) {
+            this.node = node;
+            if (node.quality) {
+                node.gxby_status = node.quality.gxby_status;
+                node.gxby_date = node.quality.gxby_date;
+                node.dagl_status = node.quality.dagl_status;
+            }
+            this.refreshQuality();
+        }
     }
     const qualityObj = new Quality();
 
@@ -920,6 +929,42 @@ $(document).ready(() => {
             return target.hitTestType === spreadNS.SheetArea.viewport || target.hitTestType === spreadNS.SheetArea.rowHeader;
         },
         items: {
+            showLast: {
+                name: '显示至最底层',
+                callback: function (key, opt, menu, e) {
+                    const node = SpreadJsObj.getSelectObject(xmjSheet);
+                    if (!node) return;
+
+                    setTimeout(() => {
+                        showWaitingView();
+                        xmjTree.expandByCustom(x => {
+                            return x.expanded || (x.id === node.id) || (x.full_path.indexOf(node.ledger_id + '-') >= 0);
+                        });
+                        SpreadJsObj.refreshTreeRowVisible(xmjSheet);
+                        closeWaitingView();
+                    }, 100);
+                },
+                disabled: function (key, opt) {
+                    const node = SpreadJsObj.getSelectObject(xmjSheet);
+                    return !node;
+                }
+            },
+            refreshSpr: '----',
+            forceRefresh: {
+                name: '刷新全部节点数据',
+                callback: function(key, opt, menu, e) {
+                    const data = { type: 'all' };
+                    const node = SpreadJsObj.getSelectObject(xmjSheet);
+                    if (node) data.load = { filter: 'detail', rela_type: node.rela_type, rela_id: node.rela_id, rela_name: node.rela_name,};
+                    postData('force', data, function(result) {
+                        if (node) {
+                            node.quality = result.detail;
+                            qualityObj.forceRefresh(node);
+                        }
+                    });
+                }
+            },
+            pushSpr: '----',
             pushInc: {
                 name: '推送状态',
                 callback: function (key, opt, menu, e) {

+ 49 - 1
app/public/js/quality_inspection_information.js

@@ -28,6 +28,54 @@ $(document).ready(function () {
         }
     });
 
+    function debounce(fn, wait) {
+        var t = null;
+        return function () {
+            var args = arguments;
+            clearTimeout(t);
+            t = setTimeout(function () { fn.apply(null, args); }, wait);
+        };
+    }
+
+    $('textarea[id]').each(function () {
+        var $el = $(this);
+        var el = $el[0];
+        var id = $el.attr('id');
+        var key = 'textarea_height_' + id;
+
+        // 1. 页面加载恢复高度
+        var saved = getLocalCache(key);
+        if (saved) {
+            $el.css('height', saved + 'px');
+        }
+
+        // 保存函数(使用 clientHeight)
+        var save = debounce(function () {
+            try {
+                var h = el.getBoundingClientRect().height; // 真实渲染高度
+                h = Math.round(h); // 防止小数
+                setLocalCache(key, h);
+            } catch (e) {
+                console.warn('保存高度失败', e);
+            }
+        }, 150); // 150ms 防抖
+
+        // 2. 实时监听拖拽高度
+        if (window.ResizeObserver) {
+            var ro = new ResizeObserver(function () {
+                save();
+            });
+            ro.observe(el);
+        } else {
+            // 兼容方案
+            $(window).on('mouseup', function () {
+                save();
+            });
+            // 也监听 blur(防止通过键盘或脚本改变尺寸)
+            $el.on('blur', function () { save(); });
+        }
+    });
+
     // 展开历史审核记录
     $('td #fold-btn').click(function () {
         const type = $(this).data('target')
@@ -70,7 +118,7 @@ $(document).ready(function () {
         oldSearchVal = e.target.value
         timer && clearTimeout(timer)
         timer = setTimeout(() => {
-            const newVal = $('#gr-search').val()
+            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 => {

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

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

+ 4 - 2
app/public/js/revise.js

@@ -956,12 +956,14 @@ $(document).ready(() => {
                 case 'sjcl_tp':
                 case 'qtcl_qty':
                 case 'qtcl_tp':
-                case 'deal_qty':
-                case 'deal_tp':
                 case 'ex_qty1':
                 case 'ex_tp1':
                     info.cancel = (node.children && node.children.length > 0) || node.settle_status === settleStatus.finish || (posRange && posRange.length > 0);
                     break;
+                case 'deal_qty':
+                case 'deal_tp':
+                    info.cancel = (node.children && node.children.length > 0) || node.settle_status === settleStatus.finish;
+                    break;
                 case 'dgn_qty1':
                 case 'dgn_qty2':
                     info.cancel = !_.isEmpty(node.b_code) || node.settle_status === settleStatus.finish;

+ 349 - 0
app/public/js/safe_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}/safe/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}/safe/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}/safe/tender/${tenderId}/inspection`;
+        const filterString = setChangeFilterData('safe-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('safe-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('safe-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('safe-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}/safe/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();
+        }
+    });
+});

+ 679 - 0
app/public/js/safe_inspection_information.js

@@ -0,0 +1,679 @@
+'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();
+        }
+    });
+
+    function debounce(fn, wait) {
+        var t = null;
+        return function () {
+            var args = arguments;
+            clearTimeout(t);
+            t = setTimeout(function () { fn.apply(null, args); }, wait);
+        };
+    }
+
+    $('textarea[id]').each(function () {
+        var $el = $(this);
+        var el = $el[0];
+        var id = $el.attr('id');
+        var key = 'textarea_height_' + id;
+
+        // 1. 页面加载恢复高度
+        var saved = getLocalCache(key);
+        if (saved) {
+            $el.css('height', saved + 'px');
+        }
+
+        // 保存函数(使用 clientHeight)
+        var save = debounce(function () {
+            try {
+                var h = el.getBoundingClientRect().height; // 真实渲染高度
+                h = Math.round(h); // 防止小数
+                setLocalCache(key, h);
+            } catch (e) {
+                console.warn('保存高度失败', e);
+            }
+        }, 150); // 150ms 防抖
+
+        // 2. 实时监听拖拽高度
+        if (window.ResizeObserver) {
+            var ro = new ResizeObserver(function () {
+                save();
+            });
+            ro.observe(el);
+        } else {
+            // 兼容方案
+            $(window).on('mouseup', function () {
+                save();
+            });
+            // 也监听 blur(防止通过键盘或脚本改变尺寸)
+            $el.on('blur', function () { save(); });
+        }
+    });
+
+    // 展开历史审核记录
+    $('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}/safe/tender/${tender_id}/inspection`;
+                let orderSetting = getLocalCache('safe-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
+    })
+}
+

+ 58 - 0
app/public/js/safe_tender.js

@@ -0,0 +1,58 @@
+'use strict';
+
+const tenderListSpec = (function(){
+    function getTenderNodeHtml(node, arr, pid) {
+        const html = [];
+        html.push('<tr pid="' + pid + '">');
+        // 名称
+        html.push('<td style="min-width: 200px" class="in-' + node.level + '">');
+        if (node.cid) {
+            html.push('<span onselectstart="return false" style="{-moz-user-select:none}" class="fold-switch mr-1" title="收起" cid="'+ node.sort_id +'"><i class="fa fa-minus-square-o"></i></span> <i class="fa fa-folder-o"></i> ', node.name);
+        } else {
+            html.push('<span class="text-muted mr-2">');
+            html.push(arr.indexOf(node) === arr.length - 1 ? '└' : '├');
+            html.push('</span>');
+            //html.push('<a href="/tender/' + node.id + '">', node[c.field], '</a>');
+            html.push(`<a href="/sp/${spid}/safe/tender/${node.id}/${isInspection ? 'inspection' : 'info'}" name="name" style="min-width: 200px;word-break:break-all;" id="${node.id}">${node.name}</a>`);
+        }
+        html.push('</td>');
+
+        // 创建时间
+        html.push('<td style="width: 8%" class="text-center">');
+        html.push(node.create_time ? moment(node.create_time).format('YYYY-MM-DD HH:mm:ss') : '');
+        html.push('</td>');
+        // 设置
+        if (is_admin) {
+            html.push('<td style="width: 10%" class="text-center">');
+            if (!node.cid) {
+                html.push(`<a href="javascript:void(0);" data-toggle="modal" data-tid="${node.id}" class="btn btn-sm btn-outline-primary member-manage"> 成员管理 </a>`);
+            }
+            html.push('</td>');
+        }
+        html.push('</tr>');
+        return html.join('');
+    }
+    function getTenderTreeHeaderHtml() {
+        const html = [];
+        const left = $('#sub-menu').css('display') === 'none' ? 56 : 176;
+        html.push('<table class="table table-hover table-bordered">');
+        html.push('<thead style="position: sticky;left:'+ left +'px;top: 0;" class="text-center">', '<tr>');
+        html.push('<th style="min-width: 50%">',  '标段名称',  tenderListOrder.getOrderButton('name'), '</th>');
+        html.push('<th style="width: 15%">', '创建时间',  tenderListOrder.getOrderButton('create_time'), '</th>');
+        if (is_admin) {
+            html.push('<th style="width: 15%">', '操作', '</th>');
+        }
+        html.push('</tr>');
+        html.push('</thead>');
+        return html.join('');
+    }
+    return { getTenderNodeHtml, getTenderTreeHeaderHtml }
+})();
+
+$(document).ready(() => {
+    const memberPermission = MemberPermission();
+    $('body').on('click', '.member-manage', function(){
+        const tid = this.getAttribute('data-tid');
+        memberPermission.show({ data: { tid }, loadUrl: `/sp/${spid}/quality/member`, saveUrl: `/sp/${spid}/quality/memberSave`});
+    });
+});

+ 0 - 5
app/public/js/se_bonus.js

@@ -413,7 +413,6 @@ $(document).ready(() => {
                 if (!sels || !sels[0]) return;
                 const hint = {
                     isOld: {type: 'warning', msg: '本项为往期数据,不可删除'},
-                    invalidDel: {type: 'warning', msg: '本项不是您新增的,只有原报和新增人可删除'},
                 };
 
                 for (let iRow = sels[0].row, iLen = sels[0].row + sels[0].rowCount; iRow < iLen; iRow++) {
@@ -422,10 +421,6 @@ $(document).ready(() => {
                         toastMessageUniq(hint.isOld);
                         continue;
                     } else {
-                        if (node.uid !== userID && stageUserId !== userID) {
-                            toastMessageUniq(hint.invalidDel);
-                            continue;
-                        }
                         datas.push(node.id);
                     }
                 }

+ 9 - 0
app/public/js/setting.js

@@ -274,6 +274,15 @@ $(document).ready(() => {
             $('#nav_tender').attr('href', item.path)
         });
     });
+
+    const $activeItem = $('.list-group-item.active');
+    if ($activeItem.length) {
+      const $scrollContainer = $activeItem.closest('.col-3');
+      const scrollTop = $activeItem.offset().top - $scrollContainer.offset().top + $scrollContainer.scrollTop();
+      $scrollContainer.animate({ scrollTop: scrollTop }, 100);
+    }
+
+
     // 设置页显示数目
     $('.nav-tabs .nav-link').each(function () {
         const pageSize = getLocalCache('account-pageSize') ? getLocalCache('account-pageSize') : '';

+ 323 - 78
app/public/js/setting_manage.js

@@ -163,6 +163,7 @@ function getFilterTenderTreeHtml() {
 }
 
 $(document).ready(() => {
+
     autoFlashHeight();
     function getObjHeight(select) {
         return select.length > 0 ? select.height() : 0;
@@ -176,10 +177,16 @@ $(document).ready(() => {
         contract: '合同管理',
         construction: '施工日志',
         quality: '质量管理',
-        inspection: '质量巡检',
+        safe: '安全管理',
+    };
+    const tabTypeKeys = ['tourist', 'schedule', 'contract', 'construction', 'quality', 'safe'];
+    // 单个tenderPermission字段用这个
+    const tenderPermissionKeys = [];
+    // 多个tenderPermission字段融合处理用这个
+    const tPsKeys = {
+        quality: ['quality', 'inspection'],
+        safe: ['safe_inspection', 'safe_payment'],
     };
-    const tabTypeKeys = ['tourist', 'schedule', 'contract', 'construction', 'quality', 'inspection'];
-
     const $filterTenderDone = $('body #filter-tender-done')
     if (window.location.search && window.location.search.split('done=')[1]) {
       $filterTenderDone.prop('checked', window.location.search.split('done=')[1] === '1' ? true : false);
@@ -215,8 +222,12 @@ $(document).ready(() => {
             setScheduleHtml(result.scheduleAuditList);
             setContractHtml(result.contractAuditList);
             setConstructionHtml(result.constructionAuditList);
-            setTenderPermissionHtml(result.qualityAuditList, 'quality');
-            setTenderPermissionHtml(result.inspectionAuditList, 'inspection');
+            for (const tpkey of tenderPermissionKeys) {
+                setTenderPermissionHtml(result[tpkey + 'AuditList'], tpkey);
+            }
+            for (const tpk in tPsKeys) {
+                setTenderPermissionsHtml(result[tpk + 'AuditList'], tpk, tPsKeys[tpk]);
+            }
             resetAddUserHtml();
         });
     });
@@ -232,21 +243,33 @@ $(document).ready(() => {
             $('#user-set').hide();
         } else {
             $('#user-set').show();
-            if ($(this).attr('href') === '#guest') {
+            const href = $(this).attr('href');
+            if (href === '#guest') {
                 $('#add_user_dropdownMenuButton').attr('data-type', 'tourist');
-            } else if ($(this).attr('href') === '#tzpro') {
+            } else if (href === '#tzpro') {
                 $('#add_user_dropdownMenuButton').attr('data-type', 'schedule');
-            } else if ($(this).attr('href') === '#htgl') {
+            } else if (href === '#htgl') {
                 $('#add_user_dropdownMenuButton').attr('data-type', 'contract');
                 $('#contract-tip').show();
-            } else if ($(this).attr('href') === '#sgrz') {
+            } else if (href === '#sgrz') {
                 $('#add_user_dropdownMenuButton').attr('data-type', 'construction');
-            } else if ($(this).attr('href') === '#zlgl') {
-                $('#add_user_dropdownMenuButton').attr('data-type', 'quality');
-            } else if ($(this).attr('href') === '#zlxj') {
-                $('#add_user_dropdownMenuButton').attr('data-type', 'inspection');
+            } else if (_.includes(tenderPermissionKeys, $(this).attr('href').substring(1))) {
+                $('#add_user_dropdownMenuButton').attr('data-type', $(this).attr('href').substring(1));
+            } else if (_.includes(Object.keys(tPsKeys), $(this).attr('href').substring(1))) {
+                $('#add_user_dropdownMenuButton').attr('data-type', $(this).attr('href').substring(1));
+            }
+            let currentNavType = null;
+            if (href === '#guest') currentNavType = 'tourist';
+            else if (href === '#tzpro') currentNavType = 'schedule';
+            else if (href === '#htgl') currentNavType = 'contract';
+            else if (href === '#sgrz') currentNavType = 'construction';
+            else if (href === '#zlgl') currentNavType = 'quality';
+            else if (href === '#zlxj') currentNavType = 'inspection';
+            if(currentNavType) {
+                setTimeout(() => updateSelectedMarks(currentNavType), 0);
             }
         }
+        
     });
 
     $('body').on('click', '.c-body a', function (e) {
@@ -325,57 +348,69 @@ $(document).ready(() => {
         postData('/sp/' + spid + '/construction/' + cur_tenderid + '/audit/save', prop, function (data) {
         });
     });
-
-    // 权限更改
-    $('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],
-            key: 'quality',
-        };
-        const _self = $(this);
-        postData('/sp/' + spid + '/quality/' + cur_tenderid + '/audit/save', prop, function (data) {
+    for (const tpKey of tenderPermissionKeys) {
+        $('body').on('click', `#${tpKey}-users input[type="checkbox"]`, function () {
+            const uid = parseInt($(this).parents('tr').data('uid'));
+            const member = {
+                uid,
+            };
+            member[tpKey] = [1];
+            $(this).parents('tr').find('input[type="checkbox"]').each(function () {
+                if ($(this).is(':checked')) {
+                    member[tpKey].push($(this).data('value'));
+                }
+            });
+            const prop = {
+                type: 'save-permission',
+                uid,
+                members: [member],
+                key: tpKey,
+            };
+            const _self = $(this);
+            postData('/sp/' + spid + '/quality/' + cur_tenderid + '/audit/save', prop, function (data) {
+            });
         });
-    });
-
-    // 权限更改
-    $('body').on('click', '#inspection-users input[type="checkbox"]', function () {
-        const uid = parseInt($(this).parents('tr').data('uid'));
-        const member = {
-            uid,
-            inspection: [1],
-        };
-        $(this).parents('tr').find('input[type="checkbox"]').each(function () {
-            if ($(this).is(':checked')) {
-                member.inspection.push($(this).data('value'));
+    }
+    for (const tPsKey in tPsKeys) {
+        $('body').on('click', `#${tPsKey}-users input[type="checkbox"]`, function () {
+            const uid = parseInt($(this).parents('tr').data('uid'));
+            const member = {
+                uid,
+            };
+            const key = $(this).data('key');
+            member[key] = [];
+            if ($(this).data('block') === 'view' && !$(this).is(':checked')) {
+                $(this).parents('tr').find(`input[data-key="${key}"]`).prop('checked', false);
+            } else {
+                let viewFlag = false;
+                $(this).parents('tr').find(`input[data-key="${key}"]`).each(function () {
+                    if ($(this).is(':checked')) {
+                        if ($(this).data('block') !== 'view') {
+                            member[key].push(1);
+                            $(this).parents('tr').find(`input[data-key="${key}"][data-block="view"]`).prop('checked', true);
+                            viewFlag = true;
+                        }
+                        member[key].push($(this).data('value'));
+                    }
+                });
             }
+            const prop = {
+                type: 'save-permission',
+                uid,
+                members: [member],
+                key,
+            };
+            const _self = $(this);
+            postData('/sp/' + spid + '/quality/' + cur_tenderid + '/audit/save', prop, function (data) {
+            });
         });
-        const prop = {
-            type: 'save-permission',
-            uid,
-            members: [member],
-            key: 'inspection',
-        };
-        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);
             $('#remove_user_id').val($(this).data('id'));
+            $('#show_id').val($(this).data('show-id'));
         });
     }
 
@@ -387,22 +422,46 @@ $(document).ready(() => {
             return;
         }
         const id = parseInt($('#remove_user_id').val());
+        const showId = parseInt($('#show_id').val());
         if (type === 'contract') {
             postData('/sp/' + spid + '/contract/tender/' + cur_tenderid + '/audit/save', { type: 'del-audit', id }, function (data) {
-                $('#'+ type + '-users').find('tr[data-id="'+ id +'"]').remove();
+                $('#'+ type + '-users').find('tr[data-uid="'+ id +'"]').remove();
                 $('#remove-user').modal('hide');
+                selectedUserIdsByType[type] = selectedUserIdsByType[type].filter(function(userId) {
+                    return userId !== showId;
+                });
+                updateSelectedMarks();
             });
         } else if (type === 'construction') {
             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' || type === 'inspection') {
+        } else if (_.includes(tenderPermissionKeys, type)) {
             postData('/sp/' + spid + '/quality/' + cur_tenderid + '/audit/save', { type: 'del-audit', id, key: type }, function (data) {
                 $('#'+ type + '-users').find('tr[data-uid="'+ id +'"]').remove();
                 $('#remove-user').modal('hide');
+                selectedUserIdsByType[type] = selectedUserIdsByType[type].filter(function(userId) {
+                    return userId !== showId;
+                });
+                updateSelectedMarks();
+            });
+        } else if (_.includes(Object.keys(tPsKeys), type)) {
+            postData('/sp/' + spid + '/quality/' + cur_tenderid + '/audit/save', { type: 'del-audit', id, key: type, together: 1 }, function (data) {
+                $('#'+ type + '-users').find('tr[data-uid="'+ id +'"]').remove();
+                $('#remove-user').modal('hide');
+                selectedUserIdsByType[type] = selectedUserIdsByType[type].filter(function(userId) {
+                    return userId !== showId;
+                });
+                updateSelectedMarks();
+            });
+        } else if (_.includes(Object.keys(tPsKeys), type)) {
+            postData('/sp/' + spid + '/quality/' + cur_tenderid + '/audit/save', { type: 'del-audit', id, key: type, together: 1 }, function (data) {
+                $('#'+ type + '-users').find('tr[data-uid="'+ id +'"]').remove();
+                $('#remove-user').modal('hide');
             });
         } else {
+            
             const prop = {
                 id: id,
                 type: 'del',
@@ -410,7 +469,12 @@ $(document).ready(() => {
             postData('/tender/' + cur_tenderid + '/' + type + '/audit/save', prop, function (data) {
                 $('#'+ type + '-users').find('tr[data-id="'+ id +'"]').remove();
                 $('#remove-user').modal('hide');
+                selectedUserIdsByType[type] = selectedUserIdsByType[type].filter(function(userId) {
+                    return userId !== showId;
+                });
+                updateSelectedMarks();
             });
+            
         }
 
     });
@@ -419,6 +483,13 @@ $(document).ready(() => {
     let timerAddUser = null;
     let oldSearchValAddUser = null;
     $('body').on('input propertychange', '#add_user_dropdownMenu2 .gr-search', function (e) {
+
+        const currentDropdownType = $('#add_user_dropdownMenuButton').attr('data-type');
+        let currentSelectedIdsForType = [];
+        if (currentDropdownType && selectedUserIdsByType.hasOwnProperty(currentDropdownType)) {
+            currentSelectedIdsForType = selectedUserIdsByType[currentDropdownType]; // 获取对应类型的ID数组
+        }
+
         oldSearchValAddUser = e.target.value;
         timerAddUser && clearTimeout(timerAddUser);
         timerAddUser = setTimeout(() => {
@@ -426,9 +497,14 @@ $(document).ready(() => {
             if (newVal && newVal === oldSearchValAddUser) {
                 let html = '';
                 accountList.filter(item => item && item.id !== cur_uid && (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}" >
+                    const itemIdInt = parseInt(item.id, 10);
+                    const isSelected = currentSelectedIdsForType.includes(itemIdInt);
+                    const selectedMarkStyle = isSelected ? '' : ' style="display:none;"';
+                    html += `<dd class="border-bottom p-2 mb-0 user-item" 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>
+                                class="ml-auto">${item.mobile || ''}</span>
+                                <span class="selected-mark text-success ml-2"${selectedMarkStyle}><i class="fa fa-check"></i></span>
+                                </p>
                         <span class="text-muted">${item.role || ''}</span>
                     </dd>`
                 });
@@ -443,6 +519,11 @@ $(document).ready(() => {
     });
 
     function resetAddUserHtml() {
+        const currentDropdownType = $('#add_user_dropdownMenuButton').attr('data-type');
+        let currentSelectedIdsForType = [];
+        if (currentDropdownType && selectedUserIdsByType.hasOwnProperty(currentDropdownType)) {
+            currentSelectedIdsForType = selectedUserIdsByType[currentDropdownType]; 
+        }
         let html = '';
         accountGroup.forEach((group, idx) => {
             if (!group) return;
@@ -451,9 +532,14 @@ $(document).ready(() => {
                         <div class="dd-content" data-toggleid="${idx}">`;
             group.groupList.forEach(item => {
                 if (item.id !== cur_uid) {
-                    html += `<dd class="border-bottom p-2 mb-0 " data-id="${item.id}" >
+                    const itemIdInt = parseInt(item.id, 10);
+                    const isSelected = currentSelectedIdsForType.includes(itemIdInt);
+                    const selectedMarkStyle = isSelected ? '' : ' style="display:none;"';
+                    html += `<dd class="border-bottom p-2 mb-0 user-item" 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>
+                                            class="ml-auto">${item.mobile || ''}</span>
+                                            <span class="selected-mark text-success ml-2"${selectedMarkStyle}><i class="fa fa-check"></i></span>
+                                    </p>
                                     <span class="text-muted">${item.role || ''}</span>
                                 </dd>`;
                 }
@@ -484,7 +570,7 @@ $(document).ready(() => {
     });
 
     // 选中用户
-    $('body').on('click', '#add_user_dropdownMenu2 dl dd', function () {
+    $('body').on('click', '#add_user_dropdownMenu2 dl dd', function (e) {
         const id = parseInt($(this).data('id'));
         if (id) {
             const user = _.find(accountList, function (item) {
@@ -498,6 +584,7 @@ $(document).ready(() => {
                 }
                 if (_.includes(saIdList, id)) {
                     toastr.error('该用户已存在列表中,无需重复添加');
+                    e.stopPropagation();
                     return;
                 }
 
@@ -506,6 +593,7 @@ $(document).ready(() => {
                     type: 'add',
                 };
                 postData('/tender/' + cur_tenderid + '/tourist/audit/save', prop, function (data) {
+                    selectedUserIdsByType.tourist.push(id);
                     const html = `<tr data-uid="${user.id}" data-id="${data.id}">
                                     <td>${user.name}</td>
                                     <td>${user.role}</td>
@@ -522,10 +610,11 @@ $(document).ready(() => {
                                         </div>
                                     </td>
                                     <td class="text-center">
-                                        <a href="#remove-user1" data-id="${data.id}" data-toggle="modal" data-target="#remove-user" class="btn btn-sm btn-outline-danger remove-tourist-user">移除</a>
+                                        <a href="#remove-user1" data-id="${data.id}" data-show-id="${user.id}" data-toggle="modal" data-target="#remove-user" class="btn btn-sm btn-outline-danger remove-tourist-user">移除</a>
                                     </td>
                                 </tr>`;
                     $('#tourist-users').append(html);
+                    updateSelectedMarks();
                 });
             } else if (type === 'schedule') {
                 const user = _.find(accountList, function (item) {
@@ -545,6 +634,7 @@ $(document).ready(() => {
                     type: 'add',
                 };
                 postData('/tender/' + cur_tenderid + '/schedule/audit/save', prop, function (data) {
+                    selectedUserIdsByType.schedule.push(user.id);
                     const html = `<tr data-uid="${user.id}" data-id="${data.id}">
                                     <td>${user.name}</td>
                                     <td>${user.role}</td>
@@ -561,10 +651,11 @@ $(document).ready(() => {
                                         </div>
                                     </td>
                                     <td class="text-center">
-                                        <a href="#remove-user1" data-id="${data.id}" data-toggle="modal" data-target="#remove-user" class="btn btn-sm btn-outline-danger remove-schedule-user">移除</a>
+                                        <a href="#remove-user1" data-id="${data.id}" data-show-id="${user.id}" data-toggle="modal" data-target="#remove-user" class="btn btn-sm btn-outline-danger remove-schedule-user">移除</a>
                                     </td>
                                 </tr>`;
                     $('#schedule-users').append(html);
+                    updateSelectedMarks();
                 });
             } else if (type === 'contract') {
                 const user = _.find(accountList, function (item) {
@@ -606,7 +697,7 @@ $(document).ready(() => {
                 postData('/sp/' + spid + '/construction/' + cur_tenderid + '/audit/save', prop, function (datas) {
                     setConstructionHtml(datas);
                 });
-            } else if (type === 'quality' || type === 'inspection') {
+            } else if (_.includes(tenderPermissionKeys, type)) {
                 const user = _.find(accountList, function (item) {
                     return item.id === id;
                 });
@@ -627,8 +718,28 @@ $(document).ready(() => {
                 postData('/sp/' + spid + '/quality/' + cur_tenderid + '/audit/save', prop, function (datas) {
                     setTenderPermissionHtml(datas, type);
                 });
+            } else if (_.includes(Object.keys(tPsKeys), type)) {
+                const saIdList = [];
+                for (let i = 0; i < $('#' + type + '-users tr').length; i++) {
+                    saIdList.push(parseInt($('#' + type + '-users tr').eq(i).data('uid')));
+                }
+                if (_.includes(saIdList, id)) {
+                    toastr.error('该用户已存在列表中,无需重复添加');
+                    return;
+                }
+
+                const prop = {
+                    id: id,
+                    type: 'add-audit',
+                    key: type,
+                    together: 1,
+                };
+                postData('/sp/' + spid + '/quality/' + cur_tenderid + '/audit/save', prop, function (datas) {
+                    setTenderPermissionsHtml(datas, type, tPsKeys[type]);
+                });
             }
         }
+        e.stopPropagation();
     });
     sortCategory4User();
     initTenderTree4User();
@@ -775,13 +886,24 @@ $(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);
-            } else if (userType === 'inspection') {
-                userData.member = [1];
-                if ($('#inspection-users tr').eq(i).find('input[data-block="add"]').eq(0).is(':checked')) userData.member.push(2);
+            } else if (_.includes(tenderPermissionKeys, userType)) {
+                userData.member = {};
+                userData.member[userType] = [1];
+                $('#'+ userType +'-users tr').eq(i).find('input[type="checkbox"]').each(function () {
+                    if ($(this).is(':checked')) {
+                        userData.member[userType].push(parseInt($(this).attr('data-value')));
+                    }
+                });
+            } else if (_.includes(Object.keys(tPsKeys), userType)) {
+                userData.member = {};
+                for (const key of tPsKeys[userType]) {
+                    userData.member[key] = [];
+                    $('#'+ userType +'-users tr').eq(i).find('input[data-key="' + key + '"]').each(function () {
+                        if ($(this).is(':checked')) {
+                            userData.member[key].push(parseInt($(this).attr('data-value')));
+                        }
+                    });
+                }
             }
             saIdList.push(userData);
         }
@@ -950,6 +1072,55 @@ const tenderListSpec = (function(){
     return { getTenderNodeHtml, getTenderTreeHeaderHtml }
 })();
 
+function updateSelectedMarks(forcedType = null) { 
+    let currentUserListSelector = '';
+    let selectedIdsArray = []; 
+    let currentType = forcedType; 
+    if (!currentType) {
+        const $activeTabPane = $('.tab-pane.active');
+        const activeTabId = $activeTabPane.attr('id');
+        switch(activeTabId) {
+            case 'guest': currentType = 'tourist'; break;
+            case 'tzpro': currentType = 'schedule'; break;
+            case 'htgl': currentType = 'contract'; break;
+            case 'sgrz': currentType = 'construction'; break;
+            case 'zlgl': currentType = 'quality'; break;
+            case 'zlxj': currentType = 'inspection'; break;
+            default: currentType = null;
+        }
+    }
+
+    if (currentType && selectedUserIdsByType.hasOwnProperty(currentType)) {
+        switch(currentType) {
+            case 'tourist': currentUserListSelector = '#tourist-users'; break;
+            case 'schedule': currentUserListSelector = '#schedule-users'; break;
+            case 'contract': currentUserListSelector = '#contract-users'; break;
+            case 'construction': currentUserListSelector = '#construction-users'; break;
+            case 'quality': currentUserListSelector = '#quality-users'; break;
+            case 'inspection': currentUserListSelector = '#inspection-users'; break;
+        }
+        selectedIdsArray = selectedUserIdsByType[currentType];
+    } else {
+        $('.user-item').find('.selected-mark').hide();
+        $('.user-item').removeClass('bg-light');
+        return;
+    }
+    if ($('#add_user_dropdownMenu2 .book-list').length > 0) {
+        $('.user-item').each(function() {
+            const userIdStr = $(this).data('id');
+            const userId = parseInt(userIdStr, 10);
+            const $mark = $(this).find('.selected-mark');
+            if (!isNaN(userId) && selectedIdsArray.includes(userId)) {
+                $mark.show();
+                $(this).addClass('bg-light');
+            } else {
+                $mark.hide();
+                $(this).removeClass('bg-light');
+            }
+        });
+    }
+}
+
 function setShenpiHtml(shenpi, tender, revising) {
     let html = '';
     if (shenpi.sp_lc.length > 0) {
@@ -1008,6 +1179,14 @@ function setShenpiHtml(shenpi, tender, revising) {
     $('#splc').html(html);
 }
 
+let selectedUserIdsByType = {
+    tourist: [],
+    schedule: [],
+    contract: [],
+    construction: [],
+    quality: [],
+    inspection: []
+};
 function setTouristHtml(tourists) {
     let html = '';
     if (tourists.length > 0) {
@@ -1028,12 +1207,21 @@ function setTouristHtml(tourists) {
                                         </div>
                                     </td>
                                     <td class="text-center">
-                                        <a href="#remove-user1" data-id="${t.id}" data-toggle="modal" data-target="#remove-user" class="btn btn-sm btn-outline-danger remove-tourist-user">移除</a>
+                                        <a href="#remove-user1" data-id="${t.id}" data-show-id="${t.user_id}" data-toggle="modal" data-target="#remove-user" class="btn btn-sm btn-outline-danger remove-tourist-user">移除</a>
                                     </td>
                                 </tr>`;
         }
+        selectedUserIdsByType.tourist = []; 
+        if (Array.isArray(tourists)) {
+            tourists.forEach(function(tourist) {
+                if (tourist.user_id !== undefined && tourist.user_id !== null) {
+                    selectedUserIdsByType.tourist.push(parseInt(tourist.user_id, 10)); 
+                }
+            });
+        }
     }
     $('#tourist-users').html(html);
+    updateSelectedMarks();
 }
 
 function setScheduleHtml(scheduleAuditList) {
@@ -1057,12 +1245,20 @@ function setScheduleHtml(scheduleAuditList) {
                                         </div>
                                     </td>
                                     <td class="text-center">
-                                        <a href="#remove-user1" data-id="${sa.id}" data-toggle="modal" data-target="#remove-user" class="btn btn-sm btn-outline-danger remove-schedule-user">移除</a>
+                                        <a href="#remove-user1" data-id="${sa.id}" data-show-id="${sa.user_id}"  data-toggle="modal" data-target="#remove-user" class="btn btn-sm btn-outline-danger remove-schedule-user">移除</a>
                                     </td>
                                 </tr>`;
         }
+        if (Array.isArray(scheduleAuditList)) {
+            scheduleAuditList.forEach(function(sa) {
+                if (sa.audit_id !== undefined && sa.audit_id !== null) {
+                    selectedUserIdsByType.schedule.push(parseInt(sa.audit_id, 10)); 
+                }
+            });
+        }
     }
     $('#schedule-users').html(html);
+    updateSelectedMarks();
 }
 
 function setContractHtml(datas) {
@@ -1090,11 +1286,19 @@ function setContractHtml(datas) {
                                 <input type="checkbox" class="permission-checkbox" data-type="permission_show_node" value="${ca.uid}" ${ca.permission_show_node ? 'checked' : ''}>
                             </td>
                             <td class="text-center">
-                                <a href="#remove-user1" data-toggle="modal" data-target="#remove-user" class="btn btn-outline-danger btn-sm ml-1 remove-contract-user" data-id="${ca.uid}">移除</a>
+                                <a href="#remove-user1" data-show-id="${ca.uid}" data-toggle="modal" data-target="#remove-user" class="btn btn-outline-danger btn-sm ml-1 remove-contract-user" data-id="${ca.uid}">移除</a>
                             </td>
                         </tr>`;
     }
+    if (Array.isArray(datas)) {
+            datas.forEach(function(ca) {
+                if (ca.uid !== undefined && ca.uid !== null) {
+                    selectedUserIdsByType.contract.push(parseInt(ca.uid, 10)); 
+                }
+            });
+        }
     $('#contract-users').html(list);
+    updateSelectedMarks();
 }
 
 function setConstructionHtml(constructionAuditList) {
@@ -1111,20 +1315,61 @@ function setConstructionHtml(constructionAuditList) {
                                         </div>
                                     </td>
                                     <td class="text-center">
-                                        <a href="#remove-user1" data-id="${sa.id}" data-toggle="modal" data-target="#remove-user" class="btn btn-sm btn-outline-danger remove-construction-user">移除</a>
+                                        <a href="#remove-user1" data-show-id="${sa.uid}" data-id="${sa.id}" data-toggle="modal" data-target="#remove-user" class="btn btn-sm btn-outline-danger remove-construction-user">移除</a>
                                     </td>
                                 </tr>`;
         }
+        if (Array.isArray(constructionAuditList)) {
+            constructionAuditList.forEach(function(sa) {
+                if (sa.uid !== undefined && sa.uid !== null) {
+                    selectedUserIdsByType.construction.push(parseInt(sa.uid, 10)); 
+                }
+            });
+        }
     }
     $('#construction-users').html(html);
+    updateSelectedMarks();
+}
+
+function setTenderPermissionsHtml(auditList, key, keys) {
+    const html = [];
+    for (const m of auditList) {
+        html.push(getUserPermissionsHtml(m, key, keys));
+    }
+    $('#' + key + '-users').html(html.join(''));
 }
 
+const getUserPermissionsHtml = function(user, key, keys) {
+    const html = [];
+    html.push(`<tr data-uid="${user.uid}" data-id="${user.id}">`);
+    html.push(`<td>${user.name}</td>`, `<td>${user.role}</td>`);
+    const block = _.find(permissionBlock, { key: key });
+    for (const block of permissionBlock) {
+        if (keys.indexOf(block.key) < 0) continue;
+        for (const p of block.permission) {
+            const checked = user[block.key] ? (user[block.key].indexOf(p.value) >= 0 ? 'checked' : '') : '';
+            html.push(`<td class="text-center"><input type="checkbox" data-key="${block.key}" 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-${key}-user">移除</a></td>`);
+    html.push('</tr>');
+    return html.join('');
+};
+
 function setTenderPermissionHtml(auditList, key) {
     const html = [];
     for (const m of auditList) {
         html.push(getUserPermissionHtml(m, key));
     }
+    if (Array.isArray(auditList)) {
+        auditList.forEach(function(sa) {
+            if (sa.uid !== undefined && sa.uid !== null) {
+                selectedUserIdsByType[key].push(parseInt(sa.uid, 10)); 
+            }
+        });
+    }
     $('#' + key + '-users').html(html.join(''));
+    updateSelectedMarks();
 }
 
 const getUserPermissionHtml = function(user, key) {
@@ -1137,7 +1382,7 @@ const getUserPermissionHtml = function(user, key) {
         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-${key}-user">移除</a></td>`);
+    html.push(`<td class="text-center"><a href="#remove-user1" data-show-id="${user.uid}" data-id="${user.uid}" data-toggle="modal" data-target="#remove-user" class="btn btn-sm btn-outline-danger remove-${key}-user">移除</a></td>`);
     html.push('</tr>');
     return html.join('');
 };

+ 5 - 0
app/public/js/shares/ali_oss.js

@@ -140,6 +140,11 @@ const AliOss = (function (){
         if (uploadInfo.callback) {
             uploadInfo.callback(uploadRes);
         }
+        // 进度条还原
+        if (uploadInfo.progressObj) {
+            uploadInfo.progressObj.attr('aria-valuenow', 0);
+            uploadInfo.progressObj.width('0%');
+        }
         uploadInfo = {};
         return uploadRes;
     };

+ 15 - 2
app/public/js/shares/db2full_code.js

@@ -78,7 +78,17 @@ const dbFullCode = {
     _completeFullCode: function (b, index) {
         for (let j = index-1; j >= 0; j--) {
             if (this.dealBills[j].tag === -1) continue;
-            if (this.dealBills[j].tag !== b.tag && this.dealBills[j].level < 4) {
+            if (this.dealBills[j].tag !== b.tag && this.dealBills[j].tag < 3 && this.dealBills[j].level < 4) {
+                b.full_code = this.dealBills[j].full_code + b.code;
+                b.level = this.dealBills[j].level + 1;
+                return;
+            }
+        }
+    },
+    _complete3FullCode: function(b, index) {
+        for (let j = index-1; j >= 0; j--) {
+            if (this.dealBills[j].tag === -1) continue;
+            if (this.dealBills[j].tag < 2 && this.dealBills[j].level < 4) {
                 b.full_code = this.dealBills[j].full_code + b.code;
                 b.level = this.dealBills[j].level + 1;
                 return;
@@ -86,7 +96,7 @@ const dbFullCode = {
         }
     },
     change: function () {
-        const regChar = /^-[a-zA-Z]$/, regNum = /^-[0-9]{1,2}$/, regCode = /^[0-9]{3,4}((-[0-9]{1,2})|(-[a-zA-Z]))*$/;
+        const regChar = /^-[a-zA-Z]$/, regNum = /^-[0-9]{1,2}$/, regCode = /^[0-9]{3,4}((-[0-9]{1,2})|(-[a-zA-Z]))*$/, regCharNum = /^-[a-zA-Z](-[0-9]{1,2})+$/;
         let invalid = 0;
         for (const [i, db] of this.dealBills.entries()) {
             if (regCode.test(db.code)) {
@@ -99,6 +109,9 @@ const dbFullCode = {
             } else if (regChar.test(db.code)) {
                 db.tag = 2;
                 this._completeFullCode(db, i);
+            } else if (regCharNum.test(db.code)) {
+                db.tag = 3;
+                this._complete3FullCode(db, i);
             } else {
                 db.tag = -1;
                 db.full_code = '';

+ 2 - 1
app/public/js/shares/dsk.js

@@ -205,7 +205,8 @@ const dsk = (function () {
             const tree = this.subjectSheet.zh_tree;
             if (!tree) return [];
 
-            return tree.datas.filter(x => { return x.type === projectTypeKey.unit && x.selected; }).map(x => { return { name: x.name, compilationId: x.compilation_id, subjectId: x.dsk_id }; });
+            return tree.nodes.filter(x => { return x.selected; }).map(x => { return { name: x.name, compilationId: x.compilation_id, subjectId: x.dsk_id, ID: x.id, parentID: x.pid, type: x.type }; });
+            // return tree.datas.filter(x => { return x.type === projectTypeKey.unit && x.selected; }).map(x => { return { name: x.name, compilationId: x.compilation_id, subjectId: x.dsk_id }; });
         },
         csButtonClicked: function(e, info) {
             if (!info.sheet.zh_setting) return;

+ 1 - 0
app/public/js/shares/export_excel.js

@@ -75,6 +75,7 @@ const SpreadExcelObj = (function() {
                 cell.hAlign(col.hAlign).vAlign(1);
             }
             if (autoFit) sheet.autoFitRow(curRow);
+            if (d._hidden) sheet.setRowVisible(curRow, false);
         }
         SpreadJsObj.endMassOperation(sheet);
     };

+ 49 - 0
app/public/js/shares/spss_stash.js

@@ -0,0 +1,49 @@
+const SpssStash = function(setting){
+    let init = false;
+
+    const refreshStashListHtml = function(list) {
+        const html = [];
+        for (const [i, l] of list.entries()) {
+            html.push('<tr class="text-center">');
+            html.push(`<td>${i+1}</td>`);
+            html.push(`<td>${moment(l.create_time).format('YYYY-MM-DD HH:mm:ss')}</td>`);
+            html.push(`<td>${l.user_name}</td>`);
+            html.push(`<td><button name="load-spss-stash" class="btn btn-sm btn-primary" sid="${l.id}">载入</button></td>`);
+            html.push('</tr>');
+        }
+        $('#spss-stash-list').html(html.join(''));
+    };
+
+    const showStash = async function() {
+        if (!init) {
+            const list = await postDataAsync(setting.url, { type: setting.type, action: 'list' });
+            refreshStashListHtml(list);
+            init = true;
+        }
+        $('#spss-stash').modal('show');
+    };
+
+    $('#spss-stash-add').click(() => {
+        const data = setting.getCurStashData();
+        if (!data) return;
+
+        data.type = setting.type;
+        data.action = 'add';
+        postData(setting.url, data, function(result) {
+            refreshStashListHtml(result);
+        });
+    });
+
+    $('body').on('click', '[name=load-spss-stash]', function() {
+        const id = this.getAttribute('sid');
+        if (!id) {
+            toastr.warning('数据错误,请刷新页面后重试');
+            return;
+        }
+
+        postData(setting.url, { type: setting.type, action: 'load', id: id }, function(result) {
+            setting.loadStashData(result);
+        });
+    });
+    return { showStash }
+};

+ 37 - 4
app/public/js/shares/tender_permission.js

@@ -13,6 +13,29 @@ const MemberPermission = function() {
     // 搜索&展开收起
     let timer = null;
     let oldSearchVal = null;
+
+    const syncSelectedMarks = function() {
+        const selectedUids = new Set(); 
+        $('#member-list tr[uid]').each(function() {
+            const uid = parseInt($(this).attr('uid'), 10); 
+            if (!isNaN(uid)) {
+                selectedUids.add(uid);
+            }
+        });
+        $('.dropdown-menu .book-list dd[data-id]').each(function() {
+            const userId = parseInt($(this).data('id'), 10); 
+            const $mark = $(this).find('.selected-mark'); 
+
+            if (!isNaN(userId) && $mark.length > 0) {
+                if (selectedUids.has(userId)) {
+                    $mark.show(); 
+                } else {
+                    $mark.hide();
+                }
+            }
+        });
+    };
+
     $('#member-search').bind('input propertychange', function(e) {
         oldSearchVal = e.target.value;
         timer && clearTimeout(timer);
@@ -25,11 +48,14 @@ const MemberPermission = function() {
                     .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>
+                                class="ml-auto">${item.mobile || ''}</span>
+                            <span class="selected-mark text-success ml-2" style="display:none;"><i class="fa fa-check"></i></span>
+                        </p>
                         <span class="text-muted">${item.role || ''}</span></dd>`
                     });
                 $('.book-list').empty();
                 $('.book-list').append(html);
+                syncSelectedMarks();
             } else {
                 if (!$('.acc-btn').length) {
                     accountGroup.forEach((group, idx) => {
@@ -40,7 +66,9 @@ const MemberPermission = function() {
                         group.groupList.forEach(item => {
                             html += `<dd class="border-bottom p-2 mb-0 " data-id="${item.id}" >
                                     <p class="mb-0 d-flex"><span class="text-primary">${item.name}</span><span
-                                            class="ml-auto">${item.mobile || ''}</span></p>
+                                            class="ml-auto">${item.mobile || ''}</span>
+                                        <span class="selected-mark text-success ml-2" style="display:none;"><i class="fa fa-check"></i></span>
+                                    </p>
                                     <span class="text-muted">${item.role || ''}</span>
                                 </dd>`
                         });
@@ -48,6 +76,7 @@ const MemberPermission = function() {
                     });
                     $('.book-list').empty();
                     $('.book-list').append(html);
+                    syncSelectedMarks();
                 }
             }
         }, 400);
@@ -70,7 +99,8 @@ const MemberPermission = function() {
         return false
     });
     // 添加
-    $('dl').on('click', 'dd', function () {
+    $('dl').on('click', 'dd', function (e) {
+        e.stopPropagation();
         const auditorId = parseInt($(this).data('id'));
         const user = accountList.find(x => { return x.id === auditorId; });
         const check = $(`tr[uid=${auditorId}]`, '#member-list');
@@ -79,9 +109,9 @@ const MemberPermission = function() {
             return;
         }
         $('#member-list').append(getUserPermissionHtml({ uid: user.id, name: user.name, role: user.role }));
+        syncSelectedMarks();
     });
 
-
     const getUserPermissionHtml = function(user) {
         const html = [];
         html.push(`<tr uid="${user.uid}">`);
@@ -109,16 +139,19 @@ const MemberPermission = function() {
         data.parts = permissionBlock.map(x => { return x.key; });
         const result = await postDataAsync(setting.loadUrl, data );
         $('#member-list').html(getPermissionHtml(result));
+        syncSelectedMarks();
     };
 
     const show = async function(info) {
         setting = info;
         await loadMemberPermission();
         $('#member').modal('show');
+        syncSelectedMarks();
     };
 
     $('#member').on('click', 'a[name="del-member"]', function () {
         $(this).parent().parent().remove();
+        syncSelectedMarks();
     });
 
     const getMemberPermission = function() {

+ 4 - 3
app/public/js/shares/tender_select_multi.js

@@ -138,7 +138,8 @@ const TenderSelectMulti = function (setting) {
             });
         },
         getSelectData: function() {
-            const selectData =  tsObj.trArray;
+            const selectData = tsObj.trArray;
+            const checked = $('#tsm-filter-checked')[0].checked;
             if (selectData.length === 0) {
                 toastr.warning('请选择标段');
                 return;
@@ -154,13 +155,13 @@ const TenderSelectMulti = function (setting) {
                             toastr.warning('请选择标段进行对比的期');
                             return;
                         }
-                        s.stageInfo = { type: 'stage', stage: s.stage };
+                        s.stageInfo = { type: 'stage', stage: s.stage, checked };
                     }
                 }
             }
             if (this.setting.type === 'gather' && this.setting.dataType === 'stage') {
                 // todo 检查汇总选项
-                const stage = { type: $('[name=tsm-source]:checked').val() };
+                const stage = { type: $('[name=tsm-source]:checked').val(), checked };
                 if (stage.type === 'stage') {
                     stage.stage = _.toInteger($('#gather-stage').val()) || 0;
                     if (!stage.stage) {

+ 7 - 1
app/public/js/shares/tenders2tree.js

@@ -108,5 +108,11 @@ const Tender2Tree = (function () {
         tenderTree.sortTreeNode(true);
         return tenderTree;
     }
-    return { convert }
+
+    function getNewConvertTree () {
+        tenderTree = createNewPathTree('gather', treeSetting);
+        tenderTree.clearDatas();
+        return tenderTree;
+    }
+    return { convert, getNewConvertTree }
 })();

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

@@ -345,7 +345,7 @@ $(document).ready(function () {
                 for(const [i, auditGroup] of flow.auditGroupList.entries()) {
                     addhtml += this.getAuditGroupHtml(this_code, auditGroup, i + 1);
                 }
-                const addGroupHtml = this_code === 'change' && (!flow.groupList || (flow.groupList && flow.groupList.length === 0)) ?
+                const addGroupHtml = (this_code === 'change' || this_code === 'stage') && (!flow.groupList || (flow.groupList && flow.groupList.length === 0)) ?
                     `<span class="pl-3"><a href="javascript:void(0);" class="show-spzsave" data-code="${this_code}"><i class="fa fa-save"></i> 存为审批组</a></span>\n` : '';
                 addhtml += '<li>\n' +
                     '                                            <span class="pl-3"><a href="javascript:void(0);" class="add-audit" ><i class="fa fa-plus"></i> 添加流程</a></span>\n' + addGroupHtml +
@@ -533,7 +533,7 @@ $(document).ready(function () {
                     const auditGroup = auditUtils.addAudit(this_code, { audit_id: data.audit_id, name: user.name, audit_type: data.audit_type, audit_order: data.audit_order }, prop.audit_order - 1);
                     if (_self.parents('ul').find('.add-audit').length === 0) {
                         const flow = sp_lc.find(x => { return x.code === this_code; });
-                        const addGroupHtml = this_code === 'change' && (!flow.groupList || (flow.groupList && flow.groupList.length === 0)) ?
+                        const addGroupHtml = (this_code === 'change'||this_code ==='stage') && (!flow.groupList || (flow.groupList && flow.groupList.length === 0)) ?
                             `<span class="pl-3"><a href="javascript:void(0);" class="show-spzsave" data-code="${this_code}"><i class="fa fa-save"></i> 存为审批组</a></span>\n` : '';
                         _self.parents('ul').append('<li>\n' +
                             '                                            <span class="pl-3"><a href="javascript:void(0);" class="add-audit" ><i class="fa fa-plus"></i> 添加流程</a></span>\n' + addGroupHtml +

+ 48 - 10
app/public/js/spss_compare_ledger.js

@@ -3,6 +3,7 @@ const posCompareField = ['quantity'];
 
 $(document).ready(() => {
     autoFlashHeight();
+    let isCache = false, selectTenders = [];
     const billsSpreadSetting = {
         cols: [
             {title: '项目节编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 185, formatter: '@', cellType: 'tree'},
@@ -58,7 +59,7 @@ $(document).ready(() => {
         full_path: 'full_path',
         rootId: -1,
         keys: ['id', 'tender_id', 'ledger_id'],
-        calcFields: ['total_price_1', 'total_price_2', 'differ_tp'],
+        calcFields: ['total_price_1', 'total_price_2', 'differ_total_price'],
         // todo 判断同一清单时,是否应区分 清单是否含计量单元?
         // findNode: function(tree, node, parent, source) {
         //     const siblings = parent ? parent.children : tree.children;
@@ -125,6 +126,19 @@ $(document).ready(() => {
     const billsTree = createNewPathTree('compare', treeSetting);
 
     const billsTreeSpreadObj = {
+        rebuildSpreadSetting: function(tenders) {
+            selectTenders.length = 0;
+            for (const [i, t] of tenders.entries()) {
+                selectTenders.push({ id: t.id, name: t.name, stage_filter: t.stage_filter });
+                const fieldName = 'quantity_' + (i + 1);
+                const billsQty = billsSpreadSetting.cols.find(x => { return x.field === fieldName; });
+                billsQty.title = billsQty.formatTitle.replace('%s', t.name);
+                const posQty = posSpreadSetting.cols.find(x => { return x.field === fieldName; });
+                posQty.title = posQty.formatTitle.replace('%s', t.name);
+            }
+            SpreadJsObj.reLoadSheetHeader(billsSheet);
+            SpreadJsObj.reLoadSheetHeader(posSheet);
+        },
         selectionChanged: function (e, info) {
             if (info.newSelections) {
                 if (!info.oldSelections || info.newSelections[0].row !== info.oldSelections[0].row) {
@@ -152,6 +166,7 @@ $(document).ready(() => {
         afterSelect: function(select) {
             const data = { filter: 'ledger', tender: select };
             postData(`/sp/${spid}/spss/load`, data, function(result) {
+                isCache = false;
                 const tenderTreeSetting = {
                     id: 'ledger_id',
                     pid: 'ledger_pid',
@@ -162,13 +177,8 @@ $(document).ready(() => {
                     calcFields: ['total_price'],
                 };
                 const tenders = [];
-                for (const [i, t] of result.entries()) {
-                    const fieldName = 'quantity_' + (i + 1);
-                    const billsQty = billsSpreadSetting.cols.find(x => { return x.field === fieldName; });
-                    billsQty.title = billsQty.formatTitle.replace('%s', t.name);
-                    const posQty = posSpreadSetting.cols.find(x => { return x.field === fieldName; });
-                    posQty.title = posQty.formatTitle.replace('%s', t.name);
-
+                billsTreeSpreadObj.rebuildSpreadSetting(result);
+                for (const t of result) {
                     const tender = {
                         billsTree: createNewPathTree('ledger', tenderTreeSetting),
                         pos: new PosData({ id: 'id', ledgerId: 'lid', }),
@@ -177,8 +187,6 @@ $(document).ready(() => {
                     tender.pos.loadDatas(t.pos);
                     tenders.push(tender);
                 }
-                SpreadJsObj.reLoadSheetHeader(billsSheet);
-                SpreadJsObj.reLoadSheetHeader(posSheet);
 
                 billsTree.loadCompareData(tenders[0], tenders[1]);
                 treeCalc.calculateAll(billsTree);
@@ -271,4 +279,34 @@ $(document).ready(() => {
             posSpread.refresh();
         }
     });
+
+    const spssStash = SpssStash({
+        type: 'compare_ledger',
+        url: `/sp/${spid}/spss/stash`,
+        getCurStashData: function() {
+            if (!billsTree || billsTree.nodes.length === 0) {
+                toastr.warning('当前无数据可暂存,请先对比标段后,再操作');
+                return null;
+            }
+            if (isCache) {
+                toastr.warning('当前数据为暂存数据,请勿重复保存');
+                return null;
+            }
+
+            const data = { result: { bills: billsTree.getDefaultData(billsTree.nodes) } };
+            data.select = selectTenders;
+            return data;
+        },
+        loadStashData: function(data) {
+            isCache = true;
+            billsTreeSpreadObj.rebuildSpreadSetting(data.spss_select);
+            billsTree.clearDatas();
+            billsTree.loadDatas(data.spss_result.bills);
+            SpreadJsObj.loadSheetData(billsSheet, SpreadJsObj.DataType.Tree, billsTree, true);
+            posSpreadObj.loadCurPosData();
+
+            $('#stash-hint').html(`当前显示:${moment(data.create_time).format('YYYY-MM-DD HH:mm:ss')} (${data.user_name})`);
+        }
+    });
+    $('#stash').click(() => { spssStash.showStash(); });
 });

+ 52 - 9
app/public/js/spss_compare_stage.js

@@ -11,6 +11,7 @@ const posCompareField = [
 
 $(document).ready(() => {
     autoFlashHeight();
+    let isCache = false, selectTenders = [];
     const billsSpreadSetting = {
         cols: [
             {title: '项目节编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 185, formatter: '@', cellType: 'tree'},
@@ -139,6 +140,19 @@ $(document).ready(() => {
 
     const billsTreeSpreadObj = {
         compareData: '',
+        rebuildSpreadSetting: function(tenders) {
+            selectTenders.length = 0;
+            for (const [i, t] of tenders.entries()) {
+                selectTenders.push({ id: t.id, name: t.name, stage_filter: t.stage_filter });
+                const fieldName = 'compare_qty_' + (i + 1);
+                const billsQty = billsSpreadSetting.cols.find(x => { return x.field === fieldName; });
+                billsQty.title = billsQty.formatTitle.replace('%s', t.name);
+                const posQty = posSpreadSetting.cols.find(x => { return x.field === fieldName; });
+                posQty.title = posQty.formatTitle.replace('%s', t.name);
+            }
+            SpreadJsObj.reLoadSheetHeader(billsSheet);
+            SpreadJsObj.reLoadSheetHeader(posSheet);
+        },
         selectionChanged: function (e, info) {
             if (info.newSelections) {
                 if (!info.oldSelections || info.newSelections[0].row !== info.oldSelections[0].row) {
@@ -192,6 +206,7 @@ $(document).ready(() => {
         afterSelect: function(select) {
             const data = { filter: 'stage', tender: select };
             postData(`/sp/${spid}/spss/load`, data, function(result) {
+                isCache = false;
                 const tenderTreeSetting = {
                     id: 'ledger_id',
                     pid: 'ledger_pid',
@@ -228,13 +243,8 @@ $(document).ready(() => {
                     }
                 };
                 const tenders = [];
-                for (const [i, t] of result.entries()) {
-                    const fieldName = 'compare_qty_' + (i + 1);
-                    const billsQty = billsSpreadSetting.cols.find(x => { return x.field === fieldName; });
-                    billsQty.title = billsQty.formatTitle.replace('%s', t.name);
-                    const posQty = posSpreadSetting.cols.find(x => { return x.field === fieldName; });
-                    posQty.title = posQty.formatTitle.replace('%s', t.name);
-
+                billsTreeSpreadObj.rebuildSpreadSetting(result);
+                for (const t of result) {
                     const tender = {
                         billsTree: createNewPathTree('ledger', tenderTreeSetting),
                         pos: new PosData(tenderPosSetting),
@@ -245,8 +255,6 @@ $(document).ready(() => {
                     tender.pos.calculateAll();
                     tenders.push(tender);
                 }
-                SpreadJsObj.reLoadSheetHeader(billsSheet);
-                SpreadJsObj.reLoadSheetHeader(posSheet);
 
                 billsTree.loadCompareData(tenders[0], tenders[1]);
                 treeCalc.calculateAll(billsTree);
@@ -344,4 +352,39 @@ $(document).ready(() => {
             posSpread.refresh();
         }
     });
+
+    const spssStash = SpssStash({
+        type: 'compare_stage',
+        url: `/sp/${spid}/spss/stash`,
+        getCurStashData: function() {
+            if (!billsTree || billsTree.nodes.length === 0) {
+                toastr.warning('当前无数据可暂存,请先对比标段后,再操作');
+                return null;
+            }
+            if (isCache) {
+                toastr.warning('当前数据为暂存数据,请勿重复保存');
+                return null;
+            }
+
+            const data = { result: { bills: billsTree.getDefaultData(billsTree.nodes) } };
+            data.select = selectTenders;
+            return data;
+        },
+        loadStashData: function(data) {
+            isCache = true;
+
+            billsTreeSpreadObj.rebuildSpreadSetting(data.spss_select);
+
+            billsTree.clearDatas();
+            billsTree.loadDatas(data.spss_result.bills);
+            billsTreeSpreadObj.loadShowData();
+            SpreadJsObj.loadSheetData(billsSheet, SpreadJsObj.DataType.Tree, billsTree, true);
+            SpreadJsObj.locateRow(billsSheet, 0);
+            posSpreadObj.loadCurPosData();
+            SpreadJsObj.locateRow(posSheet, 0);
+
+            $('#stash-hint').html(`当前显示:${moment(data.create_time).format('YYYY-MM-DD HH:mm:ss')} (${data.user_name})`);
+        }
+    });
+    $('#stash').click(() => { spssStash.showStash(); });
 });

+ 41 - 1
app/public/js/spss_gather_ledger.js

@@ -3,6 +3,7 @@ const posCompareField = ['quantity'];
 
 $(document).ready(() => {
     autoFlashHeight();
+    let isCache = false, selectTenders = [];
     const billsSpreadSetting = {
         cols: [
             {title: '项目节编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 185, formatter: '@', cellType: 'tree'},
@@ -35,6 +36,8 @@ $(document).ready(() => {
         headerFont: '12px 微软雅黑',
         font: '12px 微软雅黑',
         readOnly: true,
+        frozenColCount: 5,
+        frozenLineColor: '#93b5e4',
     };
     const posSpreadSetting = {
         cols: [
@@ -160,7 +163,6 @@ $(document).ready(() => {
     const posSpreadObj = {
         loadCurPosData: function () {
             const node = SpreadJsObj.getSelectObject(billsSheet);
-            console.log(node);
             if (node) {
                 SpreadJsObj.loadSheetData(posSheet, 'data', node.pos || []);
             } else {
@@ -176,6 +178,7 @@ $(document).ready(() => {
         afterSelect: function(select) {
             const data = { filter: 'ledger', tender: select };
             postData(`/sp/${spid}/spss/load`, data, function(result) {
+                isCache = false;
                 billsTreeSpreadObj.rebuildSpreadSetting(result);
                 SpreadJsObj.reLoadSheetHeader(billsSheet);
                 SpreadJsObj.reLoadSheetHeader(posSheet);
@@ -194,6 +197,7 @@ $(document).ready(() => {
                 const posSetting = { id: 'id', ledgerId: 'lid' };
                 const tenders = [];
                 for (const t of result) {
+                    selectTenders.push({ id: t.id, name: t.name, stage_filter: t.stage_filter });
                     const tender = {
                         billsTree: createNewPathTree('ledger', tenderTreeSetting),
                         pos: new PosData(posSetting),
@@ -208,6 +212,8 @@ $(document).ready(() => {
                 treeCalc.calculateAll(billsTree);
                 SpreadJsObj.loadSheetData(billsSheet, SpreadJsObj.DataType.Tree, billsTree, true);
                 posSpreadObj.loadCurPosData();
+
+                $('#stash-hint').html(`当前显示:${moment(data.create_time).format('YYYY-MM-DD HH:mm:ss')} (${data.user_name})`);
             });
         },
     });
@@ -296,4 +302,38 @@ $(document).ready(() => {
             posSpread.refresh();
         }
     });
+
+    const spssStash = SpssStash({
+        type: 'gather_ledger',
+        url: `/sp/${spid}/spss/stash`,
+        getCurStashData: function() {
+            if (!billsTree || billsTree.nodes.length === 0) {
+                toastr.warning('当前无数据可暂存,请先汇总标段后,再操作');
+                return null;
+            }
+            if (isCache) {
+                toastr.warning('当前数据为暂存数据,请勿重复保存');
+                return null;
+            }
+
+            const data = { result: { bills: billsTree.getDefaultData(billsTree.nodes) } };
+            data.select = selectTenders;
+            return data;
+        },
+        loadStashData: function(data) {
+            isCache = true;
+
+            billsTreeSpreadObj.rebuildSpreadSetting(data.spss_select);
+            SpreadJsObj.reLoadSheetHeader(billsSheet);
+            SpreadJsObj.reLoadSheetHeader(posSheet);
+
+            billsTree.clearDatas();
+            billsTree.loadDatas(data.spss_result.bills);
+            SpreadJsObj.loadSheetData(billsSheet, SpreadJsObj.DataType.Tree, billsTree, true);
+            posSpreadObj.loadCurPosData();
+
+            $('#stash-hint').html(`当前显示:${moment(data.create_time).format('YYYY-MM-DD HH:mm:ss')} (${data.user_name})`);
+        }
+    });
+    $('#stash').click(() => { spssStash.showStash(); });
 });

+ 43 - 1
app/public/js/spss_gather_stage.js

@@ -11,6 +11,7 @@ const posCompareField = [
 
 $(document).ready(() => {
     autoFlashHeight();
+    let isCache = false, selectTenders = [];
     const billsSpreadSetting = {
         cols: [
             {title: '项目节编号', colSpan: '1', rowSpan: '3', field: 'code', hAlign: 0, width: 185, formatter: '@', cellType: 'tree'},
@@ -43,6 +44,8 @@ $(document).ready(() => {
         headerFont: '12px 微软雅黑',
         font: '12px 微软雅黑',
         readOnly: true,
+        frozenColCount: 5,
+        frozenLineColor: '#93b5e4',
     };
     const posSpreadSetting = {
         cols: [
@@ -206,7 +209,6 @@ $(document).ready(() => {
         loadCurPosData: function () {
             const node = SpreadJsObj.getSelectObject(billsSheet);
             if (node) {
-                console.log(node.pos);
                 SpreadJsObj.loadSheetData(posSheet, 'data', node.pos || []);
             } else {
                 SpreadJsObj.loadSheetData(posSheet, 'data', []);
@@ -221,6 +223,7 @@ $(document).ready(() => {
         afterSelect: function(select) {
             const data = { filter: 'stage', tender: select };
             postData(`/sp/${spid}/spss/load`, data, function(result) {
+                isCache = false;
                 billsTreeSpreadObj.rebuildSpreadSetting(result);
                 SpreadJsObj.reLoadSheetHeader(billsSheet, true);
                 SpreadJsObj.reLoadSheetHeader(posSheet, true);
@@ -264,6 +267,7 @@ $(document).ready(() => {
                 };
                 const tenders = [];
                 for (const t of result) {
+                    selectTenders.push({ id: t.id, name: t.name, stage_filter: t.stage_filter });
                     const tender = {
                         billsTree: createNewPathTree('ledger', tenderTreeSetting),
                         pos: new PosData(posSetting),
@@ -375,4 +379,42 @@ $(document).ready(() => {
             posSpread.refresh();
         }
     });
+
+    const spssStash = SpssStash({
+        type: 'gather_stage',
+        url: `/sp/${spid}/spss/stash`,
+        getCurStashData: function() {
+            if (!billsTree || billsTree.nodes.length === 0) {
+                toastr.warning('当前无数据可暂存,请先汇总标段后,再操作');
+                return null;
+            }
+            if (isCache) {
+                toastr.warning('当前数据为暂存数据,请勿重复保存');
+                return null;
+            }
+
+            const data = { result: { bills: billsTree.getDefaultData(billsTree.nodes) } };
+            data.select = selectTenders;
+            return data;
+        },
+        loadStashData: function(data) {
+            isCache = true;
+
+            billsTreeSpreadObj.rebuildSpreadSetting(data.spss_select);
+            SpreadJsObj.reLoadSheetHeader(billsSheet, true);
+            SpreadJsObj.reLoadSheetHeader(posSheet, true);
+
+            billsTree.clearDatas();
+            billsTree.loadDatas(data.spss_result.bills);
+            billsTree.count = data.spss_select.length;
+            billsTreeSpreadObj.loadShowData();
+            SpreadJsObj.loadSheetData(billsSheet, SpreadJsObj.DataType.Tree, billsTree, true);
+            SpreadJsObj.locateRow(billsSheet, 0);
+            posSpreadObj.loadCurPosData();
+            SpreadJsObj.locateRow(posSheet, 0);
+
+            $('#stash-hint').html(`当前显示:${moment(data.create_time).format('YYYY-MM-DD HH:mm:ss')} (${data.user_name})`);
+        }
+    });
+    $('#stash').click(() => { spssStash.showStash(); });
 });

+ 59 - 0
app/public/js/spss_gather_stage_extra.js

@@ -36,6 +36,8 @@ $(document).ready(() => {
             headerFont: '12px 微软雅黑',
             font: '12px 微软雅黑',
             readOnly: true,
+            frozenColCount: 3,
+            frozenLineColor: '#93b5e4',
         },
         yjcl: {
             cols: [
@@ -65,6 +67,8 @@ $(document).ready(() => {
             headerFont: '12px 微软雅黑',
             font: '12px 微软雅黑',
             readOnly: true,
+            frozenColCount: 3,
+            frozenLineColor: '#93b5e4',
         },
         bonus: {
             cols: [
@@ -110,6 +114,8 @@ $(document).ready(() => {
             headerFont: '12px 微软雅黑',
             font: '12px 微软雅黑',
             readOnly: true,
+            frozenColCount: 3,
+            frozenLineColor: '#93b5e4',
         },
         tempLand: {
             cols: [
@@ -129,6 +135,8 @@ $(document).ready(() => {
             headerFont: '12px 微软雅黑',
             font: '12px 微软雅黑',
             readOnly: true,
+            frozenColCount: 4,
+            frozenLineColor: '#93b5e4',
         },
         other: {
             cols: [
@@ -155,6 +163,8 @@ $(document).ready(() => {
     const stageExtra = {
         seType: 'jgcl',
         seData: 'cur',
+        tenderCount: 0,
+        selectTenders: [],
         jgcl: [],
         yjcl: [],
         bonus: createNewPathTree('gather', {id: 'tree_id', pid: 'tree_pid', order: 'order', level: 'level', rootId: -1, fullPath: 'full_path', calcFields: ['tp']}),
@@ -308,6 +318,7 @@ $(document).ready(() => {
         },
         gatherStageExtraData(tenders) {
             this.tenderCount = tenders.length;
+            this.selectTenders = tenders.map(t => { return { id: t.id, name: t.name, stage_filter: t.stage_filter }; });
             this.gatherJgcl(tenders);
             this.gatherYjcl(tenders);
             this.gatherBonus(tenders);
@@ -403,6 +414,16 @@ $(document).ready(() => {
             }
             this.loadSheetData();
         },
+        loadStashData(data) {
+            this.tenderCount = data.spss_select.length;
+            this.selectTenders = data.spss_select;
+            this.jgcl = data.spss_result.jgcl;
+            this.yjcl = data.spss_result.yjcl;
+            this.bonus.loadDatas(data.spss_result.bonus);
+            this.other.loadDatas(data.spss_result.other);
+            this.safeProd = data.spss_result.safeProd;
+            this.tempLand.loadDatas(data.spss_result.tempLand);
+        }
     };
     stageExtra.loadSheetData();
 
@@ -461,4 +482,42 @@ $(document).ready(() => {
             dataSpread.refresh();
         }
     });
+
+    const spssStash = SpssStash({
+        type: 'gather_stage_extra',
+        url: `/sp/${spid}/spss/stash`,
+        getCurStashData: function() {
+            if (stageExtra.tenderCount === 0) {
+                toastr.warning('当前无数据可暂存,请先汇总标段后,再操作');
+                return null;
+            }
+            if (stageExtra.isCache) {
+                toastr.warning('当前数据为暂存数据,请勿重复保存');
+                return null;
+            }
+
+            const data = {
+                result: {
+                    jgcl: stageExtra.jgcl,
+                    yjcl: stageExtra.yjcl,
+                    bonus: stageExtra.other.getDefaultData(stageExtra.bonus.nodes),
+                    other: stageExtra.bonus.getDefaultData(stageExtra.other.nodes),
+                    safeProd: stageExtra.safeProd,
+                    tempLand: stageExtra.tempLand.getDefaultData(stageExtra.tempLand.nodes),
+                },
+                select: stageExtra.selectTenders
+            };
+            return data;
+        },
+        loadStashData: function(data) {
+            stageExtra.loadStashData(data);
+            stageExtra.rebuildSpreadSetting(data.spss_select);
+            SpreadJsObj.reLoadSheetHeader(dataSheet, true);
+            stageExtra.loadShowData(true);
+            stageExtra.loadSheetData();
+
+            $('#stash-hint').html(`当前显示:${moment(data.create_time).format('YYYY-MM-DD HH:mm:ss')} (${data.user_name})`);
+        }
+    });
+    $('#stash').click(() => { spssStash.showStash(); });
 });

+ 36 - 1
app/public/js/spss_gather_stage_info.js

@@ -71,6 +71,7 @@ $(document).ready(() => {
 
     const infoObj = {
         tenders: [],
+        isCache: false,
         rebuildInfoTree: function(categoryLevel) {
             infoTree = Tender2Tree.convert(category, this.tenders, null, null, function(node, tender) {
                 node.measure_type_str = tender.info.measure_type_str;
@@ -95,13 +96,23 @@ $(document).ready(() => {
             treeCalc.calculateAll(infoTree);
         },
         refreshInfoTree: function(categoryLevel) {
+            if (this.isCache) return;
+
             this.rebuildInfoTree(categoryLevel);
             SpreadJsObj.loadSheetData(infoSheet, SpreadJsObj.DataType.Tree, infoTree, true);
         },
         loadTenders: function(tenders) {
+            this.isCache = false;
             this.tenders = tenders;
             const categoryLevel = infoCate.getCategoryLevel();
             this.refreshInfoTree(categoryLevel);
+        },
+        loadStashData: function(data) {
+            this.isCache = true;
+            this.tenders = data.spss_select;
+            infoTree = Tender2Tree.getNewConvertTree();
+            infoTree.loadDatas(data.spss_result);
+            SpreadJsObj.loadSheetData(infoSheet, SpreadJsObj.DataType.Tree, infoTree, true);
         }
     };
 
@@ -349,5 +360,29 @@ $(document).ready(() => {
     });
     $('#spss-info-col-set').click(() => {
         customColSet.show(colSetSetting);
-    })
+    });
+
+    const spssStash = SpssStash({
+        type: 'gather_stage_info',
+        url: `/sp/${spid}/spss/stash`,
+        getCurStashData: function() {
+            if (!infoTree || infoTree.nodes.length === 0) {
+                toastr.warning('当前无数据可暂存,请先汇总标段后,再操作');
+                return null;
+            }
+            if (infoObj.isCache) {
+                toastr.warning('当前数据为暂存数据,请勿重复保存');
+                return null;
+            }
+
+            const data = { result: infoTree.getDefaultData(infoTree.nodes) };
+            data.select = infoObj.tenders.map(t => { return { id: t.id, name: t.name, stage_filter: t.stage_filter } });
+            return data;
+        },
+        loadStashData: function(data) {
+            infoObj.loadStashData(data);
+            $('#stash-hint').html(`当前显示:${moment(data.create_time).format('YYYY-MM-DD HH:mm:ss')} (${data.user_name})`);
+        }
+    });
+    $('#stash').click(() => { spssStash.showStash(); });
 });

+ 198 - 0
app/public/js/spss_gather_stage_pay.js

@@ -0,0 +1,198 @@
+$(document).ready(() => {
+    autoFlashHeight();
+    $('#data-spread').height($('.sjs-height-0').height() - $('.nav-tabs').height() - 16);
+    const paySpreadSetting = {
+        cols: [
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 235, formatter: '@'},
+            {title: '扣款', colSpan: '1', rowSpan: '2', field: 'minus', hAlign: 1, width: 60, cellType: 'checkbox'},
+        ],
+        baseCols: [
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 235, formatter: '@'},
+            {title: '扣款', colSpan: '1', rowSpan: '2', field: 'minus', hAlign: 1, width: 60, cellType: 'checkbox'},
+        ],
+        extraCols: [
+            {title: '标段1|第1期', colSpan: '1|1', rowSpan: '1|1', field: 'show_tp1', hAlign: 2, width: 80, type: 'Number', formatTitle: '%s|%f', formatField: 'show_tp_%d'},
+        ],
+        endCols: [
+            {title: '合计', colSpan: '1', rowSpan: '2', field: 'show_sum_tp', hAlign: 2, width: 80, type: 'Number'},
+        ],
+        emptyRows: 0,
+        headRows: 2,
+        headRowHeight: [25, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: true,
+        frozenColCount: 2,
+        frozenLineColor: '#93b5e4',
+    };
+
+    const paySpread = SpreadJsObj.createNewSpread($('#pay-spread')[0]);
+    const paySheet = paySpread.getActiveSheet();
+    const payGather = {
+        pays: [],
+        seData: 'cur',
+        tenderCount: 0,
+        selectTenders: [],
+        gatherPays(tenders) {
+            this.pays = [];
+            this.selectTenders = [];
+            for (const [i, t] of tenders.entries()) {
+                this.selectTenders.push({ id: t.id, name: t.name, stage_filter: t.stage_filter });
+                const endfix = '_' + (i + 1);
+                for (const data of t.pay) {
+                    if (!data.name) data.name = '';
+                    data.minus = data.minus ? 1 : 0;
+
+                    let p = this.pays.find(x => { return x.name === data.name && x.minus === data.minus; });
+                    if (!p) {
+                        p = { name: data.name, minus: data.minus };
+                        this.pays.push(p);
+                    }
+                    p['tp' + endfix] = ZhCalc.add(p['tp' + endfix], data.tp);
+                    p['pre_tp' + endfix] = ZhCalc.add(p['pre_tp' + endfix], data.pre_tp);
+                    p['end_tp' + endfix] = ZhCalc.add(p['end_tp' + endfix], data.end_tp);
+
+                    p['sum_tp' + endfix] = ZhCalc.add(p['sum_tp' + endfix], data.tp);
+                    p['sum_pre_tp' + endfix] = ZhCalc.add(p['sum_pre_tp' + endfix], data.pre_tp);
+                    p['sum_end_tp' + endfix] = ZhCalc.add(p['sum_end_tp' + endfix], data.end_tp);
+                }
+            }
+        },
+        gatherStageExtraData(tenders) {
+            this.isCache = false;
+            this.tenderCount = tenders.length;
+            this.gatherPays(tenders);
+        },
+        rebuildSpreadSetting(tenders) {
+            paySpreadSetting.cols = [...paySpreadSetting.baseCols];
+            const pushExtraCols = function(spreadSetting) {
+                for (const [i, tender] of tenders.entries()) {
+                    for (const col of spreadSetting.extraCols) {
+                        const newCol = JSON.parse(JSON.stringify(col));
+                        if (newCol.formatTitle) newCol.title = newCol.formatTitle.replace('%s', tender.name).replace('%f', tender.stage_filter);
+                        if (newCol.formatField) newCol.field = newCol.formatField.replace('%d', i + 1);
+                        spreadSetting.cols.push(newCol);
+                    }
+                }
+            };
+            pushExtraCols(paySpreadSetting);
+            paySpreadSetting.cols.push(...paySpreadSetting.endCols);
+        },
+        loadPayShowData(field) {
+            const prefix = field === 'end' ? 'end_' : '';
+            for (const cl of this.pays) {
+                for (let i = 1; i<= this.tenderCount; i++) {
+                    cl['show_tp_' + i] = cl[prefix + 'tp_' + i];
+                }
+                cl.show_sum_tp = cl['sum_' + prefix + 'tp'];
+            }
+            SpreadJsObj.reloadColData(paySheet, 2, this.tenderCount + 1);
+        },
+        loadShowData(force = false) {
+            const field = $('[name=compare-data]:checked').val();
+            if (field === this.seData && !force) return;
+            this.seData = field;
+            this.loadPayShowData(field);
+        },
+        refreshShowData() {
+            const field = $('[name=compare-data]:checked').val();
+            if (field === this.seData) return;
+
+            this.loadShowData();
+        },
+        loadSheetData() {
+            SpreadJsObj.initSheet(paySheet, paySpreadSetting);
+            SpreadJsObj.loadSheetData(paySheet, SpreadJsObj.DataType.Data, this.pays);
+            SpreadJsObj.locateRow(paySheet, 0);
+        },
+        refreshStageExtraData(type) {
+            if (type) {
+                if (this.seType === type) return;
+                this.seType = type;
+            }
+            this.loadSheetData();
+        },
+        loadStashData(data) {
+            this.isCache = true;
+            this.tenderCount = data.spss_select.length;
+            this.selectTenders = data.spss_select;
+            this.rebuildSpreadSetting(data.spss_select);
+            this.pays = data.spss_result.pays;
+        }
+    };
+    payGather.loadSheetData();
+
+    const tenderSelect = TenderSelectMulti({
+        title: '汇总标段',
+        type: 'gather',
+        dataType: 'stage',
+        zoneValid: false,
+        afterSelect: function(select) {
+            const data = { filter: 'pay', tender: select };
+            postData(`/sp/${spid}/spss/load`, data, function(result) {
+                payGather.rebuildSpreadSetting(result);
+                payGather.gatherStageExtraData(result);
+                SpreadJsObj.reLoadSheetHeader(paySheet, true);
+                payGather.loadShowData(true);
+                payGather.loadSheetData();
+            });
+        },
+    });
+    $('#gather-select').click(tenderSelect.showSelect);
+
+    $('[name=compare-data]').click(function() {
+        payGather.refreshShowData();
+    });
+
+    $('#export-excel').click(function() {
+        const sheets = [];
+        sheets.push({ name: '合同支付', setting: paySpreadSetting, data: payGather.pays });
+        SpreadExcelObj.exportSimpleXlsxSheets(sheets, "计量汇总-合同支付.xlsx");
+    });
+    $.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');
+                $('.c-body table thead').css('left', '56px');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+                $('.c-body table thead').css('left', '176px');
+            }
+            autoFlashHeight();
+            paySpread.refresh();
+        }
+    });
+
+    const spssStash = SpssStash({
+        type: 'gather_stage_pay',
+        url: `/sp/${spid}/spss/stash`,
+        getCurStashData: function() {
+            if (payGather.pays.length === 0) {
+                toastr.warning('当前无数据可暂存,请先汇总标段后,再操作');
+                return null;
+            }
+            if (payGather.isCache) {
+                toastr.warning('当前数据为暂存数据,请勿重复保存');
+                return null;
+            }
+
+            const data = { result: { pays: payGather.pays }, select: payGather.selectTenders };
+            return data;
+        },
+        loadStashData: function(data) {
+            payGather.loadStashData(data);
+            SpreadJsObj.reLoadSheetHeader(paySheet, true);
+            payGather.loadShowData(true);
+            payGather.loadSheetData();
+            $('#stash-hint').html(`当前显示:${moment(data.create_time).format('YYYY-MM-DD HH:mm:ss')} (${data.user_name})`);
+        }
+    });
+    $('#stash').click(() => { spssStash.showStash(); });
+});

+ 73 - 3
app/public/js/stage.js

@@ -1589,6 +1589,7 @@ $(document).ready(() => {
                         : findEmptyBills(tree, change, changeBills);
 
                     if (!node) continue;
+                    if (!node.changeBills) node.changeBills = [];
                     node.tz_qc_qty = ZhCalc.add(node.tz_qc_qty, changeBills.checked_amount);
                     if (tenderInfo.calc_type === 'up') {
                         node.tz_qc_tp = ZhCalc.add(node.tz_qc_tp, changeBills.checked_price);
@@ -1600,7 +1601,25 @@ $(document).ready(() => {
                     if (posData && posData.length > 0) {
                         for (const p of changeBills.pos) {
                             const changePos = p.bwmx ? posData.find(x => { return x.name === p.bwmx; }) : posData[0];
-                            if (changePos) changePos.tz_qc_qty = ZhCalc.add(changePos.tz_qc_qty, p.checked_amount);
+                            if (changePos) {
+                                changePos.tz_qc_qty = ZhCalc.add(changePos.tz_qc_qty, p.checked_amount);
+                                if (!changePos.changeBills) changePos.changeBills = [];
+                                changePos.changeBills.push({
+                                    code: change.code || '', name: change.name || '', is_valuation:  p.is_valuation,
+                                    qty: p.checked_amount || 0, tp: p.checked_price || 0, pos: changePos.name,
+                                });
+                                node.changeBills.push({
+                                    code: change.code || '', name: change.name || '', is_valuation: p.is_valuation,
+                                    qty: p.checked_amount || 0, tp: p.checked_price || 0, pos: changePos.name,
+                                });
+                            }
+                        }
+                    } else {
+                        for (const p of changeBills.pos) {
+                            node.changeBills.push({
+                                code: change.code || '', name: change.name || '', is_valuation: p.is_valuation,
+                                qty: p.checked_amount || 0, tp: p.checked_price || 0, pos: '',
+                            });
                         }
                     }
                 }
@@ -2009,6 +2028,35 @@ $(document).ready(() => {
                     return !node;
                 }
             },
+            detail: {
+                name: '查看变更后数量明细',
+                callback: function(key, opt, menu, e) {
+                    const curBills = SpreadJsObj.getSelectObject(slSheet);
+                    $('#tz2d-hint').html(`当前清单:${curBills.b_code || ''} ${curBills.name || ''}`);
+                    $('#tz2d-tz').html(`台账数量:${ curBills.quantity || 0}`);
+                    $('#tz2d-qc').html(`变更数量:${ ZhCalc.sub(curBills.tz2_qty, curBills.quantity) || 0}`);
+                    $('#tz2d-sum').html(`变更后数量:${ curBills.tz2_qty || 0}`);
+                    const html = [];
+                    html.push('<tr class="text-center"><th>变更令编号</th><th>变更令名称</th><th>计量单元</th><th>计价</th><th>批复数量</th></tr>');
+                    if (!curBills.changeBills) curBills.changeBills = [];
+                    curBills.changeBills.sort((a, b) => {
+                        if (a.code === b.code) {
+                            return a.pos > b.pos ? 1 : -1;
+                        } else {
+                            return a.code > b.code ? 1 : -1;
+                        }
+                    });
+                    for (const cb of curBills.changeBills) {
+                        html.push(`<tr><td>${cb.code}</td><td>${cb.name}</td><td>${cb.pos}</td><td class="text-center">${ cb.is_valuation ? '√' : '' }</td><td class="text-right">${cb.qty}</td></tr>`);
+                    }
+                    $('#tz2d-list').html(html.join(''));
+                    $('#tz2-detail').modal('show');
+                },
+                disabled: function(key, opt) {
+                    const curBills = SpreadJsObj.getSelectObject(slSheet);
+                    return !curBills || !curBills.b_code;
+                }
+            },
             exportSpr: '----',
             exportSelectNodeXlsx: {
                 name: '导出选中节点至Excel',
@@ -2829,7 +2877,6 @@ $(document).ready(() => {
                         icon: 'fa-sort-amount-desc',
                         callback: function (key, opt) {
                             stagePos.sort = [['add_stage_order', 'desc'], ['porder', 'asc']];
-                            const spSheet = spSpread.getActiveSheet();
                             if (spSheet.zh_data) {
                                 spSheet.zh_data.sort((x, y) => { return y.add_stage_order - x.add_stage_order || x.porder - y.porder; });
                                 SpreadJsObj.reLoadSheetData(spSheet);
@@ -2977,7 +3024,30 @@ $(document).ready(() => {
                     $('#shoufang-pos').text('计量单元:' + node.name).show();
                     $('#addshoufang').modal('show');
                 }
-            }
+            },
+            detail: {
+                name: '查看变更后数量明细',
+                callback: function(key, opt, menu, e) {
+                    const curBills = SpreadJsObj.getSelectObject(slSheet);
+                    const curPos = SpreadJsObj.getSelectObject(spSheet);
+                    $('#tz2d-hint').html(`当前清单:${curBills.b_code || ''} ${curBills.name || ''} - ${curPos.name || ''}`);
+                    $('#tz2d-tz').html(`台账数量:${ curPos.quantity || 0}`);
+                    $('#tz2d-qc').html(`变更数量:${ ZhCalc.sub(curPos.tz2_qty, curPos.quantity) || 0}`);
+                    $('#tz2d-sum').html(`变更后数量:${ curPos.tz2_qty || 0}`);
+                    const html = [];
+                    html.push('<tr class="text-center"><th>变更令编号</th><th>变更令名称</th><th>计价</th><th>批复数量</th></tr>');
+                    if (!curPos.changeBills) curPos.changeBills = [];
+                    for (const cb of curPos.changeBills) {
+                        html.push(`<tr><td>${cb.code}</td><td>${cb.name}</td><td class="text-center">${ cb.is_valuation ? '√' : '' }</td><td class="text-right">${cb.qty}</td></tr>`);
+                    }
+                    $('#tz2d-list').html(html.join(''));
+                    $('#tz2-detail').modal('show');
+                },
+                disabled: function(key, opt) {
+                    const curPos = SpreadJsObj.getSelectObject(spSheet);
+                    return !curPos;
+                }
+            },
         }
     });
     $.subMenu({

+ 67 - 0
app/public/js/stage_audit.js

@@ -479,6 +479,72 @@ $(document).ready(function () {
         return html;
     }
 
+    function makeSpHtml(datas) {
+        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" auditorId="${auditor.uid}"><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">');
+                // todo 添加会签或签时
+                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>`);
+                }
+                // html.push('<span class="badge badge-pill badge-primary badge-bg-small"><small></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">移除</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>');
+            }
+            auditorshtml.push('</div>');
+            auditorshtml.push('</li>');
+        }
+        $('#auditors').html(html.join(''));
+        $('#auditors-list').html(auditorshtml.join(''));
+    }
+
+    $('#change-sp-group').change(function () {
+        const data = {
+            type: 'change_sp_group',
+            sp_group: parseInt($(this).val()),
+        }
+        if (!data.sp_group) {
+            toastr.error('请选择固定审批组');
+            return false;
+        }
+        postData(preUrl + '/audit/spgroup', data, (datas) => {
+            makeSpHtml(datas);
+        });
+    });
+
+
 });
 // 检查上报情况
 function checkAuditorFrom () {
@@ -544,3 +610,4 @@ function codeSuccess(btn) {
         counter -= 1;
     }, 1000);
 }
+

+ 2 - 4
app/public/js/stage_bwtz.js

@@ -19,15 +19,11 @@ $(document).ready(() => {
     sjsSettingObj.setFxTreeStyle(xmjSpreadSetting, sjsSettingObj.FxTreeStyle.jz);
     if (thousandth) sjsSettingObj.setTpThousandthFormat(xmjSpreadSetting);
     SpreadJsObj.initSheet(xmjSheet, xmjSpreadSetting);
-    xmjSheet.frozenColumnCount(3);
-    xmjSheet.options.frozenlineColor = '#93b5e4';
 
     const unitSpread = SpreadJsObj.createNewSpread($('#unit-spread')[0]);
     const unitSheet = unitSpread.getActiveSheet();
     if (thousandth) sjsSettingObj.setTpThousandthFormat(xmjSpreadSetting);
     SpreadJsObj.initSheet(unitSheet, unitSpreadSetting);
-    unitSheet.frozenColumnCount(5);
-    unitSheet.options.frozenlineColor = '#93b5e4';
 
     const filterUnitTree = createNewPathTree('filter', {
         id: 'ledger_id',
@@ -361,6 +357,7 @@ $(document).ready(() => {
                 end_contract_tp: node.end_contract_tp, end_qc_tp: node.end_qc_tp, end_gather_tp: node.end_gather_tp,
                 end_gather_percent: node.end_gather_percent,
                 drawing_code: node.drawing_code, postil: node.postil, memo: node.memo, gxby: getGxbyText(node), dagl: getDaglText(node),
+                _hidden: !node.visible,
             });
             if (node.unitTree) {
                 for (const unitNode of node.unitTree.nodes) {
@@ -377,6 +374,7 @@ $(document).ready(() => {
                         end_gather_percent: unitNode.end_gather_percent,
                         drawing_code: unitNode.drawing_code_merge, postil: unitNode.postil_merge, memo: unitNode.memo_merge,
                         gxby: getGxbyText(unitNode), dagl: getDaglText(unitNode),
+                        _hidden: !node.visible || !unitNode.visible,
                     });
                 }
             }

+ 3 - 2
app/public/js/stage_pay.js

@@ -35,11 +35,12 @@ function makeAttTable(payNode) {
     if (attachment !== null) {
         for (const att of attachment) {
             const delhtml = uploadPermission && parseInt(att.uid) === userID && (att.renew || stage.status !== auditConst.status.checked || deleteFilePermission)
-                ? '<a class="delete-att text-danger" href="javascript:void(0);" data-fileid="'+ att.id +'" title="删除"><i class="fa fa-remove "></i></a>'
+                ? '<a class="delete-att text-danger ml-1" href="javascript:void(0);" data-fileid="'+ att.id +'" title="删除"><i class="fa fa-remove "></i></a>'
                 : '';
+            const viewHtml = att.viewpath ? `<a class="ml-1" href="${att.viewpath}" target="_blank" title="预览"><i class="fa fa-eye"></i></a>` : '';
             html += '<tr><td style="width: 200px">' + att.filename + att.fileext + '</td><td>' + att.username + '</td><td>' + att.in_time + '</td>' +
                 '<td><a href="/tender/'+ tender.id + '/measure/stage/' + tender.ledger_times +'/pay/download/file/'+ att.id +'" title="下载"><i class="fa fa-download "></i></a> ' +
-                delhtml +
+                viewHtml + delhtml +
                 '</td></tr>';
         }
     }

+ 1 - 1
app/public/js/tender_list_manage.js

@@ -1,6 +1,6 @@
 const modStatus = function(tid) {
     const tender = tenders.find(x => { return x.id === tid});
-    if (tender.status === 1) {
+    if (tender.build_status === 1) {
         if (tender.canFinish) {
             $('form', '#mod-status2finish').attr('action', `/tender/${tid}/build`);
             $('#mod-status2finish').modal('show');

+ 10 - 0
app/public/js/wap/global.js

@@ -112,6 +112,16 @@ const postDataWithFile = function (url, formData, successCallback, errorCallBack
     });
 };
 
+const postDataWithFileAsync = function (url, formData, showWaiting = true) {
+    return new Promise(function (resolve, reject) {
+        postDataWithFile(url, formData, result => {
+            resolve(result);
+        }, err => {
+            reject(err);
+        }, showWaiting);
+    });
+};
+
 /**
  * 提示框
  *

+ 31 - 7
app/router.js

@@ -60,7 +60,9 @@ module.exports = app => {
     const financialPayCheck = app.middlewares.financialPayCheck();
     const financialPayAuditCheck = app.middlewares.financialPayAuditCheck();
     // 质量巡检中间件
-    const inspectionCheck = app.middlewares.inspectionCheck();
+    const qualityInspectionCheck = app.middlewares.qualityInspectionCheck();
+    // 安全巡检中间件
+    const safeInspectionCheck = app.middlewares.safeInspectionCheck();
     // 登入登出相关
     app.get('/login', 'loginController.index');
     app.get('/login/:code', 'loginController.index');
@@ -297,9 +299,16 @@ module.exports = app => {
     app.get('/sp/:id/spss/gather/ledger', sessionAuth, subProjectCheck, 'spssController.gatherLedger');
     app.get('/sp/:id/spss/gather/stage', sessionAuth, subProjectCheck, 'spssController.gatherStage');
     app.get('/sp/:id/spss/gather/stage/extra', sessionAuth, subProjectCheck, 'spssController.gatherStageExtra');
+    app.get('/sp/:id/spss/gather/stage/pay', sessionAuth, subProjectCheck, 'spssController.gatherStagePay');
     app.get('/sp/:id/spss/compare/ledger', sessionAuth, subProjectCheck, 'spssController.compareLedger');
     app.get('/sp/:id/spss/compare/stage', sessionAuth, subProjectCheck, 'spssController.compareStage');
     app.post('/sp/:id/spss/load', sessionAuth, subProjectCheck, 'spssController.load');
+    app.post('/sp/:id/spss/stash', sessionAuth, subProjectCheck, 'spssController.stash');
+    // 测试用
+    if (app.config.env === 'qa' || app.config.env === 'local') {
+        app.get('/sp/:id/devTest', sessionAuth, projectManagerCheck, subProjectCheck, 'spssController.test');
+        app.post('/sp/:id/devTest/load', sessionAuth, projectManagerCheck, subProjectCheck, 'spssController.testLoad');
+    }
 
     // **标段合同管理 todo 接入项目内部
     // app.get('/sp/:id/contract', sessionAuth, subProjectCheck, 'contractController.index');
@@ -482,16 +491,30 @@ module.exports = app => {
     app.post('/sp/:id/quality/tender/:tid/file/del', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, 'qualityController.delFile');
     app.post('/sp/:id/quality/tender/:tid/file/save', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, 'qualityController.saveFile');
     app.post('/sp/:id/quality/tender/:tid/push', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, 'qualityController.pushStatus');
+    app.post('/sp/:id/quality/tender/:tid/force', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, 'qualityController.force');
     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');
+    app.get('/sp/:id/quality/tender/:tid/inspection/:qiid/information', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, qualityInspectionCheck, 'qualityController.inspectionInformation');
+    app.post('/sp/:id/quality/tender/:tid/inspection/:qiid/information/save', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, qualityInspectionCheck, 'qualityController.inspectionInformationSave');
+    app.post('/sp/:id/quality/tender/:tid/inspection/:qiid/information/file/upload', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, qualityInspectionCheck, 'qualityController.uploadInspectionFile');
+    app.post('/sp/:id/quality/tender/:tid/inspection/:qiid/information/file/delete', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, qualityInspectionCheck, 'qualityController.deleteInspectionFile');
+    app.get('/sp/:id/quality/tender/:tid/inspection/:qiid/information/file/:fid/download', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, qualityInspectionCheck, 'qualityController.downloadInspectionFile');
+
+    // 安全管理
+    // 安全计量
+    app.get('/sp/:id/safe', sessionAuth, subProjectCheck, 'safeController.tender');
+    // 安全巡检
+    app.get('/sp/:id/safe/inspection', sessionAuth, subProjectCheck, 'safeController.inspectionTender');
+    app.get('/sp/:id/safe/tender/:tid/inspection', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, 'safeController.inspection');
+    app.post('/sp/:id/safe/tender/:tid/inspection/save', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, 'safeController.inspectionSave');
+    app.get('/sp/:id/safe/tender/:tid/inspection/:qiid/information', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, safeInspectionCheck, 'safeController.inspectionInformation');
+    app.post('/sp/:id/safe/tender/:tid/inspection/:qiid/information/save', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, safeInspectionCheck, 'safeController.inspectionInformationSave');
+    app.post('/sp/:id/safe/tender/:tid/inspection/:qiid/information/file/upload', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, safeInspectionCheck, 'safeController.uploadInspectionFile');
+    app.post('/sp/:id/safe/tender/:tid/inspection/:qiid/information/file/delete', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, safeInspectionCheck, 'safeController.deleteInspectionFile');
+    app.get('/sp/:id/safe/tender/:tid/inspection/:qiid/information/file/:fid/download', sessionAuth, subProjectCheck, tenderCheck, tenderPermissionCheck, safeInspectionCheck, 'safeController.downloadInspectionFile');
 
     // ------------------------- 项目内部相关 -----------------------------
 
@@ -671,6 +694,7 @@ module.exports = app => {
     app.get('/tender/:id/measure/stage/:order/im-file/download', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, stageCheck, 'stageController.downloadImFile');
     app.get('/tender/:id/measure/stage/:order/cpd', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, stageCheck, 'stageController.exportStageData');
     app.post('/tender/:id/measure/stage/:order/cpd/load', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, stageCheck, 'stageController.importStageData');
+    app.post('/tender/:id/measure/stage/:order/audit/spgroup', sessionAuth, tenderCheck, subProjectCheck, uncheckTenderCheck, stageCheck, 'stageController.changeSpGroup');
 
     // 暂存计量
     app.post('/tender/:id/measure/stage/:order/stash/list', sessionAuth, tenderCheck, subProjectCheck, stageCheck, 'stageController.stashList');
@@ -1081,6 +1105,7 @@ module.exports = app => {
     app.get('/wap/inspection', 'wapController.inspection');
     app.post('/wap/inspection/ask', 'wapController.inspectionAiAsk');
     app.post('/wap/voice/token', 'wapController.voiceToken');
+    app.post('/wap/voice/oneshot', 'wapController.voiceOneShot');
 
     // 微信
     app.get('/wx', 'wechatController.index');
@@ -1147,5 +1172,4 @@ module.exports = app => {
     app.get('/wx/work/:corpid/project', wxWorkAuth, 'wechatController.workProject');
     app.get('/wx/work/:corpid/test', wxWorkAuth, 'wechatController.workTest');
     app.get('/wx/tips', 'wechatController.tips');
-
 };

+ 76 - 20
app/service/change.js

@@ -198,7 +198,7 @@ module.exports = app => {
                                     samount: '',
                                     gcl_id: '',
                                     mx_id: '',
-                                    spamount: p.camount,
+                                    spamount: p.samount,
                                     detail: '',
                                     xmj_code: null,
                                     xmj_jldy: null,
@@ -1359,15 +1359,17 @@ module.exports = app => {
                 const updateList = [];
                 for (const cl of changeList) {
                     const audit_amount = cl.audit_amount.split(',');
-                    const last_amount = audit_amount.length - 1 !== -1 && audit_amount[audit_amount.length - 1] !== undefined ? audit_amount[audit_amount.length - 1] : (cl.camount ? cl.camount : 0);
-                    audit_amount.splice(-1, 1);
-                    const list_update = {
-                        id: cl.id,
-                        audit_amount: audit_amount.join(','),
-                        spamount: parseFloat(last_amount),
-                    };
-                    total_price = this.ctx.helper.add(total_price, this.ctx.helper.mul(cl.unit_price, parseFloat(last_amount), tp_decimal));
-                    updateList.push(list_update);
+                    if (cl.audit_amount !== null && audit_amount.length > 0) {
+                        const last_amount = audit_amount.length - 1 !== -1 && audit_amount[audit_amount.length - 1] !== undefined ? audit_amount[audit_amount.length - 1] : (cl.camount ? cl.camount : 0);
+                        audit_amount.splice(-1, 1);
+                        const list_update = {
+                            id: cl.id,
+                            audit_amount: audit_amount.join(','),
+                            spamount: parseFloat(last_amount) || 0,
+                        };
+                        total_price = this.ctx.helper.add(total_price, this.ctx.helper.mul(cl.unit_price, parseFloat(last_amount), tp_decimal));
+                        updateList.push(list_update);
+                    }
                 }
                 if (updateList.length > 0) await this.transaction.updateRows(this.ctx.service.changeAuditList.tableName, updateList);
 
@@ -1837,16 +1839,18 @@ module.exports = app => {
                         }
                     }
                     const audit_amount = cl.audit_amount.split(',');
-                    const last_amount = audit_amount[audit_amount.length - 1] ? audit_amount[audit_amount.length - 1] : 0;
-                    audit_amount.splice(-1, 1);
-                    const list_update = {
-                        id: cl.id,
-                        audit_amount: audit_amount.join(','),
-                        samount: '',
-                        spamount: parseFloat(last_amount),
-                    };
-                    total_price = this.ctx.helper.add(total_price, this.ctx.helper.mul(cl.unit_price, parseFloat(last_amount), tp_decimal));
-                    updateChangeList.push(list_update);
+                    if (cl.audit_amount !== null && audit_amount.length > 0) {
+                        const last_amount = audit_amount[audit_amount.length - 1] ? audit_amount[audit_amount.length - 1] : 0;
+                        audit_amount.splice(-1, 1);
+                        const list_update = {
+                            id: cl.id,
+                            audit_amount: audit_amount.join(','),
+                            samount: '',
+                            spamount: parseFloat(last_amount) || 0,
+                        };
+                        total_price = this.ctx.helper.add(total_price, this.ctx.helper.mul(cl.unit_price, parseFloat(last_amount), tp_decimal));
+                        updateChangeList.push(list_update);
+                    }
                 }
                 if (updateChangeList.length > 0) await this.transaction.updateRows(this.ctx.service.changeAuditList.tableName, updateChangeList);
 
@@ -2225,6 +2229,58 @@ module.exports = app => {
             }
             return returnList;
         }
+
+        async getReCalcChangeData(tenderId, tenderInfo) {
+            const change = await this.ctx.service.change.getAllDataByCondition({
+                columns: [ 'cid', 'code', 'name', 'w_code', 'tp_decimal', 'up_decimal' ],
+                where: { tid: tenderId, valid: 1, status: audit.flow.status.checked },
+                orders: [['code', 'asc']],
+            });
+            const changeBills = await this.ctx.service.changeAuditList.getAllDataByCondition({
+                columns: [ 'id', 'cid', 'code', 'name', 'unit', 'unit_price', 'oamount2', 'checked_amount', 'checked_price', 'is_valuation', 'gcl_id', 'bwmx', 'mx_id'],
+                where: { tid: tenderId }
+            });
+            const changeIndex = {};
+            change.forEach(x => { changeIndex[x.cid] = x; x.bills = [] });
+            changeBills.forEach(cb => {
+                const c = changeIndex[cb.cid];
+                if (!c) return;
+                if (cb.gcl_id) {
+                    let b = c.bills.find(x => { return x.gcl_id === cb.gcl_id; });
+                    if (!b) {
+                        b = { code: cb.code, name: cb.name, unit: cb.unit, unit_price: cb.unit_price, pos: [], gcl_id: cb.gcl_id };
+                        c.bills.push(b);
+                    }
+                    b.checked_amount = this.ctx.helper.add(b.checked_amount, cb.checked_amount);
+                    if (cb.is_valuation) {
+                        b.qty = this.ctx.helper.add(b.qty, cb.checked_amount);
+                    } else {
+                        b.minus_qty = this.ctx.helper.add(b.minus_qty, cb.checked_amount);
+                    }
+                    b.pos.push(cb);
+                } else {
+                    c.bills.push({
+                        code: cb.code, name: cb.name, unit: cb.unit, unit_price: cb.unit_price,
+                        gcl_id: '', checked_amount: cb.checked_amount, checked_price: cb.checked_price,
+                        qty: cb.is_valuation ? cb.checked_amount : 0, tp: cb.is_valuation ? cb.checked_price : 0,
+                        minus_qty: !cb.is_valuation ? cb.checked_amount : 0, minus_tp: !cb.is_valuation ? cb.checked_price : 0,
+                        pos: [cb],
+                    });
+                }
+            });
+            const helper = this.ctx.helper;
+            change.forEach(x => {
+                const tpDecimal = x.tp_decimal || tenderInfo.decimal.tp;
+                x.bills.forEach(b => {
+                    if (!b.gcl_id) return;
+                    b.checked_price = helper.mul(b.unit_price, b.checked_amount, tpDecimal);
+                    b.tp = helper.mul(b.unit_price, b.qty, tpDecimal);
+                    b.minus_tp = helper.mul(b.unit_price, b.minus_qty, tpDecimal);
+                });
+                x.bills.sort((a, b) => { return helper.compareCode(a.code, b.code)});
+            });
+            return change;
+        }
     }
 
     return Change;

+ 3 - 0
app/service/change_plan.js

@@ -138,6 +138,9 @@ module.exports = app => {
                             oamount: c.oamount,
                             camount: c.camount,
                             spamount: c.camount,
+                            new_up: c.new_up,
+                            ex_memo1: c.ex_memo1,
+                            ex_memo2: c.ex_memo2,
                         });
                     }
                     if (insertArray.length > 0) await this.transaction.insert(this.ctx.service.changePlanList.tableName, insertArray);

+ 2 - 1
app/service/ledger.js

@@ -745,6 +745,7 @@ module.exports = app => {
             for (const subject of ybpData.subjects) {
                 if (!subject.bills || subject.bills.length === 0) continue;
 
+                const parents = YbpTrees.YbpUtils.getSubjectParentPath(subject, ybpData.subjects);
                 const ybpTree = new YbpTrees.YbpTree(ybpTreeSetting);
                 ybpTree.loadDatas(subject.bills);
                 const loadRelaFun = function(cur, node) {
@@ -770,7 +771,7 @@ module.exports = app => {
                         }
                     }
                 };
-                gatherTree.importTree(ybpTree, subject.project.name, loadRelaFun);
+                gatherTree.importTree(ybpTree, subject.project.name, parents, loadRelaFun);
             }
             gatherTree.sort(false);
             gatherTree.calculateAll();

+ 2 - 1
app/service/pay_att.js

@@ -27,7 +27,8 @@ module.exports = app => {
                 orders: [['id', 'desc']]
             });
             for (const f of files) {
-                delete f.filepath;
+                f.filepath = this.ctx.app.config.fujianOssPath + f.filepath;
+                if (this.ctx.helper.canPreview(f.fileext)) f.viewpath = f.filepath;
             }
             return files;
         }

+ 4 - 1
app/service/payment_safe_bills.js

@@ -483,7 +483,10 @@ module.exports = app => {
                 if (row.cur_qty !== undefined || row.unit_price !== undefined || row.cur_tp !== undefined) {
                     nData.unit_price = row.unit_price !== undefined ? helper.round(row.unit_price, decimal.up) : oData.unit_price;
                     nData.cur_qty = row.cur_qty !== undefined ? helper.round(row.cur_qty, decimal.qty) : oData.cur_qty;
-                    nData.cur_tp = row.cur_tp !== undefined ? helper.round(row.cur_tp, decimal.tp) : helper.mul(nData.unit_price, nData.cur_qty, decimal.tp);
+                    if (row.cur_tp !== undefined && row.unit_price === undefined) {
+                        nData.unit_price = nData.cur_qty ? helper.div(row.cur_tp, nData.cur_qty, decimal.up) : 0;
+                    }
+                    nData.cur_tp = helper.mul(nData.unit_price, nData.cur_qty, decimal.tp);
                     nData.end_qty = helper.add(nData.cur_qty, oData.pre_qty);
                     nData.end_tp = helper.add(nData.cur_tp, oData.pre_tp);
                 }

+ 1 - 1
app/service/phase_pay.js

@@ -358,7 +358,7 @@ module.exports = app => {
             phasePay.curAuditors = phasePay.auditors.filter(x => { return x.audit_status === audit.status.checking; });
             phasePay.curAuditorIds = phasePay.curAuditors.map(x => { return x.audit_id; });
             phasePay.flowAuditors = phasePay.curAuditors.length === 0 ? [] : phasePay.auditors.filter(x => { return x.active_order === phasePay.curAuditors[0].active_order; });
-            phasePay.flowAuditorIds = phasePay.curAuditors.map(x => { return x.audit_id; });
+            phasePay.flowAuditorIds = phasePay.flowAuditors.map(x => { return x.audit_id; });
             phasePay.nextAuditors = phasePay.curAuditors.length > 0 ? phasePay.auditors.filter(x => { return x.active_order === phasePay.curAuditors[0].active_order + 1; }) : [];
             phasePay.nextAuditorIds = this._.map(phasePay.nextAuditors, 'audit_id');
             phasePay.auditorGroups = this.ctx.helper.groupAuditors(phasePay.auditors, 'active_order');

+ 2 - 0
app/service/phase_pay_audit.js

@@ -732,6 +732,7 @@ module.exports = app => {
                     id: phasePay.id, audit_status: auditConst.phasePay.status.checking, audit_end_time: null, final_auditor_str: '', audit_max_sort: finalAudit.active_order + 2,
                 });
                 // 拷贝新流程合同支付
+                await this.ctx.service.phasePayDetail.initPhaseDataByAudit(transaction, phasePay, phasePay.audit_times, finalAudit.active_order + 1);
                 await this.ctx.service.phasePayDetail.initPhaseDataByAudit(transaction, phasePay, phasePay.audit_times, finalAudit.active_order + 2);
 
                 // todo 添加短信通知-需要审批提醒功能
@@ -976,6 +977,7 @@ module.exports = app => {
                 await transaction.update(this.ctx.service.phasePay.tableName, {
                     id: phasePay.id, audit_times: phasePay.audit_times - 1, audit_status: auditConst.phasePay.status.checking,
                 });
+                await this.ctx.service.phasePayDetail.initPhaseDataByAuditCancel(transaction, phasePay, selfAuditor.audit_times, selfAuditor.active_order, selfAuditor.audit_times, selfAuditor.active_order + 1);
                 await transaction.update(this.ctx.service.phasePayDetail.tableName,
                     { audit_times: selfAuditor.audit_times, audit_sort: selfAuditor.active_order + 2, master_id: `${phasePay.id}-${selfAuditor.audit_times}-${selfAuditor.active_order + 2}` },
                     { where: { phase_id: phasePay.id, audit_times: phasePay.audit_times, audit_sort: 0 } });

+ 2 - 1
app/service/phase_pay_detail.js

@@ -518,6 +518,7 @@ class PhasePayDetail extends TreeService {
 
     async addNext(phasePay, select, count = 1) {
         const masterId = this.getMasterKey(phasePay);
+        const parent = await this.getDataByKid(masterId, select[this.setting.pid]);
         this.transaction = await this.db.beginTransaction();
         try {
             if (select) await this._updateChildrenOrder(masterId, select[this.setting.pid], select[this.setting.order] + 1, count);
@@ -533,7 +534,7 @@ class PhasePayDetail extends TreeService {
                     ? select[this.setting.fullPath].replace('-' + select[this.setting.kid], '-' + newData[this.setting.kid])
                     : newData[this.setting.kid] + '';
                 newData[this.setting.isLeaf] = 1;
-                this._getDefaultData(newData, phasePay);
+                this._getDefaultData(newData, phasePay, parent);
                 newDatas.push(newData);
             }
             const insertResult = await this.transaction.insert(this.tableName, newDatas);

+ 14 - 15
app/service/quality_inspection.js

@@ -158,7 +158,7 @@ module.exports = app => {
         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) {
+            if ((this.ctx.tender.isTourist || this.ctx.session.sessionUser.is_admin || this.ctx.permission.inspection.view_all) && status === 0) {
                 sql = 'SELECT a.* FROM ?? As a WHERE a.tid = ?';
                 sqlParam = [this.tableName, tid];
             } else {
@@ -185,10 +185,10 @@ module.exports = app => {
                     case auditConst.filter.status.uncheck: // 待上报(所有的)PS:取未上报,退回,修订的变更令
                         sql =
                             'SELECT a.* FROM ?? AS a WHERE ' +
-                            'a.uid = ? AND a.tid = ? AND (a.status = ? OR a.status = ?)';
+                            'a.tid = ? AND (a.status = ? OR a.status = ?)' +
+                            (this.ctx.session.sessionUser.is_admin || this.ctx.permission.inspection.view_all ? '' : ' AND a.uid = ' + this.ctx.session.sessionUser.accountId);
                         sqlParam = [
                             this.tableName,
-                            this.ctx.session.sessionUser.accountId,
                             tid,
                             auditConst.status.uncheck,
                             auditConst.status.checkNo,
@@ -198,16 +198,16 @@ module.exports = app => {
                         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];
+                            (this.ctx.session.sessionUser.is_admin || this.ctx.permission.inspection.view_all ? '' :  ' AND (a.uid = ' + this.ctx.session.sessionUser.accountId + ' OR a.id IN (SELECT b.qiid FROM ' + this.ctx.service.qualityInspectionAudit.tableName + ' AS b WHERE b.aid = ' + this.ctx.session.sessionUser.accountId + ' GROUP BY b.qiid))');
+                        sqlParam = [this.tableName, status, auditConst.status.checkNoPre, tid];
                         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];
+                            (this.ctx.session.sessionUser.is_admin || this.ctx.permission.inspection.view_all ? '' :  ' AND (a.uid = ' + this.ctx.session.sessionUser.accountId + ' OR a.id IN (SELECT b.qiid FROM ' + this.ctx.service.qualityInspectionAudit.tableName + ' AS b WHERE b.aid = ' + this.ctx.session.sessionUser.accountId + ' GROUP BY b.qiid))');
+                        sqlParam = [this.tableName, status, tid];
                         break;
                     case auditConst.filter.status.checked: // 已完成(所有的)
                         sql = 'SELECT a.* FROM ?? as a WHERE a.status = ? AND a.tid = ?';
@@ -243,7 +243,7 @@ module.exports = app => {
          * @return {void}
          */
         async getCountByStatus(tid, status = 0) {
-            if ((this.ctx.tender.isTourist || this.ctx.session.sessionUser.is_admin) && status === 0) {
+            if ((this.ctx.tender.isTourist || this.ctx.session.sessionUser.is_admin || this.ctx.permission.inspection.view_all) && 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);
@@ -272,11 +272,10 @@ module.exports = app => {
                     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 = ?)';
+                        'SELECT count(*) AS count FROM ?? AS a WHERE a.tid = ? AND (a.status = ? OR a.status = ?)' +
+                        (this.ctx.session.sessionUser.is_admin || this.ctx.permission.inspection.view_all ? '' : ' AND a.uid = ' + this.ctx.session.sessionUser.accountId);
                     const sqlParam2 = [
                         this.tableName,
-                        this.ctx.session.sessionUser.accountId,
                         tid,
                         auditConst.status.uncheck,
                         auditConst.status.checkNo,
@@ -287,8 +286,8 @@ module.exports = app => {
                     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];
+                        (this.ctx.session.sessionUser.is_admin || this.ctx.permission.inspection.view_all ? '' : ' AND (a.uid = ' + this.ctx.session.sessionUser.accountId + ' OR a.id IN (SELECT b.qiid FROM ' + this.ctx.service.qualityInspectionAudit.tableName + ' AS b WHERE b.aid = ' + this.ctx.session.sessionUser.accountId + ' GROUP BY b.qiid))');
+                    const sqlParam7 = [this.tableName, status, auditConst.status.checkNoPre, tid];
                     const result7 = await this.db.query(sql7, sqlParam7);
                     return result7[0].count;
                 case auditConst.filter.status.rectification: // 整改中(所有的)
@@ -296,8 +295,8 @@ module.exports = app => {
                     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];
+                        (this.ctx.session.sessionUser.is_admin || this.ctx.permission.inspection.view_all ? '' : ' AND (a.uid = ' + this.ctx.session.sessionUser.accountId + ' OR a.id IN (SELECT b.qiid FROM ' + this.ctx.service.qualityInspectionAudit.tableName + ' AS b WHERE b.aid = ' + this.ctx.session.sessionUser.accountId + ' GROUP BY b.qiid))');
+                    const sqlParam3 = [this.tableName, status, tid];
                     const result3 = await this.db.query(sql3, sqlParam3);
                     return result3[0].count;
                 case auditConst.filter.status.checked: // 已完成(所有的)

+ 36 - 0
app/service/quality_inspection_audit.js

@@ -57,6 +57,42 @@ module.exports = app => {
             return await this.db.query(sql, sqlParam);
         }
 
+        async getAuditorsByStatus(qiId, status, times = 1) {
+            let auditor = [];
+            let sql = '';
+            let sqlParam = '';
+            let cur;
+            switch (status) {
+                case auditConst.status.checking :
+                case auditConst.status.checked :
+                case auditConst.status.checkNoPre :
+                case auditConst.status.rectification :
+                    cur = await this.db.queryOne('SELECT * From ?? where qiid = ? AND times = ? AND status = ? ORDER By times DESC, `order` DESC', [this.tableName, qiId, times, status]);
+                    if (!cur) return [];
+
+                    sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`qiid`, la.`order`, la.`status`, la.`audit_order`, la.`audit_type` ' +
+                        '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id` ' +
+                        '  WHERE la.`qiid` = ? and la.`order` = ? and times = ?';
+                    sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, qiId, cur.order, times];
+                    auditor = await this.db.query(sql, sqlParam);
+                    break;
+                case auditConst.status.checkNo :
+                    cur = await this.db.queryOne('SELECT * From ?? where qiid = ? AND times = ? AND status = ? ORDER By times DESC, `order` DESC', [this.tableName, qiId, parseInt(times) - 1, status]);
+                    if (!cur) return [];
+
+                    sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`qiid`, la.`order`, la.`status`, la.`audit_order`, la.`audit_type` ' +
+                        '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id` ' +
+                        '  WHERE la.`qiid` = ? and la.`order` = ? and la.`times` = ?';
+                    sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, qiId, cur.order, parseInt(times) - 1];
+                    auditor = await this.db.query(sql, sqlParam);
+                    break;
+                case auditConst.status.uncheck:
+                default:
+                    break;
+            }
+            return auditor;
+        }
+
         /**
          * 获取 最新审核顺序
          *

+ 72 - 2
app/service/report.js

@@ -11,6 +11,7 @@
 const BudgetSource = require('../lib/rm/tender_budget');
 const MaterialSource = require('../lib/rm/tender_material');
 const FinancialSource = require('../lib/rm/tender_financial');
+const SpssSource = require('../lib/rm/spss');
 
 const rptCustomData = require('../lib/rptCustomData');
 const bindData = {
@@ -20,7 +21,6 @@ const bindData = {
 };
 const sourceTypeConst = require('../const/source_type');
 
-
 module.exports = app => {
     class Report extends app.BaseService {
 
@@ -81,6 +81,7 @@ module.exports = app => {
             await service.tender.checkTender(params.tender_id);
             const materialSource = new MaterialSource(this.ctx);
             const financialSource = new FinancialSource(this.ctx);
+            const spssSource = new SpssSource(this.ctx);
             const rst = {};
             const runnableRst = [];
             const runnableKey = []; // 这个配合runnableRst用,未来考虑并行查询优化
@@ -617,6 +618,75 @@ module.exports = app => {
                     // case 'mem_material_bills_gl':
                     //     rst[filter] = await service.rptGatherMemory.getMaterialBillsGl(params.tender_id, params.material_order, memFieldKeys[filter]);
                     //     break;
+                    case 'mem_spss_gather_info_tender':
+                        rst[filter] =  await spssSource.getGatherStageInfoTender();
+                        break;
+                    case 'mem_spss_gather_info':
+                        rst[filter] =  await spssSource.getGatherStageInfo();
+                        break;
+                    case 'mem_spss_gather_ledger_tender':
+                        rst[filter] =  await spssSource.getGatherLedgerTender();
+                        break;
+                    case 'mem_spss_gather_ledger_bills':
+                        rst[filter] =  await spssSource.getGatherLedgerBills();
+                        break;
+                    case 'mem_spss_gather_ledger_pos':
+                        rst[filter] =  await spssSource.getGatherLedgerPos();
+                        break;
+                    case 'mem_spss_gather_stage_tender':
+                        rst[filter] =  await spssSource.getGatherStageTender();
+                        break;
+                    case 'mem_spss_gather_stage_bills':
+                        rst[filter] =  await spssSource.getGatherStageBills();
+                        break;
+                    case 'mem_spss_gather_stage_pos':
+                        rst[filter] =  await spssSource.getGatherStagePos();
+                        break;
+                    case 'mem_spss_gather_stage_extra_tender':
+                        rst[filter] =  await spssSource.getGatherStageExtraTender();
+                        break;
+                    case 'mem_spss_gather_stage_jgcl':
+                        rst[filter] =  await spssSource.getGatherStageJgcl();
+                        break;
+                    case 'mem_spss_gather_stage_yjcl':
+                        rst[filter] =  await spssSource.getGatherStageYjcl();
+                        break;
+                    case 'mem_spss_gather_stage_bonus':
+                        rst[filter] =  await spssSource.getGatherStageBonus();
+                        break;
+                    case 'mem_spss_gather_stage_other':
+                        rst[filter] =  await spssSource.getGatherStageOther();
+                        break;
+                    case 'mem_spss_gather_stage_safe_prod':
+                        rst[filter] =  await spssSource.getGatherStageSafeProd();
+                        break;
+                    case 'mem_spss_gather_stage_temp_land':
+                        rst[filter] =  await spssSource.getGatherStageTempLand();
+                        break;
+                    case 'mem_spss_gather_stage_pay_tender':
+                        rst[filter] =  await spssSource.getGatherStagePayTender();
+                        break;
+                    case 'mem_spss_gather_stage_pay':
+                        rst[filter] =  await spssSource.getGatherStagePay();
+                        break;
+                    case 'mem_spss_compare_ledger_tender':
+                        rst[filter] =  await spssSource.getCompareLedgerTender();
+                        break;
+                    case 'mem_spss_compare_ledger_bills':
+                        rst[filter] =  await spssSource.getCompareLedgerBills();
+                        break;
+                    case 'mem_spss_compare_ledger_pos':
+                        rst[filter] =  await spssSource.getCompareLedgerPos();
+                        break;
+                    case 'mem_spss_compare_stage_tender':
+                        rst[filter] =  await spssSource.getCompareStageTender();
+                        break;
+                    case 'mem_spss_compare_stage_bills':
+                        rst[filter] =  await spssSource.getCompareStageBills();
+                        break;
+                    case 'mem_spss_compare_stage_pos':
+                        rst[filter] =  await spssSource.getCompareStagePos();
+                        break;
                     default:
                         break;
                 }
@@ -729,7 +799,7 @@ module.exports = app => {
         async getReportData(source_type, params, sourceFilters, memFieldKeys, customDefine, customSelect) {
             this.clearReportCache();
             const sourceType = sourceTypeConst.sourceTypeData.find(x => { return x.id === source_type; });
-            if (!sourceType && !this[sourceType.key]) return {};
+            if (!sourceType || !this[sourceType.key]) return {};
 
             return await this[sourceType.key](params, sourceFilters, memFieldKeys, customDefine, customSelect);
         }

+ 18 - 2
app/service/rpt_gather_memory.js

@@ -39,6 +39,8 @@ const gatherUtils = {
         gatherNode[prefix + "tp"] = helper.add(gatherNode[prefix + "tp"], sourceNode.total_price);
         gatherNode[prefix + "deal_qty"] = helper.add(gatherNode[prefix + "deal_qty"], sourceNode.deal_qty);
         gatherNode[prefix + "deal_tp"] = helper.add(gatherNode[prefix + "deal_tp"], sourceNode.deal_tp);
+        gatherNode[prefix + 'tz_qc_qty'] = helper.add(gatherNode[prefix + 'tz_qc_qty'], sourceNode.tz_qc_qty);
+        gatherNode[prefix + 'tz_qc_tp'] = helper.add(gatherNode[prefix + 'tz_qc_tp'], sourceNode.tz_qc_tp);
 
         gatherNode[prefix + "dgn_qty1"] = helper.add(gatherNode[prefix + "dgn_qty1"], sourceNode.dgn_qty1);
         gatherNode[prefix + "dgn_qty2"] = helper.add(gatherNode[prefix + "dgn_qty2"], sourceNode.dgn_qty2);
@@ -82,6 +84,8 @@ const gatherUtils = {
         gatherNode.s_tp = helper.add(gatherNode.s_tp, sourceNode.total_price);
         gatherNode.s_deal_qty = helper.add(gatherNode.s_deal_qty, sourceNode.deal_qty);
         gatherNode.s_deal_tp = helper.add(gatherNode.s_deal_tp, sourceNode.deal_tp);
+        gatherNode.s_tz_qc_qty = helper.add(gatherNode.s_tz_qc_qty, sourceNode.tz_qc_qty);
+        gatherNode.s_tz_qc_tp = helper.add(gatherNode.s_tz_qc_tp, sourceNode.tz_qc_tp);
 
         gatherNode.s_dgn_qty1 = helper.add(gatherNode.s_dgn_qty1, sourceNode.dgn_qty1);
         gatherNode.s_dgn_qty2 = helper.add(gatherNode.s_dgn_qty2, sourceNode.dgn_qty2);
@@ -130,6 +134,8 @@ const gatherUtils = {
         gatherNode[prefix + "tp"] = helper.add(gatherNode[prefix + "tp"], sourceNode.total_price);
         gatherNode[prefix + "deal_qty"] = helper.add(gatherNode[prefix + "deal_qty"], sourceNode.deal_qty);
         gatherNode[prefix + "deal_tp"] = helper.add(gatherNode[prefix + "deal_tp"], sourceNode.deal_tp);
+        gatherNode[prefix + 'tz_qc_qty'] = helper.add(gatherNode[prefix + 'tz_qc_qty'], sourceNode.tz_qc_qty);
+        gatherNode[prefix + 'tz_qc_tp'] = helper.add(gatherNode[prefix + 'tz_qc_tp'], sourceNode.tz_qc_tp);
 
         gatherNode[prefix + "dgn_qty1"] = helper.add(gatherNode[prefix + "dgn_qty1"], sourceNode.dgn_qty1);
         gatherNode[prefix + "dgn_qty2"] = helper.add(gatherNode[prefix + "dgn_qty2"], sourceNode.dgn_qty2);
@@ -151,6 +157,8 @@ const gatherUtils = {
         gatherNode.s_tp = helper.add(gatherNode.s_tp, sourceNode.total_price);
         gatherNode.s_deal_qty = helper.add(gatherNode.s_deal_qty, sourceNode.deal_qty);
         gatherNode.s_deal_tp = helper.add(gatherNode.s_deal_tp, sourceNode.deal_tp);
+        gatherNode.s_tz_qc_qty = helper.add(gatherNode.s_tz_qc_qty, sourceNode.tz_qc_qty);
+        gatherNode.s_tz_qc_tp = helper.add(gatherNode.s_tz_qc_tp, sourceNode.tz_qc_tp);
 
         gatherNode.s_contract_qty = helper.add(gatherNode.s_contract_qty, sourceNode.contract_qty);
         gatherNode.s_contract_tp = helper.add(gatherNode.s_contract_tp, sourceNode.contract_tp);
@@ -169,6 +177,8 @@ const gatherUtils = {
         gatherNode[prefix + "tp"] = helper.add(gatherNode[prefix + "tp"], sourceNode.total_price);
         gatherNode[prefix + "deal_qty"] = helper.add(gatherNode[prefix + "deal_qty"], sourceNode.deal_qty);
         gatherNode[prefix + "deal_tp"] = helper.add(gatherNode[prefix + "deal_tp"], sourceNode.deal_tp);
+        gatherNode[prefix + 'tz_qc_qty'] = helper.add(gatherNode[prefix + 'tz_qc_qty'], sourceNode.tz_qc_qty);
+        gatherNode[prefix + 'tz_qc_tp'] = helper.add(gatherNode[prefix + 'tz_qc_tp'], sourceNode.tz_qc_tp);
 
         gatherNode[prefix + "dgn_qty1"] = helper.add(gatherNode[prefix + "dgn_qty1"], sourceNode.dgn_qty1);
         gatherNode[prefix + "dgn_qty2"] = helper.add(gatherNode[prefix + "dgn_qty2"], sourceNode.dgn_qty2);
@@ -177,6 +187,8 @@ const gatherUtils = {
         gatherNode.s_tp = helper.add(gatherNode.s_tp, sourceNode.total_price);
         gatherNode.s_deal_qty = helper.add(gatherNode.s_deal_qty, sourceNode.deal_qty);
         gatherNode.s_deal_tp = helper.add(gatherNode.s_deal_tp, sourceNode.deal_tp);
+        gatherNode.s_tz_qc_qty = helper.add(gatherNode.s_tz_qc_qty, sourceNode.tz_qc_qty);
+        gatherNode.s_tz_qc_tp = helper.add(gatherNode.s_tz_qc_tp, sourceNode.tz_qc_tp);
     },
     gatherSpecial: function (gatherNode, sourceNode, prefix, helper) {
         gatherNode[prefix + "qty"] = helper.add(gatherNode[prefix + "qty"], sourceNode.quantity);
@@ -578,7 +590,7 @@ module.exports = app => {
                     'contract_tp', 'qc_tp', 'contract_pc_tp', 'qc_pc_tp', 'pc_tp', 'gather_tp',
                     'pre_contract_tp', 'pre_qc_tp', 'pre_gather_tp',
                     'end_contract_tp', 'end_qc_tp', 'end_gather_tp',
-                    'due_qc_tp', 'due_final_tp',
+                    'due_qc_tp', 'due_final_tp', 'tz_qc_tp',
                 ],
                 calc: function (node) {
                     if (node.children && node.children.length === 0) {
@@ -642,9 +654,13 @@ module.exports = app => {
                     { data: preStagePos, fields: ['contract_qty', 'qc_qty'], prefix: 'pre_', relaId: 'pid' },
                 ]);
             }
+
             billsTree.loadDatas(billsData);
-            billsTree.calculateAll();
             pos.loadDatas(posData);
+            const tenderInfo = await this.ctx.service.tenderInfo.getTenderInfo(tender.id);
+            const reCalcChange = await this.ctx.service.change.getReCalcChangeData(tender.id, tenderInfo);
+            Ledger.treeUtils.loadChangeData(billsTree, pos, reCalcChange, tenderInfo, helper);
+            billsTree.calculateAll();
             pos.calculateAll();
 
             this.resultTree.loadGatherTree(billsTree, function (gatherNode, sourceNode) {

+ 314 - 0
app/service/safe_inspection.js

@@ -0,0 +1,314 @@
+'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 SafeInspection extends app.BaseService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'safe_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.safeInspectionAudit.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.safeInspectionAudit.getAuditorHistory(inspection.id, times);
+            // 获取审批流程中左边列表
+            if (inspection.status === auditConst.status.checkNo && inspection.uid !== this.ctx.session.sessionUser.accountId) {
+                const auditors = await this.ctx.service.safeInspectionAudit.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.safeInspectionAudit.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.safeInspectionAudit.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.safeInspectionAtt.tableName, { where: { qiid: id } });
+                await transaction.delete(this.ctx.service.safeInspectionAtt.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.safeInspectionAudit.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.safeInspectionAudit.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 || this.ctx.permission.safe_inspection.view_all) && 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.safeInspectionAudit.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.safeInspectionAudit.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.tid = ? AND (a.status = ? OR a.status = ?)' +
+                            (this.ctx.session.sessionUser.is_admin || this.ctx.permission.safe_inspection.view_all ? '' : ' AND a.uid = ' + this.ctx.session.sessionUser.accountId);
+                        sqlParam = [
+                            this.tableName,
+                            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 || this.ctx.permission.safe_inspection.view_all ? '' : ' AND (a.uid = ' + this.ctx.session.sessionUser.accountId + ' OR a.id IN (SELECT b.qiid FROM ' + this.ctx.service.safeInspectionAudit.tableName + ' AS b WHERE b.aid = ' + this.ctx.session.sessionUser.accountId + ' GROUP BY b.qiid))');
+                        sqlParam = [this.tableName, status, auditConst.status.checkNoPre, tid, this.ctx.service.safeInspectionAudit.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 || this.ctx.permission.safe_inspection.view_all ? '' : ' AND (a.uid = ' + this.ctx.session.sessionUser.accountId + ' OR a.id IN (SELECT b.qiid FROM ' + this.ctx.service.safeInspectionAudit.tableName + ' AS b WHERE b.aid = ' + this.ctx.session.sessionUser.accountId + ' GROUP BY b.qiid))');
+                        sqlParam = [this.tableName, status, tid, this.ctx.service.safeInspectionAudit.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 || this.ctx.permission.safe_inspection.view_all) && 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.safeInspectionAudit.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.safeInspectionAudit.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.tid = ? AND (a.status = ? OR a.status = ?)' +
+                        (this.ctx.session.sessionUser.is_admin || this.ctx.permission.safe_inspection.view_all ? '' : ' AND a.uid = ' + this.ctx.session.sessionUser.accountId);
+                    const sqlParam2 = [
+                        this.tableName,
+                        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 || this.ctx.permission.safe_inspection.view_all ? '' : ' AND (a.uid = ' + this.ctx.session.sessionUser.accountId + ' OR a.id IN (SELECT b.qiid FROM ' + this.ctx.service.safeInspectionAudit.tableName + ' AS b WHERE b.aid = ' + this.ctx.session.sessionUser.accountId + ' GROUP BY b.qiid))');
+                    const sqlParam7 = [this.tableName, status, auditConst.status.checkNoPre, tid, this.ctx.service.safeInspectionAudit.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 || this.ctx.permission.safe_inspection.view_all ? '' : ' AND (a.uid = ' + this.ctx.session.sessionUser.accountId + ' OR a.id IN (SELECT b.qiid FROM ' + this.ctx.service.safeInspectionAudit.tableName + ' AS b WHERE b.aid = ' + this.ctx.session.sessionUser.accountId + ' GROUP BY b.qiid))');
+                    const sqlParam3 = [this.tableName, status, tid, this.ctx.service.safeInspectionAudit.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 SafeInspection;
+};

+ 82 - 0
app/service/safe_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 SafeInspectionAtt extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'safe_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 SafeInspectionAtt;
+};
+

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 1221 - 0
app/service/safe_inspection_audit.js


+ 26 - 0
app/service/shenpi_group.js

@@ -139,6 +139,32 @@ module.exports = app => {
             return await this.getDataByCondition({ tid, sp_type, is_select: 1 });
         }
 
+        async getGroupListByStage(tid, sp_type) {
+            const sql = 'select * from ?? where tid= ? and sp_type= ? order by id asc';
+            const sqlParam = [this.tableName, tid, sp_type];
+            return await this.db.query(sql, sqlParam);
+        }
+
+        async getSelectGroupByStageType(tid, sp_type, group_id = 0) {
+            const result = await this.getGroupListByStage(tid, sp_type);
+            if (result && result.length > 0) {
+                if (group_id !== 0) {
+                    const group = await this._.find(result, function(item) {
+                        return item.id === group_id;
+                    });
+                    if (group) return group;
+                }
+                const hadSelect = this._.find(result, function(item) {
+                    return item.is_select === 1;
+                });
+                if (hadSelect) {
+                    return hadSelect;
+                }
+                return result[0];
+            }
+            return null;
+        }
+
         async getGroupListByChangeType(tid, sp_type, change_type) {
             const sql = 'select * from ?? where tid= ? and sp_type= ? and JSON_CONTAINS(change_type, json_object(?, true)) order by id asc';
             const sqlParam = [this.tableName, tid, sp_type, change_type];

+ 76 - 0
app/service/spss_stash.js

@@ -0,0 +1,76 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2025/12/05
+ * @version
+ */
+
+const maxStashCount = 5;
+
+module.exports = app => {
+
+    class SpssStash extends app.BaseService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'spss_stash';
+        }
+
+        async getSpssStashList(spid, spssType) {
+            const result = await this.getAllDataByCondition({
+                where: { spid, spss_type: spssType },
+                columns: [ 'id', 'user_id', 'user_name', 'create_time' ],
+                orders: [['create_time', 'desc']],
+            });
+            if (result.length > maxStashCount) {
+                const id = [...result].reverse().slice(0, result.length - maxStashCount).map(x => { return x.id; });
+                await this.db.delete(this.tableName, { id });
+                return result.slice(0, maxStashCount);
+            }
+            return result;
+        }
+
+        async getSpssStash(id) {
+            const data = await this.getDataById(id);
+            data.spss_select = JSON.parse(data.spss_select);
+            data.spss_result = JSON.parse(data.spss_result);
+            return data;
+        }
+
+        async getLatestSpssStash(spid, type) {
+            const data = await this.getAllDataByCondition({
+                where: { spid, spss_type: type },
+                orders: [['create_time', 'desc']],
+                limit: 1,
+                offset: 0,
+            });
+            if (data.length === 0) return { spss_select: [], spss_result: null };
+            data[0].spss_select = JSON.parse(data[0].spss_select);
+            data[0].spss_result = JSON.parse(data[0].spss_result);
+            return data[0];
+        }
+
+        async addSpssStash(type, select, result) {
+            const data = {
+                id: this.uuid.v4(), spid: this.ctx.subProject.id,
+                user_id: this.ctx.session.sessionUser.accountId, user_name: this.ctx.session.sessionUser.name,
+                spss_type: type,
+                spss_select: JSON.stringify(select),
+                spss_result: JSON.stringify(result),
+            };
+            await this.db.insert(this.tableName, data);
+        }
+
+    }
+
+    return SpssStash;
+};

+ 20 - 0
app/service/stage_audit.js

@@ -2404,6 +2404,26 @@ module.exports = app => {
             ];
             return await this.db.query(sql, sqlParam);
         }
+
+        async stageSpGroup(stage, sp_group) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                const group = await this.ctx.service.shenpiGroup.getDataById(sp_group);
+                if (!group) {
+                    throw '该固定审批组不存在,请刷新页面重新获取';
+                }
+
+                const shenpiList = await this.ctx.service.shenpiAudit.getAllDataByCondition({ where: { tid: this.ctx.tender.id, sp_type: shenpiConst.sp_type.stage, sp_status: shenpiConst.sp_status.gdspl, sp_group: group.id } });
+                // console.log('stageSpGroup shenpiList:', shenpiList);
+                await this.updateNewAuditList(stage, shenpiList);
+                await transaction.update(this.ctx.service.stage.tableName, { sp_group: group.id }, { where: { id: stage.id } });
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+            return true;
+        }
     }
 
     return StageAudit;

+ 3 - 3
app/service/stage_pos.js

@@ -176,7 +176,7 @@ module.exports = app => {
                     if (d.contract_qty !== undefined) ps.contract_qty = this.round(d.contract_qty, precision.value);
                     if (d.contract_expr !== undefined) ps.contract_expr = d.contract_expr;
                     if (d.postil!== undefined) ps.postil = d.postil;
-                    if (d.ex_stage_qty1 !== undefined) ps.ex_stage_qty1 = this.round(d.ex_stage_qty1, precision.value);
+                    if (d.ex_stage_qty1 !== undefined) ps.ex_stage_qty1 = this.round(d.ex_stage_qty1, precision.value) || 0;
                     insertPosStage.push(ps);
                     if ((d.contract_qty || d.ex_stage_qty1) && calcStageBills.indexOf(ps.lid) === -1) {
                         calcStageBills.push(ps.lid);
@@ -273,7 +273,7 @@ module.exports = app => {
                         if (d.postil !== undefined) {
                             sp.postil = d.postil;
                         }
-                        if (d.ex_stage_qty1 !== undefined) sp.ex_stage_qty1 = this.ctx.helper.round(d.ex_stage_qty1, precision.value);
+                        if (d.ex_stage_qty1 !== undefined) sp.ex_stage_qty1 = this.ctx.helper.round(d.ex_stage_qty1 || 0, precision.value) || 0;
                         updatePosStage.push(sp);
                     } else {
                         const sp = {
@@ -292,7 +292,7 @@ module.exports = app => {
                             sp.postil = d.postil === undefined && osp ? osp.postil : d.postil;
                         }
                         if (d.ex_stage_qty1 !== undefined) {
-                            sp.ex_stage_qty1 = this.ctx.helper.round(d.ex_stage_qty1, precision.value) || 0;
+                            sp.ex_stage_qty1 = this.ctx.helper.round(d.ex_stage_qty1 || 0, precision.value) || 0;
                         } else {
                             sp.ex_stage_qty1 = osp ? osp.ex_stage_qty1 || 0 : 0;
                         }

+ 18 - 10
app/service/sub_proj_permission.js

@@ -74,7 +74,7 @@ module.exports = app => {
             this.PermissionBlock = [
                 { key: 'datacollect', name: '决策大屏', field: 'datacollect_permission' },
                 { key: 'info', name: '项目概况', field: 'info_permission' },
-                { key: 'contract', name: '合同管理', field: 'contract_permission', hint: ['1、编辑节点:编辑合同管理内页树结构',
+                { key: 'contract', name: '项目合同', field: 'contract_permission', hint: ['1、编辑节点:编辑合同管理内页树结构',
                         '2、添加合同:允许添加合同',
                         '3、编辑合同:编辑非自己上传的合同信息',
                         '4、添加支付:给非自己上传的合同添加合同支付',
@@ -341,24 +341,32 @@ module.exports = app => {
             return updateData;
         }
 
-        async _copyUserPermission(copyData) {
+        async _copyUserPermission(copyData, force = false) {
             const copyPermission = await this.getAllDataByCondition({ where: { spid: this.ctx.subProject.id, uid: copyData.uid }});
             if (copyPermission.length === 0) throw '选择的用户不存在';
 
-            const insertData = [];
+            const insertData = [], updateData = [];
             for (const spid of copyData.spid) {
                 const exist = await this.getAllDataByCondition({ columns: ['uid'], where: { spid } });
                 copyPermission.forEach(cp => {
-                    if (exist.find(x => { return x.uid === cp.uid; })) return;
-
-                    const idata = { id: this.uuid.v4(), spid, pid: cp.pid, uid: cp.uid };
-                    for (const prop in cp) {
-                        if (prop.indexOf('_permission') > 1) idata[prop] = cp[prop];
+                    const ecp = exist.find(x => { return x.uid === cp.uid; });
+                    if (ecp && force) {
+                        const udata = { id: ecp.id };
+                        for (const prop in cp) {
+                            if (prop.indexOf('_permission') > 1) udata[prop] = cp[prop];
+                        }
+                        updateData.push(udata)
+                    } else {
+                        const idata = { id: this.uuid.v4(), spid, pid: cp.pid, uid: cp.uid };
+                        for (const prop in cp) {
+                            if (prop.indexOf('_permission') > 1) idata[prop] = cp[prop];
+                        }
+                        insertData.push(idata);
                     }
-                    insertData.push(idata);
                 });
             }
-            await this.db.insert(this.tableName, insertData);
+            if (insertData.length > 0) await this.db.insert(this.tableName, insertData);
+            if (force && updateData.length > 0) await this.db.updateRows(this.tableName, updateData);
         }
 
         async updatePermission(subProject, data) {

+ 16 - 4
app/service/tender_permission.js

@@ -31,11 +31,23 @@ module.exports = app => {
                 inspection: {
                     view: { title: '查看', value: 1, isDefault: 1 },
                     add: { title: '新增巡检', value: 2 },
+                    view_all: { title: '查看所有巡检', value: 3 },
+                },
+                safe_inspection: {
+                    view: { title: '查看', value: 1, isDefault: 1 },
+                    add: { title: '新增巡检', value: 2 },
+                    view_all: { title: '查看所有巡检', value: 3 },
+                },
+                safe_payment: {
+                    view: { title: '查看', value: 1, isDefault: 1 },
+                    add: { title: '上报人', value: 2 },
                 }
             };
             this.PermissionBlock = [
-                { key: 'quality', name: '质量管理', field: 'quality' },
+                { key: 'quality', name: '工程资料', field: 'quality' },
                 { key: 'inspection', name: '质量巡检', field: 'inspection' },
+                { key: 'safe_inspection', name: '安全巡检', field: 'safe_inspection' },
+                { key: 'safe_payment', name: '安全计量', field: 'safe_payment' },
             ];
             for (const p of this.PermissionBlock) {
                 if (p.children) {
@@ -216,20 +228,20 @@ module.exports = app => {
                 const oldTouristList = await this.getAllDataByCondition({ where: { tid: tidList.split(',') } });
                 const insertData = [];
                 const updateData = [];
-                for (const user of userList) {
+                for (const user of userList.reverse()) {
                     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(',');
+                                um[p] = user.member[p].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(',');
+                                um[p] = user.member[p].join(',');
                             }
                             insertData.push(um);
                         }

+ 20 - 7
app/view/budget/compare_modal.ejs

@@ -8,14 +8,27 @@
                 <h5>可选标段</h5>
                 <div id="sf-spread" style="height: 300px"></div>
             </div>
-            <div class="modal-footer">
-                <div class="form-check form-check-inline">
-                    <input class="form-check-input" type="checkbox" id="sf-select-all">
-                    <label class="form-check-label" for="sr-select-all">全选</label>
+            <div class="modal-footer" style="justify-content: space-between">
+                <div class="d-flex">
+                    <div class="form-check form-check-inline">汇总条件:</div>
+                    <div class="form-check form-check-inline">
+                        <input class="form-check-input" type="radio" name="final_type" id="inlineRadio2" value="code" <%if (ctx.budget.final_type === 'code') {%> checked <% }%>>
+                        <label class="form-check-label" for="inlineRadio2">编号</label>
+                    </div>
+                    <div class="form-check form-check-inline">
+                        <input class="form-check-input" type="radio" name="final_type" id="inlineRadio3" value="code_name" <%if (ctx.budget.final_type === 'code_name') {%> checked <% }%>>
+                        <label class="form-check-label" for="inlineRadio3">编号+名称</label>
+                    </div>
                 </div>
-                <div>
-                    <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
-                    <button type="button" class="btn btn-sm btn-primary" id="select-final-ok">确定</button>
+                <div class="d-flex">
+                    <div class="form-check form-check-inline">
+                        <input class="form-check-input" type="checkbox" id="sf-select-all">
+                        <label class="form-check-label" for="sr-select-all">全选</label>
+                    </div>
+                    <div class="d-inline-flex">
+                        <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                        <button type="button" class="btn btn-sm btn-primary ml-1" id="select-final-ok">确定</button>
+                    </div>
                 </div>
             </div>
         </div>

+ 24 - 14
app/view/change/apply_information_modal.ejs

@@ -221,7 +221,7 @@
                                                 <span class="mr-1"><i class="fa fa fa-play-circle fa-rotate-90"></i></span>
                                                 <span class="text-muted">
                                                     <% for (const u of item) { %>
-                                                        <small class="d-inline-block text-dark mx-1" title="<%- u.role %>" data-auditorId="<%- u.aid %>"><%- u.name %></small>
+                                                        <small class="d-inline-block text-dark mx-1" title="<%- u.company %>" data-auditorId="<%- u.aid %>"><%- u.name %></small>
                                                     <% } %>
                                                 </span>
                                                 <span class="badge badge-light badge-pill ml-auto"><small>原报</small></span>
@@ -231,7 +231,7 @@
                                                 <span class="mr-1"><i class="fa fa fa-stop-circle"></i></span>
                                                 <span class="text-muted">
                                                     <% for (const u of item) { %>
-                                                        <small class="d-inline-block text-dark mx-1" title="<%- u.role %>" data-auditorId="<%- u.aid %>"><%- u.name %></small>
+                                                        <small class="d-inline-block text-dark mx-1" title="<%- u.company %>" data-auditorId="<%- u.aid %>"><%- u.name %></small>
                                                     <% } %>
                                                 </span>
                                                 <div class="d-flex ml-auto">
@@ -246,7 +246,7 @@
                                                 <span class="mr-1"><i class="fa fa-chevron-circle-down"></i></span>
                                                 <span class="text-muted">
                                                     <% for (const u of item) { %>
-                                                        <small class="d-inline-block text-dark mx-1" title="<%- u.role %>" data-auditorId="<%- u.aid %>"><%- u.name %></small>
+                                                        <small class="d-inline-block text-dark mx-1" title="<%- u.company %>" data-auditorId="<%- u.aid %>"><%- u.name %></small>
                                                     <% } %>
                                                 </span>
                                                 <div class="d-flex ml-auto">
@@ -290,9 +290,14 @@
                                                         <div class="card">
                                                             <div class="card-body px-3 py-0">
                                                                 <div class="card-text p-2 py-3 row">
-                                                                    <div class="col">
+                                                                    <div class="col-10">
                                                                         <span class="h6"><%- ctx.change.user.name %></span>
-                                                                        <span class="text-muted ml-1"><%- ctx.change.user.role %></span>
+                                                                        <span class="text-muted ml-1">
+                                                                            <%- ctx.change.user.company %>
+                                                                                    <% if (ctx.change.user.role && ctx.change.user.role.trim()) { %>
+                                                                                        - <%- ctx.change.user.role %>
+                                                                                    <% } %>
+                                                                        </span>
                                                                     </div>
                                                                     <div class="col">
                                                                         <span class="pull-right text-success"><i class="fa fa-check-circle"></i></span>
@@ -415,7 +420,7 @@
                                                     <span class="mr-1"><i class="fa fa fa-play-circle fa-rotate-90"></i></span>
                                                     <span class="text-muted">
                                                         <% for (const u of item) { %>
-                                                            <small class="d-inline-block text-dark mx-1" title="<%- u.role %>" data-auditorId="<%- u.aid %>"><%- u.name %></small>
+                                                            <small class="d-inline-block text-dark mx-1" title="<%- u.company %>" data-auditorId="<%- u.aid %>"><%- u.name %></small>
                                                         <% } %>
                                                     </span>
                                                     <span class="badge badge-light badge-pill ml-auto"><small>原报</small></span>
@@ -426,7 +431,7 @@
                                                     <span class="mr-1"><i class="fa fa fa-stop-circle"></i></span>
                                                     <span class="text-muted">
                                                         <% for (const u of item) { %>
-                                                            <small class="d-inline-block text-dark mx-1" title="<%- u.role %>" data-auditorId="<%- u.aid %>"><%- u.name %></small>
+                                                            <small class="d-inline-block text-dark mx-1" title="<%- u.company %>" data-auditorId="<%- u.aid %>"><%- u.name %></small>
                                                         <% } %>
                                                     </span>
                                                     <div class="d-flex ml-auto">
@@ -441,7 +446,7 @@
                                                     <span class="mr-1"><i class="fa fa-chevron-circle-down"></i></span>
                                                     <span class="text-muted">
                                                         <% for (const u of item) { %>
-                                                            <small class="d-inline-block text-dark mx-1" title="<%- u.role %>" data-auditorId="<%- u.aid %>"><%- u.name %></small>
+                                                            <small class="d-inline-block text-dark mx-1" title="<%- u.company %>" data-auditorId="<%- u.aid %>"><%- u.name %></small>
                                                         <% } %>
                                                     </span>
                                                     <div class="d-flex ml-auto">
@@ -485,9 +490,14 @@
                                                             <div class="card">
                                                                 <div class="card-body px-3 py-0">
                                                                     <div class="card-text p-2 py-3 row">
-                                                                        <div class="col">
+                                                                        <div class="col-10">
                                                                             <span class="h6"><%- ctx.change.user.name %></span>
-                                                                            <span class="text-muted ml-1"><%- ctx.change.user.role %></span>
+                                                                            <span class="text-muted ml-1">
+                                                                                <%- ctx.change.user.company %>
+                                                                                    <% if (ctx.change.user.role) { %>
+                                                                                        - <%- ctx.change.user.role %>
+                                                                                    <% } %>
+                                                                            </span>
                                                                         </div>
                                                                         <div class="col">
                                                                             <span class="pull-right text-success"><i class="fa fa-check-circle"></i></span>
@@ -620,7 +630,7 @@
                                                     <span class="mr-1"><i class="fa fa fa-play-circle fa-rotate-90"></i></span>
                                                     <span class="text-muted">
                                                         <% for (const u of item) { %>
-                                                            <small class="d-inline-block text-dark mx-1" title="<%- u.role %>" data-auditorId="<%- u.aid %>"><%- u.name %></small>
+                                                            <small class="d-inline-block text-dark mx-1" title="<%- u.company %>" data-auditorId="<%- u.aid %>"><%- u.name %></small>
                                                         <% } %>
                                                     </span>
                                                     <span class="badge badge-light badge-pill ml-auto"><small>原报</small></span>
@@ -630,7 +640,7 @@
                                                     <span class="mr-1"><i class="fa fa fa-stop-circle"></i></span>
                                                     <span class="text-muted">
                                                         <% for (const u of item) { %>
-                                                            <small class="d-inline-block text-dark mx-1" title="<%- u.role %>" data-auditorId="<%- u.aid %>"><%- u.name %></small>
+                                                            <small class="d-inline-block text-dark mx-1" title="<%- u.company %>" data-auditorId="<%- u.aid %>"><%- u.name %></small>
                                                         <% } %>
                                                     </span>
                                                     <div class="d-flex ml-auto">
@@ -645,7 +655,7 @@
                                                     <span class="mr-1"><i class="fa fa-chevron-circle-down"></i></span>
                                                     <span class="text-muted">
                                                         <% for (const u of item) { %>
-                                                            <small class="d-inline-block text-dark mx-1" title="<%- u.role %>" data-auditorId="<%- u.aid %>"><%- u.name %></small>
+                                                            <small class="d-inline-block text-dark mx-1" title="<%- u.company %>" data-auditorId="<%- u.aid %>"><%- u.name %></small>
                                                         <% } %>
                                                     </span>
                                                     <div class="d-flex ml-auto">
@@ -1506,7 +1516,7 @@
                     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 u of data) {
-                        auditorshtml.push(`<small class="d-inline-block text-dark mx-1" title="${u.role}" data-auditorId="${u.aid}">${u.name}</small>`);
+                        auditorshtml.push(`<small class="d-inline-block text-dark mx-1" title="${u.company}" data-auditorId="${u.aid}">${u.name}</small>`);
                     }
                     auditorshtml.push('</span>');
                     auditorshtml.push('<div class="d-flex ml-auto">');

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

@@ -54,7 +54,7 @@
                 <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">
+                        <input type="text" class="form-control form-control-sm is-invalid" placeholder="请输入编号" 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>

+ 0 - 0
app/view/change/modal.ejs


Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác