소스 검색

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

Tony Kang 3 주 전
부모
커밋
54b68a6ae9
100개의 변경된 파일6148개의 추가작업 그리고 1751개의 파일을 삭제
  1. 9 2
      app/base/base_controller.js
  2. 1 1
      app/const/account_permission.js
  3. 138 0
      app/const/audit.js
  4. 3 0
      app/const/code_rule.js
  5. 2 0
      app/const/shenpi.js
  6. 2 0
      app/const/sp_page_show.js
  7. 15 2
      app/const/spread.js
  8. 2 0
      app/const/tender_info.js
  9. 25 6
      app/controller/change_controller.js
  10. 3 3
      app/controller/contract_controller.js
  11. 17 0
      app/controller/dashboard_controller.js
  12. 30 25
      app/controller/financial_controller.js
  13. 3 2
      app/controller/ledger_controller.js
  14. 1 3
      app/controller/pay_controller.js
  15. 521 3
      app/controller/quality_controller.js
  16. 8 4
      app/controller/stage_controller.js
  17. 1 1
      app/controller/stage_extra_controller.js
  18. 1 1
      app/controller/sub_proj_controller.js
  19. 9 5
      app/controller/sub_proj_setting_controller.js
  20. 1 1
      app/extend/helper.js
  21. 75 8
      app/lib/bills_pos_convert.js
  22. 2 2
      app/lib/ledger.js
  23. 10 0
      app/lib/spread_setting.js
  24. 3 3
      app/lib/sum_load.js
  25. 126 0
      app/middleware/inspection_check.js
  26. 1 1
      app/middleware/sub_project_check.js
  27. 51 13
      app/public/css/main.css
  28. 8 5
      app/public/css/qa_side.css
  29. 6 0
      app/public/css/wap/main.css
  30. 3 3
      app/public/js/change_information.js
  31. 653 1
      app/public/js/change_revise.js
  32. 1 1
      app/public/js/file_detail.js
  33. 24 1
      app/public/js/global.js
  34. 4 5
      app/public/js/ledger_gather.js
  35. 3 3
      app/public/js/material_checklist.js
  36. 7 0
      app/public/js/phase_pay_list.js
  37. 6 1
      app/public/js/project_spread.js
  38. 349 0
      app/public/js/quality_inspection.js
  39. 631 0
      app/public/js/quality_inspection_information.js
  40. 2 2
      app/public/js/quality_tender.js
  41. 1 1
      app/public/js/schedule_plan.js
  42. 5 5
      app/public/js/schedule_stage_gcl.js
  43. 4 4
      app/public/js/schedule_stage_tp.js
  44. 82 3
      app/public/js/setting_manage.js
  45. 2 2
      app/public/js/shares/sjs_setting.js
  46. 11 6
      app/public/js/spreadjs_rela/spreadjs_zh.js
  47. 71 25
      app/public/js/stage.js
  48. 12 1
      app/router.js
  49. 2 2
      app/service/filing.js
  50. 11 7
      app/service/financial_pay.js
  51. 315 0
      app/service/quality_inspection.js
  52. 82 0
      app/service/quality_inspection_att.js
  53. 1185 0
      app/service/quality_inspection_audit.js
  54. 14 0
      app/service/spec_pull.js
  55. 1 1
      app/service/stage.js
  56. 13 12
      app/service/stage_bonus.js
  57. 5 4
      app/service/stage_change.js
  58. 18 17
      app/service/stage_jgcl.js
  59. 9 8
      app/service/stage_other.js
  60. 11 10
      app/service/stage_safe_prod.js
  61. 2 3
      app/service/stage_stash.js
  62. 11 10
      app/service/stage_temp_land.js
  63. 2 1
      app/service/stage_yjcl.js
  64. 1 0
      app/service/tender_info.js
  65. 86 2
      app/service/tender_permission.js
  66. 2 2
      app/view/change/index.ejs
  67. 7 0
      app/view/change/revise.ejs
  68. 46 0
      app/view/change/revise_modal.ejs
  69. 20 26
      app/view/contract/tender.ejs
  70. 33 1
      app/view/dashboard/index.ejs
  71. 23 0
      app/view/dashboard/workspace.ejs
  72. 1 1
      app/view/financial/pay_detail_modal.ejs
  73. 29 10
      app/view/layout/menu.ejs
  74. 6 2
      app/view/material/audit_modal.ejs
  75. 3 1
      app/view/phase_pay/index.ejs
  76. 1 6
      app/view/phase_pay/modal.ejs
  77. 2 2
      app/view/quality/flaw.ejs
  78. 2 2
      app/view/quality/info.ejs
  79. 111 0
      app/view/quality/inspection.ejs
  80. 468 0
      app/view/quality/inspection_information.ejs
  81. 248 0
      app/view/quality/inspection_information_modal.ejs
  82. 166 0
      app/view/quality/inspection_modal.ejs
  83. 2 2
      app/view/quality/lab.ejs
  84. 17 0
      app/view/quality/list_sub_menu.ejs
  85. 9 0
      app/view/quality/list_sub_menu_list.ejs
  86. 16 0
      app/view/quality/list_sub_mini_menu.ejs
  87. 2 1
      app/view/quality/sub_memu_list.ejs
  88. 0 0
      app/view/quality/sub_menu.ejs
  89. 3 30
      app/view/quality/tender.ejs
  90. 29 0
      app/view/shares/check_modal.ejs
  91. 30 0
      app/view/sp_setting/manage.ejs
  92. 26 24
      app/view/sp_setting/permission.ejs
  93. 2 1
      app/view/stage/modal.ejs
  94. 9 1
      app/view/tender/detail_modal.ejs
  95. 1 1
      app/view/tender/list_sub_menu.ejs
  96. 0 18
      app/view/tender/list_sub_menu_list.ejs
  97. 33 12
      config/menu.js
  98. 34 0
      config/web.js
  99. 54 1376
      sql/update.sql
  100. 0 0
      sql/update20250928.sql

+ 9 - 2
app/base/base_controller.js

@@ -36,17 +36,24 @@ class BaseController extends Controller {
             menuList.info.display = ctx.subProject.page_show.openInfo || false;
             menuList.datacollect.display = ctx.subProject.showDataCollect || false;
             menuList.file.display = ctx.subProject.page_show.openFile || false;
-            menuList.contract.display = ctx.subProject.page_show.openContract || false;
+            menuList.contract.display = ctx.subProject.page_show.openContract || 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;
             for (const index in menuList) {
                 const im = menuList[index];
                 if (!im.url) {
                     if (index === 'tender') {
                         im.url = `/sp/${ctx.subProject.id}${ctx.curListUrl}`;
                     } else if (index === 'contract') {
-                        im.url = `/sp/${ctx.subProject.id}/${im.controller}/panel`;
+                        for (const child of im.children) {
+                            if (child.msg === 'subproj') {
+                                child.url = `/sp/${ctx.subProject.id}/contract/panel`;
+                            } else if (child.msg === 'tender') {
+                                child.url = `/sp/${ctx.subProject.id}/contract/tender`;
+                            }
+                        }
                     } else if (index === 'financial') {
                         im.url = `/sp/${ctx.subProject.id}/${im.controller}/${ctx.subProject.financialToUrl}`;
                     } else {

+ 1 - 1
app/const/account_permission.js

@@ -23,7 +23,7 @@ create_tender_group[create_tender.cjgcbg] = '创建工程变更';
 const permission = {
     tender: {
         class: 'fa fa-list-ul',
-        title: '标段管理',
+        title: '计量管理',
         type: 'checkbox',
         children: [
             { title: '创建标段', value: 1 },

+ 138 - 0
app/const/audit.js

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

+ 3 - 0
app/const/code_rule.js

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

+ 2 - 0
app/const/shenpi.js

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

+ 2 - 0
app/const/sp_page_show.js

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

+ 15 - 2
app/const/spread.js

@@ -16,7 +16,8 @@ const realCompleteCols = ['real_qty', 'estimate_qty'];
 const priceDiffCols = ['org_price', 'pc_tp'];
 const thirdPartyCols = {
     gxby: ['gxby'],
-    dagl: ['dagl']
+    dagl: ['dagl'],
+    wbs_url: ['wbs_url'],
 };
 const minusNoValueCols = ['qc_minus_qty', 'end_qc_minus_qty'];
 
@@ -166,6 +167,7 @@ const BaseSetCol = {
         { key: 'is_tp', name: '总额计量', fixed: [], bills: 1, pos: 0 },
         { key: 'gxby', name: '工序报验', fixed: ['valid', 'alias'], bills: 1, pos: 1, },
         { key: 'dagl', name: '档案管理', fixed: ['valid', 'alias'], bills: 1, pos: 1, },
+        { key: 'wbs_url', name: '3方链接', fixed: ['valid', 'alias'], bills: 1, pos: 1, init: 1 },
         { key: 'ex_calc1', name: '计算1', fixed: [], bills: 1, pos: 1, },
         { key: 'cur_ex_calc1', name: '本期计算1', fixed: [], bills: 1, pos: 1, },
         { key: 'end_ex_calc1', name: '截止本期计算1', fixed: [], bills: 1, pos: 1, },
@@ -218,6 +220,7 @@ const BaseSetCol = {
         { key: 'is_tp', name: '总额计量', fixed: [], bills: 1, pos: 0 },
         { key: 'gxby', name: '工序报验', fixed: ['valid', 'alias'], bills: 1, pos: 1, },
         { key: 'dagl', name: '档案管理', fixed: ['valid', 'alias'], bills: 1, pos: 1, },
+        { key: 'wbs_url', name: '3方链接', fixed: ['valid', 'alias'], bills: 1, pos: 1, init: 1 },
         { key: 'ex_calc1', name: '计算1', fixed: [], bills: 1, pos: 1, },
         { key: 'cur_ex_calc1', name: '本期计算1', fixed: [], bills: 1, pos: 1, },
         { key: 'end_ex_calc1', name: '截止本期计算1', fixed: [], bills: 1, pos: 1, },
@@ -272,6 +275,7 @@ const glSpreadTemplate = {
         { key: 'is_tp', valid: 1},
         { key: 'gxby', valid: 1},
         { key: 'dagl', valid: 1},
+        { key: 'wbs_url', valid: 1 },
         { key: 'ex_calc1', valid: 0 },
         { key: 'cur_ex_calc1', valid: 0 },
         { key: 'end_ex_calc1', valid: 0 },
@@ -324,6 +328,7 @@ const glSpreadTemplate = {
         { key: 'add_stage_order', valid: 1},
         { key: 'gxby', valid: 1},
         { key: 'dagl', valid: 1},
+        { key: 'wbs_url', valid: 1 },
         { key: 'ex_calc1', valid: 0 },
         { key: 'cur_ex_calc1', valid: 0 },
         { key: 'end_ex_calc1', valid: 0 },
@@ -377,6 +382,7 @@ const szSpreadTemplate = {
         { key: 'is_tp', valid: 1},
         { key: 'gxby', valid: 1},
         { key: 'dagl', valid: 1},
+        { key: 'wbs_url', valid: 1 },
         { key: 'ex_calc1', valid: 0 },
         { key: 'cur_ex_calc1', valid: 0 },
         { key: 'end_ex_calc1', valid: 0 },
@@ -429,6 +435,7 @@ const szSpreadTemplate = {
         { key: 'add_stage_order', valid: 1},
         { key: 'gxby', valid: 1},
         { key: 'dagl', valid: 1},
+        { key: 'wbs_url', valid: 1 },
         { key: 'ex_calc1', valid: 0 },
         { key: 'cur_ex_calc1', valid: 0 },
         { key: 'end_ex_calc1', valid: 0 },
@@ -482,6 +489,7 @@ const fjSpreadTemplate = {
         { key: 'is_tp', valid: 1},
         { key: 'gxby', valid: 1},
         { key: 'dagl', valid: 1},
+        { key: 'wbs_url', valid: 1 },
         { key: 'ex_calc1', valid: 0 },
         { key: 'cur_ex_calc1', valid: 0 },
         { key: 'end_ex_calc1', valid: 0 },
@@ -534,6 +542,7 @@ const fjSpreadTemplate = {
         { key: 'add_stage_order', valid: 1},
         { key: 'gxby', valid: 1},
         { key: 'dagl', valid: 1},
+        { key: 'wbs_url', valid: 1 },
         { key: 'ex_calc1', valid: 0 },
         { key: 'cur_ex_calc1', valid: 0 },
         { key: 'end_ex_calc1', valid: 0 },
@@ -543,7 +552,7 @@ const ProjectSpreadTemplate = [
     { code: 'gl', name: '公路', template: glSpreadTemplate, isDefault: 1 },
     { code: 'sz', name: '市政', template: szSpreadTemplate },
     { code: 'fj', name: '房建', template: fjSpreadTemplate },
-    { code: 'xxby', name: '小修保养', template: glSpreadTemplate },
+    { code: 'xxby', name: '养', template: glSpreadTemplate },
 ];
 const BaseSpreadColSetting = {
     tz_ledger_set: {
@@ -656,6 +665,7 @@ const BaseSpreadColSetting = {
             is_tp: [{title: '总额计量', colSpan: '1', rowSpan: '2', field: 'is_tp', hAlign: 1, width: 60, cellType: 'checkbox'}],
             gxby: [{title: '工序报验', colSpan: '1', rowSpan: '2', field: 'gxby', hAlign: 1, width: 80, formatter: '@', readOnly: true}],
             dagl: [{title: '档案管理', colSpan: '1', rowSpan: '2', field: 'dagl', hAlign: 1, width: 80, formatter: '@', readOnly: true}],
+            wbs_url: [{ title: '3方链接', colSpan: '1', rowSpan: '2', field: 'wbs_url', hAlign: 0, width: 60, formatter: '@', readOnly: true }],
             drawing_code: [{title: '图(册)号', colSpan: '1', rowSpan: '2', field: 'drawing_code', hAlign: 0, width: 80, formatter: '@', textIndent: 1}],
             node_type: [{title: '费用类别', colSpan: '1', rowSpan: '2', field: 'node_type', hAlign: 0, width: 100, cellType: 'customizeCombo'}],
             memo: [{title: '备注', colSpan: '1', rowSpan: '2', field: 'memo', hAlign: 0, width: 100, formatter: '@', cellType: 'ellipsisAutoTip'}],
@@ -705,6 +715,7 @@ const BaseSpreadColSetting = {
             add_stage_order: [{title: '添加期数', colSpan: '1', rowSpan: '2', field: 'add_stage_order', hAlign:1, width: 80, readOnly: true}],
             gxby: [{title: '工序报验', colSpan: '1', rowSpan: '2', field: 'gxby', hAlign: 0, width: 80, formatter: '@', readOnly: true}],
             dagl: [{title: '档案管理', colSpan: '1', rowSpan: '2', field: 'dagl', hAlign: 0, width: 80, formatter: '@', readOnly: true}],
+            wbs_url: [{ title: '3方链接', colSpan: '1', rowSpan: '2', field: 'wbs_url', hAlign: 0, width: 60, formatter: '@', readOnly: true }],
             ex_calc1: [{title: '计算1数量', colSpan: '1', rowSpan: '2', field: 'ex_qty1', hAlign: 2, width: 100, type: 'Number', readOnly: true, aliasFormat: '{%s}数量'}],
             cur_ex_calc1: [{title: '本期计量1数量', colSpan: '1', rowSpan: '2', field: 'ex_stage_qty1', hAlign: 2, width: 100, type: 'Number', aliasFormat: '{%s}数量'}],
             end_ex_calc1: [{title: '截止本期计量1数量', colSpan: '1', rowSpan: '2', field: 'end_ex_stage_qty1', hAlign: 2, width: 100, type: 'Number', readOnly: true, aliasFormat: '{%s}数量'}],
@@ -820,6 +831,7 @@ const BaseSpreadColSetting = {
             is_tp: [{title: '总额计量', colSpan: '1', rowSpan: '2', field: 'is_tp', hAlign: 1, width: 60, cellType: 'checkbox'}],
             gxby: [{title: '工序报验', colSpan: '1', rowSpan: '2', field: 'gxby', hAlign: 0, width: 80, formatter: '@', readOnly: true}],
             dagl: [{title: '档案管理', colSpan: '1', rowSpan: '2', field: 'dagl', hAlign: 0, width: 80, formatter: '@', readOnly: true}],
+            wbs_url: [{ title: '3方链接', colSpan: '1', rowSpan: '2', field: 'wbs_url', hAlign: 0, width: 60, formatter: '@', readOnly: true }],
             drawing_code: [{title: '图(册)号', colSpan: '1', rowSpan: '2', field: 'drawing_code', hAlign: 0, width: 80, formatter: '@', textIndent: 1}],
             memo: [{title: '备注', colSpan: '1', rowSpan: '2', field: 'memo', hAlign: 0, width: 100, formatter: '@', cellType: 'ellipsisAutoTip'}],
             node_type: [{title: '费用类别', colSpan: '1', rowSpan: '2', field: 'node_type', hAlign: 0, width: 100, cellType: 'customizeCombo'}],
@@ -869,6 +881,7 @@ const BaseSpreadColSetting = {
             add_stage_order: [{title: '添加期数', colSpan: '1', rowSpan: '2', field: 'add_stage_order', hAlign:1, width: 80, readOnly: true}],
             gxby: [{title: '工序报验', colSpan: '1', rowSpan: '2', field: 'gxby', hAlign: 0, width: 80, formatter: '@', readOnly: true}],
             dagl: [{title: '档案管理', colSpan: '1', rowSpan: '2', field: 'dagl', hAlign: 0, width: 80, formatter: '@', readOnly: true}],
+            wbs_url: [{ title: '3方链接', colSpan: '1', rowSpan: '2', field: 'wbs_url', hAlign: 0, width: 60, formatter: '@', readOnly: true }],
             ex_calc1: [{title: '计算1数量', colSpan: '1', rowSpan: '2', field: 'ex_qty1', hAlign: 2, width: 100, type: 'Number', aliasFormat: '{%s}数量'}],
             cur_ex_calc1: [{title: '本期计量1数量', colSpan: '1', rowSpan: '2', field: 'ex_stage_qty1', hAlign: 2, width: 100, type: 'Number', aliasFormat: '{%s}数量'}],
             end_ex_calc1: [{title: '截止本期计量1数量', colSpan: '1', rowSpan: '2', field: 'end_ex_stage_qty1', hAlign: 2, width: 100, type: 'Number', readOnly: true, aliasFormat: '{%s}数量'}],

+ 2 - 0
app/const/tender_info.js

@@ -145,6 +145,7 @@ const defaultInfo = {
             realComplete: false,
             correct: true,
             priceDiff: false,
+            posContractExpr: true,
         },
         dayMode: false,
     },
@@ -193,6 +194,7 @@ const defaultInfo = {
         change: 1,
         material: 1,
         phasePay: 1,
+        inspection: 1,
     },
     ledger_check: {
         same_code: true,

+ 25 - 6
app/controller/change_controller.js

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

+ 3 - 3
app/controller/contract_controller.js

@@ -81,7 +81,7 @@ module.exports = app => {
          */
         async tender(ctx) {
             try {
-                if (!ctx.subProject.page_show.openContract) {
+                if (!ctx.subProject.page_show.openTenderContract) {
                     throw '该功能已关闭或无法查看';
                 }
                 // 获取用户新建标段权利
@@ -146,7 +146,7 @@ module.exports = app => {
             } catch (err) {
                 ctx.log(err);
                 ctx.session.postError = err.toString();
-                ctx.redirect(this.menu.menu.dashboard.url);
+                ctx.redirect(`/sp/${ctx.subProject.id}/dashboard`);
             }
         }
 
@@ -437,7 +437,7 @@ module.exports = app => {
         async _addStd(ctx, data, options) {
             if ((isNaN(data.id) || data.id <= 0) || !data.stdType || !data.stdNode) throw '参数错误';
             // todo 校验项目是否使用该库的权限
-            if (!(ctx.session.sessionUser.is_admin || ctx.audit_permission.permission_edit)) {
+            if (!(ctx.session.sessionUser.is_admin || ctx.contract_audit_permission.permission_edit)) {
                 throw '没有操作权限';
             }
             let stdLib,

+ 17 - 0
app/controller/dashboard_controller.js

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

+ 30 - 25
app/controller/financial_controller.js

@@ -822,7 +822,7 @@ module.exports = app => {
             filter.count[filter.status.checking] = await ctx.service.financialPay.getCountByStatus(ctx.subProject.id, null, payStage.id, filter.status.checking, filterTids, used);// await ctx.service.change.checkedDatas(tender.id, ctx.session.sessionUser.accountId);
             filter.count[filter.status.checked] = await ctx.service.financialPay.getCountByStatus(ctx.subProject.id, null, payStage.id, filter.status.checked, filterTids, used);// await ctx.service.change.pendingDatas(tender.id, ctx.session.sessionUser.accountId);
             const payList = await ctx.service.financialPay.getListByStatus(ctx.subProject.id, null, payStage.id, status, filterTids, used, 1);
-            const userTenderList = await ctx.service.financialPay.getUserTenderList(ctx.subProject.id, ctx.session.sessionUser.accountId, payStage.id);
+            const userTenderList = await ctx.service.financialPay.getUserTenderList(ctx.subProject.id, ctx.session.sessionUser.accountId, null, payStage.id);
             const total = await ctx.service.financialPay.getCountByStatus(ctx.subProject.id, null, payStage.id, status, filterTids, used);
             // 分页相关
             const page = ctx.page;
@@ -1028,8 +1028,6 @@ module.exports = app => {
             const companyInfo = company ? ctx.helper._.find(unitList, { name: company }) : null;
             const userCompanyList = await ctx.service.financialPayStage.getUserCompanyList(ctx.subProject.id, userCompany ? userCompany.id : 0, unitList, qi);
             const userOrderList = await ctx.service.financialPayStage.getUserOrderList(ctx.subProject.id, userCompany ? userCompany.id : 0, companyInfo ? companyInfo.id : null);
-            const userTenderList = await ctx.service.financialPay.getUserTenderList(ctx.subProject.id, ctx.session.sessionUser.accountId);
-            const filterTids = tid === null ? (ctx.session.sessionUser.is_admin ? null : userTenderList.length > 0 ? ctx.helper._.map(userTenderList, 'tid') : []) : [tid];
             let fpsidList = null;
             if (company || qi) {
                 const fpstageList = await ctx.service.financialPayStage.getListByStatus(ctx.subProject.id, companyInfo ? companyInfo.id : null, userCompany ? userCompany.id : 0, qi, 0);
@@ -1037,6 +1035,8 @@ module.exports = app => {
             }
             const allPayStages = !ctx.session.sessionUser.is_admin ? await ctx.service.financialPayStage.getListByStatus(ctx.subProject.id, null, userCompany ? userCompany.id : 0, null, 0) : null;
             const allFpsidList = !ctx.session.sessionUser.is_admin ? ctx.helper._.map(allPayStages, 'id') : null;
+            const userTenderList = await ctx.service.financialPay.getUserTenderList(ctx.subProject.id, ctx.session.sessionUser.accountId, allFpsidList);
+            const filterTids = tid === null ? (ctx.session.sessionUser.is_admin ? null : userTenderList.length > 0 ? ctx.helper._.map(userTenderList, 'tid') : []) : [tid];
             // const payList = await ctx.service.financialPayStage.getListByStatus(ctx.subProject.id, companyInfo ? companyInfo.id : null, userCompany ? userCompany.id : 0, qi);
             const filter = JSON.parse(JSON.stringify(auditConst.financial.filter));
             filter.count = [];
@@ -1162,7 +1162,15 @@ module.exports = app => {
             const fptAuditTids = ctx.helper._.map(fptAudits, 'tid');
             const fptReportTids = ctx.helper._.map(ctx.helper._.filter(fptAudits, { is_report: 1 }), 'tid');
             const unitList = await ctx.service.constructionUnit.getAllDataByCondition({ where: { pid: ctx.session.sessionProject.id } });
-            const userTenderList = await ctx.service.financialPay.getUserTenderList(ctx.subProject.id, ctx.session.sessionUser.accountId);
+            const accountList = await ctx.service.projectAccount.getAllSubProjectAccount(ctx.subProject, ['id', 'account', 'name', 'company', 'company_id', 'role', 'enable', 'is_admin', 'account_group', 'mobile']);
+            const user = accountList.find(item => item.id === ctx.session.sessionUser.accountId) || null;
+            const userCompany = user ? ctx.helper._.find(unitList, { name: user.company }) : null;
+            if (!userCompany) {
+                throw '请联系管理员添加用户所在单位信息';
+            }
+            const allPayStages = await ctx.service.financialPayStage.getListByStatus(ctx.subProject.id, null, userCompany ? userCompany.id : 0, null, 0);
+            const allFpsidList = !ctx.session.sessionUser.is_admin ? ctx.helper._.map(allPayStages, 'id') : null;
+            const userTenderList = await ctx.service.financialPay.getUserTenderList(ctx.subProject.id, ctx.session.sessionUser.accountId, allFpsidList);
             const filterTids = ctx.session.sessionUser.is_admin ? null : userTenderList.length > 0 ? ctx.helper._.map(userTenderList, 'tid') : [];
             // const filterTids = ctx.session.sessionUser.is_admin ? null : fptAuditTids;
             const tenderCondition = { spid: ctx.subProject.id, filter_fund: 0 };
@@ -1179,19 +1187,14 @@ module.exports = app => {
             const categoryData = await this.ctx.service.category.getAllCategory(ctx.subProject);
             const tenders = hadTender ? await ctx.service.tender.getAllDataByCondition({ where: tenderCondition, columns: ['id', 'name', 'category'] }) : [];
             const transfersCondition = { spid: ctx.subProject.id };
+            let notTids = false;
             if (filterTids) {
+                if (filterTids.length === 0) {
+                    notTids = true;
+                }
                 transfersCondition.tid = filterTids;
             }
-            const allTransfers = await ctx.service.financialTransferTender.getAllDataByCondition({ where: transfersCondition });
-            const accountList = await ctx.service.projectAccount.getAllSubProjectAccount(ctx.subProject, ['id', 'account', 'name', 'company', 'company_id', 'role', 'enable', 'is_admin', 'account_group', 'mobile']);
-            const user = accountList.find(item => item.id === ctx.session.sessionUser.accountId) || null;
-            const userCompany = user ? ctx.helper._.find(unitList, { name: user.company }) : null;
-            if (!userCompany) {
-                throw '请联系管理员添加用户所在单位信息';
-            }
-            const allPayStages = await ctx.service.financialPayStage.getListByStatus(ctx.subProject.id, null, userCompany ? userCompany.id : 0, null, 0);
-            // const allPayStages = !ctx.session.sessionUser.is_admin ? await ctx.service.financialPayStage.getListByStatus(ctx.subProject.id, null, userCompany ? userCompany.id : 0, null, 0) : null;
-            const allFpsidList = !ctx.session.sessionUser.is_admin ? ctx.helper._.map(allPayStages, 'id') : null;
+            const allTransfers = !notTids ? await ctx.service.financialTransferTender.getAllDataByCondition({ where: transfersCondition }) : [];
             const allPays = await ctx.service.financialPay.getListByStatus(ctx.subProject.id, allFpsidList, null, 0, filterTids);
             for (const t of tenders) {
                 t.category = t.category && t.category !== '' ? JSON.parse(t.category) : null;
@@ -1361,7 +1364,7 @@ module.exports = app => {
                 await this.layout('financial/pay_detail.ejs', renderData, 'financial/pay_detail_modal.ejs');
             } catch (err) {
                 this.log(err);
-                ctx.redirect(`/sp/${ctx.subProject.id}/financial/pay`);
+                ctx.redirect(`/sp/${ctx.subProject.id}/financial/pay/stage`);
             }
         }
 
@@ -1701,7 +1704,18 @@ module.exports = app => {
                 const tid = parseInt(ctx.query.tid) || null;
                 const fptAudits = await ctx.service.financialPayTenderAudit.getAllDataByCondition({ where: { spid: ctx.subProject.id, uid: ctx.session.sessionUser.accountId } });
                 const fptAuditTids = ctx.helper._.map(fptAudits, 'tid');
-                const filterTids = tid === null ? (ctx.session.sessionUser.is_admin ? null : fptAuditTids) : [tid];
+                const accountList = await ctx.service.projectAccount.getAllSubProjectAccount(ctx.subProject, ['id', 'account', 'name', 'company', 'company_id', 'role', 'enable', 'is_admin', 'account_group', 'mobile']);
+                const unitList = await ctx.service.constructionUnit.getAllDataByCondition({ where: { pid: ctx.session.sessionProject.id } });
+                const user = accountList.find(item => item.id === ctx.session.sessionUser.accountId) || null;
+                const userCompany = user ? ctx.helper._.find(unitList, { name: user.company }) : null;
+                if (!userCompany) {
+                    throw '请联系管理员添加用户所在单位信息';
+                }
+                const allPayStages = !ctx.session.sessionUser.is_admin ? await ctx.service.financialPayStage.getListByStatus(ctx.subProject.id, null, userCompany ? userCompany.id : 0, null, 0) : null;
+                const allFpsidList = !ctx.session.sessionUser.is_admin ? ctx.helper._.map(allPayStages, 'id') : null;
+                const userTenderList = await ctx.service.financialPay.getUserTenderList(ctx.subProject.id, ctx.session.sessionUser.accountId, allFpsidList);
+                const filterTids = tid === null ? (ctx.session.sessionUser.is_admin ? null : userTenderList.length > 0 ? ctx.helper._.map(userTenderList, 'tid') : []) : [tid];
+                // const filterTids = tid === null ? (ctx.session.sessionUser.is_admin ? null : fptAuditTids) : [tid];
                 const tenderCondition = { spid: ctx.subProject.id };
                 let hadTender = false;
                 if (ctx.session.sessionUser.is_admin) {
@@ -1716,15 +1730,6 @@ module.exports = app => {
                 responseData.data.tenders = hadTender ? await ctx.service.tender.getAllDataByCondition({ where: tenderCondition, columns: ['id', 'name'] }) : [];
                 responseData.data.transferList = tid === null ? await ctx.service.financialTransfer.getAllDataByCondition({ where: { spid: ctx.subProject.id } }) : [];
                 responseData.data.transferTenderList = tid === null ? await ctx.service.financialTransferTender.getAllDataByCondition({ where: { spid: ctx.subProject.id } }) : [];
-                const accountList = await ctx.service.projectAccount.getAllSubProjectAccount(ctx.subProject, ['id', 'account', 'name', 'company', 'company_id', 'role', 'enable', 'is_admin', 'account_group', 'mobile']);
-                const unitList = await ctx.service.constructionUnit.getAllDataByCondition({ where: { pid: ctx.session.sessionProject.id } });
-                const user = accountList.find(item => item.id === ctx.session.sessionUser.accountId) || null;
-                const userCompany = user ? ctx.helper._.find(unitList, { name: user.company }) : null;
-                if (!userCompany) {
-                    throw '请联系管理员添加用户所在单位信息';
-                }
-                const allPayStages = !ctx.session.sessionUser.is_admin ? await ctx.service.financialPayStage.getListByStatus(ctx.subProject.id, null, userCompany ? userCompany.id : 0, null, 0) : null;
-                const allFpsidList = !ctx.session.sessionUser.is_admin ? ctx.helper._.map(allPayStages, 'id') : null;
                 responseData.data.payList = await ctx.service.financialPay.getListByStatus(ctx.subProject.id, allFpsidList, null, 0, filterTids);
                 ctx.body = responseData;
             } catch (err) {

+ 3 - 2
app/controller/ledger_controller.js

@@ -728,7 +728,9 @@ module.exports = app => {
                     ? await ctx.service.pos.getPosData({ tid: ctx.tender.id }) : [];
                 const convert = new billsPosConvert(ctx);
                 convert.loadData(ledgerData, posData, []);
+                console.time('convert-all');
                 const result = await convert.convert();
+                console.timeEnd('convert-all');
                 // const wbsCodeHis = await ctx.service.externalData.getExValue(ctx.tender.id, -1,
                 //     externalDataConst.FuLong.exType, externalDataConst.FuLong.exFields.wbsCode) || [];
                 // const [result, needUpdate] = convert.convertByWbsCode(wbsCodeHis);
@@ -791,8 +793,7 @@ module.exports = app => {
         async loadGatherData(ctx) {
             try {
                 const billsData = await ctx.service.ledger.getData(ctx.tender.id);
-                const posData = this.ctx.tender.data.measure_type === measureType.tz.value
-                    ? await ctx.service.pos.getPosData({ tid: ctx.tender.id }) : [];
+                const posData = await ctx.service.pos.getPosData({ tid: ctx.tender.id });
                 const dealBills = await ctx.service.dealBills.getAllDataByCondition({ where: { tender_id: this.ctx.tender.id } });
                 const ancGcl = this.ctx.tender.data.measure_type === measureType.tz.value
                     ? await ctx.service.ancillaryGcl.getAllDataByCondition({ where: { tid: ctx.tender.id } }) : [];

+ 1 - 3
app/controller/pay_controller.js

@@ -64,14 +64,12 @@ module.exports = app => {
                 const date = ctx.request.body.date;
                 if (!date) throw '请选择支付年月';
                 const stage = ctx.request.body.stage;
-                if (!stage) throw '请选择计量期';
                 const memo = ctx.request.body.memo;
 
                 const pays = await ctx.service.phasePay.getAllPhasePay(ctx.tender.id, 'DESC');
                 const unCompleteCount = pays.filter(s => { return s.status !== audit.common.status.checked; }).length;
                 if (unCompleteCount.length > 0) throw `最新一起未审批通过,请审批通过后再新增`;
-                // 预留可以关联多期
-                const stages = await ctx.service.stage.getAllDataByCondition({ where: { tid: ctx.tender.id, order: stage } });
+                const stages = stage ? await ctx.service.stage.getAllDataByCondition({ where: { tid: ctx.tender.id, order: stage } }) : [];
 
                 const newPhase = await ctx.service.phasePay.add(ctx.tender.id, stages, date, memo);
                 if (!newPhase) throw '新增期失败';

+ 521 - 3
app/controller/quality_controller.js

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

+ 8 - 4
app/controller/stage_controller.js

@@ -160,8 +160,9 @@ module.exports = app => {
                 const renderData = await this._getDefaultRenderData(ctx);
                 const projectFunInfo = this.ctx.subProject.fun_rela;
                 renderData.minusNoValue = projectFunInfo.minusNoValue && ctx.tender.info.fun_rela.stage_change.minusNoValue;
+                renderData.load3fRelaFile = await this.ctx.service.specPull.loadWbsFile(this.ctx.session.sessionProject.id);
                 [renderData.ledgerSpread, renderData.posSpread] = await spreadSetting.getStageSpreadSetting(ctx, ctx.tender.id,
-                    this.ctx.stage.readOnly || this.ctx.stage.revising, {minusNoValue: renderData.minusNoValue});
+                    this.ctx.stage.readOnly || this.ctx.stage.revising, renderData);
                 renderData.changeConst = changeConst;
                 renderData.jsFiles = this.app.jsFiles.common.concat(this.app.jsFiles.stage.index);
                 renderData.whiteList = ctx.app.config.multipart.whitelist;
@@ -213,7 +214,7 @@ module.exports = app => {
             return surplus;
         }
 
-        async _getLedgerColumn() {
+        async _getLedgerColumn(load3fRelaFile) {
             const tender = this.ctx.tender;
             this.ledgerColumn = [
                 'id', 'tender_id', 'ledger_id', 'ledger_pid', 'level', 'order', 'full_path', 'is_leaf',
@@ -223,6 +224,7 @@ module.exports = app => {
             if (tender.info.display.ledger.dgnQty) this.ledgerColumn.push('dgn_qty1', 'dgn_qty2');
 
             this.ledgerExtraColumn = ['is_tp'];
+            if (load3fRelaFile) this.ledgerExtraColumn.push('wbs_url');
             if (this.ctx.session.sessionProject.gxby) this.ledgerExtraColumn.push('gxby_status', 'gxby_url', 'gxby_limit');
             if (this.ctx.session.sessionProject.dagl) this.ledgerExtraColumn.push('dagl_status', 'dagl_url', 'dagl_limit');
             // if (tender.data.s2b_multi_check) this.ledgerExtraColumn.push('multi_limit', 'gxby_date');
@@ -233,6 +235,7 @@ module.exports = app => {
             if (tender.info.display.stage.realComplete) this.posColumn.push('real_qty');
 
             this.posExtraColumn = [];
+            if (load3fRelaFile) this.posExtraColumn.push('wbs_url');
             if (this.ctx.session.sessionProject.gxby) this.posExtraColumn.push('gxby_status', 'gxby_url', 'gxby_limit');
             if (this.ctx.session.sessionProject.dagl) this.posExtraColumn.push('dagl_status', 'dagl_url', 'dagl_limit');
             // if (tender.data.s2b_multi_check) this.posExtraColumn.push('gxby_date');
@@ -414,8 +417,9 @@ module.exports = app => {
                 const data = JSON.parse(ctx.request.body.data);
                 const filter = data.filter.split(';');
                 const responseData = { err: 0, msg: '', data: {}, hpack: [] };
+                const load3fRelaFile = await this.ctx.service.specPull.loadWbsFile(this.ctx.session.sessionProject.id);
                 const hpack = true;
-                await this._getLedgerColumn();
+                await this._getLedgerColumn(load3fRelaFile);
                 for (const f of filter) {
                     switch (f) {
                         case 'ledger':
@@ -545,7 +549,7 @@ module.exports = app => {
                         { qty: 'qc_qty', tp: 'qc_tp' },
                     ], this.ctx.tender.info.decimal, x => { return x.is_tp; });
                 }
-                checkData.checkBillsQty(['contract_qty', 'qc_qty']);
+                checkData.checkBillsQty(['contract_qty', 'qc_qty', 'qc_minus_qty']);
                 if (projRela.banMinusChangeBills && ctx.tender.info.ledger_check.banMinusChangeBills) {
                     const change = await this.ctx.service.change.getAllChangeHasMinus(ctx.tender.id);
                     if (change.length > 0) {

+ 1 - 1
app/controller/stage_extra_controller.js

@@ -157,7 +157,7 @@ module.exports = app => {
          */
         async loadBonus (ctx) {
             try {
-                const data = await ctx.service.stageBonus.getStageData(ctx.stage.id);
+                const data = await ctx.service.stageBonus.getStageData(ctx.stage);
                 for (const d of data) {
                     for (const pf of d.proof_file) {
                         delete pf.filepath;

+ 1 - 1
app/controller/sub_proj_controller.js

@@ -479,7 +479,7 @@ module.exports = app => {
             try{
                 const data = JSON.parse(ctx.request.body.data);
                 if (!data) throw '缺少参数';
-                const result = await ctx.service.subProjFile.delFiles(data);
+                const result = await ctx.service.subProjFile.delFiles(data.id);
                 ctx.body = { err: 0, msg: '', data: result };
             } catch(error) {
                 ctx.log(error);

+ 9 - 5
app/controller/sub_proj_setting_controller.js

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

+ 1 - 1
app/extend/helper.js

@@ -1289,7 +1289,7 @@ module.exports = {
     isMobile(agent) {
         const ua = new UAParser(agent);
         const osInfo = ua.getOS();
-        return (agent ? agent.match(/(iphone|ipod|android)/i) : false) || osInfo.name === 'Android' || osInfo.name === 'iOS';
+        return (agent ? agent.match(/(iphone|ipod|android|Harmony)/i) : false) || osInfo.name === 'Android' || osInfo.name === 'iOS' || osInfo.name === 'HarmonyOS';
     },
 
     /**

+ 75 - 8
app/lib/bills_pos_convert.js

@@ -170,9 +170,12 @@ class BillsPosConvert {
         const pos = this.bpcPos.getLedgerPos(node.id);
         if (pos && pos.length > 0) {
             for (const p of pos) {
-                const posUnit = xmj.unitTree.addNode({pos_name: p.name,
-                    b_code: null, name: '', unit: null, unit_price: null,
-                    gxby_status: p.gxby_status, dagl_status: p.dagl_status, gxby_url: p.gxby_url, dagl_url: p.dagl_url});
+                let posUnit = xmj.unitTree.children.find(x => { return x.pos_name === p.name; });
+                if (!posUnit) {
+                    posUnit = xmj.unitTree.addNode({pos_name: p.name,
+                        b_code: null, name: '', unit: null, unit_price: null,
+                        gxby_status: p.gxby_status, dagl_status: p.dagl_status, gxby_url: p.gxby_url, dagl_url: p.dagl_url}, null, false);
+                }
                 if (p.drawing_code && posUnit.drawing_code.indexOf(p.drawing_code) < 0)
                     posUnit.drawing_code.push(p.drawing_code);
                 if (p.memo) posUnit.memo.push(p.memo);
@@ -205,7 +208,7 @@ class BillsPosConvert {
             const unit = xmj.unitTree.addNode({
                 pos_name: '',
                 b_code: node.b_code, name: node.name, unit: node.unit, unit_price: node.unit_price, org_price: node.org_price,
-            });
+            }, null);
             if (node.drawing_code && unit.drawing_code.indexOf(node.drawing_code) < 0)
                 unit.drawing_code.push(node.drawing_code);
             if (node.memo) unit.memo.push(node.memo);
@@ -372,12 +375,56 @@ class BillsPosConvert {
             }
         }
     }
+    _mapTreeNode(tree) {
+        const setting = tree.setting;
+        let map = {}, maxLevel = 0;
+        for (const node of tree.nodes) {
+            let levelArr = map[node[setting.level]];
+            if (!levelArr) {
+                levelArr = [];
+                map[node[setting.level]] = levelArr;
+            }
+            if (node[setting.level] > maxLevel) {
+                maxLevel = node[setting.level];
+            }
+            levelArr.push(node);
+        }
+        return [maxLevel, map];
+    }
+    _calculateAndSortNodes() {
+        const self = this;
+        const [maxLevel, levelMap] = this._mapTreeNode(this.resultTree);
+        for (let i = maxLevel; i >= 0; i--) {
+            const levelNodes = levelMap[i];
+            if (!levelNodes || levelNodes.length === 0) continue;
+
+            for (const node of levelNodes) {
+                if (node.unitTree) {
+                    node.unitTree.sortTreeNodeCustom(function (a, b) {
+                        if (a.b_code && a.b_code !== '') {
+                            return b.b_code && b.b_code !== '' ? self.ctx.helper.compareCode(a.b_code, b.b_code) : 1;
+                        } else {
+                            return b.b_code && b.b_code !== '' ? -1 : a.order - b.order;
+                        }
+                    });
+                    this._calculateNode(node, node.unitTree.children, this.tpFields);
+                } else if (node.children && node.children.length > 0) {
+                    this._calculateNode(node, node.children, this.tpFields);
+                } else {
+                    this._calculateChild(node);
+                }
+            }
+        }
+    }
     _calculateAndSortResult() {
         this.resultTree.sortTreeNode();
-        this._recursiveCalculateAndSort(this.resultTree.children);
+        console.time('recurCalc');
+        // this._recursiveCalculateAndSort(this.resultTree.children);
+        this._calculateAndSortNodes();
+        console.timeEnd('recurCalc');
     }
 
-    _generateCodeByWbsCode(wbsCode) {
+    _generateCodeByWbsCodeWithHis(wbsCode) {
         let needUpdate = false;
         for (const node of this.resultTree.nodes) {
             if (!node.unitTree) continue;
@@ -406,6 +453,22 @@ class BillsPosConvert {
         return needUpdate;
     }
 
+    _generateCodeByWbsCode() {
+        let needUpdate = false;
+        for (const node of this.resultTree.nodes) {
+            if (!node.unitTree) continue;
+
+            let index = 1;
+            for (const unit of node.unitTree.nodes) {
+                if (!unit.pos_name || unit.pos_name === '') continue;
+
+                unit.code = node.code + splitChar + index;
+                index ++;
+            }
+        }
+        return needUpdate;
+    }
+
     _getResultData(filterFun) {
         const result = this.resultTree.getDefaultDatas(filterFun);
         for (const r of result) {
@@ -418,9 +481,13 @@ class BillsPosConvert {
     }
     // 转换数据
     convert(filter) {
+        console.time('convert');
         this._recursiveConvertNode(this.bpcTree.children);
+        console.timeEnd('convert');
+        console.time('calc');
         this._calculateAndSortResult();
-        this._generateCodeByWbsCode([]);
+        console.timeEnd('calc');
+        this._generateCodeByWbsCode();
         switch (filter) {
             case 'cur':
                 return this._getResultData(function (node) {
@@ -454,7 +521,7 @@ class BillsPosConvert {
     convertByWbsCode(wbsCodeHis) {
         this._recursiveConvertNode(this.bpcTree.children);
         this._calculateAndSortResult();
-        const needUpdate = this._generateCodeByWbsCode(wbsCodeHis);
+        const needUpdate = this._generateCodeByWbsCodeWithHis(wbsCodeHis);
         return [this._getResultData(), needUpdate];
     }
 }

+ 2 - 2
app/lib/ledger.js

@@ -479,9 +479,9 @@ class filterGatherTree extends baseTree {
         return this._maxId;
     }
 
-    addNode(data, parent) {
+    addNode(data, parent, checkSame = true) {
         data[this.setting.pid] = parent ? parent[this.setting.id] : this.setting.rootId;
-        let item = this.ctx.helper._.find(this.items, data);
+        let item = checkSame ? this.ctx.helper._.find(parent ? parent.children : this.children, data) : null;
         if (item) return item;
 
         item = data;

+ 10 - 0
app/lib/spread_setting.js

@@ -126,11 +126,21 @@ const getStageSpreadSetting = async function (ctx, tid, readOnly, funInfo) {
         removeFieldCols(ledger, spreadConst.filterCols.thirdPartyCols.dagl);
         removeFieldCols(pos, spreadConst.filterCols.thirdPartyCols.dagl);
     }
+    if (!funInfo.load3fRelaFile) {
+        removeFieldCols(ledger, spreadConst.filterCols.thirdPartyCols.wbs_url);
+        removeFieldCols(pos, spreadConst.filterCols.thirdPartyCols.wbs_url);
+    } else if(funInfo.load3fRelaFile.extra_option.wbs_url) {
+        const ledgerCol = ledger.cols.find(x => { return x.field === 'wbs_url'; });
+        if (ledgerCol) ledgerCol.title = funInfo.load3fRelaFile.extra_option.wbs_url;
+        const posCol = pos.cols.find(x => { return x.field === 'wbs_url'; });
+        if (posCol) posCol.title = funInfo.load3fRelaFile.extra_option.wbs_url;
+    }
     if (!funInfo.minusNoValue || !tender.info.fun_rela.stage_change.minusNoValue) {
         hiddenFieldCols(ledger, spreadConst.filterCols.minusNoValueCols);
         hiddenFieldCols(pos, spreadConst.filterCols.minusNoValueCols);
     }
     if (!tender.info.display.stage.priceDiff) hiddenFieldCols(ledger, spreadConst.filterCols.priceDiffCols);
+    if (!tender.info.display.stage.posContractExpr) hiddenFieldCols(pos, ['contract_expr']);
     ledger.readOnly = readOnly;
     pos.readOnly = readOnly;
 

+ 3 - 3
app/lib/sum_load.js

@@ -415,11 +415,11 @@ class gatherStageGclTree extends loadGclBaseTree {
             return this.ctx.helper.sub(end_contract_tp, node.pre_contract_tp);
         } else if (info.calc_type === 'up') {
             if (correct) {
-                let activeQty = this.ctx.helper.add(node.quantity, node.qc_minus_qty);
+                let activeQty = this.ctx.helper.add(node.quantity, node.qc_minus_qty || node.org_qc_minus_qty);
                 let end_contract_qty = node.contract_qty;
                 activeQty = this.ctx.helper.add(activeQty, node.pre_qc_minus_qty);
                 end_contract_qty = this.ctx.helper.add(end_contract_qty, node.pre_contract_qty);
-                const end_contract_tp = this.ctx.helper.mul(end_contract_qty, node.total_price, info.decimal.tp);
+                const end_contract_tp = this.ctx.helper.mul(end_contract_qty, node.unit_price, info.decimal.tp);
                 return activeQty === end_contract_qty ? this.ctx.helper.sub(end_contract_tp, node.pre_contract_tp) : this.ctx.helper.mul(node.unit_price, node.contract_qty, info.decimal.tp);
             } else {
                 return this.ctx.helper.mul(node.unit_price, node.contract_qty, info.decimal.tp);
@@ -460,7 +460,7 @@ class gatherStageGclTree extends loadGclBaseTree {
         for (const bn of this.baseNodes) {
             if (!this.cover && !bn.is_import && !bn.contract_qty && !bn.qc_qty && !bn.contract_tp && !bn.qc_minus_qty) continue;
 
-            if (!bn.is_import && bn.org_qc_qty !== 0 && bn.qc_qty !== 0) {
+            if (!bn.is_import && ((bn.org_qc_qty !== 0 && bn.qc_qty !== 0) || (bn.org_qc_minus_qty !== 0 && bn.qc_minus_qty !== 0))) {
                 result.errors.push({ b_code: bn.b_code, name: bn.name, unit: bn.unit, qc_qty: bn.qc_qty, qc_minus_qty: bn.qc_minus_qty, ledger_id: bn.ledger_id, type: 'qc-conflict'});
                 continue;
             }

+ 126 - 0
app/middleware/inspection_check.js

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

+ 1 - 1
app/middleware/sub_project_check.js

@@ -53,7 +53,7 @@ module.exports = options => {
             if (financialPermission.transfer_show) {
                 this.subProject.financialToUrl = 'transfer';
             } else if (financialPermission.pay_show) {
-                this.subProject.financialToUrl = 'pay';
+                this.subProject.financialToUrl = 'pay/stage';
             } else if (!financialPermission.transfer_show && !financialPermission.pay_show) {
                 this.subProject.financialToUrl = 'transfer';
             }

+ 51 - 13
app/public/css/main.css

@@ -324,14 +324,14 @@ input.nospin[type="number"]{-moz-appearance:textfield;}
 .main-nav {
   position: fixed;
   z-index: 99;
-  width:55px;
+  width:110px;
   left: 0;
   top: 0;
   height: 100%;
   background: #33425b;
 }
 .main-panel{
-  padding-left:55px;
+  padding-left:110px;
   box-sizing: border-box;
 }
 .panel-sidebar{
@@ -340,7 +340,7 @@ input.nospin[type="number"]{-moz-appearance:textfield;}
   position: fixed;
   height: 100%;
   z-index: 4;
-  left:55px;
+  left:110px;
   /*padding-top:50px;*/
   border-right: 1px solid #ddd;
   width: 200px;
@@ -418,7 +418,7 @@ input.nospin[type="number"]{-moz-appearance:textfield;}
   padding-right: 20px;
 }
 .panel-content .panel-title.fluid{
-  padding-left:55px
+  padding-left:110px
 }
 .panel-title>.title-bar{
   padding-left: 20px
@@ -691,18 +691,23 @@ input.nospin[type="number"]{-moz-appearance:textfield;}
 }
 /*侧栏主菜单*/
 .nav-top,.nav-bottom{
-  width: 55px
+  width: 110px
 }
 .bg-nav a{
-  color:#7786ab;
-  width:55px;
+    display: flex;
+    align-items: center;     /* 垂直居中 */
+    justify-content: flex-start;  /* 图标和文字水平排列 */
+  color:#fff;
+  width:110px;
   text-align: center;
-  display: inline-block;
+  /*display: inline-block;*/
   padding:10px 0;
   font-size: 12px
 }
 .bg-nav a i{
-  font-size:18px;
+    margin: 0 5px 0 8px;
+  width: 25px;
+  font-size:15px;
 }
 
 .bg-nav a span{
@@ -751,6 +756,14 @@ input.nospin[type="number"]{-moz-appearance:textfield;}
 .bg-nav .menu-arrow{
   margin:22px 8px 0 0
 }
+.bg-nav .child-bg {
+    /*background-color: #10354D;*/
+}
+.child-up-down {
+    width: 12px!important;
+    margin: 0 0 0 8px!important;
+    font-size: 12px!important;
+}
 .nav-padding{
   margin-top: 30px;
 }
@@ -1143,7 +1156,7 @@ label{
   top:34px;
 }
 .panel-content .panel-title{
-  padding-left:175px;
+  padding-left:230px;
   background: linear-gradient( #ccc,2%, #ffffff);
 }
 .nav-link{
@@ -1250,9 +1263,9 @@ a.maintain-icon{
 }
 a.maintain-icon span{
   position: absolute;
-  left:55px;
-  height:40px;
-  line-height: 40px;
+  left:110px;
+  height:35px;
+  line-height: 35px;
   width:390px;
   top:0;
   display:none;
@@ -1791,6 +1804,25 @@ overflow-y: auto;
   font-size: 14px;
   color: rgba(255, 255, 255, 0.8);
 }
+.logo-parent{
+    display: flex;
+    align-items: center;      /* 🔹让图标和文字整体上下居中 */
+    justify-content: flex-start;
+    box-sizing: border-box;
+    height: 55px;             /* 固定高度 */
+    overflow: hidden;         /* 防止内容超出 */
+    box-sizing: border-box;
+}
+.logo-project-name{
+    flex: 1;
+    line-height: 1.4;        /* 行高 */
+    display: -webkit-box;    /* Flex-like box for WebKit */
+    -webkit-box-orient: vertical;
+    -webkit-line-clamp: 2;   /* 最多显示3行 */
+    overflow: hidden;
+    text-overflow: ellipsis;
+    word-break: break-word;
+}
 .side-border{
   width: 2px;
   height: 24px;
@@ -1910,6 +1942,9 @@ overflow-y: auto;
 .bg-new-financial{
     background: rgba(58, 88, 50, 0.08) !important;
 }
+.bg-new-inspection{
+    background: rgba(158, 88, 80, 0.08) !important;
+}
 .text-new-advance{
   color: rgba(241, 82, 91, 1) !important;
 }
@@ -1943,6 +1978,9 @@ overflow-y: auto;
 .text-new-financial{
     color: rgba(58, 88, 50, 1) !important;
 }
+.text-new-inspection{
+    color: rgba(158, 58, 80, 1) !important;
+}
 .text-width{
   width: 66px;
   text-align: center;

+ 8 - 5
app/public/css/qa_side.css

@@ -1,13 +1,16 @@
 .main-nav {
-  background:#B13719;
+  background:#3C6E71;
 }
 .bg-nav > li.active a {
-    background:#6E220F;
+    background:#0C3E00;
     color: #fff;
 }
 .bg-nav a {
-    color: #ccc;
+    color: #fff;
 }
 .bg-nav > li > a:hover, .bg-nav > li.active > a:hover{
-  background:#6E220F;
-}
+  background:#0C3E00;
+}
+.bg-nav .child-bg {
+  /*background-color: #2C6051;*/
+}

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

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

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

@@ -6314,7 +6314,7 @@ function checkAndChange(changeListData, showTips = false) {
                         continue;
                     }
                 }
-                const info = makePushBwmx(clinfo, listinfo, removeList, updateList);
+                const info = makePushBwmx(clinfo, listinfo, removeList, updateList, showTips);
                 if (info && _.findIndex(changeLedgerList, { id: clinfo.gcl_id }) !== -1) {
                     // 可能因为升降级关系:细目,分部分项等会发生变化,更新清单
                     const updateInfo = {};
@@ -6372,7 +6372,7 @@ function checkAndChange(changeListData, showTips = false) {
     }
 }
 
-function makePushBwmx(clinfo, listinfo, removeList, updateList) {
+function makePushBwmx(clinfo, listinfo, removeList, updateList, showTips = false) {
     let info = '';
     const checkKey = ['name', 'code', 'unit', 'unit_price'];
     const checkLeafKey = ['oamount', 'bwmx', 'code', 'dwgc', 'fbgc', 'fxgc', 'jldy'];
@@ -6445,7 +6445,7 @@ function makePushBwmx(clinfo, listinfo, removeList, updateList) {
             }
             info = leafInfo;
         } else {
-            toastr.warning('台账清单列表已不存在'+ clinfo.code +',已更新变更清单列表');
+            if (!showTips) toastr.warning('台账清单列表已不存在'+ clinfo.code +',已更新变更清单列表');
             if (changeStatus !== auditConst.status.revise) {
                 removeList.push(clinfo);
             } else {

+ 653 - 1
app/public/js/change_revise.js

@@ -299,7 +299,7 @@ $(document).ready(() => {
         treeSetting.calcFields.push('deal_tp');
     }
     treeSetting.calcFun = function (node) {
-        if (node && node.b_code) {
+        if (node && node.b_code && node.is_leaf) {
             const posData = pos.getLedgerPos(node.id) || [];
             let camount = ZhCalc.round(node.camount, findDecimal(node.unit)) || 0;
             if (posData.length > 0) {
@@ -5492,6 +5492,658 @@ $(document).ready(() => {
                 }
             });
         });
+
+
+        const batchPosSpreadObj = {
+            clearBpSheet: function (sheet) {
+                sheet.zh_data = [];
+                sheet.deleteRows(0, sheet.getRowCount() - 3);
+                SpreadJsObj.resetTopAndSelect(sheet);
+            },
+            clearSpSheet: function (sheet) {
+                sheet.zh_data = [];
+                sheet.deleteRows(0, sheet.getRowCount());
+                SpreadJsObj.resetTopAndSelect(sheet);
+                for (const bp of bpObj.bpData) {
+                    bp.add = false;
+                    bpObj.bpSheet.zh_data[bpObj.bpData.indexOf(bp)].add = false;
+                }
+                SpreadJsObj.reLoadSheetData(bpObj.bpSheet);
+            },
+            genId: function (prefix) {
+                const id = prefix + '-' + Date.now().toString(36) + '-' + Math.floor(Math.random() * 0x100000).toString(36);
+                // 若要绝对确保在 DOM 中唯一,可检查并重试:
+                return id;
+            },
+            bpClipboardPasted: function (e, info) {
+                if (info.sheet.getColumnCount() > info.sheet.zh_setting.cols.length) {
+                    info.sheet.setColumnCount(info.sheet.zh_setting.cols.length);
+                }
+                const node = SpreadJsObj.getSelectObject(billsSheet);
+                const updateDatas = [];
+                const insertDatas = [];
+                const sortData = info.sheet.zh_data || [];
+                const hint = {
+                    expr: {type: 'warning', msg: '粘贴了表达式非法,已过滤'},
+                };
+                let bHint = false;
+                for (let iRow = 0; iRow < info.cellRange.rowCount; iRow++) {
+                    let bPaste = true;
+                    const curRow = info.cellRange.row + iRow;
+                    const posData = curRow >= sortData.length ? {} : sortData[curRow];
+                    for (let iCol = 0; iCol < info.cellRange.colCount; iCol++) {
+                        const curCol = info.cellRange.col + iCol;
+                        const colSetting = info.sheet.zh_setting.cols[curCol];
+                        if (!colSetting) continue;
+                        let validText = colSetting.wordWrap ? info.sheet.getText(curRow, curCol) : trimInvalidChar(info.sheet.getText(curRow, curCol));
+                        if (colSetting.field === 'camount') {
+                            bPaste = true;
+                            const exprQuantity = {
+                                expr: '',
+                                quantity: 0,
+                            };
+                            const [valid, msg] = billsTreeSpreadObj._checkExpr(validText, exprQuantity);
+                            if (!valid) {
+                                toastMessageUniq(hint.expr);
+                                bPaste = false;
+                                continue;
+                            }
+                            if (isNaN(exprQuantity.quantity)) {
+                                toastMessageUniq(hint.expr);
+                                bPaste = false;
+                                continue;
+                            }
+                            validText = parseFloat(exprQuantity.quantity);
+                            posData.camount = ZhCalc.round(validText, findDecimal(node.unit)) || 0;
+                            posData.camount_expr = exprQuantity.expr;
+                            continue;
+                        }
+                        posData[colSetting.field] = validText;
+                    }
+                    if (bPaste && posData.name) {
+                        delete posData.waitingLoading;
+                        if (curRow >= sortData.length) {
+                            posData.id = batchPosSpreadObj.genId(curRow);
+                            bpObj.bpData.push(posData);
+                            insertDatas.push(posData);
+                        } else {
+                            // 更改bpObj.bpData同行数据用posData替换
+                            bpObj.bpData[curRow] = posData;
+                            updateDatas.push(posData);
+                        }
+                    }
+                }
+                if (insertDatas.length > 0) {
+                    for (const d of insertDatas) {
+                        const pos = _.find(bpObj.posData, { name: d.name });
+                        const bpPoss = _.filter(bpObj.bpData, { name: d.name });
+                        const spPos = _.find(bpObj.spData, { name: d.name });
+                        if (pos && bpPoss.length > 0) {
+                            const cInfo = _.find(changeList, { gcl_id: pos.lid, mx_id: pos.id });
+                            if (cInfo) {
+                                continue;
+                            }
+                            // 其它bpPoss add为false
+                            for (const b of bpPoss) {
+                                b.add = false;
+                            }
+                            const bp = _.find(bpObj.bpData, { id: d.id });
+                            bp.add = true;
+                            if (spPos) {
+                                spPos.id = d.id;
+                                spPos.camount = d.camount;
+                                spPos.camount_expr = d.camount_expr;
+                            } else {
+                                bpObj.spData.push({
+                                    id: d.id,
+                                    gcl_id: pos.lid,
+                                    mx_id: pos.id,
+                                    name: d.name,
+                                    camount: d.camount || 0,
+                                    camount_expr: d.camount_expr || '',
+                                    is_valuation: 1,
+                                });
+                            }
+                        }
+                    }
+                }
+                if (updateDatas.length > 0) {
+                    for (const d of updateDatas) {
+                        const pos = _.find(bpObj.posData, { name: d.name });
+                        const spPos = _.find(bpObj.spData, { id: d.id });
+                        if (pos && spPos) {
+                            const cInfo = _.find(changeList, { gcl_id: pos.lid, mx_id: pos.id });
+                            if (cInfo) {
+                                continue;
+                            }
+                            spPos.camount = d.camount || 0;
+                            spPos.camount_expr = d.camount_expr || '';
+                        }
+                    }
+                }
+                SpreadJsObj.loadSheetData(bpObj.bpSheet, SpreadJsObj.DataType.Data, bpObj.bpData);
+                SpreadJsObj.loadSheetData(bpObj.spSheet, SpreadJsObj.DataType.Data, bpObj.spData);
+            },
+            bpEditStarting: function (e, info) {
+                const sel = info.sheet.getSelections();
+                if (!sel || !sel[0]) return;
+                const col = info.sheet.zh_setting.cols[sel[0].col];
+                const node = SpreadJsObj.getSelectObject(info.sheet);
+                if (!node || !node.id) return;
+                if (col.field === 'camount') {
+                    if (node.camount_expr && node.camount_expr !== '') {
+                        info.sheet.getCell(info.row, info.col).text(node.camount_expr);
+                    }
+                }
+            },
+            bpEditEnded: function (e, info) {
+                if (!info.sheet.zh_setting) {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    return;
+                }
+                const posData = info.sheet.zh_data ? info.sheet.zh_data[info.row] : null;
+                const node = SpreadJsObj.getSelectObject(billsSheet);
+                const col = info.sheet.zh_setting.cols[info.col];
+                if (!posData || !posData.id || !col) {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    return;
+                }
+                if (col.field === 'camount') {
+                    let validText = info.editingText ? trimInvalidChar(info.editingText) : '';
+                    const orgValue = validText && validText !== ''
+                        ? (_.toNumber(validText) ? posData.camount : posData.camount_expr)
+                        : (posData.camount_expr && posData.camount_expr !== '' ? posData.camount_expr : posData.camount);
+                    if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === '' || validText === null))) {
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        return;
+                    }
+                    const exprQuantity = {
+                        expr: '',
+                        quantity: 0,
+                    };
+                    const [valid, msg] = billsTreeSpreadObj._checkExpr(validText, exprQuantity);
+                    if (!valid) {
+                        toastr.error(msg);
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        return;
+                    }
+                    if (isNaN(exprQuantity.quantity)) {
+                        toastr.error('不能输入其它非数字类型字符');
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        return;
+                    }
+                    validText = parseFloat(exprQuantity.quantity);
+                    posData[col.field] = ZhCalc.round(validText, findDecimal(node.unit)) || 0;
+                    posData.camount_expr = exprQuantity.expr;
+                    const spPos = _.find(bpObj.spData, { id: posData.id });
+                    if (spPos) {
+                        spPos.camount = posData.camount;
+                        spPos.camount_expr = posData.camount_expr;
+                        const spRow = _.findIndex(bpObj.spSheet.zh_data, { id: posData.id });
+                        if (spRow >= 0) {
+                            bpObj.spSheet.zh_data[spRow].camount = posData.camount;
+                            bpObj.spSheet.zh_data[spRow].camount_expr = posData.camount_expr;
+                            SpreadJsObj.reLoadRowData(bpObj.spSheet, spRow);
+                        }
+                    }
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                }
+            },
+            spClipboardPasted: function (e, info) {
+                if (info.sheet.getColumnCount() > info.sheet.zh_setting.cols.length) {
+                    info.sheet.setColumnCount(info.sheet.zh_setting.cols.length);
+                }
+                const node = SpreadJsObj.getSelectObject(billsSheet);
+                const data = [];
+                const rows = [];
+                const sortData = info.sheet.zh_data || [];
+                const hint = {
+                    expr: {type: 'warning', msg: '粘贴了表达式非法,已过滤'},
+                };
+                let bHint = false;
+                for (let iRow = 0; iRow < info.cellRange.rowCount; iRow++) {
+                    let bPaste = true;
+                    const curRow = info.cellRange.row + iRow;
+                    const posData = sortData[curRow];
+                    if (!posData || !posData.id) continue;
+                    for (let iCol = 0; iCol < info.cellRange.colCount; iCol++) {
+                        const curCol = info.cellRange.col + iCol;
+                        const colSetting = info.sheet.zh_setting.cols[curCol];
+                        if (!colSetting) continue;
+                        let validText = colSetting.wordWrap ? info.sheet.getText(curRow, curCol) : trimInvalidChar(info.sheet.getText(curRow, curCol));
+                        if (colSetting.field === 'camount') {
+                            bPaste = true;
+                            const exprQuantity = {
+                                expr: '',
+                                quantity: 0,
+                            };
+                            const [valid, msg] = billsTreeSpreadObj._checkExpr(validText, exprQuantity);
+                            if (!valid) {
+                                toastMessageUniq(hint.expr);
+                                bPaste = false;
+                                continue;
+                            }
+                            if (isNaN(exprQuantity.quantity)) {
+                                toastMessageUniq(hint.expr);
+                                bPaste = false;
+                                continue;
+                            }
+                            validText = parseFloat(exprQuantity.quantity);
+                            posData.camount = ZhCalc.round(validText, findDecimal(node.unit)) || 0;
+                            posData.camount_expr = exprQuantity.expr;
+                            continue;
+                        }
+                    }
+                    if (bPaste) {
+                        delete posData.waitingLoading;
+                        bpObj.spData[curRow] = posData;
+                        data.push(posData);
+                    }
+                }
+                if (data.length > 0) {
+                    for (const [i,d] of data.entries()) {
+                        const bpPos = _.find(bpObj.bpData, { id: d.id });
+                        if (bpPos) {
+                            bpPos.camount = d.camount;
+                            bpPos.camount_expr = d.camount_expr;
+                            const bpRow = _.findIndex(bpObj.bpSheet.zh_data, { id: d.id });
+                            if (bpRow >= 0) {
+                                bpObj.bpSheet.zh_data[bpRow].camount = d.camount;
+                                bpObj.bpSheet.zh_data[bpRow].camount_expr = d.camount_expr;
+                                SpreadJsObj.reLoadRowData(bpObj.bpSheet, bpRow);
+                            }
+                        }
+                    }
+                }
+                SpreadJsObj.loadSheetData(bpObj.spSheet, SpreadJsObj.DataType.Data, bpObj.spData);
+            },
+            spEditStarting: function (e, info) {
+                const sel = info.sheet.getSelections();
+                if (!sel || !sel[0]) return;
+                const col = info.sheet.zh_setting.cols[sel[0].col];
+                const node = SpreadJsObj.getSelectObject(info.sheet);
+                if (!node || !node.id) return;
+                if (col.field === 'camount') {
+                    if (node.camount_expr && node.camount_expr !== '') {
+                        info.sheet.getCell(info.row, info.col).text(node.camount_expr);
+                    }
+                }
+            },
+            spEditEnded: function (e, info) {
+                if (!info.sheet.zh_setting) {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    return;
+                }
+                const posData = info.sheet.zh_data ? info.sheet.zh_data[info.row] : null;
+                const node = SpreadJsObj.getSelectObject(billsSheet);
+                const col = info.sheet.zh_setting.cols[info.col];
+                if (!posData || !posData.id || !col) {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    return;
+                }
+                if (col.field === 'camount') {
+                    let validText = info.editingText ? trimInvalidChar(info.editingText) : '';
+                    const orgValue = validText && validText !== ''
+                        ? (_.toNumber(validText) ? posData.camount : posData.camount_expr)
+                        : (posData.camount_expr && posData.camount_expr !== '' ? posData.camount_expr : posData.camount);
+                    if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === '' || validText === null))) {
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        return;
+                    }
+                    const exprQuantity = {
+                        expr: '',
+                        quantity: 0,
+                    };
+                    const [valid, msg] = billsTreeSpreadObj._checkExpr(validText, exprQuantity);
+                    if (!valid) {
+                        toastr.error(msg);
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        return;
+                    }
+                    if (isNaN(exprQuantity.quantity)) {
+                        toastr.error('不能输入其它非数字类型字符');
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        return;
+                    }
+                    validText = parseFloat(exprQuantity.quantity);
+                    posData[col.field] = ZhCalc.round(validText, findDecimal(node.unit)) || 0;
+                    posData.camount_expr = exprQuantity.expr;
+                    const bpPos = _.find(bpObj.bpData, { id: posData.id });
+                    if (bpPos) {
+                        bpPos.camount = posData.camount;
+                        bpPos.camount_expr = posData.camount_expr;
+                        const bpRow = _.findIndex(bpObj.bpSheet.zh_data, { id: posData.id });
+                        if (bpRow >= 0) {
+                            bpObj.bpSheet.zh_data[bpRow].camount = posData.camount;
+                            bpObj.bpSheet.zh_data[bpRow].camount_expr = posData.camount_expr;
+                            SpreadJsObj.reLoadRowData(bpObj.bpSheet, bpRow);
+                        }
+                    }
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                }
+            },
+            spButtonClicked: function (e, info) {
+                if (!info.sheet.zh_setting) return;
+
+                const col = info.sheet.zh_setting.cols[info.col];
+                if (col.field !== 'is_valuation') return;
+
+                const node = SpreadJsObj.getSelectObject(info.sheet);
+                node.is_valuation = !node.is_valuation ? 1 : 0;
+                SpreadJsObj.reLoadSheetData(bpObj.spSheet);
+            },
+            deleteBp: function () {
+                if (bpObj.bpData.length === 0) return;
+                if (bpObj.bpSheet.getSelections().length === 0) return;
+                const selections = bpObj.bpSheet.getSelections();
+                const sel = selections ? selections[0] : bpObj.bpSheet.getSelections()[0];
+                const row = sel && sel.row !== undefined ? sel.row : -1;
+                if (row === -1 || sel.row + sel.rowCount > bpObj.bpData.length) {
+                    return false;
+                }
+                const delList = [];
+                for (let r = 0; r < sel.rowCount; r++) {
+                    const select = bpObj.bpData[row + r];
+                    delList.push(select);
+                    const spRow = _.findIndex(bpObj.spData, { id: select.id });
+                    bpObj.spData.splice(spRow, 1);
+                    bpObj.spSheet.deleteRows(spRow, 1);
+                }
+                _.pullAll(bpObj.bpData, delList);
+                bpObj.bpSheet.deleteRows(row, sel.rowCount);
+                // SpreadJsObj.loadSheetData(bpObj.bpSheet, SpreadJsObj.DataType.Data, bpObj.bpData);
+                SpreadJsObj.resetTopAndSelect(bpObj.bpSheet);
+            },
+            deleteSp: function () {
+                if (bpObj.spData.length === 0) return;
+                if (bpObj.spSheet.getSelections().length === 0) return;
+                const selections = bpObj.spSheet.getSelections();
+                const sel = selections ? selections[0] : bpObj.spSheet.getSelections()[0];
+                const row = sel && sel.row !== undefined ? sel.row : -1;
+                if (row === -1 || sel.row + sel.rowCount > bpObj.spData.length) {
+                    return false;
+                }
+                const delList = [];
+                for (let r = 0; r < sel.rowCount; r++) {
+                    const select = bpObj.spData[row + r];
+                    const bpSelect = _.filter(bpObj.bpData, { name: select.name });
+                    for (const b of bpSelect) {
+                        b.add = false;
+                    }
+                    delList.push(select);
+                }
+                _.pullAll(bpObj.spData, delList);
+                bpObj.spSheet.deleteRows(row, sel.rowCount);
+                SpreadJsObj.resetTopAndSelect(bpObj.spSheet);
+                SpreadJsObj.reLoadSheetData(bpObj.bpSheet);
+            },
+        };
+
+
+        $('#batch-pos').on('show.bs.modal', function (e) {
+            makeBatchPosSjsHtml();
+        });
+        let bp = false;
+        let bpObj = {
+            setting: null,
+            opSheet: null,
+            bpSheet: null,
+            spSheet: null,
+            posData: [],
+            spData: [],
+            bpData: [],
+        };
+
+        function makeBatchPosSjsHtml() {
+            if (!bp) {
+                initBatchPos();
+                bp = true;
+            }
+            const node = SpreadJsObj.getSelectObject(billsSheet);
+            if (node) {
+                const posData = pos.getLedgerPos(node.id) || [];
+                bpObj.posData = posData;
+                $('#batch-ledger-name').text(node.b_code);
+                SpreadJsObj.loadSheetData(bpObj.opSheet, SpreadJsObj.DataType.Data, posData);
+            } else {
+                bpObj.posData = [];
+                $('#batch-ledger-name').text('');
+                SpreadJsObj.loadSheetData(bpObj.opSheet, SpreadJsObj.DataType.Data, []);
+            }
+            $('#clear-bp-btn').click();
+        }
+
+        const initBatchPos = function () {
+            const opSpread = SpreadJsObj.createNewSpread($('#origin-pos-spread')[0]);
+            bpObj.opSheet = opSpread.getActiveSheet();
+            const opSpreadSetting = {
+                emptyRows: 0,
+                headRows: 2,
+                headRowHeight: [25, 25],
+                defaultRowHeight: 21,
+                headerFont: '12px 微软雅黑',
+                font: '12px 微软雅黑',
+                headColWidth: [],
+                readOnly: true,
+                // selectedBackColor: '#fffacd',
+            };
+            opSpreadSetting.cols = posSpreadSetting.cols;
+            SpreadJsObj.initSheet(bpObj.opSheet, opSpreadSetting);
+            const bpSpread = SpreadJsObj.createNewSpread($('#batch-pos-spread')[0]);
+            bpObj.bpSheet = bpSpread.getActiveSheet();
+            const bpSpreadSetting = {
+                cols: [
+                    {
+                        title: '计量单元',
+                        field: 'name',
+                        hAlign: 0,
+                        width: 200,
+                        formatter: '@',
+                        readOnly: true,
+                    },
+                    {
+                        title: '申请变更数量',
+                        field: 'camount',
+                        hAlign: 2,
+                        width: 120,
+                        // readOnly: true,
+                    },
+                ],
+                emptyRows: 3,
+                headRows: 1,
+                headRowHeight: [32],
+                defaultRowHeight: 21,
+                headerFont: '12px 微软雅黑',
+                font: '12px 微软雅黑',
+                headColWidth: []
+                // selectedBackColor: '#fffacd',
+            };
+            bpSpreadSetting.getColor = function (sheet, data, row, col, defaultColor) {
+                if (data && data.add) {
+                    return '#c3e6cb';
+                }
+                return defaultColor;
+            };
+            SpreadJsObj.initSheet(bpObj.bpSheet, bpSpreadSetting);
+            bpSpread.bind(spreadNS.Events.EditStarting, batchPosSpreadObj.bpEditStarting);
+            bpSpread.bind(spreadNS.Events.EditEnded, batchPosSpreadObj.bpEditEnded);
+            bpSpread.bind(spreadNS.Events.ClipboardPasted, batchPosSpreadObj.bpClipboardPasted);
+
+            const spSpread = SpreadJsObj.createNewSpread($('#save-pos-spread')[0]);
+            bpObj.spSheet = spSpread.getActiveSheet();
+            const spSpreadSetting = {
+                cols: [
+                    {
+                        title: '计量单元',
+                        field: 'name',
+                        hAlign: 0,
+                        width: 200,
+                        formatter: '@',
+                        readOnly: true,
+                    },
+                    {
+                        title: '申请变更数量',
+                        field: 'camount',
+                        hAlign: 2,
+                        width: 120,
+                        // readOnly: true,
+                    },
+                    {
+                        title: '计价',
+                        field: 'is_valuation',
+                        hAlign: 1,
+                        width: 40,
+                        formatter: '@',
+                        cellType: 'checkbox',
+                        readOnly: true,
+                    },
+                ],
+                emptyRows: 0,
+                headRows: 1,
+                headRowHeight: [32],
+                defaultRowHeight: 21,
+                headerFont: '12px 微软雅黑',
+                font: '12px 微软雅黑',
+                headColWidth: []
+            };
+            SpreadJsObj.initSheet(bpObj.spSheet, spSpreadSetting);
+            spSpread.bind(spreadNS.Events.ButtonClicked, batchPosSpreadObj.spButtonClicked);
+            spSpread.bind(spreadNS.Events.EditStarting, batchPosSpreadObj.spEditStarting);
+            spSpread.bind(spreadNS.Events.EditEnded, batchPosSpreadObj.spEditEnded);
+            spSpread.bind(spreadNS.Events.ClipboardPasted, batchPosSpreadObj.spClipboardPasted);
+
+            // 右键删除已选粘贴
+            const bpContextMenuOptions = {
+                selector: '#batch-pos-spread',
+                build: function ($trigger, e) {
+                    const target = SpreadJsObj.safeRightClickSelection($trigger, e, bpSpread);
+                    return target.hitTestType === spreadNS.SheetArea.viewport || target.hitTestType === spreadNS.SheetArea.rowHeader;
+                },
+                items: {
+                    delete: {
+                        name: '删除',
+                        icon: 'fa-remove',
+                        callback: function (key, opt) {
+                            batchPosSpreadObj.deleteBp();
+                        },
+                        disabled: function (key, opt) {
+                            const selection = bpObj.bpSheet.getSelections();
+                            const sel = selection ? selection[0] : bpObj.bpSheet.getSelections()[0];
+                            const row = sel && sel.row !== undefined ? sel.row : -1;
+                            if (row === -1 || sel.row + sel.rowCount > bpObj.bpData.length) {
+                                return true;
+                            }
+                            return false;
+                        }
+                    },
+                }
+            };
+            $.contextMenu(bpContextMenuOptions);
+
+            // 右键删除已选录入
+            const spContextMenuOptions = {
+                selector: '#save-pos-spread',
+                build: function ($trigger, e) {
+                    const target = SpreadJsObj.safeRightClickSelection($trigger, e, spSpread);
+                    return target.hitTestType === spreadNS.SheetArea.viewport || target.hitTestType === spreadNS.SheetArea.rowHeader;
+                },
+                items: {
+                    delete: {
+                        name: '删除',
+                        icon: 'fa-remove',
+                        callback: function (key, opt) {
+                            batchPosSpreadObj.deleteSp();
+                        },
+                        disabled: function (key, opt) {
+                            const selection = bpObj.spSheet.getSelections();
+                            const sel = selection ? selection[0] : bpObj.spSheet.getSelections()[0];
+                            const row = sel && sel.row !== undefined ? sel.row : -1;
+                            if (row === -1 || sel.row + sel.rowCount > bpObj.spData.length) {
+                                return true;
+                            }
+                            return false;
+                        }
+                    },
+                }
+            };
+            // $.contextMenu(spContextMenuOptions);
+
+            $('#batch-pos').bind('shown.bs.modal', function () {
+                if (opSpread) opSpread.refresh();
+                if (bpSpread) bpSpread.refresh();
+                if (spSpread) spSpread.refresh();
+            });
+
+            $('#clear-bp-btn').click(function () {
+                bpObj.bpData = [];
+                batchPosSpreadObj.clearBpSheet(bpObj.bpSheet);
+                bpObj.spData = [];
+                batchPosSpreadObj.clearSpSheet(bpObj.spSheet);
+            });
+
+            // $('#clear-op-btn').click(function () {
+            //     bpObj.spData = [];
+            //     batchPosSpreadObj.clearSpSheet(bpObj.spSheet);
+            // });
+
+            $('#add-changelist-btn').click(function () {
+                if (bpObj.spData.length === 0) {
+                    toastr.warning('录入区计量单元为空');
+                    return;
+                }
+                const datas = [];
+                makeGclGatherData();
+                for (const posData of bpObj.spData) {
+                    const gclInfo = _.find(gclGatherData, function (item) {
+                        return item.leafXmjs && _.find(item.leafXmjs, {gcl_id: posData.gcl_id, mx_id: posData.mx_id });
+                    });
+                    const xmjInfo = _.find(gclInfo.leafXmjs, { mx_id: posData.mx_id });
+                    const oldCInfo = _.find(oldChangeList, { gcl_id: posData.gcl_id, mx_id: posData.mx_id });
+                    if (gclInfo && xmjInfo) {
+                        const data = {
+                            lid: xmjInfo.gcl_id,
+                            code: gclInfo.b_code,
+                            name: gclInfo.name || '',
+                            unit: gclInfo.unit || '',
+                            unit_price: gclInfo.unit_price,
+                            oamount: xmjInfo.quantity,
+                            oamount2: oldCInfo ? oldCInfo.oamount2 : xmjInfo.quantity,
+                            bwmx: xmjInfo.bwmx || xmjInfo.jldy || '',
+                            xmj_code: xmjInfo.code || '',
+                            xmj_jldy: xmjInfo.jldy || '',
+                            xmj_dwgc: xmjInfo.dwgc || '',
+                            xmj_fbgc: xmjInfo.fbgc || '',
+                            xmj_fxgc: xmjInfo.fxgc || '',
+                            gcl_id: xmjInfo.gcl_id,
+                            mx_id: xmjInfo.mx_id || '',
+                            is_valuation: posData.is_valuation,
+                            camount: posData.camount,
+                            camount_expr: posData.camount_expr,
+                            spamount: posData.camount,
+                        }
+                        if (oldCInfo) {
+                            data.detail = oldCInfo.detail;
+                            data.delimit = oldCInfo.delimit;
+                        }
+                        datas.push(data);
+                    }
+                }
+                if (datas.length > 0) {
+                    console.log(datas);
+                    postData('/tender/' + window.location.pathname.split('/')[2] + '/change/' + window.location.pathname.split('/')[4] + '/information/save', { type:'paste', insertData: datas, updateData: [] }, function (result) {
+                        changeList = result;
+                        const billsNode = SpreadJsObj.getSelectObject(billsSheet);
+                        billsTreeSpreadObj.reCalcCamount(billsNode);
+                        const loadResult = { update: [billsNode] };
+                        const refreshNode = billsTree.loadPostData(loadResult);
+                        billsTreeSpreadObj.refreshTree(billsSheet, refreshNode);
+                        posSpreadObj.loadCurPosData();
+                        $('#batch-pos').modal('hide');
+                    });
+                }
+                return;
+            });
+        };
     }
 });
 function findDecimal(unit) {

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

@@ -464,7 +464,7 @@ $(document).ready(function() {
             });
         }
         _clearAllFileCache() {
-            const nodes = this.filingTree.getNodes();
+            const nodes = this.filingTree.transformToArray(this.filingTree.getNodes());
             for (const node of nodes) {
                 if (node.children && node.children.length > 0) continue;
                 if (node.source_node.files) node.source_node.files.length = 0;

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

@@ -29,7 +29,8 @@ function autoFlashHeight(){
     $(".sjs-height-5").height($(window).height()-cHeader-sBar-492+55);/*492*/
     $(".sjs-height-6").height($(window).height()-cHeader-34-sBar1);
     $(".sjs-height-7").height($(window).height()-cHeader-34-41);
-    $(".sp-wrap").height(bcontent-30);
+    const bcBar = getObjHeight($('.bcontent-wrap .bc-bar'));
+    $(".sp-wrap").height(bcontent-(bcBar || 30));
     /*侧栏高度*/
     $(".sjs-sh-1").height($(window).height()-cHeader-sBar1-92+55);
     $(".sjs-sh-2").height($(window).height()-cHeader-sBar2-92+55);
@@ -100,6 +101,28 @@ $(function(){
         }
     });
 
+    $('.bg-nav > li > .nav-up-down').on('click', function() {
+        const nav = $(this).attr('id');
+        if ($(this).children('.child-up-down').hasClass('fa-angle-down')) {
+            $(this).children('.child-up-down').removeClass('fa-angle-down').addClass('fa-angle-right');
+            $('.bg-nav li[data-index="'+ nav + '"]').slideUp('fast');
+        } else {
+            $(this).children('.child-up-down').removeClass('fa-angle-right').addClass('fa-angle-down');
+            $('.bg-nav li[data-index="'+ nav + '"]').slideDown('fast');
+        }
+        setLocalCache('nav-' + nav, $(this).children('.child-up-down').hasClass('fa-angle-down') ? 1 : 0);
+    });
+
+    // 侧栏菜单状态判断展开收起
+    $('.bg-nav > li > .nav-up-down').each(function() {
+        const nav = $(this).attr('id');
+        if (getLocalCache('nav-' + nav) == 0) {
+            $(this).children('.child-up-down').removeClass('fa-angle-down').addClass('fa-angle-right');
+            $('.bg-nav li[data-index="'+ nav + '"]').slideUp(0);
+        }
+    });
+
+
     // 数据提交
     $("#submit-form").click(function() {
         $("#save-form").submit();

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

@@ -99,7 +99,6 @@ $(document).ready(() => {
             colWidth: true,
         },
     };
-    if (!isTz) leafXmjSpreadSetting.cols.splice(1, 1);
     if (thousandth) sjsSettingObj.setTpThousandthFormat(leafXmjSpreadSetting);
     SpreadJsObj.initSheet(leafXmjSpread.getActiveSheet(), leafXmjSpreadSetting);
     const leafXmjSheet = leafXmjSpread.getActiveSheet();
@@ -192,39 +191,39 @@ $(document).ready(() => {
     // 获取项目节数据
     function loadLeafXmjData(iGclRow) {
         const gcl = iGclRow ? gclGatherData[iGclRow] : SpreadJsObj.getSelectObject(gclSheet);
-        SpreadJsObj.resetTopAndSelect(leafXmjSheet);
         if (gcl) {
             SpreadJsObj.loadSheetData(leafXmjSheet, SpreadJsObj.DataType.Data, gcl.leafXmjs);
         } else {
             SpreadJsObj.loadSheetData(leafXmjSheet, SpreadJsObj.DataType.Data, []);
         }
+        SpreadJsObj.resetTopAndSelect(leafXmjSheet);
     }
     function loadGatherLeafXmjData(iGclRow) {
         const gcl = iGclRow ? gclGatherData[iGclRow] : SpreadJsObj.getSelectObject(gclSheet);
-        SpreadJsObj.resetTopAndSelect(gatherLeafXmjSheet);
         if (gcl) {
             SpreadJsObj.loadSheetData(gatherLeafXmjSheet, SpreadJsObj.DataType.Data, gcl.gatherLeafXmjs);
         } else {
             SpreadJsObj.loadSheetData(gatherLeafXmjSheet, SpreadJsObj.DataType.Data, []);
         }
+        SpreadJsObj.resetTopAndSelect(gatherLeafXmjSheet);
     }
     function loadGatherAncGclData(iGclRow) {
         const gcl = iGclRow ? gclGatherData[iGclRow] : SpreadJsObj.getSelectObject(gclSheet);
-        SpreadJsObj.resetTopAndSelect(gatherAncGclSheet);
         if (gcl) {
             SpreadJsObj.loadSheetData(gatherAncGclSheet, SpreadJsObj.DataType.Data, gcl.gatherAncGcl);
         } else {
             SpreadJsObj.loadSheetData(gatherAncGclSheet, SpreadJsObj.DataType.Data, []);
         }
+        SpreadJsObj.resetTopAndSelect(gatherAncGclSheet);
     }
     function loadChangeData(iGclRow) {
         const gcl = iGclRow ? gclGatherData[iGclRow] : SpreadJsObj.getSelectObject(gclSheet);
-        SpreadJsObj.resetTopAndSelect(changeSheet);
         if (gcl) {
             SpreadJsObj.loadSheetData(changeSheet, SpreadJsObj.DataType.Data, gcl.change);
         } else {
             SpreadJsObj.loadSheetData(changeSheet, SpreadJsObj.DataType.Data, []);
         }
+        SpreadJsObj.resetTopAndSelect(changeSheet);
     }
     function loadGclGatherRelaData(iGclRow) {
         loadLeafXmjData(iGclRow);

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

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

+ 7 - 0
app/public/js/phase_pay_list.js

@@ -153,4 +153,11 @@ $(document).ready(() => {
             autoFlashHeight();
         }
     });
+
+    $('select[name=stage]').select2({
+        language: 'zh-CN',
+        width: '100%',
+        multiple: true,
+        maximumSelectionlength: 6,
+    });
 });

+ 6 - 1
app/public/js/project_spread.js

@@ -50,7 +50,12 @@ $(document).ready(() => {
         });
         BaseSetCol.forEach(x => {
             const col = colSet.find(c => { return c.key === x.key });
-            if (!col) data.push({ ...x, valid: 0});
+            if (!col) {
+                const colData = { ...x, valid: x.init || 0 };
+                if (colData.bills && colData.bills_valid === undefined) colData.bills_valid = colData.valid;
+                if (colData.pos && colData.pos_valid === undefined) colData.pos_valid = colData.valid;
+                data.push(colData);
+            }
         });
         SpreadJsObj.loadSheetData(colSheet, SpreadJsObj.DataType.Data, data);
     };

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

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

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

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

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

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

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

@@ -615,7 +615,7 @@ function setMonthToLedger(ledgerList, slm) {
     if (slm.length > 0) {
         for(const s of slm) {
             const index = _.findIndex(ledgerList, { 'ledger_id': s.lid });
-            if (index && index !== -1) {
+            if (index !== -1) {
                 ledgerList[index][s.yearmonth + '_tp'] = s.plan_tp;
                 ledgerList[index][s.yearmonth + '_gcl'] = s.plan_gcl;
             }

+ 5 - 5
app/public/js/schedule_stage_gcl.js

@@ -703,7 +703,7 @@ function setMonthToLedger(ledgerList, slm) {
         for(const s of slm) {
             const index = _.findIndex(ledgerList, { 'ledger_id': s.lid });
             const canCalc = _.find(scheduleMonth, { 'yearmonth': s.yearmonth, 'stage_gcl_used': 1});
-            if (index && index !== -1 && canCalc) {
+            if (index !== -1 && canCalc) {
                 ledgerList[index][s.yearmonth + '_plan_tp'] = s.plan_tp;
                 ledgerList[index][s.yearmonth + '_plan_gcl'] = s.plan_gcl;
                 ledgerList[index][s.yearmonth + '_sj_tp'] = s.sj_tp;
@@ -717,7 +717,7 @@ function setGclMonthToLedger(ledgerList, slm, nextSlm, endSlm, yearSlm) {
     if (slm.length > 0) {
         for(const s of slm) {
             const index = _.findIndex(ledgerList, { 'ledger_id': s.lid });
-            if (index && index !== -1) {
+            if (index !== -1) {
                 ledgerList[index].plan_tp = s.plan_tp;
                 ledgerList[index].plan_gcl = s.plan_gcl;
                 ledgerList[index].sj_tp = s.sj_tp;
@@ -728,7 +728,7 @@ function setGclMonthToLedger(ledgerList, slm, nextSlm, endSlm, yearSlm) {
     if (nextSlm.length > 0) {
         for (const ns of nextSlm) {
             const index = _.findIndex(ledgerList, {'ledger_id': ns.lid});
-            if (index && index !== -1) {
+            if (index !== -1) {
                 ledgerList[index].next_plan_tp = ns.plan_tp;
                 ledgerList[index].next_plan_gcl = ns.plan_gcl;
             }
@@ -737,7 +737,7 @@ function setGclMonthToLedger(ledgerList, slm, nextSlm, endSlm, yearSlm) {
     if (endSlm.length > 0) {
         for (const es of endSlm) {
             const index = _.findIndex(ledgerList, {'ledger_id': es.lid});
-            if (index && index !== -1) {
+            if (index !== -1) {
                 ledgerList[index].end_plan_tp = es.plan_tp;
                 ledgerList[index].end_plan_gcl = es.plan_gcl;
                 ledgerList[index].end_sj_tp = es.sj_tp;
@@ -748,7 +748,7 @@ function setGclMonthToLedger(ledgerList, slm, nextSlm, endSlm, yearSlm) {
     if (yearSlm.length > 0) {
         for (const ys of yearSlm) {
             const index = _.findIndex(ledgerList, {'ledger_id': ys.lid});
-            if (index && index !== -1) {
+            if (index !== -1) {
                 ledgerList[index].year_plan_tp = ys.plan_tp;
                 ledgerList[index].year_plan_gcl = ys.plan_gcl;
                 ledgerList[index].year_sj_tp = ys.sj_tp;

+ 4 - 4
app/public/js/schedule_stage_tp.js

@@ -258,7 +258,7 @@ function setMonthToLedger(ledgerList, slm, nextSlm, endSlm, yearSlm, yearLedgerD
     if (slm.length > 0) {
         for(const s of slm) {
             const index = _.findIndex(ledgerList, { 'ledger_id': s.lid });
-            if (index && index !== -1) {
+            if (index !== -1) {
                 ledgerList[index].plan_tp = s.plan_tp;
                 ledgerList[index].plan_gcl = s.plan_gcl;
             }
@@ -267,7 +267,7 @@ function setMonthToLedger(ledgerList, slm, nextSlm, endSlm, yearSlm, yearLedgerD
     if (nextSlm.length > 0) {
         for (const ns of nextSlm) {
             const index = _.findIndex(ledgerList, {'ledger_id': ns.lid});
-            if (index && index !== -1) {
+            if (index !== -1) {
                 ledgerList[index].next_plan_tp = ns.plan_tp;
                 ledgerList[index].next_plan_gcl = ns.plan_gcl;
             }
@@ -276,7 +276,7 @@ function setMonthToLedger(ledgerList, slm, nextSlm, endSlm, yearSlm, yearLedgerD
     if (endSlm.length > 0) {
         for (const es of endSlm) {
             const index = _.findIndex(ledgerList, {'ledger_id': es.lid});
-            if (index && index !== -1) {
+            if (index !== -1) {
                 ledgerList[index].end_plan_tp = es.plan_tp;
                 ledgerList[index].end_plan_gcl = es.plan_gcl;
             }
@@ -285,7 +285,7 @@ function setMonthToLedger(ledgerList, slm, nextSlm, endSlm, yearSlm, yearLedgerD
     if (yearSlm.length > 0) {
         for (const ys of yearSlm) {
             const index = _.findIndex(ledgerList, {'ledger_id': ys.lid});
-            if (index && index !== -1) {
+            if (index !== -1) {
                 ledgerList[index].year_plan_tp = ys.plan_tp;
                 ledgerList[index].year_plan_gcl = ys.plan_gcl;
             }

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

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

+ 2 - 2
app/public/js/shares/sjs_setting.js

@@ -127,9 +127,9 @@ const sjsSettingObj = (function () {
     const set3FCols = function (cols, rela) {
         setRelaCols(cols, rela, function(col, r) {
             col.getValue = r.getValue;
-            col.cellType = 'imageBtn';
+            col.cellType = r.cellType || 'imageBtn';
             col.normalImg = '#rela-file-icon';
-            col.indent = 12;
+            col.indent = r.indent || 12;
             col.imgAlign = 2;
             col.showImage = function (data) { return data && data[r.url_field]; }
         });

+ 11 - 6
app/public/js/spreadjs_rela/spreadjs_zh.js

@@ -2027,7 +2027,7 @@ const SpreadJsObj = {
                             canvas.fillRect((x + x + w - indent - img.width) / 2, y, (indent + img.width + img.width) / 2, h);
                             canvas.restore();
                         }
-                        canvas.drawImage(img, (x + 10 + x + w - indent - img.width) / 2, y + (h - img.height) / 2);
+                        canvas.drawImage(img, (x + x + w - indent - img.width) / 2, y + (h - img.height) / 2);
                         w = w - indent - img.width;
                     }
                     // Drawing Text
@@ -2037,10 +2037,10 @@ const SpreadJsObj = {
                         if (style.backColor) {
                             canvas.save();
                             canvas.fillStyle = style.backColor;
-                            canvas.fillRect(x, y, indent + img.width, h);
+                            canvas.fillRect(x + indent, y, indent + img.width, h);
                             canvas.restore();
                         }
-                        canvas.drawImage(img, x + 10, y + (h - img.height) / 2);
+                        canvas.drawImage(img, x + indent, y + (h - img.height) / 2);
                         if (style.hAlign !== spreadNS.HorizontalAlign.left) {
                             style.hAlign = spreadNS.HorizontalAlign.left;
                         }
@@ -2122,9 +2122,14 @@ const SpreadJsObj = {
                 const img = this.getImage(hitinfo.sheet, hitinfo.row, hitinfo.col);
                 const halfX = img.width / 2, halfY = img.height / 2;
                 const indent = col.indent ? col.indent : 10;
-                const centerX = hitinfo.cellStyle.hAlign === spreadNS.HorizontalAlign.right
-                    ? hitinfo.cellRect.x + hitinfo.cellRect.width - indent -halfX
-                    : hitinfo.cellRect.x + indent + halfX;
+                let centerX;
+                if (hitinfo.cellStyle.hAlign === spreadNS.HorizontalAlign.right) {
+                    centerX = hitinfo.cellRect.x + hitinfo.cellRect.width - indent - halfX;
+                } else if (hitinfo.cellStyle.hAlign === spreadNS.HorizontalAlign.center) {
+                    centerX = hitinfo.cellRect.x + (hitinfo.cellRect.width - indent) / 2 + halfX;
+                } else {
+                    centerX = hitinfo.cellRect.x + indent + halfX
+                }
                 const centerY = hitinfo.cellRect.y + hitinfo.cellRect.height / 2;
 
                 if (Math.abs(hitinfo.x - centerX) < halfX && Math.abs(hitinfo.y - centerY) < halfY) {

+ 71 - 25
app/public/js/stage.js

@@ -179,6 +179,9 @@ function getDaglText(data) {
     });
     return def ? def.name : '';
 }
+function getWbsText(data) {
+    return '';
+}
 
 function getHintMsg () {
     return {
@@ -772,6 +775,7 @@ $(document).ready(() => {
         switch (col.field) {
             case 'dagl': data.dagl_url && window.open(data.dagl_url); break;
             case 'gxby': data.gxby_url && window.open(data.gxby_url); break;
+            case 'wbs_url': data.wbs_url && window.open(data.wbs_url, '_target'); break;
             case 'qc_qty':
             case 'qc_minus_qty':
                 if (data.children && data.children.length > 0 || data.lock || data.is_import || data.settle_status === settleStatus.finish) return;
@@ -812,6 +816,7 @@ $(document).ready(() => {
     sjsSettingObj.set3FCols(ledgerSpreadSetting.cols, [
         {field: 'gxby', getValue: getGxbyText, url_field: 'gxby_url'},
         {field: 'dagl', getValue: getDaglText, url_field: 'dagl_url'},
+        {field: 'wbs_url', getValue: getWbsText, url_field: 'wbs_url', cellType: 'imageBtn', indent: 20},
     ]);
     if (thousandth) sjsSettingObj.setTpThousandthFormat(ledgerSpreadSetting);
     ledgerSpreadSetting.headColWidth = [50];
@@ -869,6 +874,7 @@ $(document).ready(() => {
         switch (col.field) {
             case 'gxby': data.gxby_url && window.open(data.gxby_url); break;
             case 'dagl': data.dagl_url && window.open(data.dagl_url); break;
+            case 'wbs_url': data.wbs_url && window.open(data.wbs_url, '_blank'); break;
             case 'qc_qty':
             case 'qc_minus_qty':
                 const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
@@ -903,6 +909,7 @@ $(document).ready(() => {
     sjsSettingObj.set3FCols(posSpreadSetting.cols, [
         {field: 'gxby', getValue: getGxbyText, url_field: 'gxby_url'},
         {field: 'dagl', getValue: getDaglText, url_field: 'dagl_url'},
+        {field: 'wbs_url', getValue: getWbsText, url_field: 'wbs_url', cellType: 'imageBtn', indent: 20},
     ]);
     SpreadJsObj.initSheet(spSpread.getActiveSheet(), posSpreadSetting);
 
@@ -2046,6 +2053,39 @@ $(document).ready(() => {
                     return readOnly || stage.revising || stage.status !== auditConst.status.uncheck;
                 },
             },
+            exportTestData: {
+                name: '导出测试数据',
+                callback: function (key, opt, menu, e) {
+                    const data = stageTree.nodes.map(x => {
+                        const parent = stageTree.getParent(x);
+                        return {
+                            id: x.id, parentId: parent ? parent.id : '-1', seq: x.order,
+                            code: x.code || '', b_code: x.b_code || '', name: x.name || '', unit: x.unit || '', unit_price: x.unit_price || 0,
+                            quantity: x.quantity || 0, total_price: x.total_price || 0,
+                            is_tp: x.is_tp || 0,
+                            contract_qty: x.contract_qty || 0, contract_tp: x.contract_tp || 0,
+                            qc_qty: x.qc_qty || 0, qc_tp: x.qc_tp || 0,
+                            gather_qty: x.gather_qty || 0, gather_tp: x.gather_tp || 0,
+                            pre_contract_qty: x.pre_contract_qty || 0, pre_contract_tp: x.pre_contract_tp || 0,
+                            pre_qc_qty: x.pre_qc_qty || 0, pre_qc_tp: x.pre_qc_tp || 0,
+                            pre_gather_qty: x.pre_gather_qty || 0, pre_gather_tp: x.pre_gather_tp || 0,
+                            end_contract_qty: x.end_contract_qty || 0, end_contract_tp: x.end_contract_tp || 0,
+                            end_qc_qty: x.end_qc_qty || 0, end_qc_tp: x.end_qc_tp || 0,
+                            end_gather_qty: x.end_gather_qty || 0, end_gather_tp: x.end_gather_tp || 0,
+
+                            memo: x.memo || '',
+                            gxby_status: x.gxby_status || 0, gxby_url: x.gxby_status || '',
+                            dagl_status: x.gxby_status || 0, dagl_url: x.dagl_url || '',
+                            wbs_url: x.wbs_url || '',
+                        };
+                    });
+                    const blob = new Blob([JSON.stringify(data, '', '\t')], { type: 'application/text'});
+                    saveAs(blob, 'test.json');
+                },
+                visible: function() {
+                    return is_debug || false;
+                }
+            },
             sumLoadSpr: '---',
             importStageGcl: {
                 name: '导入(其他标段)工程量清单计量数据',
@@ -3132,6 +3172,7 @@ $(document).ready(() => {
     $.divResizer({
         select: '#right-spr',
         callback: function () {
+            autoFlashHeight();
             slSpread.refresh();
             spSpread.refresh();
             if (searchLedger) {
@@ -4546,30 +4587,32 @@ $(document).ready(() => {
                         name: '自动调用',
                         icon: 'fa-play',
                         callback: function (key, opt) {
-                            const curChange = SpreadJsObj.getSelectObject(self.changeSheet);
-                            const changeBills = self.changeBillsSheet.zh_data;
-                            const data = { bills: [], autoType: 'bills' };
-                            for (const cb of changeBills) {
-                                const billsPos = self.findBillsPos(curChange, cb);
-                                if (billsPos) data.bills.push({ ...billsPos, cid: cb.cid, cbid: cb.id })
-                            }
-                            if (data.bills.length === 0) {
-                                toastr.warning('无可调用的清单或计量单元');
-                                return;
-                            }
-                            postData(window.location.pathname + '/auto-use-change', data, function(result) {
-                                if (result.pos) {
-                                    stagePos.loadCurStageData(result.pos.curStageData);
+                            checkAgainHint(function(){
+                                const curChange = SpreadJsObj.getSelectObject(self.changeSheet);
+                                const changeBills = self.changeBillsSheet.zh_data;
+                                const data = { bills: [], autoType: 'bills' };
+                                for (const cb of changeBills) {
+                                    const billsPos = self.findBillsPos(curChange, cb);
+                                    if (billsPos) data.bills.push({ ...billsPos, cid: cb.cid, cbid: cb.id })
                                 }
-                                const nodes = stageTree.loadPostStageData(result.bills);
-                                stageTreeSpreadObj.refreshTreeNodes(slSpread.getActiveSheet(), nodes);
-                                stagePosSpreadObj.loadCurPosData();
-                                if (detail) {
-                                    detail.loadStageChangeUpdateData(result, nodes);
-                                } else {
-                                    stageIm.loadUpdateChangeData(result, nodes)
+                                if (data.bills.length === 0) {
+                                    toastr.warning('无可调用的清单或计量单元');
+                                    return;
                                 }
-                            });
+                                postData(window.location.pathname + '/auto-use-change', data, function(result) {
+                                    if (result.pos) {
+                                        stagePos.loadCurStageData(result.pos.curStageData);
+                                    }
+                                    const nodes = stageTree.loadPostStageData(result.bills);
+                                    stageTreeSpreadObj.refreshTreeNodes(slSpread.getActiveSheet(), nodes);
+                                    stagePosSpreadObj.loadCurPosData();
+                                    if (detail) {
+                                        detail.loadStageChangeUpdateData(result, nodes);
+                                    } else {
+                                        stageIm.loadUpdateChangeData(result, nodes)
+                                    }
+                                });
+                            }, ['自动调用后,已调用变更令无法批量取消,请谨慎操作。', '确定自动调用变更令?']);
                         },
                         disabled: function (key, opt) {
                             const curChange = SpreadJsObj.getSelectObject(self.changeSheet);
@@ -4584,9 +4627,11 @@ $(document).ready(() => {
                         name: '自动调用(全部变更令)',
                         icon: 'fa-play',
                         callback: function (key, opt) {
-                            postData(window.location.pathname + '/auto-use-change', { autoType: 'all' }, function(result) {
-                                window.location.reload();
-                            });
+                            checkAgainHint(function(){
+                                postData(window.location.pathname + '/auto-use-change', { autoType: 'all' }, function(result) {
+                                    window.location.reload();
+                                });
+                            }, ['自动调用后,已调用变更令无法批量取消,请谨慎操作。', '确定自动调用变更令?']);
                         },
                         disabled: function (key, opt) {
                             const curChange = SpreadJsObj.getSelectObject(self.changeSheet);
@@ -4868,6 +4913,7 @@ $(document).ready(() => {
             tabPanel.removeClass('active');
             showSideTools(tab.hasClass('active'));
         }
+        autoFlashHeight();
         slSpread.refresh();
         spSpread.refresh();
     });

+ 12 - 1
app/router.js

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

+ 2 - 2
app/service/filing.js

@@ -220,7 +220,7 @@ module.exports = app => {
             const delData = posterity.map(x => {return { id: x.id, is_deleted: 1 }; });
             delData.push({ id: data.id, is_deleted: 1});
 
-            const sibling = await this.getAllDataByCondition({ where: { tree_pid: filing.tree_pid } });
+            const sibling = await this.getAllDataByCondition({ where: { tree_pid: filing.tree_pid, spid: delData.spid, is_deleted: 0 } });
             const updateData = [];
             sibling.forEach(x => {
                 if (x.tree_order > filing.tree_order) updateData.push({ id: x.id, tree_order: x.tree_order - 1});
@@ -267,7 +267,7 @@ module.exports = app => {
                     });
                 }
             } else {
-                const orgSibling = await this.getAllDataByCondition({ where: { tree_pid: filing.tree_pid, is_deleted: 0 } });
+                const orgSibling = await this.getAllDataByCondition({ where: { spid: filing.spid, tree_pid: filing.tree_pid, is_deleted: 0 } });
                 orgSibling.forEach(x => {
                     if (x.id === filing.id) return;
                     if (x.tree_order < filing.tree_order) return;

+ 11 - 7
app/service/financial_pay.js

@@ -46,10 +46,10 @@ module.exports = app => {
                 fpsid = fpsid instanceof Array ? fpsid : [fpsid];
                 addSql += ' AND a.fpsid in (' + this.ctx.helper.getInArrStrSqlFilter(fpsid) + ')';
             } else if (!this.ctx.session.sessionUser.is_admin && this._.includes([0, 5, 3], status)) {
-                addSql += ` AND (a.id in(SELECT b.fpid FROM ${this.ctx.service.financialPayAudit.tableName} as b WHERE b.spid = '${spid}' AND b.aid = ${this.ctx.session.sessionUser.accountId}) OR a.uid = ${this.ctx.session.sessionUser.accountId}`;
+                addSql += ` AND (a.id in (SELECT b.fpid FROM ${this.ctx.service.financialPayAudit.tableName} as b WHERE b.spid = '${spid}' AND b.aid = ${this.ctx.session.sessionUser.accountId}) OR a.uid = ${this.ctx.session.sessionUser.accountId}`;
                 if (allfpsid !== null) {
-                    allfpsid = fpsid instanceof Array ? allfpsid : [allfpsid];
-                    addSql += ' OR a.fpsid in (' + this.ctx.helper.getInArrStrSqlFilter(allfpsid) + ')';
+                    allfpsid = allfpsid instanceof Array ? allfpsid : [allfpsid];
+                    addSql += allfpsid.length > 0 ? ' OR a.fpsid in (' + this.ctx.helper.getInArrStrSqlFilter(allfpsid) + ')' : '';
                 }
                 addSql += ')';
             }
@@ -131,8 +131,8 @@ module.exports = app => {
             } else if (!this.ctx.session.sessionUser.is_admin && this._.includes([0, 5, 3], status)) {
                 addSql += ` AND (a.id in(SELECT b.fpid FROM ${this.ctx.service.financialPayAudit.tableName} as b WHERE b.spid = '${spid}' AND b.aid = ${this.ctx.session.sessionUser.accountId}) OR a.uid = ${this.ctx.session.sessionUser.accountId}`;
                 if (allfpsid !== null) {
-                    allfpsid = fpsid instanceof Array ? allfpsid : [allfpsid];
-                    addSql += ' OR a.fpsid in (' + this.ctx.helper.getInArrStrSqlFilter(allfpsid) + ')';
+                    allfpsid = allfpsid instanceof Array ? allfpsid : [allfpsid];
+                    addSql += allfpsid.length > 0 ? ' OR a.fpsid in (' + this.ctx.helper.getInArrStrSqlFilter(allfpsid) + ')' : '';
                 }
                 addSql += ')';
             }
@@ -186,14 +186,18 @@ module.exports = app => {
             }
         }
 
-        async getUserTenderList(spid, uid, fpsid = null) {
+        async getUserTenderList(spid, uid, allFpsidList, fpsid = null) {
             let addSql = '';
             let notAdminSql = '';
             if (fpsid) {
                 addSql += ' AND a.fpsid = ' + fpsid;
             }
             if (!this.ctx.session.sessionUser.is_admin) {
-                notAdminSql += 'a.tid in (SELECT tid FROM ' + this.ctx.service.financialPayAudit.tableName + ' WHERE spid = "' + spid + '" AND aid = ' + this.ctx.session.sessionUser.accountId + (fpsid ? ' AND fpsid = ' + fpsid : '') + ' GROUP BY tid)';
+                notAdminSql += '(a.tid in (SELECT tid FROM ' + this.ctx.service.financialPayAudit.tableName + ' WHERE spid = "' + spid + '" AND aid = ' + this.ctx.session.sessionUser.accountId + (fpsid ? ' AND fpsid = ' + fpsid : '') + ' GROUP BY tid)';
+                if (!fpsid && allFpsidList && allFpsidList.length > 0) {
+                    notAdminSql += ' OR a.tid in (SELECT tid FROM ' + this.tableName + ' WHERE spid = "' + spid + '" AND fpsid in (' + this.ctx.helper.getInArrStrSqlFilter(allFpsidList) + ') GROUP BY tid)';
+                }
+                notAdminSql += ')';
                 addSql += ' AND (a.uid = ' + uid + ' OR ' + notAdminSql + ')';
             }
             const sql = 'SELECT a.tid, t.name FROM ?? As a LEFT JOIN ?? As t ON a.tid = t.id' +

+ 315 - 0
app/service/quality_inspection.js

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

+ 82 - 0
app/service/quality_inspection_att.js

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

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1185 - 0
app/service/quality_inspection_audit.js


+ 14 - 0
app/service/spec_pull.js

@@ -25,6 +25,20 @@ module.exports = app => {
             const pull = await this.getDataByCondition({ pid, valid: 1, pull_type: 'sync-ledger' });
             return pull;
         }
+
+        async syncRevise(pid) {
+            const pull = await this.getDataByCondition({ pid, valid: 1, pull_type: 'sync-revise' });
+            return pull;
+        }
+
+        async loadWbsFile(pid) {
+            const pull = await this.getDataByCondition({ pid, valid: 1, pull_type: 'load-wbs-info' });
+            if (pull) {
+                pull.extra_option = pull.extra_option ? JSON.parse(pull.extra_option) : {};
+                delete pull.info;
+            }
+            return pull;
+        }
     }
 
     return SpecPull;

+ 1 - 1
app/service/stage.js

@@ -807,7 +807,7 @@ module.exports = app => {
                 await transaction.delete(this.ctx.service.stageAtt.tableName, { tid: stageInfo.tid, sid: stageInfo.order });
                 // 其他台账
                 await transaction.delete(this.ctx.service.stageJgcl.tableName, { sid: id });
-                const bonus = await this.ctx.service.stageBonus.getStageData(id);
+                const bonus = await this.ctx.service.stageBonus.getStageData(stageInfo);
                 if (bonus && bonus.length > 0) {
                     for (const b of bonus) {
                         for (const f of b.proof_file) {

+ 13 - 12
app/service/stage_bonus.js

@@ -40,18 +40,19 @@ module.exports = app => {
             }
         }
 
-        async getStageData(sid, cancel = false) {
-            const data = await this.getAllDataByCondition({where: { sid: sid }});
-            if (!cancel && this.ctx.stage && this.ctx.stage.readOnly && !this.ctx.tender.isTourist && this.ctx.stage.status !== auditConst.status.checked) {
-                for (const d of data) {
-                    const his = d.shistory ? JSON.parse(d.shistory) : [];
-                    const h = this.ctx.helper._.find(his, {
-                        stimes: this.ctx.stage.curTimes, sorder: this.ctx.stage.curOrder
-                    });
-                    d.tp = h ? h.tp : null;
-                }
-            }
+        async getStageData(stage, cancel = false) {
+            const data = await this.getAllDataByCondition({where: { sid: stage.id }});
             this._parseData(data);
+            if (!stage || cancel || !stage.readOnly || this.ctx.tender.isTourist || stage.status === auditConst.status.checked
+                || (stage.flowAuditorIds && stage.flowAuditorIds.indexOf(this.ctx.session.sessionUser.accountId) > 0)) return data;
+
+            for (const d of data) {
+                const his = d.shistory ? JSON.parse(d.shistory) : [];
+                const h = this.ctx.helper._.find(his, {
+                    stimes: this.ctx.stage.curTimes, sorder: this.ctx.stage.curOrder
+                });
+                d.tp = h ? h.tp : null;
+            }
             return data;
         }
 
@@ -176,7 +177,7 @@ module.exports = app => {
         }
 
         async updateHistory4TimesOrder(stage, times, order, transaction) {
-            const datas = await this.getStageData(stage.id);
+            const datas = await this.getStageData(stage);
             if (datas.length === 0) return;
 
             const updateDatas = [];

+ 5 - 4
app/service/stage_change.js

@@ -167,7 +167,7 @@ class autoUseChange {
                 end_contract_qty = this.helper.add(end_contract_qty, preSb.contract_qty);
             }
             const end_contract_tp = this.helper.mul(this.helper.div(end_contract_qty, activeQty), bills.total_price, this.decimal.tp);
-            return preSb ? this.helper.sub(end_contract_tp, preSb.pre_contract_tp) : end_contract_tp;
+            return preSb ? this.helper.sub(end_contract_tp, preSb.contract_tp) : end_contract_tp;
         } else if (info.calc_type === 'up') {
             if (this.correct) {
                 const preSb = this.preStageBills.find(x => { return x.lid === bills.id; });
@@ -178,7 +178,7 @@ class autoUseChange {
                     end_contract_qty = this.helper.add(end_contract_qty, preSb.contract_qty);
                 }
                 const end_contract_tp = this.helper.mul(end_contract_qty, bills.unit_price, this.decimal.tp);
-                return activeQty === end_contract_qty ? this.helper.sub(end_contract_tp, preSb.pre_contract_tp) : this.helper.mul(contract_qty, bills.unit_price, this.decimal.tp);
+                return activeQty === end_contract_qty ? this.helper.sub(end_contract_tp, preSb.contract_tp) : this.helper.mul(contract_qty, bills.unit_price, this.decimal.tp);
             } else {
                 return this.helper.mul(contract_qty, bills.unit_price, this.decimal.tp);
             }
@@ -249,12 +249,13 @@ class autoUseChange {
             const sb = this.stageBills.find(x => {return x.lid === lid});
 
             if (sb) {
-                const contract_tp = qc_minus_qty && sb.contract_qty ? this.calcContractTp(this.info, cb.bills, sb.contract_qty, qc_minus_qty) : 0;
+                const contract_tp = qc_minus_qty ? this.calcContractTp(this.info, cb.bills, sb.contract_qty || 0, qc_minus_qty) : sb.contract_tp;
                 this.updateBills.push({ id: sb.id, qc_qty, qc_tp, positive_qc_qty, positive_qc_tp, negative_qc_qty, negative_qc_tp, qc_minus_qty, contract_tp });
             } else {
+                const contract_tp = qc_minus_qty ? this.calcContractTp(this.info, cb.bills, 0, qc_minus_qty) : 0;
                 this.insertBills.push({
                     tid: this.default.tid, sid: this.default.sid, said: this.default.said,
-                    lid, qc_qty, qc_tp, positive_qc_qty, positive_qc_tp, negative_qc_qty, negative_qc_tp, qc_minus_qty, times: 1, order: 0
+                    lid, contract_tp, qc_qty, qc_tp, positive_qc_qty, positive_qc_tp, negative_qc_qty, negative_qc_tp, qc_minus_qty, times: 1, order: 0
                 });
             }
         }

+ 18 - 17
app/service/stage_jgcl.js

@@ -24,23 +24,24 @@ module.exports = app => {
 
         async getStageData(stage, cancel = false) {
             const data = await this.getAllDataByCondition({where: { sid: stage.id }});
-            if (!cancel && stage && stage.readOnly && !this.ctx.tender.isTourist && stage.status !== auditConst.status.checked) {
-                for (const d of data) {
-                    const his = d.shistory ? JSON.parse(d.shistory) : [];
-                    const h = this.ctx.helper._.find(his, {
-                        stimes: stage.curTimes, sorder: stage.curOrder
-                    });
-                    if (h) {
-                        d.arrive_qty = h.arrive_qty ? h.arrive_qty : h.arraive_qty;
-                        d.arrive_tp = h.arrive_tp;
-                        d.deduct_qty = h.deduct_qty;
-                        d.deduct_tp = h.deduct_tp;
-                    } else {
-                        d.arrive_qty = null;
-                        d.arrive_tp = null;
-                        d.deduct_qty = null;
-                        d.deduct_tp = null;
-                    }
+            if (!stage || cancel || !stage.readOnly || this.ctx.tender.isTourist || stage.status === auditConst.status.checked
+                || (stage.flowAuditorIds && stage.flowAuditorIds.indexOf(this.ctx.session.sessionUser.accountId) > 0)) return data;
+
+            for (const d of data) {
+                const his = d.shistory ? JSON.parse(d.shistory) : [];
+                const h = this.ctx.helper._.find(his, {
+                    stimes: stage.curTimes, sorder: stage.curOrder
+                });
+                if (h) {
+                    d.arrive_qty = h.arrive_qty ? h.arrive_qty : h.arraive_qty;
+                    d.arrive_tp = h.arrive_tp;
+                    d.deduct_qty = h.deduct_qty;
+                    d.deduct_tp = h.deduct_tp;
+                } else {
+                    d.arrive_qty = null;
+                    d.arrive_tp = null;
+                    d.deduct_qty = null;
+                    d.deduct_tp = null;
                 }
             }
             return data;

+ 9 - 8
app/service/stage_other.js

@@ -24,14 +24,15 @@ module.exports = app => {
 
         async getStageData(stage, cancel = false) {
             const data = await this.getAllDataByCondition({where: { sid: stage.id }});
-            if (!cancel && stage && stage.readOnly && !this.ctx.tender.isTourist && stage.status !== auditConst.status.checked) {
-                for (const d of data) {
-                    const his = d.shistory ? JSON.parse(d.shistory) : [];
-                    const h = this.ctx.helper._.find(his, {
-                        stimes: stage.curTimes, sorder: stage.curOrder
-                    });
-                    d.tp = h ? h.tp : null;
-                }
+            if (!stage || cancel || !stage.readOnly || this.ctx.tender.isTourist || stage.status === auditConst.status.checked
+                || (stage.flowAuditorIds && stage.flowAuditorIds.indexOf(this.ctx.session.sessionUser.accountId) > 0)) return data;
+
+            for (const d of data) {
+                const his = d.shistory ? JSON.parse(d.shistory) : [];
+                const h = this.ctx.helper._.find(his, {
+                    stimes: stage.curTimes, sorder: stage.curOrder
+                });
+                d.tp = h ? h.tp : null;
             }
             return data;
         }

+ 11 - 10
app/service/stage_safe_prod.js

@@ -25,16 +25,17 @@ module.exports = app => {
 
         async getStageData(stage, cancel) {
             const data = await this.getAllDataByCondition({where: { sid: stage.id }});
-            if (!cancel && stage && stage.readOnly && !this.ctx.tender.isTourist && stage.status !== auditConst.status.checked) {
-                for (const d of data) {
-                    const his = d.shistory ? JSON.parse(d.shistory) : [];
-                    const h = this.ctx.helper._.find(his, {
-                        stimes: stage.curTimes, sorder: stage.curOrder
-                    });
-                    delete d.shistory;
-                    d.qty = h ? h.qty : null;
-                    d.tp = h ? h.tp : null;
-                }
+            if (!stage || cancel || !stage.readOnly || this.ctx.tender.isTourist || stage.status === auditConst.status.checked
+                || (stage.flowAuditorIds && stage.flowAuditorIds.indexOf(this.ctx.session.sessionUser.accountId) > 0)) return data;
+
+            for (const d of data) {
+                const his = d.shistory ? JSON.parse(d.shistory) : [];
+                const h = this.ctx.helper._.find(his, {
+                    stimes: stage.curTimes, sorder: stage.curOrder
+                });
+                delete d.shistory;
+                d.qty = h ? h.qty : null;
+                d.tp = h ? h.tp : null;
             }
             return data;
         }

+ 2 - 3
app/service/stage_stash.js

@@ -73,7 +73,7 @@ class loadStageExcelTree {
                 end_contract_qty = this.ctx.helper.add(end_contract_qty, preSb.contract_qty);
             }
             const end_contract_tp = this.ctx.helper.mul(this.ctx.helper.div(end_contract_qty, activeQty), node.total_price, this.decimal.tp);
-            return preSb ? this.ctx.helper.sub(end_contract_tp, preSb.pre_contract_tp) : end_contract_tp;
+            return preSb ? this.ctx.helper.sub(end_contract_tp, preSb.contract_tp) : end_contract_tp;
         } else if (this.info.calc_type === 'up') {
             if (correct) {
                 const preSb = this.preStageBills.find(x => { return x.lid === node.id; });
@@ -84,7 +84,7 @@ class loadStageExcelTree {
                     end_contract_qty = this.ctx.helper.add(end_contract_qty, preSb.contract_qty);
                 }
                 const end_contract_tp = this.ctx.helper.mul(end_contract_qty, node.unit_price, this.decimal.tp);
-                return activeQty === end_contract_qty ? this.ctx.helper.sub(end_contract_tp, preSb.pre_contract_tp) : this.ctx.helper.mul(contract_qty, node.unit_price, this.decimal.tp);
+                return activeQty === end_contract_qty ? this.ctx.helper.sub(end_contract_tp, preSb.contract_tp) : this.ctx.helper.mul(contract_qty, node.unit_price, this.decimal.tp);
             } else {
                 return this.ctx.helper.mul(contract_qty, node.unit_price, this.decimal.tp);
             }
@@ -280,7 +280,6 @@ module.exports = app => {
             return data.bills;
         }
 
-
         async reCalcStashData(stage, data) {
             const correct = this.ctx.subProject.page_show.correctCalcContractTp;
             const calcContractTp = function(helper, info, stageBills, bills) {

+ 11 - 10
app/service/stage_temp_land.js

@@ -25,16 +25,17 @@ module.exports = app => {
 
         async getStageData(stage, cancel = false) {
             const data = await this.getAllDataByCondition({where: { sid: stage.id }});
-            if (!cancel && stage && stage.readOnly && !this.ctx.tender.isTourist && stage.status !== auditConst.status.checked) {
-                for (const d of data) {
-                    const his = d.shistory ? JSON.parse(d.shistory) : [];
-                    const h = this.ctx.helper._.find(his, {
-                        stimes: stage.curTimes, sorder: stage.curOrder
-                    });
-                    delete d.shistory;
-                    d.qty = h ? h.qty : null;
-                    d.tp = h ? h.tp : null;
-                }
+            if (!stage || cancel || !stage.readOnly || this.ctx.tender.isTourist || stage.status === auditConst.status.checked
+                || (stage.flowAuditorIds && stage.flowAuditorIds.indexOf(this.ctx.session.sessionUser.accountId) > 0)) return data;
+
+            for (const d of data) {
+                const his = d.shistory ? JSON.parse(d.shistory) : [];
+                const h = this.ctx.helper._.find(his, {
+                    stimes: stage.curTimes, sorder: stage.curOrder
+                });
+                delete d.shistory;
+                d.qty = h ? h.qty : null;
+                d.tp = h ? h.tp : null;
             }
             return data;
         }

+ 2 - 1
app/service/stage_yjcl.js

@@ -26,7 +26,8 @@ module.exports = app => {
 
         async getStageData(stage) {
             const data = await this.getAllDataByCondition({ where: { sid: stage.id } });
-            if (!stage.readOnly || stage.status === auditConst.status.checked) return data;
+            if (!stage.readOnly  || stage.status === auditConst.status.checked
+                || (stage.flowAuditorIds && stage.flowAuditorIds.indexOf(this.ctx.session.sessionUser.accountId) > 0)) return data;
 
             for (const d of data) {
                 const his = d.shistory ? JSON.parse(d.shistory) : [];

+ 1 - 0
app/service/tender_info.js

@@ -519,6 +519,7 @@ module.exports = app => {
          */
         async getAllTenderShenpiInfo(tenderIds) {
             // const defaultInfo = await this.getDefaultInfo(tenderId);
+            if (!tenderIds || tenderIds.length === 0) return [];
             const infos = await this.getAllDataByCondition({ columns: ['tid', 'shenpi'], where: { tid: tenderIds } });
             const shenpiInfos = [];
             if (!infos || infos.length === 0) return shenpiInfos;

+ 86 - 2
app/service/tender_permission.js

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

+ 2 - 2
app/view/change/index.ejs

@@ -108,9 +108,9 @@
                         <td class="text-center"><%- (pageInfo.page-1)*pageInfo.pageSize + index+1 %></td>
                         <td><a href="/tender/<%- tender.id %>/change/<%- c.cid %>/information"><% if (c.status !== auditConst.status.checked) { %><%- c.code %><% } else { %><%- c.p_code %><% } %></a></td>
                         <td><%- c.name %></td>
-                        <td><%- qualityArray[c.quality] %><% c.quality %></td>
+                        <td class="text-center"><%- qualityArray[c.quality] %><% c.quality %></td>
                         <% if (ctx.subProject.page_show.openChangeState) { %>
-                        <td><%- ctx.helper._.find(changeState, { order: c.state }).name %></td>
+                        <td class="text-center"><%- ctx.helper._.find(changeState, { order: c.state }).name %></td>
                         <% } %>
                         <td style="text-align: right"><%= ctx.helper.roundNum(c.total_price, tpUnit) %></td>
                         <td style="text-align: right"><%= ctx.helper.roundNum(c.valuation_tp, tpUnit) %></td>

+ 7 - 0
app/view/change/revise.ejs

@@ -86,6 +86,13 @@
                                     </div>
                                 </div>
                             </li>
+                            <li class="nav-item">
+                                <div class="d-inline-block">
+                                    <% if (!readOnly) { %>
+                                    <a href="javascript:void(0);"  data-toggle="modal" data-target="#batch-pos" class="btn btn-sm btn-primary ml-3">批量录入</a>
+                                    <% } %>
+                                </div>
+                            </li>
                         </ul>
                     </div>
                     <div class="sp-wrap" id="pos-spread">

+ 46 - 0
app/view/change/revise_modal.ejs

@@ -126,6 +126,52 @@
     </div>
 </div>
 <% } %>
+<% if (!readOnly) { %>
+    <div class="modal fade" id="batch-pos" data-backdrop="static">
+        <div class="modal-dialog modal-lgx" role="document" >
+            <div class="modal-content">
+                <div class="modal-header">
+                    <h5 class="modal-title">批量插入变更数量</h5>
+                </div>
+                <div class="modal-body">
+                    <div class="row">
+                        <div class="col-12">
+                            <div class="pl-2" style="line-height: 28px;"><b id="batch-ledger-name">403-1-b</b> 计量单元</div>
+                            <div class="modal-height-250" id="origin-pos-spread">
+                            </div>
+                        </div>
+                    </div>
+                    <div class="row">
+                        <div class="col-6">
+                            <div class="d-inline-block">
+                                <h5 class="pl-2" style="line-height: 28px;">粘贴区 </h5>
+                            </div>
+                            <div class="d-inline-block ml-3">
+                                <button class="btn btn-sm btn-outline-primary" id="clear-bp-btn">清空</button>
+                            </div>
+                            <div class="d-inline-block ml-3">
+                                <p class="text-warning">计量单元仅支持粘贴;绿色行表示已存在录入区的计量单元。</p>
+                            </div>
+                            <div class="modal-height-300" id="batch-pos-spread">
+                            </div>
+                        </div>
+                        <div class="col-6">
+                            <div class="d-inline-block">
+                                <h5 class="pl-2" style="line-height: 28px;">录入区 </h5>
+                            </div>
+                            <div class="modal-height-300" id="save-pos-spread">
+                            </div>
+                        </div>
+                    </div>
+                </div>
+                <div class="modal-footer">
+                    <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                    <button type="button" class="btn btn-sm btn-primary" id="add-changelist-btn">确定</button>
+                </div>
+            </div>
+        </div>
+    </div>
+<% } %>
 <% include ../shares/delete_hint_modal.ejs %>
 <% include ../shares/check_data_modal.ejs %>
 <% include ../shares/check_modal2.ejs %>

+ 20 - 26
app/view/contract/tender.ejs

@@ -1,13 +1,7 @@
-<% include ../tender/list_sub_menu.ejs %>
 <div class="panel-content">
     <div class="panel-title fluid">
-        <div class="title-main  d-flex">
-            <% include ../tender/list_sub_mini_menu.ejs %>
-            <div class="d-inline-block">
-                <div class="btn-group group-tab">
-                </div>
-            </div>
-            <div class="d-inline-block mr-2" id="show-level"></div>
+        <div class="title-main d-flex justify-content-between">
+            <div id="show-level"></div>
         </div>
     </div>
     <div class="content-wrap">
@@ -36,22 +30,22 @@
 
     const uphlname = 'user_' + uid + '_pro_' + pid + '_category_hide_contract_list';
 
-    $.subMenu({
-        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
-        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
-        key: 'list.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();
-        }
-    });
+    // $.subMenu({
+    //     menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+    //     toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+    //     key: 'list.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();
+    //     }
+    // });
 </script>

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

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

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

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

+ 1 - 1
app/view/financial/pay_detail_modal.ejs

@@ -10,7 +10,7 @@
                 </button>
             </div>
             <div class="modal-body">
-                <p>单个文件大小限制:f0MB,支持office等文档格式、图片格式、压缩包格式</p>
+                <p>单个文件大小限制:50MB,支持office等文档格式、图片格式、压缩包格式</p>
                 <!-- <p><a href="javascript: void(0);" class="btn btn-primary" id="file-modal-target">选择文件</a></p> -->
                 <input type="file" id="file-modal" multiple="multiple">
             </div>

+ 29 - 10
app/view/layout/menu.ejs

@@ -1,6 +1,7 @@
 <div class="main-nav d-flex align-items-start flex-column">
-    <div class="logo">
-        <img class="w-100" style="padding: 5px" src="/public/css/logo.png">
+    <div class="logo w-100 logo-parent">
+        <img class="w-50" style="padding: 5px" src="/public/css/logo.png">
+        <span class="text-white logo-project-name"><% if (ctx.subProject) { %> <%- ctx.subProject.name %><% } else { %> <span data-toggle="tooltip" data-placement="right" title="" data-original-title="<%- ctx.session.sessionProject.name %>"><%- ctx.session.sessionProject.name %></span><% } %></span>
         <% if (ctx.subProject) { %>
         <span class="poj-info btn-warning" data-toggle="tooltip" data-placement="right" title="" data-original-title="<%- ctx.subProject.name %>"><i class="fa fa-cube te"></i></span>
         <% } %>
@@ -10,36 +11,54 @@
             <% if (maintainData.status !== maintainConst.status.notset && new Date().getTime() + (60*60*1000) > parseFloat(maintainData.maintain_time)) { %>
             <!--系统维护信息-->
             <li class="bg-danger">
-                <a class="text-white maintain-icon"><i class="fa fa-wrench "></i>
+                <a class="text-white maintain-icon"><i class="fa fa-wrench "></i><div class="d-inline-block" style="vertical-align: text-bottom;"> 系统维护</div>
                     <span class="bg-danger maintain-info">系统将于 <%- ctx.helper.dateTran(parseFloat(maintainData.maintain_time)) %> 开始停机维护<%- (maintainData.duration !== maintainConst.duration.forever ? ',持续'+ maintainConst.durationString[maintainData.duration] +'。' : '') %></span></a></li>
             <!--系统维护信息 end-->
             <% } %>
             <% for (const index in ctx.menuList) { %>
             <% const menu = ctx.menuList[index]; %>
             <% if (!menu.display) { continue } %>
+            <% if (menu.children && menu.children.length > 0) { %>
+            <li>
+                <a href="javascript:void(0);" id="<%- 'nav_' + index%>" class="nav-up-down">
+                    <i class="fa <%- menu.icon %>"></i>
+                    <% if (menu.caption) { %><span class="d-inline-block" style="vertical-align: text-bottom;"><%- menu.caption %></span><% } %>
+                    <i class="fa fa-angle-down d-inline-block child-up-down"></i>
+                </a>
+            </li>
+            <% for (const child of menu.children) { %>
+                <li data-index="<%- 'nav_' + index %>" class="child-bg <% if((ctx.controllerName === child.controller && (!child.notIncludedUrl || !child.notIncludedUrl.some(item => ctx.url.includes(item)))) || (!!child.controllers && child.controllers.indexOf(ctx.controllerName) >= 0 && (!child.includedUrl || !child.includedUrl[ctx.controllerName] || child.includedUrl[ctx.controllerName].some(item => ctx.url.includes(item)))) || (child.url && child.url === ctx.url)) { %>active<% } %>">
+                    <a href="<%- child.url %>" data-toggle="tooltip" data-placement="right" title="" data-original-title="<%- child.name %>">
+                        <i></i>
+                        <% if (child.caption) { %><span class="d-inline-block" style="vertical-align: text-bottom;"><%- child.caption %></span><% } %>
+                    </a>
+                </li>
+            <% } %>
+            <% } else { %>
             <li <% if((ctx.controllerName === menu.controller && (!menu.notIncludedUrl || !menu.notIncludedUrl.some(item => ctx.url.includes(item)))) || (!!menu.controllers && menu.controllers.indexOf(ctx.controllerName) >= 0 && (!menu.includedUrl || !menu.includedUrl[ctx.controllerName] || menu.includedUrl[ctx.controllerName].some(item => ctx.url.includes(item)))) || (menu.url && menu.url === ctx.url)) { %>class="active"<% } %>>
                 <a href="<%- menu.url %>" id="<%- 'nav_' + index%>" data-toggle="tooltip" data-placement="right" title="" data-original-title="<%- menu.name %>">
                     <i class="fa <%- menu.icon %>"></i>
-                    <% if (menu.caption) { %><span><%- menu.caption %></span><% } %>
+                    <% if (menu.caption) { %><span class="d-inline-block" style="vertical-align: text-bottom;"><%- menu.caption %></span><% } %>
                 </a>
             </li>
             <% } %>
+            <% } %>
         </ul>
     </div>
     <div class="nav-bottom mt-auto">
         <ul class="nav nav-pills nav-stacked bg-nav">
-            <li><a href="https://doc.zhzdwd.com/docs/yunjiliangAPI/yunjiliangAPI-1ccjk7h426enp" target="_blank" data-toggle="tooltip" data-placement="right" title="" data-original-title="数据接口">API</a></li>
+            <li><a href="https://doc.zhzdwd.com/docs/yunjiliangAPI/yunjiliangAPI-1ccjk7h426enp" target="_blank" data-toggle="tooltip" data-placement="right" title="" data-original-title="数据接口" class="d-inline-block">API</a></li>
         </ul>
         <ul class="nav nav-pills nav-stacked bg-nav">
             <% if (ctx.isProjectController) { %>
-            <li <% if (ctx.controllerName === 'setting') { %>class="active"<% } %>><a href="/setting/info" data-toggle="tooltip" data-placement="right" title="" data-original-title="平台设置"><i class="fa fa-cogs"></i><span>平台设置</span></a></li>
+            <li <% if (ctx.controllerName === 'setting') { %>class="active"<% } %>><a href="/setting/info" data-toggle="tooltip" data-placement="right" title="" data-original-title="平台设置"><i class="fa fa-cogs"></i><span class="d-inline-block" style="vertical-align: text-bottom;">平台设置</span></a></li>
             <% } else if (ctx.session.sessionUser.is_admin) { %>
-            <li <% if (ctx.controllerName === 'setting') { %>class="active"<% } %>><a href="/sp/<%- ctx.subProject.id %>/setting/category" data-toggle="tooltip" data-placement="right" title="" data-original-title="项目设置"><i class="fa fa-cogs"></i><span>项目设置</span></a></li>
+            <li <% if (ctx.controllerName === 'setting') { %>class="active"<% } %>><a href="/sp/<%- ctx.subProject.id %>/setting/category" data-toggle="tooltip" data-placement="right" title="" data-original-title="项目设置"><i class="fa fa-cogs"></i><span class="d-inline-block" style="vertical-align: text-bottom;"> 项目设置</span></a></li>
             <% } %>
         </ul>
-        <div class="dropup mb-1 ml-1 mr-1">
-            <a href="" class="btn btn-sm btn-light p-1 w-100" data-toggle="dropdown" aria-haspopup="false" aria-expanded="false">
-                <%- ctx.session.sessionUser.name.substr(ctx.session.sessionUser.name.length > 3 ? ctx.session.sessionUser.name.length - 3 : 0) %>
+        <div class="dropup mb-1 ml-1 mr-1 d-inline-block text-center" style="width: 100px;">
+            <a href="" class="btn btn-sm btn-light p-1" style="width: 70px;" data-toggle="dropdown" aria-haspopup="false" aria-expanded="false">
+                <%- ctx.session.sessionUser.name.substr(ctx.session.sessionUser.name.length > 6 ? ctx.session.sessionUser.name.length - 6 : 0) %>
             </a>
             <div class="dropdown-menu">
                 <a href="/profile/info" class="dropdown-item">账号资料</a>

+ 6 - 2
app/view/material/audit_modal.ejs

@@ -445,8 +445,7 @@
         <!--审批退回-->
         <div class="modal fade sp-location-list" id="sp-back" data-backdrop="static">
             <div class="modal-dialog modal-lg" role="document">
-                <form class="modal-content modal-lg" action="<%- preUrl %>/audit/check" method="post"
-                      onsubmit="return auditCheck(1);">
+                <form class="modal-content modal-lg" action="<%- preUrl %>/audit/check" method="post" id="audit-check1">
                     <div class="modal-header">
                         <h5 class="modal-title">审批退回</h5>
                     </div>
@@ -1012,6 +1011,11 @@
         }
         return false;
     });
+    function auditCheck(i) {
+        const opinion = $('textarea[name="opinion"]').eq(i).val().replace(/\r\n/g, '<br/>').replace(/\n/g, '<br/>').replace(/\s/g, ' ');
+        $('textarea[name="opinion"]').eq(i).val(opinion);
+        return true;
+    }
 
     // 展开历史审核记录
     $('.modal-body #fold-btn').click(function () {

+ 3 - 1
app/view/phase_pay/index.ejs

@@ -6,7 +6,7 @@
             <h2>
                 合同支付列表
             </h2>
-            <% if (ctx.session.sessionUser.accountId === ctx.tender.data.user_id && (phasePays.length === 0 || phasePays[0].audit_status === auditConst.status.checked) && validStages.length > 0) { %>
+            <% if (ctx.session.sessionUser.accountId === ctx.tender.data.user_id && (phasePays.length === 0 || phasePays[0].audit_status === auditConst.status.checked)) { %>
             <div class="ml-auto">
                 <a href="#add-qi" data-toggle="modal" data-target="#add-qi" class="btn btn-primary btn-sm">开始新一期</a>
             </div>
@@ -88,6 +88,8 @@
         </div>
     </div>
 </div>
+<link href="/public/css/bootstrap/select2.min.css" rel="stylesheet" />
+<script src="/public/js/bootstrap/select2.min.js"></script>
 <script>
     const phasePays = JSON.parse('<%- JSON.stringify(phasePays) %>');
     const auditType = JSON.parse('<%- JSON.stringify(auditType) %>');

+ 1 - 6
app/view/phase_pay/modal.ejs

@@ -18,7 +18,7 @@
                     <label>计量期</label>
                     <select class="form-control form-control-sm" name="stage">
                         <% for (const s of validStages) { %>
-                            <option value="<%- s.order %>" <%- (s.order === validStages[0].order ? 'selected' : '') %>>第 <%- s.order %> 期</option>
+                            <option value="<%- s.order %>">第 <%- s.order %> 期</option>
                         <% } %>
                     </select>
                 </div>
@@ -98,11 +98,6 @@
             toastr.error('请选择计量年月');
             return false;
         }
-
-        if ($('[name=stage]', '#add-qi').val() == '') {
-            toastr.error('请选择计量期');
-            return false;
-        }
     }
     const checkEditValid = function() {
         if ($('[name=date]', '#edit-qi').val() == '') {

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

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

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

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

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

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

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 468 - 0
app/view/quality/inspection_information.ejs


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

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

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

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

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

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

+ 17 - 0
app/view/quality/list_sub_menu.ejs

@@ -0,0 +1,17 @@
+<div class="panel-sidebar" id="sub-menu">
+    <div class="sidebar-title text-center" data-toggle="tooltip" data-placement="right" data-original-title="<%- ctx.subProject.name %>">
+        <%- (ctx.subProject.name.length > 15 ? ctx.subProject.name.substring(0,15) + '...' : ctx.subProject.name) %>
+    </div>
+    <div class="scrollbar-auto">
+        <% include ./list_sub_menu_list.ejs %>
+        <div class="side-show"></div>
+        <div class="side-fold" data-toggle="tooltip" data-placement="top" data-original-title="折叠侧栏" id="to-mini-menu">
+            <i class="fa fa-angle-left"></i>
+        </div>
+    </div>
+    <script>
+        new Vue({
+            el: '.scrollbar-auto',
+        });
+    </script>
+</div>

+ 9 - 0
app/view/quality/list_sub_menu_list.ejs

@@ -0,0 +1,9 @@
+<% if (ctx.subProject.page_show.quality) { %>
+<div class="nav-box">
+    <ul class="nav-list list-unstyled">
+        <li class="<% if (ctx.url === '/sp/' + ctx.subProject.id + '/quality/tender') { %>active<% } %>">
+            <a href="/sp/<%- ctx.subProject.id %>/quality/tender"><span class="ml-3">质量管理</span></a>
+        </li>
+    </ul>
+</div>
+<% } %>

+ 16 - 0
app/view/quality/list_sub_mini_menu.ejs

@@ -0,0 +1,16 @@
+<!--折起的菜单-->
+<div class="min-side" id="sub-mini-menu" style="display: none;">
+    <div class="side-switch" data-toggle="tooltip" data-placement="left" data-original-title="点击这里打开收起的菜单栏">
+        <i class="fa fa-bars mt-2"></i>
+        <i class="fa fa-indent mt-2 text-primary" style="display: none;cursor: pointer;" id="to-menu"></i>
+    </div>
+    <div class="side-menu" id="mini-menu-list" style="display: none">
+        <% include ./list_sub_menu_list.ejs %>
+        <div class="side-fold"><a href="javascript: void(0);" data-toggle="tooltip" data-placement="top" data-original-title="展开侧栏" id="to-menu"><i class="fa fa-upload fa-rotate-90"></i></a></div>
+    </div>
+</div>
+<script>
+    new Vue({
+        el: '.side-menu',
+    });
+</script>

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

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

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


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

@@ -1,15 +1,7 @@
-<% include ../tender/list_sub_menu.ejs %>
 <div class="panel-content">
     <div class="panel-title fluid">
-        <div class="title-main  d-flex justify-content-between">
-            <% include ../tender/list_sub_mini_menu.ejs %>
-            <div class="d-inline-block mr-2">
-                <button type="button" class="btn btn-sm btn-light dropdown-toggle text-primary" data-toggle="dropdown">展开/收起</button>
-                <div class="dropdown-menu">
-                    <a class="dropdown-item tree-toggle" href="javascript:void(0);" data-item="open">展开所有</a>
-                    <a class="dropdown-item tree-toggle" href="javascript:void(0);" data-item="hide">收起所有</a>
-                </div>
-            </div>
+        <div class="title-main  d-flex">
+            <div class="d-inline-block" id="show-level"></div>
             <div class="ml-auto">
                 <% if (ctx.session.sessionUser.is_admin) { %>
                 <a class="btn btn-sm btn-primary mr-2" href="/sp/<%- ctx.subProject.id %>/quality/rule">设置状态规则</a>
@@ -32,23 +24,4 @@
 
     const pid = '<%- ctx.session.sessionProject.id %>';
     const uphlname = 'user_<%- ctx.session.sessionUser.accountId %>_pro_<% ctx.session.sessionProject.id %>_category_hide_list';
-
-    $.subMenu({
-        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
-        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
-        key: 'list.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();
-        }
-    });
-</script>
+</script>

+ 29 - 0
app/view/shares/check_modal.ejs

@@ -0,0 +1,29 @@
+<!--确认-->
+<div class="modal fade" id="check-again-modal" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title" id="cam-caption">确认</h5>
+            </div>
+            <div class="modal-body" id="cam-hint">
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                <button type="button" class="btn btn-sm btn-primary" id="cam-ok" data-dismiss="modal">确定</button>
+            </div>
+        </div>
+    </div>
+</div>
+<script>
+    const checkAgainHint = function (fun, hint, caption = '确认') {
+        $('#cam-cation').html(caption);
+        const hints = hint instanceof Array ? hint : [hint];
+        const hintHtml = hints.map(h => { return `<h6>${h || ''}</h6>`});
+        if (hintHtml) $('#cam-hint').html(hintHtml);
+        $('#check-again-modal').bind('hidden.bs.modal', function () {
+            $('#cam-ok').unbind('click');
+        });
+        $('#cam-ok').bind('click', fun);
+        $('#check-again-modal').modal('show');
+    }
+</script>

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

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

+ 26 - 24
app/view/sp_setting/permission.ejs

@@ -67,31 +67,33 @@
                                             <button class="btn btn-sm btn-warning" id="save-permission" style="display: none;">保存</button>
                                         </div>
                                     </div>
-                                    <div class="card-body py-2 scroll-y" id="sp-pageshow-content">
-                                        <table class="table table-hover table-bordered table-sm">
-                                            <thead><tr class="text-center">
-                                                <th width="120px">用户</th>
-                                                <th width="240px">公司</th>
-                                                <th width="150px">职位</th>
-                                                <% for (const key in permissionConst) { %>
-                                                <th><%- permissionConst[key].title %><br/><input class="ml-1" type="checkbox" name="permission-check-all" ptype="<%- ptype %>" pvalue="<%- permissionConst[key].value %>" style="vertical-align: middle"></th>
+                                    <div class="card-body py-2" id="sp-pageshow-content">
+                                        <div class="scroll-y" style="height: 100%">
+                                            <table class="table table-hover table-bordered table-sm">
+                                                <thead><tr class="text-center" style="position: sticky; top: 0; z-index: 10">
+                                                    <th width="120px">用户</th>
+                                                    <th width="240px">公司</th>
+                                                    <th width="150px">职位</th>
+                                                    <% for (const key in permissionConst) { %>
+                                                    <th><%- permissionConst[key].title %><br/><input class="ml-1" type="checkbox" name="permission-check-all" ptype="<%- ptype %>" pvalue="<%- permissionConst[key].value %>" style="vertical-align: middle"></th>
+                                                    <% } %>
+                                                </tr>
+                                                </thead>
+                                                <tbody class="text-center">
+                                                <% for (const user of subProjectAccountList) { %>
+                                                <% const tp = user[ptype + '_permission'].split(','); %>
+                                                <tr name="user-permission" class="permission-parent" pid="<%- user.permission_id %>">
+                                                    <td><%- user.name %></td>
+                                                    <td><%- user.company %></td>
+                                                    <td><%- user.role %></td>
+                                                    <% for (const key in permissionConst) { %>
+                                                    <td><input type="checkbox" name="permission-check" ptype="<%- ptype %>" pvalue="<%- permissionConst[key].value %>" <%- (tp.indexOf(permissionConst[key].value + '') >= 0 ? 'checked' : '') %> onchange="$('#save-permission').show();"></td>
+                                                    <% } %>
+                                                </tr>
                                                 <% } %>
-                                            </tr>
-                                            </thead>
-                                            <tbody class="text-center">
-                                            <% for (const user of subProjectAccountList) { %>
-                                            <% const tp = user[ptype + '_permission'].split(','); %>
-                                            <tr name="user-permission" class="permission-parent" pid="<%- user.permission_id %>">
-                                                <td><%- user.name %></td>
-                                                <td><%- user.company %></td>
-                                                <td><%- user.role %></td>
-                                                <% for (const key in permissionConst) { %>
-                                                <td><input type="checkbox" name="permission-check" ptype="<%- ptype %>" pvalue="<%- permissionConst[key].value %>" <%- (tp.indexOf(permissionConst[key].value + '') >= 0 ? 'checked' : '') %> onchange="$('#save-permission').show();"></td>
-                                                <% } %>
-                                            </tr>
-                                            <% } %>
-                                            </tbody>
-                                        </table>
+                                                </tbody>
+                                            </table>
+                                        </div>
                                     </div>
                                 </div>
                             </div>

+ 2 - 1
app/view/stage/modal.ejs

@@ -711,4 +711,5 @@
 <% include ../shares/delete_hint_modal.ejs%>
 <% include ../shares/batch_import_modal.ejs%>
 <% include ../shares/import_file_modal.ejs%>
-<% include ../spss/spss_select_modal.ejs %>
+<% include ../spss/spss_select_modal.ejs %>
+<% include ../shares/check_modal.ejs %>

+ 9 - 1
app/view/tender/detail_modal.ejs

@@ -842,6 +842,10 @@
                         <label class="custom-control-label" for="stage-priceDiff">显示“本期补差”列</label>
                     </div>
                     <div class="custom-control custom-checkbox mb-2">
+                        <input type="checkbox" class="custom-control-input" checked="" id="stage-pos-contract-expr">
+                        <label class="custom-control-label" for="stage-pos-contract-expr">显示计量单元“合同公示”列</label>
+                    </div>
+                    <div class="custom-control custom-checkbox mb-2">
                         <input type="checkbox" class="custom-control-input" checked="" id="stage-correct">
                         <label class="custom-control-label" for="stage-correct">使用数量纠正完成率</label>
                     </div>
@@ -1666,6 +1670,7 @@
         $('#thousandth')[0].checked = property.display.thousandth;
         $('#stage-rc')[0].checked = property.display.stage.realComplete;
         $('#stage-priceDiff')[0].checked = property.display.stage.priceDiff;
+        $('#stage-pos-contract-expr')[0].checked = property.display.stage.posContractExpr;
         $('#stage-correct')[0].checked = property.display.stage.correct;
         $('#dayMode')[0].checked = property.display.dayMode;
     }
@@ -1677,7 +1682,10 @@
             display: {
                 ledger: { deal: $('#ledger-deal')[0].checked, dgnQty: $('#ledger-dgn-qty')[0].checked, clQty: $('#ledger-cl-qty')[0].checked, ancillaryGcl: $('#ancillary-gcl')[0].checked, },
                 thousandth: $('#thousandth')[0].checked,
-                stage: { realComplete: $('#stage-rc')[0].checked, correct: $('#stage-correct')[0].checked, priceDiff: $('#stage-priceDiff')[0].checked },
+                stage: {
+                    realComplete: $('#stage-rc')[0].checked, correct: $('#stage-correct')[0].checked,
+                    priceDiff: $('#stage-priceDiff')[0].checked, posContractExpr: $('#stage-pos-contract-expr')[0].checked,
+                },
                 dayMode: $('#dayMode')[0].checked,
             },
         };

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

@@ -1,5 +1,5 @@
 <div class="panel-sidebar" id="sub-menu">
-    <div class="sidebar-title text-center"><i class="fa fa-list-ul fa-fw"></i> 标段管理</div>
+    <div class="sidebar-title text-center"><i class="fa fa-list-ul fa-fw"></i> 计量管理</div>
     <div class="scrollbar-auto">
         <% include ./list_sub_menu_list.ejs %>
         <div class="side-show"></div>

+ 0 - 18
app/view/tender/list_sub_menu_list.ejs

@@ -5,15 +5,6 @@
         </li>
     </ul>
 </div>
-<% if (ctx.subProject.page_show.openTenderContract) { %>
-<div class="nav-box">
-    <ul class="nav-list list-unstyled">
-        <li class="<% if (ctx.url === '/sp/' + ctx.subProject.id + '/contract/tender') { %>active<% } %>">
-            <a href="/sp/<%- ctx.subProject.id %>/contract/tender"><span class="ml-3">合同管理</span></a>
-        </li>
-    </ul>
-</div>
-<% } %>
 <% if (ctx.subProject.page_show.drawing) { %>
 <div class="nav-box">
     <ul class="nav-list list-unstyled">
@@ -23,15 +14,6 @@
     </ul>
 </div>
 <% } %>
-<% if (ctx.subProject.page_show.quality) { %>
-<div class="nav-box">
-    <ul class="nav-list list-unstyled">
-        <li class="<% if (ctx.url === '/sp/' + ctx.subProject.id + '/quality/tender') { %>active<% } %>">
-            <a href="/sp/<%- ctx.subProject.id %>/quality/tender"><span class="ml-3">质量管理</span></a>
-        </li>
-    </ul>
-</div>
-<% } %>
 <% if (ctx.subProject.page_show.openConstruction) { %>
 <div class="nav-box">
     <ul class="nav-list list-unstyled">

+ 33 - 12
config/menu.js

@@ -50,7 +50,7 @@ const projectMenu = {
         name: '项目管理系统',
         icon: 'fa-cubes',
         display: false,
-        caption: '项目管理系统',
+        caption: 'PMSS',
         children: null,
     },
 };
@@ -58,7 +58,7 @@ const projectMenu = {
 const menu = {
     back: {
         name: '项目列表',
-        icon: 'fa-cubes',
+        icon: 'fa-mail-reply-all',
         display: true,
         url: '/subproj',
         children: null,
@@ -70,7 +70,7 @@ const menu = {
         icon: 'fa-check-square-o',
         display: true,
         children: null,
-        caption: '待办',
+        caption: '待办事项',
         controller: 'dashboard',
     },
     datacollect: {
@@ -91,24 +91,45 @@ const menu = {
         controllers: ['info', 'progress', 'data', 'push'],
     },
     tender: {
-        name: '标段管理',
+        name: '计量管理',
         icon: 'fa-list-ul',
         display: true,
         children: null,
-        caption: '标段管理',
+        caption: '计量管理',
         controller: 'list',
-        controllers: ['list', 'tender', 'contract', 'construction', 'spss'],
-        includedUrl: { contract: ['/contract/tender'] },
+        controllers: ['list', 'tender', 'construction', 'spss'],
+        // includedUrl: { contract: ['/contract/tender'] },
     },
     contract: {
         name: '合同管理',
         icon: 'fa-file-text-o',
         display: true,
         // url: '/contract/detail',
-        children: null,
+        children: [
+            {
+                msg: 'subproj',
+                name: '项目合同',
+                caption: '项目合同',
+                controller: 'contract',
+                notIncludedUrl: ['/contract/tender'],
+            },
+            {
+                msg: 'tender',
+                name: '标段合同',
+                caption: '标段合同',
+                controllers: ['contract'],
+                includedUrl: { contract: ['/contract/tender'] },
+            },
+        ],
         caption: '合同管理',
-        controller: 'contract',
-        notIncludedUrl: ['/contract/tender'],
+    },
+    quality: {
+        name: '质量管理',
+        icon: 'fa-file-text-o',
+        display: true,
+        children: null,
+        caption: '质量管理',
+        controller: 'quality',
     },
     file: {
         name: '资料管理',
@@ -390,10 +411,10 @@ const settingMenu = {
         caption: '显示设置',
     },
     manage: {
-        name: '标段管理',
+        name: '计量管理',
         display: false,
         url: '/setting/manage',
-        caption: '标段管理',
+        caption: '计量管理',
     },
 };
 

+ 34 - 0
config/web.js

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

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 54 - 1376
sql/update.sql


+ 0 - 0
sql/update20250928.sql


이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.