Bläddra i källkod

Merge branch 'dev' into uat

MaiXinRong 3 år sedan
förälder
incheckning
7005a9b6ca
100 ändrade filer med 6597 tillägg och 637 borttagningar
  1. 1 0
      app.js
  2. 1 0
      app/base/base_bills_service.js
  3. 6 1
      app/base/base_controller.js
  4. 2 0
      app/const/account_permission.js
  5. 90 0
      app/const/audit.js
  6. 5 0
      app/const/change.js
  7. 7 0
      app/const/code_rule.js
  8. 5 0
      app/const/page_show.js
  9. 3 0
      app/const/sign.js
  10. 548 3
      app/controller/change_controller.js
  11. 3 12
      app/controller/dashboard_controller.js
  12. 7 2
      app/controller/ledger_controller.js
  13. 193 3
      app/controller/login_controller.js
  14. 175 5
      app/controller/material_controller.js
  15. 25 75
      app/controller/revise_controller.js
  16. 3 1
      app/controller/setting_controller.js
  17. 14 5
      app/controller/stage_controller.js
  18. 21 4
      app/controller/tender_controller.js
  19. 10 1
      app/extend/context.js
  20. 19 5
      app/extend/helper.js
  21. 1 0
      app/lib/analysis_excel.js
  22. 15 2
      app/lib/pay_calc.js
  23. 56 0
      app/middleware/api3management_check.js
  24. 120 0
      app/middleware/change_project_check.js
  25. 3 0
      app/middleware/material_check.js
  26. 9 2
      app/middleware/revise_check.js
  27. 5 0
      app/middleware/stage_check.js
  28. 1 0
      app/middleware/tender_check.js
  29. 40 4
      app/public/css/main.css
  30. 20 14
      app/public/js/change_information_approval.js
  31. 7 2
      app/public/js/change_information_show.js
  32. 312 0
      app/public/js/change_project.js
  33. 228 0
      app/public/js/change_project_audit.js
  34. 195 0
      app/public/js/change_project_information.js
  35. 72 1
      app/public/js/global.js
  36. 7 3
      app/public/js/ledger.js
  37. 37 45
      app/public/js/material.js
  38. 928 0
      app/public/js/material_checklist.js
  39. 9 9
      app/public/js/material_exponent.js
  40. 89 61
      app/public/js/material_list.js
  41. 14 15
      app/public/js/path_tree.js
  42. 10 6
      app/public/js/revise.js
  43. 1 1
      app/public/js/revise_compare.js
  44. 1 1
      app/public/js/revise_gcl_compare.js
  45. 5 5
      app/public/js/revise_history.js
  46. 11 2
      app/public/js/setting_datacollect_tender.js
  47. 3 3
      app/public/js/shares/tender_list_order.js
  48. 14 32
      app/public/js/stage.js
  49. 4 1
      app/public/js/stage_compare.js
  50. 85 91
      app/public/js/stage_pay.js
  51. 12 3
      app/public/js/tender_copy_setting.js
  52. 0 1
      app/public/js/zh_calc.js
  53. 19 0
      app/public/report/js/rpt_main.js
  54. 48 0
      app/reports/rpt_component/jpc_cross_tab.js
  55. 17 5
      app/reports/rpt_component/jpc_ex.js
  56. 10 0
      app/reports/rpt_component/jpc_flow_tab.js
  57. 2 1
      app/reports/util/rpt_excel_util.js
  58. 50 13
      app/router.js
  59. 2 2
      app/service/change.js
  60. 9 5
      app/service/change_audit_list.js
  61. 1 0
      app/service/change_ledger.js
  62. 2 0
      app/service/change_pos.js
  63. 363 0
      app/service/change_project.js
  64. 137 0
      app/service/change_project_att.js
  65. 571 0
      app/service/change_project_audit.js
  66. 27 0
      app/service/change_revise_log.js
  67. 2 2
      app/service/ledger.js
  68. 26 0
      app/service/ledger_audit.js
  69. 130 0
      app/service/ledger_history.js
  70. 8 25
      app/service/ledger_revise.js
  71. 1 0
      app/service/manager.js
  72. 39 6
      app/service/material.js
  73. 64 59
      app/service/material_audit.js
  74. 72 22
      app/service/material_bills.js
  75. 67 0
      app/service/material_checklist.js
  76. 4 4
      app/service/material_exponent.js
  77. 12 6
      app/service/material_list.js
  78. 12 12
      app/service/material_month.js
  79. 7 8
      app/service/pos.js
  80. 52 0
      app/service/project.js
  81. 64 0
      app/service/project_account.js
  82. 5 6
      app/service/revise_audit.js
  83. 2 2
      app/service/revise_bills.js
  84. 7 8
      app/service/revise_pos.js
  85. 4 0
      app/service/rpt_gather_memory.js
  86. 2 0
      app/service/stage_audit.js
  87. 3 3
      app/service/stage_pay.js
  88. 1 1
      app/service/stage_pos.js
  89. 5 1
      app/service/tender.js
  90. 1 1
      app/view/budget/list.ejs
  91. 3 3
      app/view/change/information.ejs
  92. 110 0
      app/view/change/project.ejs
  93. 135 0
      app/view/change/project_information.ejs
  94. 765 0
      app/view/change/project_information_modal.ejs
  95. 148 0
      app/view/change/project_modal.ejs
  96. 33 3
      app/view/dashboard/index.ejs
  97. 13 13
      app/view/datacollect/index.ejs
  98. 49 1
      app/view/layout/layout.ejs
  99. 41 4
      app/view/layout/modal.ejs
  100. 0 0
      app/view/login/login.ejs

+ 1 - 0
app.js

@@ -115,4 +115,5 @@ module.exports = app => {
 
     app.signPdfOss = app.oss.get('signPdf');
     app.fujianOss = app.oss.get('fujian');
+    app.hisOss = app.oss.get('his');
 };

+ 1 - 0
app/base/base_bills_service.js

@@ -633,6 +633,7 @@ class BaseBillsSerivce extends TreeService {
                             sjcl_expr: pos.sjcl_expr ? pos.sjcl_expr : '',
                             qtcl_expr: pos.qtcl_expr ? pos.qtcl_expr : '',
                             add_stage: 0,
+                            add_stage_order: 0,
                             add_times: 0,
                             ex_memo1: pos.ex_memo1,
                             ex_memo2: pos.ex_memo2,

+ 6 - 1
app/base/base_controller.js

@@ -36,6 +36,11 @@ class BaseController extends Controller {
             }
         }
         menuList.datacollect.display = ctx.session && ctx.session.sessionProject ? ctx.session.sessionProject.showDataCollect : false;
+        if (ctx.session && ctx.session.sessionProject && ctx.session.sessionProject.page_show && ctx.session.sessionProject.page_show.openManagement) {
+            menuList.management.display = true;
+        } else {
+            menuList.management.display = false;
+        }
         // 菜单列表
         ctx.menuList = menuList;
         ctx.showProject = false;
@@ -89,7 +94,7 @@ class BaseController extends Controller {
                 maintainConst,
             };
             await this.ctx.render('layout/layout.ejs', renderData);
-        } catch(err) {
+        } catch (err) {
             this.log(err);
         }
     }

+ 2 - 0
app/const/account_permission.js

@@ -29,6 +29,8 @@ const permission = {
             { title: '创建标段', value: 1 },
             { title: '查阅所有标段', value: 2 },
             { title: '维护签约清单', value: 3, hint: '开启该选项,台账审批通过后,可上传签约清单', hintIcon: 'fa-question-circle' },
+            { title: '变更意向', value: 5, hint: '开启该选项,变更立项可新建变更意向书', hintIcon: 'fa-question-circle' },
+            { title: '材差清单设置', value: 4, hint: '开启该选项,当前账号可设置允许调差的清单', hintIcon: 'fa-question-circle' },
         ],
     },
     // cooperation: {

+ 90 - 0
app/const/audit.js

@@ -421,6 +421,94 @@ const advance = (function() {
     auditStringClass[status.checkNo] = 'text-warning';
     return { type, status, statusString, statusClass, auditString, auditStringClass };
 })();
+
+// 变更立项 审批流程
+const changeProject = (function() {
+    const status = {
+        uncheck: 1, // 待上报
+        checking: 2, // 待审批|审批中
+        checked: 3, // 审批通过
+        checkNo: 4, // 审批终止
+        back: 5, // 退回到原报人重新上报
+    };
+    const statusString = [];
+    statusString[status.uncheck] = '待上报';
+    statusString[status.checking] = '审批中';
+    statusString[status.checked] = '审批通过';
+    statusString[status.checkNo] = '终止';
+    statusString[status.back] = '审批退回';
+
+    const statusClass = [];
+    statusClass[status.uncheck] = '';
+    statusClass[status.checking] = 'text-warning';
+    statusClass[status.checked] = 'text-success';
+    statusClass[status.checkNo] = 'text-danger';
+    statusClass[status.back] = 'text-warning';
+
+    // 标段概况页
+    // 描述文本
+    const auditString = [];
+    auditString[status.uncheck] = '';
+    auditString[status.checking] = '审批中';
+    auditString[status.checked] = '审批通过';
+    auditString[status.checkNo] = '终止';
+    auditString[status.back] = '审批退回';
+    // 文字样式
+    const auditStringClass = [];
+    auditStringClass[status.uncheck] = '';
+    auditStringClass[status.checking] = 'text-warning';
+    auditStringClass[status.checked] = 'text-success';
+    auditStringClass[status.checkNo] = 'text-danger';
+    auditStringClass[status.back] = 'text-warning';
+    // 描述文本
+    const auditProgress = [];
+    auditProgress[status.uncheck] = '草稿';
+    auditProgress[status.checking] = '审批中';
+    auditProgress[status.checked] = '审批通过';
+    auditProgress[status.checkNo] = '终止';
+    auditProgress[status.back] = '审批退回';
+    // 样式
+    const auditProgressClass = [];
+    auditProgressClass[status.uncheck] = '';
+    auditProgressClass[status.checking] = 'text-warning';
+    auditProgressClass[status.checked] = 'text-success';
+    auditProgressClass[status.checkNo] = 'text-danger';
+    auditProgressClass[status.back] = 'text-warning';
+
+    const filter = {
+        status: {
+            pending: 1,
+            uncheck: 5,
+            checking: 2,
+            checked: 3,
+            checkNo: 4,
+        },
+        statusString: [],
+    };
+    filter.statusString[filter.status.pending] = '待处理';
+    filter.statusString[filter.status.uncheck] = '待上报';
+    filter.statusString[filter.status.checking] = '进行中';
+    filter.statusString[filter.status.checked] = '已通过';
+    filter.statusString[filter.status.checkNo] = '终止';
+
+    // 按钮
+    const statusButton = [];
+    statusButton[status.uncheck] = '待上报';
+    statusButton[status.checking] = '审批';
+    statusButton[status.checked] = '';
+    statusButton[status.checkNo] = '';
+    statusButton[status.back] = '重新上报';
+
+    // 按钮样式
+    const statusButtonClass = [];
+    statusButtonClass[status.uncheck] = 'btn-primary';
+    statusButtonClass[status.checking] = 'btn-success';
+    statusButtonClass[status.checked] = '';
+    statusButtonClass[status.checkNo] = '';
+    statusButtonClass[status.back] = 'btn-warning';
+    return { status, statusString, statusClass, auditString, auditStringClass, auditProgress, auditProgressClass, filter, statusButton, statusButtonClass };
+})();
+
 // 推送类型
 const pushType = {
     material: 1,
@@ -429,6 +517,7 @@ const pushType = {
     revise: 4,
     ledger: 5,
     advance: 6,
+    changeProject: 7,
 };
 
 module.exports = {
@@ -449,4 +538,5 @@ module.exports = {
     filter,
     pushType,
     advance,
+    changeProject,
 };

+ 5 - 0
app/const/change.js

@@ -106,4 +106,9 @@ module.exports = {
         souban: { unit: '艘班' },
         mu: { unit: '亩' },
     },
+    // 立项类型
+    project_type: {
+        3: '变更建议',
+        4: '变更意向',
+    },
 };

+ 7 - 0
app/const/code_rule.js

@@ -11,13 +11,20 @@
 const ruleType = {
     measure: 1,
     change: 2,
+    suggestion: 3,
+    will: 4,
 };
 const ruleField = [];
 ruleField[ruleType.measure] = 'm_rule';
 ruleField[ruleType.change] = 'c_rule';
+ruleField[ruleType.suggestion] = 'suggestion';
+ruleField[ruleType.will] = 'will';
 const ruleString = [];
 ruleString[ruleType.measure] = 'measure';
 ruleString[ruleType.change] = 'change';
+ruleString[ruleType.suggestion] = 'suggestion';
+ruleString[ruleType.will] = 'will';
+
 
 // 中间计量编号规则
 const measure = {

+ 5 - 0
app/const/page_show.js

@@ -37,6 +37,11 @@ const defaultSetting = {
     openMaterialTax: 0,
     addDataCollect: 1,
     closeWapYfSf: 0,
+    openManagement: 0,
+    openMaterialChecklist: 0,
+    close1stStageCheckDealParam: 0,
+    openChangeProject: 0,
+    openChangeApply: 0,
 };
 
 

+ 3 - 0
app/const/sign.js

@@ -13,7 +13,10 @@ const path = {
     api: 'http://106.52.243.222:9091/eseal',
 };
 
+/** 请求项目管理token所需秘钥 */
+const managementApiSecretKey = 'JL_CM_lksjdofuosdjflj01231209uljsf90@@#(lnm8';
 
 module.exports = {
     path,
+    managementApiSecretKey,
 };

+ 548 - 3
app/controller/change_controller.js

@@ -194,7 +194,20 @@ module.exports = app => {
                     throw '当前未打开标段';
                 }
                 const tenderData = await ctx.service.tender.getDataById(tenderId);
-                const cCodeRule = tenderData.c_rule !== null ? JSON.parse(tenderData.c_rule) : [];
+                const data = JSON.parse(ctx.request.body.data);
+                let cCodeRule;
+                let cConnector;
+                let changeCount = 0;
+                if (data && data.type) {
+                    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;
+                    changeCount = await ctx.service.changeProject.count({ tid: tenderId, type: codeRuleConst.ruleType[data.type] });
+                } else {
+                    cCodeRule = tenderData.c_rule !== null ? JSON.parse(tenderData.c_rule) : [];
+                    cConnector = tenderData.c_connector;
+                    changeCount = await ctx.service.change.count({ tid: tenderId });
+                }
 
                 const code = [];
                 for (const rule of cCodeRule) {
@@ -213,14 +226,14 @@ module.exports = app => {
                             break;
                         case codeRuleConst.measure.ruleType.addNo:
                             let s = '0000000000';
-                            const count = rule.start + await ctx.service.change.count({ tid: tenderId });
+                            const count = rule.start + changeCount;
                             s = s + count;
                             code.push(s.substr(s.length - rule.format));
                             break;
                         default: break;
                     }
                 }
-                responseData.data = code.join(tenderData.c_connector !== null && tenderData.c_connector !== 3 ? codeRuleConst.measure.connectorString[tenderData.c_connector] : '');
+                responseData.data = code.join(cConnector !== null && cConnector !== 3 ? codeRuleConst.measure.connectorString[cConnector] : '');
             } catch (err) {
                 responseData.err = 1;
                 responseData.msg = err;
@@ -1805,6 +1818,538 @@ module.exports = app => {
                     throw '未知操作';
             }
         }
+
+        async _filterChangesProject(ctx, status = 0) {
+            const tenderId = ctx.params.id;
+            ctx.session.sessionUser.tenderId = tenderId;
+            const tender = await this.service.tender.getDataById(ctx.tender.id);
+            // const tender = ctx.tender;
+            // const tenderList = await this.service.tender.getList();
+
+            const page = ctx.page;
+            const sorts = ctx.query.sort ? ctx.query.sort : 0;
+            const orders = ctx.query.order ? ctx.query.order : 0;
+            const changes = await ctx.service.changeProject.getListByStatus(tender.id, status, 1, sorts, orders);
+            const total = await ctx.service.changeProject.getCountByStatus(tender.id, status);
+            for (const c of changes) {
+                c.curAuditor = await ctx.service.changeProjectAudit.getAuditorByStatus(c.id, c.status, c.times);
+            }
+            const accountInfo = await this.ctx.service.projectAccount.getDataById(this.ctx.session.sessionUser.accountId);
+            const userPermission = accountInfo !== undefined && accountInfo.permission !== ''
+                ? JSON.parse(accountInfo.permission) : null;
+            // let page_total = 0;
+            // if (changes !== null) {
+            //     let i = 0;
+            //     for (const c of changes) {
+            //         page_total = ctx.helper.add(page_total, c.total_price);
+            //         const status = c.status === audit.uncheck ? 0 : 1;
+            //         // 根据审批人对当前变更令的状态取不同的展示方式。
+            //         let changeAudit = '';
+            //         let auditStatus = 0;
+            //         switch (c.status) {
+            //             case 1:
+            //                 auditStatus = 1;
+            //                 break;
+            //             case 2:
+            //                 changeAudit = await ctx.service.changeAudit.getLastUser(c.cid, c.times, status);
+            //                 auditStatus = changeAudit.uid === ctx.session.sessionUser.accountId ? 1 : 0;
+            //                 break;
+            //             case 3:
+            //             case 4:
+            //                 auditStatus = 0;
+            //                 changeAudit = await ctx.service.changeAudit.getLastUser(c.cid, c.times, status);
+            //                 break;
+            //             case 5:
+            //                 changeAudit = await ctx.service.changeAudit.getLastUser(c.cid, c.times - 1, status);
+            //                 auditStatus = c.uid === ctx.session.sessionUser.accountId ? 1 : 0;
+            //                 const back_changeUsedData = await ctx.service.stageChange.getFinalUsedData(ctx.tender.id, c.cid);
+            //                 c.stageChangeNum = this.ctx.helper.sum(back_changeUsedData.map(x => { return Math.abs(x.used_qty); }));
+            //                 break;
+            //             case 6:
+            //                 changeAudit = await ctx.service.changeAudit.getLastBackUser(c.cid, c.times);
+            //                 const checkingAudit = await ctx.service.changeAudit.getLastUser(c.cid, c.times, status);
+            //                 auditStatus = checkingAudit.uid === ctx.session.sessionUser.accountId ? 1 : 0;
+            //                 break;
+            //             case 9:
+            //                 auditStatus = 9;
+            //                 const changeUsedData = await ctx.service.stageChange.getFinalUsedData(ctx.tender.id, c.cid);
+            //                 c.stageChangeNum = this.ctx.helper.sum(changeUsedData.map(x => { return Math.abs(x.used_qty); }));
+            //                 break;
+            //             default:
+            //                 break;
+            //         }
+            //         changes[i].changeAudit = changeAudit;
+            //         changes[i].auditStatus = auditStatus;
+            //         i++;
+            //     }
+            // }
+
+            // 分页相关
+            const pageInfo = {
+                page,
+                total: Math.ceil(total / app.config.pageSize),
+                queryData: JSON.stringify(ctx.urlInfo.query),
+            };
+
+            const filter = JSON.parse(JSON.stringify(audit.changeProject.filter));
+            filter.count = [];
+            filter.count[filter.status.pending] = await ctx.service.changeProject.getCountByStatus(tender.id, filter.status.pending);// await ctx.service.change.pendingDatas(tender.id, ctx.session.sessionUser.accountId);
+            filter.count[filter.status.uncheck] = await ctx.service.changeProject.getCountByStatus(tender.id, filter.status.uncheck);// await ctx.service.change.checkingDatas(tender.id, ctx.session.sessionUser.accountId);
+            filter.count[filter.status.checking] = await ctx.service.changeProject.getCountByStatus(tender.id, filter.status.checking);// await ctx.service.change.checkedDatas(tender.id, ctx.session.sessionUser.accountId);
+            filter.count[filter.status.checked] = await ctx.service.changeProject.getCountByStatus(tender.id, filter.status.checked);// await ctx.service.change.pendingDatas(tender.id, ctx.session.sessionUser.accountId);
+            filter.count[filter.status.checkNo] = await ctx.service.changeProject.getCountByStatus(tender.id, filter.status.checkNo);// await ctx.service.change.pendingDatas(tender.id, ctx.session.sessionUser.accountId);
+            let codeRule = [];
+            let c_connector = '1';
+            let c_rule_first = 1;
+            const rule_type = tender.user_id === ctx.session.sessionUser.accountId ? 'suggestion' : 'will';
+            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 = {
+                uid: ctx.session.sessionUser.accountId,
+                userPermission,
+                tender,
+                pageInfo,
+                changes,
+                status,
+                rule_type,
+                codeRule,
+                c_connector,
+                c_rule_first,
+                filter,
+                ruleType: codeRuleConst.ruleType[rule_type],
+                dealCode: ctx.tender.info.deal_info.dealCode,
+                auditConst: audit.changeProject,
+                ruleConst: codeRuleConst.measure,
+                changeConst,
+                tenderMenu: this.menu.tenderMenu,
+                preUrl: '/tender/' + tender.id,
+                jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.change.project),
+            };
+            await this.layout('change/project.ejs', renderData, 'change/project_modal.ejs');
+        }
+
+        /**
+         * 变更立项列表 页面 (Get)
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async project(ctx) {
+            try {
+                await this._filterChangesProject(ctx);
+            } catch (err) {
+                this.log(err);
+                ctx.redirect('/dashboard');
+            }
+        }
+
+        /**
+         * 新增变更立项 (Post)
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async projectAdd(ctx) {
+            try {
+                const tenderId = ctx.params.id;
+                if (!tenderId) {
+                    throw '当前未打开标段';
+                }
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.code || data.code === '' || !data.name || data.name === '') {
+                    throw '变更立项书编号不能为空';
+                }
+
+                const change = await ctx.service.changeProject.add(tenderId, ctx.session.sessionUser.accountId, data.code, data.name);
+
+                ctx.body = { err: 0, msg: '', data: change };
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString() };
+            }
+        }
+
+        /**
+         * 变更管理 状态筛选 页面 (Get)
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async projectStatus(ctx) {
+            try {
+                const status = parseInt(ctx.params.status);
+                await this._filterChangesProject(ctx, status);
+            } catch (err) {
+                this.logger.error(err);
+                ctx.redirect('/tender/' + ctx.params.id + '/change/project');
+            }
+        }
+
+        /**
+         * 获取审批界面所需的 原报、审批人数据等
+         * @param ctx
+         * @return {Promise<void>}
+         * @private
+         */
+        async _getChangeProjectAuditViewData(ctx) {
+            const auditConst = audit.changeProject;
+            const times = ctx.change.status === auditConst.status.back ? ctx.change.times - 1 : ctx.change.times;
+            ctx.change.user = await ctx.service.projectAccount.getAccountInfoById(ctx.change.uid);
+            ctx.change.auditHistory = [];
+            if (times >= 1) {
+                for (let i = 1; i <= times; i++) {
+                    ctx.change.auditHistory.push(await ctx.service.changeProjectAudit.getAuditors(ctx.change.id, i));
+                }
+            }
+            // 获取审批流程中左边列表
+            ctx.change.auditors2 = ctx.change.status === auditConst.status.back && ctx.change.user_id !== ctx.session.sessionUser.accountId ?
+                await ctx.service.changeProjectAudit.getAuditorsWithOwner(ctx.change.id, times) :
+                await ctx.service.changeProjectAudit.getAuditorsWithOwner(ctx.change.id, ctx.change.times);
+            if (ctx.change.status === auditConst.status.uncheck || ctx.change.status === auditConst.status.back) {
+                ctx.change.auditorList = await ctx.service.changeProjectAudit.getAuditors(ctx.change.id, ctx.change.times);
+            }
+        }
+
+        async projectInformation(ctx) {
+            try {
+                const whiteList = this.ctx.app.config.multipart.whitelist;
+                const tender = await ctx.service.tender.getDataById(ctx.tender.id);
+                // 获取附件列表
+                const fileList = await ctx.service.changeProjectAtt.getAllChangeProjectAtt(ctx.tender.id, ctx.change.id);
+                await this._getChangeProjectAuditViewData(ctx);
+                const renderData = {
+                    tender,
+                    change: ctx.change,
+                    changeConst,
+                    auditConst: audit.changeProject,
+                    fileList,
+                    whiteList,
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.change.project_information),
+                    preUrl: '/tender/' + ctx.tender.id + '/change/project/' + ctx.change.id + '/information',
+                };
+                if ((ctx.change.status === audit.changeProject.status.uncheck || ctx.change.status === audit.changeProject.status.back) && (ctx.session.sessionUser.accountId === ctx.change.uid || ctx.tender.isTourist)) {
+                    // data.accountGroup = accountGroup;
+                    // 获取所有项目参与者
+                    const accountList = await ctx.service.projectAccount.getAllDataByCondition({
+                        where: { project_id: ctx.session.sessionProject.id, enable: 1 },
+                        columns: ['id', 'name', 'company', 'role', 'enable', 'is_admin', 'account_group', 'mobile'],
+                    });
+                    renderData.accountList = accountList;
+                    renderData.accountGroup = accountGroup.map((item, idx) => {
+                        const groupList = accountList.filter(item => item.account_group === idx);
+                        return { groupName: item, groupList };
+                    });
+                }
+                await this.layout('change/project_information.ejs', renderData, 'change/project_information_modal.ejs');
+            } catch (err) {
+                this.log(err);
+                ctx.redirect('/tender/' + ctx.params.id + '/change');
+            }
+        }
+
+        // 审批相关
+        /**
+         * 添加审批人
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async addProjectAudit(ctx) {
+            try {
+                const auditConst = audit.changeProject;
+                const data = JSON.parse(ctx.request.body.data);
+                const id = this.app._.toInteger(data.auditorId);
+                if (isNaN(id) || id <= 0) {
+                    throw '参数错误';
+                }
+                // 检查权限等
+                if (ctx.change.uid !== ctx.session.sessionUser.accountId) {
+                    throw '您无权添加审核人';
+                }
+                if (ctx.change.status === auditConst.status.checking || ctx.change.status === auditConst.status.checked) {
+                    throw '当前不允许添加审核人';
+                }
+
+                ctx.change.auditorList = await ctx.service.changeProjectAudit.getAuditors(ctx.change.id, ctx.change.times);
+                // 检查审核人是否已存在
+                const exist = this.app._.find(ctx.change.auditorList, { aid: id });
+                if (exist) {
+                    throw '该审核人已存在,请勿重复添加';
+                }
+                // const shenpiInfo = await ctx.service.shenpiAudit.getDataByCondition({ tid: ctx.tender.id, sp_type: shenpiConst.sp_type.material, sp_status: shenpiConst.sp_status.gdzs });
+                // const is_gdzs = shenpiInfo && ctx.tender.info.shenpi.material === shenpiConst.sp_status.gdzs ? 1 : 0;
+                const result = await ctx.service.changeProjectAudit.addAuditor(ctx.change.id, id, ctx.change.times);
+                if (!result) {
+                    throw '添加审核人失败';
+                }
+
+                const auditors = await ctx.service.changeProjectAudit.getAuditorsWithOwner(ctx.change.id, ctx.change.times);
+                ctx.body = { err: 0, msg: '', data: auditors };
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+        }
+
+        /**
+         * 移除审批人
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async deleteProjectAudit(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const id = data.auditorId instanceof Number ? data.auditorId : this.app._.toNumber(data.auditorId);
+                if (isNaN(id) || id <= 0) {
+                    throw '参数错误';
+                }
+
+                const result = await ctx.service.changeProjectAudit.deleteAuditor(ctx.change.id, id, ctx.change.times);
+                if (!result) {
+                    throw '移除审核人失败';
+                }
+
+                const auditors = await ctx.service.changeProjectAudit.getAuditors(ctx.change.id, ctx.change.times);
+                ctx.body = { err: 0, msg: '', data: auditors };
+            } catch (err) {
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+        }
+
+        /**
+         * 上传附件
+         * @param {*} ctx 上下文
+         */
+        async uploadProjectFile(ctx) {
+            let stream;
+            try {
+                const auditConst = audit.changeProject;
+                // 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.change.status === auditConst.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()}/change_project/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,
+                        cpid: ctx.change.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.changeProjectAtt.saveFileMsgToDb(payload);
+                // 将最新的当前标段的所有文件信息返回
+                const data = await ctx.service.changeProjectAtt.getAllChangeProjectAtt(ctx.tender.id, ctx.change.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 deleteProjectFile(ctx) {
+            try {
+                const { id } = JSON.parse(ctx.request.body.data);
+                const fileInfo = await ctx.service.changeProjectAtt.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.changeProjectAtt.delete(id);
+                } else {
+                    throw '不存在该文件';
+                }
+                const data = await ctx.service.changeProjectAtt.getAllChangeProjectAtt(ctx.tender.id, ctx.change.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 downloadProjectFile(ctx) {
+            const id = ctx.params.fid;
+            if (id) {
+                try {
+                    const fileInfo = await ctx.service.changeProjectAtt.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.file_name);
+                        } else if (userAgent.indexOf('firefox') >= 0) {
+                            disposition = 'attachment; filename*="utf8\'\'' + encodeURIComponent(fileInfo.file_name) + '"';
+                        } else {
+                            /* safari等其他非主流浏览器只能自求多福了 */
+                            disposition = 'attachment; filename=' + new Buffer(fileInfo.file_name).toString('binary');
+                        }
+                        ctx.response.set({
+                            'Content-Type': 'application/octet-stream',
+                            'Content-Disposition': disposition,
+                            'Content-Length': fileInfo.file_size,
+                        });
+                        // 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);
+                }
+            }
+        }
+
+        async projectInformationSave(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                if (data.name === 'code') {
+                    const info = await ctx.service.changeProject.isRepeat(ctx.change.id, data.val, ctx.tender.id, ctx.change.type);
+                    if (info) {
+                        throw '该编号已存在';
+                    }
+                }
+                const result = await ctx.service.changeProject.saveInfo(ctx.change.id, data);
+                if (!result) {
+                    throw '修改失败';
+                }
+                ctx.body = { err: 0, msg: '请求成功', data: null };
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+        }
+
+        /**
+         * 上报和重新上报
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async startProjectAudit(ctx) {
+            try {
+                const auditConst = audit.changeProject;
+                // 检查权限等
+                if (!ctx.change) {
+                    throw '数据错误';
+                }
+                if (ctx.change.uid !== ctx.session.sessionUser.accountId) {
+                    throw '您无权上报该期数据';
+                }
+                if (ctx.change.status === auditConst.status.checking || ctx.change.status === auditConst.status.checked) {
+                    throw '该材料调差期数据当前无法上报';
+                }
+
+                await ctx.service.changeProjectAudit.start(ctx.change.id, ctx.change.times);
+
+                ctx.redirect(ctx.request.header.referer);
+            } catch (err) {
+                this.log(err);
+                ctx.session.postError = err.toString();
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 审批
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async checkProjectAudit(ctx) {
+            try {
+                const auditConst = audit.changeProject;
+                if (!ctx.change || ctx.change.status !== auditConst.status.checking) {
+                    throw '当前材料调差期数据有误';
+                }
+                if (!ctx.change.curAuditor || ctx.change.curAuditor.aid !== ctx.session.sessionUser.accountId) {
+                    throw '您无权进行该操作';
+                }
+                const data = {
+                    checkType: parseInt(ctx.request.body.checkType),
+                    opinion: ctx.request.body.opinion,
+                };
+                if (!data.checkType || isNaN(data.checkType)) {
+                    throw '提交数据错误';
+                }
+                // if (data.checkType === auditConst.status.checkNo) {
+                //     if (!data.checkType || isNaN(data.checkType)) {
+                //         throw '提交数据错误';
+                //     }
+                // }
+                await ctx.service.changeProjectAudit.check(ctx.change.id, data, ctx.change.times);
+
+                ctx.redirect(ctx.request.header.referer);
+            } catch (err) {
+                this.log(err);
+                ctx.session.postError = err.toString();
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
     }
 
     return ChangeController;

+ 3 - 12
app/controller/dashboard_controller.js

@@ -29,20 +29,9 @@ module.exports = app => {
             const auditRevise = await ctx.service.reviseAudit.getAuditRevise(ctx.session.sessionUser.accountId);
             const auditMaterial = await ctx.service.materialAudit.getAuditMaterial(ctx.session.sessionUser.accountId);
             const auditAdvance = await ctx.service.advanceAudit.getAuditAdvance(ctx.session.sessionUser.accountId);
+            const auditChangeProject = ctx.session.sessionProject.page_show.openChangeProject ? await ctx.service.changeProjectAudit.getAuditChangeProject(ctx.session.sessionUser.accountId) : [];
             const pa = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
-            // const lastNotice = pa.last_notice ? pa.last_notice : (pa.last_notice === 0 ? new Date() : new Date(pa.last_login * 1000));
-            // const noticeLedger = await ctx.service.ledgerAudit.getNoticeTender(ctx.session.sessionProject.id, pa.id);
-            // const noticeStage = await ctx.service.stageAudit.getNoticeStage(ctx.session.sessionProject.id, pa.id);
-            // const noticeChange = await ctx.service.changeAudit.getNoticeChange(ctx.session.sessionProject.id, pa.id);
-            // const noticeRevise = await ctx.service.reviseAudit.getNoticeRevise(ctx.session.sessionProject.id, pa.id);
-            // const noticeMaterial = await ctx.service.materialAudit.getNoticeMaterial(ctx.session.sessionProject.id, pa.id);
-            // const noticeList = [...noticeLedger, ...noticeStage, ...noticeChange, ...noticeRevise, ...noticeMaterial].sort((a, b) => a.name.localeCompare(b.name, 'zh') && b.create_time - a.create_time).slice(0, 10);
             const noticeList = await ctx.service.noticePush.getNotice(ctx.session.sessionProject.id, pa.id);
-            // const noticeLedger = [];
-            // const noticeStage = [];
-            // const noticeChange = [];
-            // const noticeRevise = [];
-            // const noticeMaterial = [];
             const projectData = await ctx.service.project.getDataById(ctx.session.sessionProject.id);
             // 获取销售人员数据
             const salesmanData = await ctx.service.manager.getDataById(projectData.manager_id);
@@ -64,6 +53,7 @@ module.exports = app => {
                 auditRevise,
                 auditMaterial,
                 auditAdvance,
+                auditChangeProject,
                 role: pa.role,
                 authMobile: pa.auth_mobile,
                 acLedger: auditConst.ledger,
@@ -72,6 +62,7 @@ module.exports = app => {
                 acRevise: auditConst.revise,
                 acMaterial: auditConst.material,
                 acAdvance: auditConst.advance,
+                acChangeProject: auditConst.changeProject,
                 noticeList,
                 pushType: auditConst.pushType,
                 projectData,

+ 7 - 2
app/controller/ledger_controller.js

@@ -473,9 +473,14 @@ module.exports = app => {
          */
         async loadExplodeData(ctx) {
             try {
-                const ledgerData = await ctx.service.ledger.getData(ctx.tender.id);
+                const ledgerData = ctx.tender.ledgerReadOnly
+                    ? await ctx.helper.loadLedgerDataFromOss(ctx.tender.his.bills_file)
+                    : 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 }) : [];
+                    ? (ctx.tender.ledgerReadOnly
+                        ? await ctx.helper.loadLedgerDataFromOss(ctx.tender.his.pos_file)
+                        : await ctx.service.pos.getPosData({tid: ctx.tender.id}))
+                    : [];
                 const ledgerTags = await this.ctx.service.ledgerTag.getDatas(ctx.tender.id);
                 ctx.body = { err: 0, msg: '', data: { bills: ledgerData, pos: posData, tags: ledgerTags } };
             } catch (err) {

+ 193 - 3
app/controller/login_controller.js

@@ -10,7 +10,7 @@
 const URL = require('url');
 const maintainConst = require('../const/maintain');
 const OAuth = require('co-wechat-oauth');
-
+const office = require('../const/cld_office')
 module.exports = app => {
 
     class LoginController extends app.BaseController {
@@ -154,7 +154,6 @@ module.exports = app => {
          */
         async login(ctx) {
             let loginType = ctx.request.body.type;
-
             try {
                 loginType = parseInt(loginType);
                 const result = await ctx.service.projectAccount.accountLogin(ctx.request.body, loginType);
@@ -279,7 +278,6 @@ module.exports = app => {
         async port(ctx) {
             // 获取系统维护信息
             const maintainData = await ctx.service.maintain.getDataById(1);
-
             if (!ctx.app.config.is_debug) {
                 await ctx.service.maintain.syncMaintainData();
             }
@@ -369,6 +367,198 @@ module.exports = app => {
                 ctx.redirect('/login');
             }
         }
+
+        /**
+         * (项目管理)获取账号
+         * @param {Object} ctx - egg全局变量
+         */
+        async account(ctx) {
+            const data = ctx.data;
+            const response = {
+                code: 0,
+                data: 0,
+                msg: '',
+            };
+            try {
+                const project = await ctx.service.project.getProjectByCode(data.code);
+                if (!project) throw '未找到该项目';
+                const pa = await ctx.service.projectAccount.getAccountInfoByAccountWithPid(data.account, project.id);
+                if (!pa ) throw '该账号不存在,请联系管理员';
+                const manager = await ctx.service.manager.getDataById(project.manager_id)
+                const { is_admin: isAdmin, account_group: accountGroup, ...otherObj } = pa
+                response.data = {
+                  account: { ...otherObj, accountGroup, isAdmin},
+                  project: {
+                    code: project.code,
+                    name: project.name,
+                    categoryId: project.manager_office,
+                    category: office.list[project.manager_office],
+                    staff: manager.real_name
+                  }
+                }
+            } catch (error) {
+                console.log(error);
+                response.code = -1;
+                response.msg = error.toString();
+            }
+            ctx.body = response;
+        }
+
+        /**
+         * (项目管理)获取项目
+         * @param {Object} ctx - egg全局变量
+         */
+        async project(ctx) {
+            const data = ctx.data;
+            const response = {
+                code: 0,
+                data: 0,
+                msg: '',
+            };
+            try {
+                const project = await ctx.service.project.getProjectByCode(data.code);
+                if (!project) throw '未找到该项目';
+                response.data = project;
+            } catch (error) {
+                response.code = -1;
+                response.msg = error.toString();
+            }
+            ctx.body = response;
+        }
+
+        /**
+         * (项目管理)校验项目
+         * @param {Object} ctx - egg全局变量
+         */
+        async vertifyProject(ctx) {
+            const response = {
+                code: 0,
+                data: 0,
+                msg: '',
+            };
+            try {
+              const code = ctx.session.sessionProject.code
+              const accountData = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId)
+              if (!code || !accountData) {
+                throw new Error('参数错误')
+              }
+                const result = await ctx.service.project.verifyManagementProject(ctx.helper.createJWT({ code }));
+                const token = ctx.helper.createJWT({ code, account: accountData.account })
+                const redirect = `${app.config.managementPath}/auth?token=${token}`
+                response.data = { ...result, is_admin: accountData.is_admin, redirect, env: app.config.env};
+            } catch (error) {
+                response.code = -1;
+                response.msg = error.toString();
+            }
+            ctx.body = response;
+        }
+
+        /**
+         * (项目管理)新增项目
+         * @param {Object} ctx - egg全局变量
+         */
+        async addProject(ctx) {
+          const response = {
+              code: 0,
+              data: {},
+              msg: '',
+          };
+          try {
+              const result = await ctx.service.project.addProjectFromManagement()
+              if (!result) {
+                throw new Error('请求失败')
+              }
+              const { code = -1, data: { token = ''} = { token: ''}, msg = '请求失败' } = result
+              if ( code === 0 && token) {
+                response.data = {
+                  redirect: `${app.config.managementPath}/auth?token=${token}`,
+                }
+              } else {
+                throw new Error(msg)
+              }
+          } catch (error) {
+              response.code = -1;
+              response.msg = error.toString();
+          }
+          ctx.body = response;
+        }
+        
+        /**
+         * 项目管理授权页面
+         *
+         * @param {Object} ctx - egg全局页面
+         * @return {void}
+         */
+        async management(ctx) {
+          const data = ctx.data;
+          // 获取系统维护信息
+          const maintainData = await ctx.service.maintain.getDataById(1);
+          if (!ctx.app.config.is_debug) {
+              await ctx.service.maintain.syncMaintainData();
+          }
+          let account, project;
+          try {
+              if (ctx.session.loginError !== null) {
+                  throw ctx.session.loginError;
+              }
+              if (!data) {
+                  throw '参数有误';
+              }
+              project = await ctx.service.project.getDataByCondition({ code: data.code })
+              if (!project) {
+                throw '未找到该项目';
+              }
+              account = await ctx.service.projectAccount.getDataByCondition({ project_id: project.id, account: data.account });
+              if (!account) {
+                  throw '您无权限登录系统。';
+              }
+              if (account.enable !== 1) {
+                throw '该账号已被停用,请联系销售人员';
+            }
+            const result = await ctx.service.projectAccount.accountLogin({ project, accountData: account }, 3);
+            if (!result) {
+                throw '登录出错';
+            }
+            ctx.redirect('/dashboard');
+          } catch (error) {
+              // this.log(error);
+              console.log(error);
+              ctx.session.loginError = error;
+          }
+          const errorMessage = ctx.session.loginError;
+          // 显示完删除
+          ctx.session.loginError = null;
+          const renderData = {
+              maintainData,
+              maintainConst,
+              errorMessage,
+              projectData: project,
+              accountData: account,
+          };
+          await ctx.render('login/login_management.ejs', renderData);
+        }
+
+        /** 拉取项目下所有计量账号至项目管理 */
+        async syncProjectAccount(ctx) {
+          const response = {
+            code: 0,
+            data: [],
+            msg: '',
+          };
+          try {
+              const { code = ''} = ctx.data
+              if (!code) throw '参数错误';
+              const projectData = await ctx.service.project.getProjectByCode(code)
+              if (!projectData) throw '未找到项目';
+              const data = await ctx.service.projectAccount.getAllProjectAccountByPid(projectData.id)
+              response.data = data || []
+          } catch (error) {
+            response.code = -1
+            response.msg = error.toString()
+          }
+          ctx.body = response
+        }
+      
     }
 
     return LoginController;

+ 175 - 5
app/controller/material_controller.js

@@ -79,6 +79,7 @@ module.exports = app => {
                     if (!openMaterialTax && s.material_tax === 1) {
                         openMaterialTax = 1;
                     }
+                    s.decimal = s.decimal ? JSON.parse(s.decimal) : materialConst.decimal;
                 }
                 renderData.stages = stages;
                 renderData.openMaterialTax = openMaterialTax;
@@ -358,8 +359,8 @@ module.exports = app => {
 
                 // 取当前期截止上期含税金额
                 renderData.material.m_tax_tp = renderData.material.m_tax_tp ? renderData.material.m_tax_tp : renderData.material.m_tp;
-                renderData.pre_tp_hs = await ctx.service.material.getPreTpHs(ctx.tender.id, ctx.material.order);
-                renderData.ex_pre_tp_hs = await ctx.service.material.getExPreTpHs(ctx.tender.id, ctx.material.order);
+                renderData.pre_tp_hs = await ctx.service.material.getPreTpHs(ctx.tender.id, ctx.material.order, ctx.material.decimal.tp);
+                renderData.ex_pre_tp_hs = await ctx.service.material.getExPreTpHs(ctx.tender.id, ctx.material.order, ctx.material.decimal.tp);
                 // renderData.tax_pre_tp_hs = await ctx.service.material.getTaxPreTpHs(ctx.tender.id, ctx.material.order);
 
                 renderData.months = ctx.material.months ? ctx.material.months.split(',') : [];
@@ -444,6 +445,11 @@ module.exports = app => {
                 // 获取所选期数据并合并相加同类清单项
                 responseData.data.curLedgerData = await ctx.service.stageBills.getStagesData(ctx.tender.id, ctx.material.stage_id);
                 responseData.data.curPosData = await ctx.service.stagePos.getStagesData(ctx.tender.id, ctx.material.stage_id, 'list');
+                // 获取清单设置已选清单
+                const materialChecklistData = await ctx.service.materialChecklist.getAllDataByCondition({ where: { tid: ctx.tender.id } });
+                responseData.data.materialChecklistData = materialChecklistData.sort(function(a, b) {
+                    return ctx.helper.compareCode(a.b_code, b.b_code);
+                });
                 ctx.body = responseData;
             } catch (err) {
                 this.log(err);
@@ -502,8 +508,8 @@ module.exports = app => {
                 }
 
                 // 取当前期截止上期含税金额
-                renderData.pre_tp_hs = await ctx.service.material.getPreTpHs(ctx.tender.id, ctx.material.order);
-                renderData.ex_pre_tp_hs = await ctx.service.material.getExPreTpHs(ctx.tender.id, ctx.material.order);
+                renderData.pre_tp_hs = await ctx.service.material.getPreTpHs(ctx.tender.id, ctx.material.order, ctx.material.decimal.tp);
+                renderData.ex_pre_tp_hs = await ctx.service.material.getExPreTpHs(ctx.tender.id, ctx.material.order, ctx.material.decimal.tp);
                 renderData.materialType = materialConst;
                 renderData.jsFiles = this.app.jsFiles.common.concat(this.app.jsFiles.material.exponent);
                 // 判断之前期有无调用过材料税
@@ -542,6 +548,42 @@ module.exports = app => {
         }
 
         /**
+         *  调差操作 (form)
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async saveDecimal(ctx) {
+            try {
+                const data = ctx.request.body;
+                if (ctx.material.status !== auditConst.status.uncheck && ctx.material.status !== auditConst.status.checkNo) {
+                    throw '当前调差期无法调整小数位数';
+                }
+                const newDecimalUp = parseInt(data.up);
+                const newDecimalTp = parseInt(data.tp);
+                console.log(newDecimalUp, newDecimalTp);
+                if (ctx.app._.isNaN(newDecimalUp) || newDecimalUp > 6 || newDecimalUp < 0) {
+                    throw '单价小数位数设置不能大于6或小于0';
+                }
+                if (ctx.app._.isNaN(newDecimalTp) || newDecimalTp > 6 || newDecimalTp < 0) {
+                    throw '金额小数位数设置不能大于6或小于0';
+                }
+                if (ctx.material.decimal.up === newDecimalUp && ctx.material.decimal.tp === newDecimalTp) {
+                    throw '小数位数设置未发生变化';
+                }
+                const result = await ctx.service.material.saveDecimal(newDecimalUp, newDecimalTp);
+                if (!result) {
+                    throw '小数位数设置失败';
+                }
+                this.setMessage('设置小数位数成功', this.messageType.SUCCESS);
+            } catch (err) {
+                console.log(err);
+                this.log(err);
+                ctx.session.postError = err.toString();
+            }
+            ctx.redirect(ctx.request.header.referer);
+        }
+
+        /**
          * 调差清单 - 工料操作 (Ajax)
          * @param ctx
          * @return {Promise<void>}
@@ -554,6 +596,9 @@ module.exports = app => {
                     msg: '',
                     data: {},
                 };
+                if (ctx.session.sessionProject.page_show.openMaterialChecklist && (data.type !== 'join' && data.type !== 'notjoin')) {
+                    throw '清单设置功能已启动,请前往清单设置页操作清单内容';
+                }
                 switch (data.type) {
                     case 'add':
                         responseData.data = await ctx.service.materialList.add(data.postData);
@@ -672,7 +717,7 @@ module.exports = app => {
                         // 更新quantity值并重新返回计算本期金额,截止本期金额
                         const updateData = {
                             id: data.id,
-                            quantity: quantity !== 0 ? this.ctx.helper.round(quantity, 3) : null,
+                            quantity: quantity !== 0 ? ctx.helper.round(quantity, ctx.material.decimal.qty) : null,
                             expr: data.expr,
                         };
                         responseData.data = await ctx.service.materialBills.updateFYQuantity(updateData);
@@ -716,8 +761,13 @@ module.exports = app => {
                 };
                 const monthList = await ctx.service.materialMonth.getListByMid(ctx.material.id);
                 const mbList = await this._getMaterialBillsData(ctx);
+                const material_month = this.ctx.material.months ? this.ctx.material.months.split(',') : [];
                 switch (data.type) {
                     case 'add':
+                        // 判断是否已存在该月份
+                        if (ctx.app._.indexOf(material_month, data.updateData.yearmonth) !== -1) {
+                            throw '调差期已创建过本月的信息价,请刷新页面重新获取';
+                        }
                         const tp = await ctx.service.materialMonth.add(data.updateData, monthList, mbList);
                         const materialBillsData = await this._getMaterialBillsData(ctx);
                         const monthsList = await this._getMaterialMonthsData(ctx, materialBillsData);
@@ -727,6 +777,10 @@ module.exports = app => {
                         };
                         break;
                     case 'del':
+                        // 判断是否已不存在该月份
+                        if (ctx.app._.intersection(data.updateData.del_yearmonth, material_month).length !== data.updateData.del_yearmonth.length) {
+                            throw '调差期已删除本月的信息价,请刷新页面重新获取';
+                        }
                         const tp2 = await ctx.service.materialMonth.del(data.updateData.del_yearmonth, monthList, mbList);
                         const materialBillsData2 = await this._getMaterialBillsData(ctx);
                         const monthsList2 = await this._getMaterialMonthsData(ctx, materialBillsData2);
@@ -1186,6 +1240,122 @@ module.exports = app => {
                 ctx.body = responseData;
             }
         }
+
+        async _setChecklistPermission(ctx) {
+            // 清单设置权限判断
+            ctx.material.checklistPermission = false;
+            if (ctx.session.sessionProject.page_show.openMaterialChecklist && ctx.material.highOrder === ctx.material.order && ctx.material.status !== auditConst.status.checked) {
+                const permission = ctx.session.sessionUser.permission;
+                if ((permission.tender !== undefined && permission.tender.indexOf('4') !== -1) || (ctx.material.order === 1 && ctx.session.sessionUser.accountId === ctx.material.user_id && (ctx.material.status === auditConst.status.uncheck || ctx.material.status === auditConst.status.checkNo))) {
+                    ctx.material.checklistPermission = true;
+                }
+            }
+        }
+
+        /**
+         * 清单设置页
+         * @param {Object} ctx - 全局上下文
+         */
+        async checklist(ctx) {
+            try {
+                await this._getMaterialAuditViewData(ctx);
+                await this._setChecklistPermission(ctx);
+                ctx.material.readOnly = !ctx.material.checklistPermission;
+                const renderData = await this._getDefaultRenderData(ctx);
+                // 根据期判断需要获取的工料信息值表
+                const searchsql = { tid: ctx.tender.id };
+                let midList = [];
+                if (ctx.material.highOrder !== ctx.material.order) {
+                    midList = await ctx.service.material.getPreMidList(ctx.tender.id, ctx.material.order);
+                    searchsql.mid = midList;
+                }
+                searchsql.t_type = materialConst.t_type[0].value;
+                renderData.materialBillsData = await ctx.service.materialBills.getAllDataByCondition({ where: searchsql, orders: [['order', 'asc']] });
+                // 取对应期的截取上期的调差金额和应耗数量
+                if (ctx.material.highOrder !== ctx.material.order) {
+                    for (const [mindex, mb] of renderData.materialBillsData.entries()) {
+                        const result = await ctx.service.materialBillsHistory.getByMbId(ctx.material.id, ctx.material.order, mb.id);
+                        _.forEach(result, function(value, key) {
+                            if (key === 'mb_id') {
+                                renderData.materialBillsData[mindex].id = result ? result[key] : null;
+                            } else {
+                                renderData.materialBillsData[mindex][key] = result ? result[key] : null;
+                            }
+                        });
+                    }
+                }
+                // 取所有已被调用的工料清单表
+                // renderData.materialListData = await ctx.service.materialList.getMaterialData(ctx.tender.id, ctx.material.id);
+                // renderData.materialNotJoinListData = await ctx.service.materialListNotjoin.getAllDataByCondition({ where: { tid: ctx.tender.id, mid: ctx.material.id } });
+                renderData.materialType = JSON.stringify(materialConst);
+                renderData.jsFiles = this.app.jsFiles.common.concat(this.app.jsFiles.material.checklist);
+                // 获取清单数据
+                // renderData.ledger = await ctx.service.ledger.getData(ctx.tender.id);
+                // renderData.pos = await ctx.service.pos.getPosData({ tid: ctx.tender.id });
+                // 获取所选期数据并合并相加同类清单项
+                // renderData.curLedgerData = await ctx.service.stageBills.getStagesData(ctx.tender.id, ctx.material.stage_id);
+                // renderData.curPosData = await ctx.service.stagePos.getStagesData(ctx.tender.id, ctx.material.stage_id, 'list');
+                await this.layout('material/checklist.ejs', renderData, 'material/checklist_modal.ejs');
+            } catch (err) {
+                this.log(err);
+                ctx.redirect('/tender/' + ctx.tender.id + '/measure/material');
+            }
+        }
+
+        /**
+         * 清单设置 - 编辑指数清单项 (Ajax)
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async saveChecklistData(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const responseData = {
+                    err: 0,
+                    msg: '',
+                    data: {},
+                };
+                await this._setChecklistPermission(ctx);
+                if (ctx.material.order !== ctx.material.highOrder) {
+                    throw '无法设置非最新期的清单设置数据';
+                }
+                // 权限控制
+                if (!ctx.material.checklistPermission && data.type !== 'resetChecklist') {
+                    throw '本期已审批完成或权限不足,无法设置清单数据';
+                }
+                switch (data.type) {
+                    case 'adds':
+                        responseData.data = await ctx.service.materialList.adds(data.postData, data.checklist);
+                        break;
+                    case 'dels':
+                        responseData.data = await ctx.service.materialList.dels(data.postData, data.checklist);
+                        break;
+                    case 'updates':
+                        if (data.updateData.quantity === '' || data.updateData.quantity === null) {
+                            throw '请输入数量';
+                        }
+                        // 判断数量是否为数字
+                        if (isNaN(data.updateData.quantity)) {
+                            throw '不能输入其它非数字类型字符';
+                        }
+                        responseData.data = await ctx.service.materialList.saves(data.updateData);
+                        break;
+                    case 'pastes':
+                        responseData.data = await ctx.service.materialList.savePastes(data.updateData);
+                        // 取所有工料表
+                        break;
+                    case 'resetChecklist':
+                        responseData.data = await ctx.service.materialChecklist.resetData(data.pushData, data.removeData, data.updateData);
+                        break;
+                    default: throw '参数有误';
+                }
+
+                ctx.body = responseData;
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+        }
     }
 
     return MaterialController;

+ 25 - 75
app/controller/revise_controller.js

@@ -189,7 +189,7 @@ module.exports = app => {
                     throw '台账修订会影响审批中的变更令(包含新增部位),请审批完成后再创建台账修订';
                 }
                 const revise = await ctx.service.ledgerRevise.add(ctx.tender.id, ctx.session.sessionUser.accountId);
-                ctx.redirect('/tender/' + ctx.tender.id + '/revise/info');
+                ctx.redirect(`/tender/${ctx.tender.id}/revise/${revise.id}/info`);
             } catch (err) {
                 this.log(err);
                 ctx.redirect(ctx.request.header.referer);
@@ -289,13 +289,6 @@ module.exports = app => {
         }
 
         async _getDefaultReviseInfoData(ctx, revise) {
-            // const reviseBills = revise.bills_file
-            //     ? JSON.parse(await fs.readFileSync(this.ctx.app.config.filePath + revise.bills_file, 'utf8'))
-            //     : await ctx.service.reviseBills.getData(ctx.tender.id);
-
-            // const revisePos = revise.pos_file
-            //     ? JSON.parse(await fs.readFileSync(this.ctx.app.config.filePath + revise.pos_file, 'utf8'))
-            //     : await ctx.service.revisePos.getData(ctx.tender.id);
             const [ledgerSpread, posSpread] = this._getSpreadSetting(revise);
             const sjsRela = await this.ctx.service.project.getSjsRela(ctx.session.sessionProject.id);
             this.ctx.helper.refreshSpreadShow(sjsRela.ledgerCol, [ledgerSpread, posSpread]);
@@ -315,9 +308,7 @@ module.exports = app => {
             }
             return {
                 revise, tender: ctx.tender.data,
-                // reviseBills, revisePos,
                 ledgerSpread, posSpread, tenderMenu, measureType,
-                preUrl: '/tender/' + ctx.tender.id,
                 audit: audit.revise,
                 jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.revise.info),
                 stdBills,
@@ -346,6 +337,7 @@ module.exports = app => {
             renderData.history = true;
             renderData.historyRevise = await ctx.service.ledgerRevise.getAllReviseList(ctx.tender.id);
             renderData.categoryData = await this.ctx.service.category.getAllCategory(this.ctx.session.sessionProject.id);
+            renderData.preUrl = ctx.url.replace('/info', '');
             await this.layout('revise/info.ejs', renderData, 'revise/info_modal.ejs');
         }
 
@@ -365,6 +357,7 @@ module.exports = app => {
             renderData.historyRevise = [];
             renderData.curAuditor = await ctx.service.reviseAudit.getCurAuditor(revise.id, revise.times);
             renderData.categoryData = await this.ctx.service.category.getAllCategory(this.ctx.session.sessionProject.id);
+            renderData.preUrl = ctx.url.replace('/info', '');
             await this.layout('revise/info.ejs', renderData, 'revise/info_modal.ejs');
         }
 
@@ -388,6 +381,7 @@ module.exports = app => {
             renderData.auditorList = await ctx.service.reviseAudit.getAuditors(revise.id, revise.times);
             renderData.curAuditor = await ctx.service.reviseAudit.getCurAuditor(revise.id, revise.times);
             renderData.categoryData = await this.ctx.service.category.getAllCategory(this.ctx.session.sessionProject.id);
+            renderData.preUrl = ctx.url.replace('/info', '');
             await this.layout('revise/info.ejs', renderData, 'revise/info_modal.ejs');
         }
 
@@ -417,21 +411,14 @@ module.exports = app => {
 
         async loadInfoData(ctx) {
             try {
-                const revise = await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id);
-                if (!revise) throw '台账修订数据有误';
+                const revise = await ctx.revise;
 
-                const billsFile = revise.status === audit.revise.status.checked && revise.bills_file
-                    ? this.ctx.app.config.filePath + revise.bills_file
-                    : undefined;
-                const reviseBills = billsFile && fs.existsSync(billsFile)
-                    ? JSON.parse(await fs.readFileSync(billsFile, 'utf8'))
+                const reviseBills = revise.readOnly
+                    ? await ctx.helper.loadLedgerDataFromOss(revise.curHis.bills_file)
                     : await ctx.service.reviseBills.getData(ctx.tender.id);
 
-                const posFile = revise.status === audit.revise.status.checked && revise.pos_file
-                    ? this.ctx.app.config.filePath + revise.pos_file
-                    : undefined;
-                const revisePos = posFile && fs.existsSync(posFile)
-                    ? JSON.parse(await fs.readFileSync(posFile, 'utf8'))
+                const revisePos = revise.readOnly
+                    ? await ctx.helper.loadLedgerDataFromOss(revise.curHis.pos_file)
                     : await ctx.service.revisePos.getData(ctx.tender.id);
 
                 if (revise.uid === ctx.session.sessionUser.accountId &&
@@ -497,11 +484,8 @@ module.exports = app => {
 
         async history(ctx) {
             try {
-                const revise = await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id, false);
-                if (!revise) throw '台账修订数据有误';
+                const revise = ctx.revise;
 
-                // const reviseBills = await ctx.service.ledger.getData(ctx.tender.id);
-                // const revisePos = await ctx.service.pos.getPosData({tid: ctx.tender.id});
                 const [ledgerSpread, posSpread] = this._getSpreadSetting(revise);
                 const sjsRela = await this.ctx.service.project.getSjsRela(ctx.session.sessionProject.id);
                 this.ctx.helper.refreshSpreadShow(sjsRela.ledgerCol, [ledgerSpread, posSpread]);
@@ -520,9 +504,9 @@ module.exports = app => {
                 // 获取审批流程中左边列表
                 const auditors = await ctx.service.reviseAudit.getAuditorsWithOwner(revise.id, times);
                 const renderData = {
+                    preUrl: ctx.url.replace('/info', ''),
                     measureType, audit, revise,
                     ledgerSpread, posSpread,
-                    // reviseBills, revisePos,
                     readOnly: true,
                     historyRevise,
                     auditHistory,
@@ -538,37 +522,6 @@ module.exports = app => {
             }
         }
 
-        async historyInfo(ctx) {
-            try {
-                const data = JSON.parse(ctx.request.body.data);
-                if (!data || !data.rid || data.rid === '') throw '查询的台账修订有误';
-                const reviseInfo = await ctx.service.ledgerRevise.getRevise(ctx.tender.id, data.rid);
-                reviseInfo.end_time_str = reviseInfo.end_time ? ctx.moment(reviseInfo.end_time).format('YYYY-MM-DD HH:mm:ss') : '';
-                ctx.body = { err: 0, msg: '', data: reviseInfo };
-            } catch (err) {
-                this.log(err);
-                ctx.body = this.ajaxErrorBody(err, '获取台账修订历史数据错误');
-            }
-        }
-
-        /**
-         * 加载 数据
-         * @param {} ctx
-         */
-        async loadHistoryData(ctx) {
-            try {
-                const revise = await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id, false);
-                if (!revise) throw '台账修订数据有误';
-
-                const reviseBills = await ctx.service.ledger.getData(ctx.tender.id);
-                const revisePos = await ctx.service.pos.getPosData({ tid: ctx.tender.id });
-                ctx.body = { err: 0, msg: '', data: { bills: reviseBills, pos: revisePos } };
-            } catch (error) {
-                ctx.helper.log(error);
-                this.ajaxErrorBody(error, '获取台账修订数据错误,请刷新页面');
-            }
-        }
-
         async checkRevise(ctx) {
             const revise = await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id);
             if (revise.uid !== ctx.session.sessionUser.accountId) {
@@ -901,12 +854,10 @@ module.exports = app => {
 
                 await ctx.service.reviseAudit.check(revise, checkType, ctx.request.body.opinion, revise.times);
 
-                // ctx.redirect('/tender/' + ctx.tender.id + '/revise/info');
                 ctx.redirect(ctx.request.headers.referer);
             } catch (err) {
                 this.log(err);
                 this.postError(err, '审批失败');
-                // ctx.redirect('/tender/' + ctx.tender.id + '/revise/info');
                 ctx.redirect(ctx.request.headers.referer);
             }
         }
@@ -933,6 +884,7 @@ module.exports = app => {
             const lastStage = await this._getLastStage(ctx);
 
             const renderData = {
+                preUrl: ctx.url.replace('/compare', ''),
                 revise,
                 measureType,
                 lastStage,
@@ -996,10 +948,18 @@ module.exports = app => {
 
         async _loadDataByFilter(ctx, filter) {
             switch(filter) {
-                case 'bills': return await ctx.service.ledger.getAllDataByCondition({where: {tender_id: ctx.tender.id} });
-                case 'pos': return await ctx.service.pos.getAllDataByCondition({where: {tid: ctx.tender.id} });
-                case 'reviseBills': return await ctx.service.reviseBills.getAllDataByCondition({where: {tender_id: ctx.tender.id}});
-                case 'revisePos': return await ctx.service.revisePos.getAllDataByCondition({where: {tid: ctx.tender.id}});
+                case 'bills':
+                    return ctx.revise.preHis ? await this.ctx.helper.loadLedgerDataFromOss(ctx.revise.preHis.bills_file) : [];
+                case 'pos':
+                    return ctx.revise.preHis ? await this.ctx.helper.loadLedgerDataFromOss(ctx.revise.preHis.pos_file) : [];
+                case 'reviseBills':
+                    return ctx.revise.readOnly && ctx.revise.curHis
+                        ? await this.ctx.helper.loadLedgerDataFromOss(ctx.revise.curHis.bills_file)
+                        : await ctx.service.reviseBills.getAllDataByCondition({ where: { tender_id: ctx.tender.id } });
+                case 'revisePos':
+                    return ctx.revise.readOnly && ctx.revise.curHis
+                        ? await this.ctx.helper.loadLedgerDataFromOss(ctx.revise.curHis.pos_file)
+                        : await ctx.service.revisePos.getAllDataByCondition({ where: { tid: ctx.tender.id } });
                 case 'stageBills':
                 case 'stagePos':
                     if (!ctx.lastStage) ctx.lastStage = await this._getLastStage(ctx);
@@ -1044,22 +1004,12 @@ module.exports = app => {
             if (!revise) throw '台账修订数据有误';
 
             const renderData = {
+                preUrl: ctx.url.replace('/gcl-compare', ''),
                 revise,
                 jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.revise.gclCompare),
             };
             await this.layout('revise/gcl_compare.ejs', renderData);
         }
-
-        async bwtz(ctx) {
-            const revise = await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id);
-            if (!revise) throw '台账修订数据有误';
-
-            const renderData = {
-                revise,
-                jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.revise.bwtz),
-            };
-            await this.layout('revise/bwtz.ejs', renderData);
-        }
     }
 
     return ReviseController;

+ 3 - 1
app/controller/setting_controller.js

@@ -763,8 +763,10 @@ module.exports = app => {
 
                 const result = await ctx.service.project.updateFunRela(projectId, ctx.request.body);
                 if (!result) throw '保存数据失败';
-                this.ctx.session.sessionProject.page_show.openChangeRevise = data.openChangeRevise ? 1 : 0;
+                // this.ctx.session.sessionProject.page_show.openChangeProject = data.openChangeProject ? 1 : 0;
+                // this.ctx.session.sessionProject.page_show.openChangeApply = data.openChangeApply ? 1 : 0;
                 this.ctx.session.sessionProject.page_show.openMaterialTax = data.openMaterialTax ? 1 : 0;
+                this.ctx.session.sessionProject.page_show.openMaterialChecklist = data.openMaterialChecklist ? 1 : 0;
                 const result2 = await ctx.service.project.updatePageshow(projectId);
                 if (!result2) throw '保存数据失败';
 

+ 14 - 5
app/controller/stage_controller.js

@@ -234,7 +234,9 @@ module.exports = app => {
         }
 
         async _getStageLedgerData(ctx) {
-            const ledgerData = await ctx.service.ledger.getData(ctx.tender.id);
+            const ledgerData = ctx.stage.ledgerHis
+                ? await ctx.helper.loadLedgerDataFromOss(ctx.stage.ledgerHis.bills_file)
+                : await ctx.service.ledger.getData(ctx.tender.id);
             const dgnData = await ctx.service.stageBillsDgn.getDgnData(ctx.tender.id);
             for (const d of dgnData) {
                 const l = ctx.app._.find(ledgerData, { id: d.id });
@@ -267,7 +269,9 @@ module.exports = app => {
         async _getStagePosData(ctx) {
             let curStageData,
                 preStageData;
-            const posData = await ctx.service.pos.getPosDataWithAddStageOrder({ tid: ctx.tender.id });
+            const posData = ctx.stage.ledgerHis
+                ? await ctx.helper.loadLedgerDataFromOss(ctx.stage.ledgerHis.pos_file)
+                : await ctx.service.pos.getPosDataWithAddStageOrder({ tid: ctx.tender.id });
             // 根据当前人,或指定对象查询数据
             // console.time('cur');
             if (ctx.stage.readOnly) {
@@ -947,6 +951,7 @@ module.exports = app => {
 
                 ctx.body = responseData;
             } catch (err) {
+                console.log(err);
                 this.log(err);
                 ctx.body = { err: 1, msg: err.toString(), data: null };
             }
@@ -1340,8 +1345,12 @@ module.exports = app => {
                 };
                 if (data.main) {
                     result.main = {};
-                    result.main.ledger = await ctx.service.ledger.getData(ctx.tender.id);
-                    result.main.pos = await ctx.service.pos.getPosData({ tid: ctx.tender.id });
+                    result.main.ledger = ctx.stage.ledgerHis
+                        ? await ctx.helper.loadLedgerDataFromOss(ctx.stage.ledgerHis.bills_file)
+                        : await ctx.service.ledger.getData(ctx.tender.id);
+                    result.main.pos = ctx.stage.ledgerHis
+                        ? await ctx.helper.loadLedgerDataFromOss(ctx.stage.ledgerHis.pos_file)
+                        : await ctx.service.pos.getPosData({ tid: ctx.tender.id });
                 }
                 for (const order of data.roles) {
                     const data = { order, bills: [], pos: [] };
@@ -1393,7 +1402,7 @@ module.exports = app => {
                 ctx.body = { err: 0, msg: '', data: result };
             } catch (err) {
                 this.log(err);
-                ctx.body = this.ajaxErrorBody(err, '加载合同支付数据错误');
+                ctx.body = this.ajaxErrorBody(err, '加载部位台账数据错误');
             }
         }
 

+ 21 - 4
app/controller/tender_controller.js

@@ -846,9 +846,18 @@ module.exports = app => {
                 const updateData = {
                     id: tenderId,
                 };
-                updateData[codeRuleConst.ruleField[data.rule]] = data.data;
-                updateData.c_connector = data.connector;
-                updateData.c_rule_first = 0;
+                if (data.type) {
+                    const tenderData = await ctx.service.tender.getDataById(tenderId);
+                    const c_code_rules = tenderData.c_code_rules ? JSON.parse(tenderData.c_code_rules) : {};
+                    c_code_rules[data.type + '_rule'] = JSON.parse(data.data);
+                    c_code_rules[data.type + '_rule_first'] = 0;
+                    c_code_rules[data.type + '_connector'] = data.connector;
+                    updateData.c_code_rules = JSON.stringify(c_code_rules);
+                } else {
+                    updateData[codeRuleConst.ruleField[data.rule]] = data.data;
+                    updateData.c_connector = data.connector;
+                    updateData.c_rule_first = 0;
+                }
 
                 const result = await ctx.service.tender.db.update(ctx.service.tender.tableName, updateData);
                 if (result.affectedRows !== 1) {
@@ -877,7 +886,15 @@ module.exports = app => {
                 const updateData = {
                     id: tenderId,
                 };
-                updateData.c_rule_first = 0;
+                const data = JSON.parse(ctx.request.body.data);
+                if (data && data.type) {
+                    const tenderData = await ctx.service.tender.getDataById(tenderId);
+                    const c_code_rules = tenderData.c_code_rules ? JSON.parse(tenderData.c_code_rules) : {};
+                    c_code_rules[data.type + '_rule_first'] = 0;
+                    updateData.c_code_rules = JSON.stringify(c_code_rules);
+                } else {
+                    updateData.c_rule_first = 0;
+                }
 
                 const result = await ctx.service.tender.db.update(ctx.service.tender.tableName, updateData);
                 if (result.affectedRows !== 1) {

+ 10 - 1
app/extend/context.js

@@ -66,5 +66,14 @@ module.exports = {
                 body: this.session.body,
             }));
         }
-    }
+    },
+
+    get hisOssPath() {
+        return this.app.config.hisOssPath;
+    },
+
+    get hisOss() {
+        return this.app.hisOss;
+    },
+
 };

+ 19 - 5
app/extend/helper.js

@@ -23,7 +23,8 @@ const UAParser = require('ua-parser-js');
 const math = require('mathjs');
 const syncApiConst = require('../const/sync_api');
 const crypto = require('crypto');
-
+const jwt = require('jsonwebtoken');
+const sign = require('../const/sign');
 module.exports = {
     _,
 
@@ -430,7 +431,7 @@ module.exports = {
         } else if (!str2) {
             return -1;
         }
-
+        const numReg = /^[0-9]+$/;
         function compareSubCode(code1, code2) {
             if (numReg.test(code1)) {
                 if (numReg.test(code2)) {
@@ -443,10 +444,7 @@ module.exports = {
                 return 1;
             }
             return code1 === code2 ? 0 : (code1 < code2 ? -1 : 1); // code1.localeCompare(code2);
-
-
         }
-        const numReg = /^[0-9]+$/;
         const aCodes = str1.split(symbol),
             bCodes = str2.split(symbol);
         for (let i = 0, iLength = Math.min(aCodes.length, bCodes.length); i < iLength; ++i) {
@@ -1465,4 +1463,20 @@ module.exports = {
         const endMonth = moment(month).endOf('month').format('YYYY-MM-DD HH:mm:ss');
         return [startMonth, endMonth];
     },
+
+    /**
+     * 创建json web token
+     * @param {Object} data - 签名数据
+     * @return {String} token
+     */
+    createJWT(data) {
+        return jwt.sign({ data }, sign.managementApiSecretKey, { expiresIn: '15s' });
+    },
+
+    async loadLedgerDataFromOss(url) {
+        const File = await this.ctx.hisOss.get(this.ctx.hisOssPath + url);
+        if (File.res.status !== 200) return '获取修订台账有误';
+        const result = JSON.parse(File.content);
+        return result;
+    },
 };

+ 1 - 0
app/lib/analysis_excel.js

@@ -292,6 +292,7 @@ class ImportBaseTree {
             pos.lid = this.finalNode.id;
             pos.tid = this.ctx.tender.id;
             pos.add_stage = 0;
+            pos.add_stage_order = 0;
             pos.add_times = 0;
             pos.in_time = new Date();
             pos.porder = this.finalNode.pos.length + 1;

+ 15 - 2
app/lib/pay_calc.js

@@ -9,6 +9,9 @@
  */
 
 const math = require('mathjs');
+math.config({
+    number: 'BigNumber',
+});
 const PayConst = require('../const/deal_pay.js');
 const payType = PayConst.payType;
 const deadlineType = PayConst.deadlineType;
@@ -72,7 +75,12 @@ class PayCalculate {
             }
         }
         try {
-            const value = math.eval(formula);
+            // 使用mathjs计算 math.eval('17259401.95*0.9') = 15533461.754999999,正确应为 15533461.755
+            // const value = math.eval(formula);
+            // 使用逆波兰法四则运算,可防止出现误差,但是只支持四则运算,不支持科学运算
+            // const value = this.ctx.helper.calcExprStrRpn(formula);
+            // 使用mathjs的大数运算,可支持所有
+            const value = parseFloat(math.eval(formula));
             return value;
         } catch(err) {
             return 0;
@@ -92,7 +100,12 @@ class PayCalculate {
             }
         }
         try {
-            const value = math.eval(formula);
+            // 使用mathjs计算 math.eval('17259401.95*0.9') = 15533461.754999999,正确应为 15533461.755
+            // const value = math.eval(formula);
+            // 使用逆波兰法四则运算,可防止出现误差,但是只支持四则运算,不支持科学运算
+            // const value = this.ctx.helper.calcExprStrRpn(formula);
+            // 使用mathjs的大数运算,可支持所有
+            const value = parseFloat(math.eval(formula));
             return value;
         } catch(err) {
             return 0;

+ 56 - 0
app/middleware/api3management_check.js

@@ -0,0 +1,56 @@
+'use strict';
+
+/**
+ *
+ * @author LanJianRong
+ * @date  2021-12-27
+ * @version
+ */
+const maintainConst = require('../const/maintain');
+const sign = require('../const/sign');
+const jwt = require('jsonwebtoken');
+module.exports = options => {
+    return function* api3managementCheck(next) {
+        try {
+            // 获取系统维护信息
+            const maintainData = yield this.service.maintain.getDataById(1);
+            if (maintainData.status === maintainConst.status.ongoing) {
+                throw '系统维护中~';
+            }
+            const token = this.query.auth;
+            if (!token) {
+                throw '参数有误';
+            }
+            try {
+                const decoded = jwt.verify(token, sign.managementApiSecretKey);
+                if (!decoded.data) throw '参数有误';
+                this.data = decoded.data;
+            } catch (error) {
+                throw error;
+            }
+            // const data = yield this.service.project.getProjectByCode(code.toString().trim());
+            // if (data === null) {
+            //     throw '不存在项目数据';
+            // }
+            // if (data.custom === 0) {
+            //     throw '无法通过接口登录本系统';
+            // }
+            // if (data.custom === 1 && data.can_api === 0) {
+            //     throw '接口已关闭,无法使用';
+            // }
+            // const encryptSign = crypto.createHash('md5').update(data.code + data.secret + time.toString()).digest('hex').toString();
+            // if (encryptSign !== sign) {
+            //     throw '参数验证失败';
+            // }
+            // this.projectData = data;
+            yield next;
+        } catch (err) {
+            this.body = {
+                code: -1,
+                msg: err.toString(),
+                data: null,
+            };
+            return;
+        }
+    };
+};

+ 120 - 0
app/middleware/change_project_check.js

@@ -0,0 +1,120 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Ellisran
+ * @date 2020/10/15
+ * @version
+ */
+
+const status = require('../const/audit').changeProject.status;
+const _ = require('lodash');
+
+module.exports = options => {
+    /**
+     * 标段校验 中间件
+     * 1. 读取标段数据(包括属性)
+     * 2. 检验用户是否可见标段(不校验具体权限)
+     *
+     * @param {function} next - 中间件继续执行的方法
+     * @return {void}
+     */
+    return function* changeProjectCheck(next) {
+        try {
+            // 获取revise
+            if (!this.session.sessionProject.page_show.openChangeProject) {
+                throw '该功能已关闭';
+            }
+            const cpid = this.params.cpid || this.request.body.cpid;
+            if (!cpid) {
+                throw '您访问的变更立项不存在';
+            }
+            const change = yield this.service.changeProject.getDataById(cpid);
+            // 读取原报、审核人数据
+            change.auditors = yield this.service.changeProjectAudit.getAuditors(change.id, change.times);
+            change.curAuditor = yield this.service.changeProjectAudit.getCurAuditor(change.id, change.times);
+
+            if (!change) throw '变更令数据有误';
+            // 权限相关
+            // todo 校验权限 (标段参与人、分享)
+            const accountId = this.session.sessionUser.accountId,
+                auditorIds = _.map(change.auditors, 'aid'),
+                shareIds = [];
+            if (accountId === change.uid) { // 原报
+                // if (change.curAuditor) {
+                //     change.readOnly = change.status === status.checking && change.curAuditor.user_id === accountId;
+                // } else {
+                //     change.readOnly = change.status !== status.uncheck && change.status !== status.back;
+                // }
+                change.curTimes = change.times;
+                if (change.status === status.uncheck || change.status === status.back || change.status === status.checkNo) {
+                    change.curOrder = 0;
+                } else if (change.status === status.checked) {
+                    change.curOrder = _.max(_.map(change.auditors, 'order'));
+                } else {
+                    change.curOrder = change.curAuditor.aid === accountId ? change.curAuditor.order : change.curAuditor.order - 1;
+                }
+                change.filePermission = true;
+            } else if (this.tender.isTourist) {
+                change.curTimes = change.times;
+                if (change.status === status.uncheck || change.status === status.back || change.status === status.checkNo) {
+                    change.curOrder = 0;
+                } else if (change.status === status.checked) {
+                    change.curOrder = _.max(_.map(change.auditors, 'order'));
+                } else {
+                    change.curOrder = change.curAuditor.order;
+                }
+                change.filePermission = this.tender.touristPermission.file || auditorIds.indexOf(accountId) !== -1;
+            } else if (auditorIds.indexOf(accountId) !== -1) { // 审批人
+                if (change.status === status.uncheck) {
+                    throw '您无权查看该数据';
+                }
+                // change.readOnly = change.status !== status.checking || accountId !== change.curAuditor.aid;
+                change.curTimes = change.status === status.back ? change.times - 1 : change.times;
+                if (change.status === status.checked) {
+                    change.curOrder = _.max(_.map(change.auditors, 'order'));
+                } else if (change.status === status.back) {
+                    const audit = this.service.changeProjectAudit.getDataByCondition({
+                        cpid: change.id, times: change.times, status: status.back,
+                    });
+                    change.curOrder = audit.order;
+                } else if (change.status === status.checkNo) {
+                    change.curOrder = 0;
+                } else {
+                    change.curOrder = accountId === change.curAuditor.aid ? change.curAuditor.order : change.curAuditor.order - 1;
+                }
+                change.filePermission = true;
+            } else if (shareIds.indexOf(accountId) !== -1) { // 分享人
+                if (change.status === status.uncheck) {
+                    throw '您无权查看该数据';
+                }
+                // change.readOnly = true;
+                change.curTimes = change.status === status.back ? change.times - 1 : change.times;
+                change.curOrder = change.status === status.checked ? _.max(_.map(change.auditors, 'order')) : (change.status !== status.checkNo ? change.curAuditor.order - 1 : 0);
+                change.filePermission = false;
+            } else { // 其他不可见
+                throw '您无权查看该数据';
+            }
+            // 调差的readOnly 指表格和页面只能看不能改,和审批无关
+            change.readOnly = !((change.status === status.uncheck || change.status === status.back) && accountId === change.uid);
+            this.change = change;
+            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);
+        }
+    };
+};

+ 3 - 0
app/middleware/material_check.js

@@ -163,6 +163,9 @@ module.exports = options => {
                 }));
             }
             // 重定向值标段管理
+            if (err === '您无权查看该数据') {
+                this.tender ? this.redirect('/tender/' + this.tender.id + '/measure/material') : this.redirect('/list');
+            }
             this.redirect(this.request.headers.referer);
         }
     };

+ 9 - 2
app/middleware/revise_check.js

@@ -18,11 +18,16 @@ module.exports = options => {
      * @param {function} next - 中间件继续执行的方法
      * @return {void}
      */
-    return function* reviseAuditCheck(next) {
+    return function* reviseCheck(next) {
         try {
             // 获取revise
-            const revise = yield this.service.ledgerRevise.getLastestRevise(this.tender.id);
+            const revise = this.params.rid
+                ? yield this.service.ledgerRevise.getRevise(this.tender.id, this.params.rid)
+                : yield this.service.ledgerRevise.getLastestRevise(this.tender.id);
             if (!revise) throw '台账修订数据有误';
+            // 修订前后,历史台账
+            revise.preHis = revise.pre_his_id ? yield this.service.ledgerHistory.getDataById(revise.pre_his_id) : null;
+            revise.curHis = revise.his_id ? yield this.service.ledgerHistory.getDataById(revise.his_id) : null;
             revise.reviseUsers = [revise.uid];
             if (revise.status !== auditConst.status.uncheck) {
                 const times = revise.status === auditConst.status.checkNo ? revise.times - 1 : revise.times;
@@ -30,6 +35,8 @@ module.exports = options => {
                 const auditorsId = this.helper._.map(auditors, 'audit_id');
                 revise.reviseUsers.push(...auditorsId);
             }
+            revise.readOnly = revise.uid !== this.session.sessionUser.accountId ||
+                revise.status === auditConst.status.checking || revise.status === auditConst.status.checked;
             this.revise = revise;
             yield next;
         } catch (err) {

+ 5 - 0
app/middleware/stage_check.js

@@ -50,6 +50,11 @@ module.exports = options => {
             stage.auditors = yield this.service.stageAudit.getAuditors(stage.id, stage.times);
             stage.curAuditor = yield this.service.stageAudit.getCurAuditor(stage.id, stage.times);
 
+            // 历史台账
+            if (stage.status === status.checked) {
+                stage.ledgerHis = yield this.service.ledgerHistory.getDataById(stage.his_id);
+            }
+
             // 获取最新的期
             stage.highOrder = yield this.service.stage.count({
                 tid: this.tender.id,

+ 1 - 0
app/middleware/tender_check.js

@@ -46,6 +46,7 @@ module.exports = options => {
             if (tender.data.project_id !== this.session.sessionProject.id) {
                 throw '您无权查看该项目';
             }
+            tender.his = yield this.service.ledgerHistory.getLatestHistory(tender.id);
             const accountId = this.session.sessionUser.accountId;
             const advanceAuditors = yield this.service.advanceAudit.getAllAuditors(tender.id);
             const advanceAuditorsId = this.helper._.map(advanceAuditors, 'audit_id');

+ 40 - 4
app/public/css/main.css

@@ -828,6 +828,14 @@ input.nospin[type="number"]{-moz-appearance:textfield;}
   height:250px;
   overflow:auto
 }
+.modal-height-150{
+  height: 150px;
+  overflow: auto;
+}
+.modal-height-max150{
+  max-height: 150px;
+  overflow: auto;
+}
 .modal-fullscreen{
   overflow: auto;
 }
@@ -1530,6 +1538,11 @@ overflow-y: auto;
   font-size: 2.80rem;
   position: relative;
 }
+.card .card-approve-title .card-approve-big{
+  position: absolute;
+  left: 45%;
+  bottom: -8px;
+}
 .card .card-approve-title small{
   position: absolute;
   font-size: 0.15rem;
@@ -1700,15 +1713,15 @@ overflow-y: auto;
   transform: translate(-50%, -45%);
 }
 .left-login{
-  width: 476px;
-  height: 513px;
+  width: 428px;
+  height: 462px;
   border-radius: 8px;
   background: rgba(51, 119, 255, 0.9);
   box-shadow: 6px 0px 6px rgba(0, 0, 0, 0.16);
 }
 .right-login{
-  width: 460px;
-  height: 465px;
+  width: 414px;
+  height: 418px;
   border-radius: 0 8px 8px 0;
   background: rgba(255, 255, 255, 1);
 }
@@ -1872,4 +1885,27 @@ overflow-y: auto;
 .small-text{
   font-size: 0.75rem !important;
   font-weight: 400;
+}
+.chaosong{
+  margin: 0 0 0 70px;
+  height: 300px;
+}
+.inputErrow{
+-webkit-animation:shake 1s .2s ease both;
+-moz-animation:shake 1s .2s ease both;
+animation:shake 1s .2s ease both;}
+@-webkit-keyframes shake{
+0%,100%{-webkit-transform:translateX(0);}
+10%,30%,50%,70%, 90%{-webkit-transform:translateX(-10px);}
+20%,40%,60%,80%{-webkit-transform:translateX(10px);}
+}
+@-moz-keyframes shake{
+0%,100%{-moz-transform:translateX(0);}
+10%,30%,50%,70%, 90%{-moz-transform:translateX(-10px);}
+20%,40%,60%,80%{-moz-transform:translateX(10px);}
+}
+@keyframes shake{
+0%,100%{transform:translateX(0);}
+10%,30%,50%,70%, 90%{transform:translateX(-10px);}
+20%,40%,60%,80%{transform:translateX(10px);}
 }

+ 20 - 14
app/public/js/change_information_approval.js

@@ -71,7 +71,7 @@ $(document).ready(() => {
         const newColTp = {
             title: '|金额',
             colSpan: '|1', rowSpan: '|1',
-            field: 'sa_tp',
+            field: 'sa_tp_' + aid,
             hAlign: 2, width: 80, type: 'Number',
             readOnly: true
         };
@@ -108,18 +108,23 @@ $(document).ready(() => {
             changeSpreadObj.countSum();
         },
         setAuditValue: function () {
-            const rowCount = changeSpreadSheet.getRowCount();
-            // 用户的数据合计
-            for (const j in aidList) {
-                for(let i = 0; i <= rowCount - 1; i++){
-                    const data = {
-                        unit_price: changeSpreadSheet.getValue(i, 5),
-                        amount: parseFloat(changeSpreadSheet.getValue(i, 10 + parseInt(j)*2)),
-                    };
-                    const sum = ZhCalc.round(ZhCalc.mul(data.unit_price, data.amount), totalPriceUnit);
-                    changeSpreadSheet.setValue(i, 11 + j*2, sum !== 0 ? sum : null);
+            for (const c  of changeList) {
+                for (const j of aidList) {
+                    c['sa_tp_' + j] = ZhCalc.round(ZhCalc.mul(c['audit_amount_' + j], c.unit_price), totalPriceUnit);
                 }
             }
+            // const rowCount = changeSpreadSheet.getRowCount();
+            // // 用户的数据合计
+            // for (const j in aidList) {
+            //     for(let i = 0; i <= rowCount - 1; i++){
+            //         const data = {
+            //             unit_price: changeSpreadSheet.getValue(i, 5),
+            //             amount: parseFloat(changeSpreadSheet.getValue(i, 10 + parseInt(j)*2)),
+            //         };
+            //         const sum = ZhCalc.round(ZhCalc.mul(data.unit_price, data.amount), totalPriceUnit);
+            //         changeSpreadSheet.setValue(i, 11 + j*2, sum !== 0 ? sum : null);
+            //     }
+            // }
         },
         resetXmjSpread: function(data = null) {
             const xmj = [];
@@ -321,13 +326,13 @@ $(document).ready(() => {
             // 更新至服务器
             postData(window.location.pathname + '/save', { type:'paste_amount_rows', updateData: data }, function (result) {
                 changeList = result;
-                SpreadJsObj.loadSheetData(changeSpreadSheet, SpreadJsObj.DataType.Data, changeList);
                 changeSpreadObj.setAuditValue();
+                SpreadJsObj.loadSheetData(changeSpreadSheet, SpreadJsObj.DataType.Data, changeList);
                 changeSpreadObj.makeSjsFooter();
                 changeSpreadObj.resetXmjSpread(SpreadJsObj.getSelectObject(changeSpreadSheet));
             }, function () {
-                SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
                 changeSpreadObj.setAuditValue();
+                SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
                 return;
             });
         },
@@ -335,8 +340,9 @@ $(document).ready(() => {
 
     SpreadJsObj.initSpreadSettingEvents(changeSpreadSetting, changeCol);
     SpreadJsObj.initSheet(changeSpreadSheet, changeSpreadSetting);
-    SpreadJsObj.loadSheetData(changeSpreadSheet, SpreadJsObj.DataType.Data, changeList);
     changeSpreadObj.setAuditValue();
+    SpreadJsObj.loadSheetData(changeSpreadSheet, SpreadJsObj.DataType.Data, changeList);
+    console.log(changeList);
     changeSpreadObj.makeSjsFooter();
     changeSpreadObj.resetXmjSpread(SpreadJsObj.getSelectObject(changeSpreadSheet));
     const userIndex = aidList.indexOf(parseInt(accountId));

+ 7 - 2
app/public/js/change_information_show.js

@@ -60,7 +60,7 @@ $(document).ready(() => {
         const newColTp = {
             title: '|金额',
             colSpan: '|1', rowSpan: '|1',
-            field: 'sa_tp',
+            field: 'sa_tp_' + aid,
             hAlign: 2, width: 80, type: 'Number',
         };
         changeSpreadSetting.cols.push(newColcount);
@@ -163,8 +163,13 @@ $(document).ready(() => {
 
     SpreadJsObj.initSpreadSettingEvents(changeSpreadSetting, changeCol);
     SpreadJsObj.initSheet(changeSpreadSheet, changeSpreadSetting);
+    for (const c  of changeList) {
+        for (const j of aidList) {
+            c['sa_tp_' + j] = ZhCalc.round(ZhCalc.mul(c['audit_amount_' + j], c.unit_price), totalPriceUnit);
+        }
+    }
     SpreadJsObj.loadSheetData(changeSpreadSheet, SpreadJsObj.DataType.Data, changeList);
-    changeSpreadObj.setAuditValue();
+    // changeSpreadObj.setAuditValue();
     changeSpreadObj.makeSjsFooter();
     changeSpreadObj.showHideAudit();
     changeSpreadObj.resetXmjSpread(SpreadJsObj.getSelectObject(changeSpreadSheet));

+ 312 - 0
app/public/js/change_project.js

@@ -0,0 +1,312 @@
+'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');
+    }
+    // else if ($('#changeList').children.length === 0) {
+    //     $('#add-bj').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();
+                // $('#add-bj-modal').modal('show');
+            } 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 ($('#bj-name').val().length === 0) {
+            $('#bj-name').addClass('is-invalid');
+            $('#name_error_msg').show();
+            $('#name_error_msg').text('工程名称不能为空。');
+            $(this).attr('disabled', false);
+            setTimeout(function () {
+                $('#bj-name').removeClass('is-invalid');
+                $('#name_error_msg').hide();
+            }, 2000);
+            return;
+        }
+        if ($('#bj-name').val().length > 100) {
+            $('#bj-name').addClass('is-invalid');
+            $('#name_error_msg').show();
+            $('#name_error_msg').text('名称超过100个字,请缩减名称。');
+            $(this).attr('disabled', false);
+            setTimeout(function () {
+                $('#bj-name').removeClass('is-invalid');
+                $('#name_error_msg').hide();
+            }, 2000);
+            return;
+        }
+        const data = {
+            code: $('#bj-code').val(),
+            name: $('#bj-name').val(),
+        };
+        if (data.code || data.code !== '' || data.name || data.name !== '') {
+            postData('/tender/'+ tenderId +'/change/project/add', data, function (rst) {
+                $('#bj-code').removeClass('is-invalid');
+                $('#mj-add').modal('hide');
+                $(this).attr('disabled', false);
+                window.location.href = '/tender/'+ tenderId +'/change/project/' + rst.id + '/information';
+            }, function () {
+                $('#mj-code').addClass('is-invalid');
+                $('#mj-Hint').show();
+                $(this).attr('disabled', false);
+            });
+        }
+    });
+
+    //状态切换
+    $('#status_select a').on('click', function () {
+       const status = $(this).data('val');
+       let url = '/tender/'+ tenderId +'/change/project';
+       if (status !== 0) {
+           url += '/status/'+ status;
+       }
+       let orderSetting = getLocalCache('change-project-'+ tenderId +'-list-order');
+       if (orderSetting) {
+           const orders = orderSetting.split('|');
+           url += '?sort=' + orders[0] + '&order=' + orders[1];
+       }
+       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 () {
+        });
+    });
+
+    // 弹出删除变更框赋值
+    $('.delete-cid-modal').on('click', function () {
+        $('#delete-cid').val($(this).attr('cid'));
+    });
+
+    // 排序初始化
+    let orderSetting = getLocalCache('change-project-'+ 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('排序:变更立项书编号');
+    }
+    // let sortSetting = getLocalCache('change-'+ $('#tenderId').val() +'-list-sort');
+    // if (sortSetting && parseInt(sortSetting) === 1) {
+    //     $('#bpaixu').click();
+    // }
+    // $('#sort-dropdown').on('shown.bs.dropdown', function () {
+    //     setLocalCache('change-'+ $('#tenderId').val() +'-list-sort', 1);
+    // });
+    // $('#sort-dropdown').on('hidden.bs.dropdown', function () {
+    //     setLocalCache('change-'+ $('#tenderId').val() +'-list-sort', 0);
+    // });
+
+    $('#sort-radio input[name="paizhi"]').click(function () {
+        const orderStr = $(this).val() + '|' + $('#order-radio input[name="paixu"]:checked').val();
+        setLocalCache('change-project-'+ tenderId +'-list-order', orderStr);
+        // setLocalCache('change-'+ $('#tenderId').val() +'-list-sort', 1);
+        const link = window.location.origin + window.location.pathname + '?sort='+ $(this).val() + '&order=' + $('#order-radio input[name="paixu"]:checked').val();
+        window.location.href = link;
+    });
+    $('#order-radio input[name="paixu"]').click(function () {
+        const orderStr = $('#sort-radio input[name="paizhi"]:checked').val() + '|' + $(this).val();
+        setLocalCache('change-project-'+ tenderId +'-list-order', orderStr);
+        // setLocalCache('change-'+ $('#tenderId').val() +'-list-sort', 1);
+        const link = window.location.origin + window.location.pathname + '?sort='+ $('#sort-radio input[name="paizhi"]:checked').val() + '&order=' + $(this).val();
+        window.location.href = link;
+    })
+
+    $.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();
+        }
+    });
+});

+ 228 - 0
app/public/js/change_project_audit.js

@@ -0,0 +1,228 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2019/2/27
+ * @version
+ */
+
+$(document).ready(function () {
+    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 && cur_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 !== cur_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);
+    })
+
+    // 添加审批流程按钮逻辑
+    $('.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
+    })
+
+    // 添加到审批流程中
+    $('dl').on('click', 'dd', function () {
+        const id = parseInt($(this).data('id'));
+        if (id) {
+            postData(preUrl + '/audit/add', { auditorId: id }, (datas) => {
+                const html = [];
+                // 如果是重新上报,添加到重新上报列表中
+                const auditorshtml = [];
+                for (const [index,data] of datas.entries()) {
+                    if (index !== 0) {
+                        html.push('<li class="list-group-item" auditorId="'+ data.aid +'">');
+                        html.push('<a href="javascript: void(0)" class="text-danger pull-right">移除</a>');
+                        html.push('<span>');
+                        html.push(data.order + ' ');
+                        html.push(data.name + ' ');
+                        html.push('</span>');
+                        html.push('<small class="text-muted">');
+                        html.push(data.role);
+                        html.push('</small></li>');
+                    }
+                    // 添加新审批人流程修改
+                    auditorshtml.push('<li class="list-group-item" data-auditorid="' + data.aid + '">');
+                    auditorshtml.push('<i class="fa ' + (index+1 === datas.length ? 'fa-stop-circle' : 'fa-chevron-circle-down') + '"></i> ');
+                    auditorshtml.push(data.name + ' <small class="text-muted">' + data.role + '</small>');
+                    if (index === 0) {
+                        auditorshtml.push('<span class="pull-right">原报</span>');
+                    } else if (index+1 === datas.length) {
+                        auditorshtml.push('<span class="pull-right">终审</span>');
+                    } else {
+                        auditorshtml.push('<span class="pull-right">'+ transFormToChinese(index) +'审</span>');
+                    }
+                    auditorshtml.push('</li>');
+                }
+                $('#auditors').html(html.join(''));
+
+
+                // 重新上报时。令其它的审批人流程图标转换
+                // $('#auditors-list li i').removeClass('fa-stop-circle').addClass('fa-chevron-circle-down');
+                // for (let i = 0; i < $('#auditors-list li').length; i++) {
+                //     $('#auditors-list li').eq(i).find('.pull-right').text(transFormToChinese(i+1) + '审');
+                //     $('#auditors-list2 li').eq(i).find('.pull-right').text(transFormToChinese(i+1) + '审');
+                // }
+
+                $('#auditors-list').html(auditorshtml.join(''));
+
+                // const auditorshtml2 = [];
+                // // 重新上报时。令其它的审批人流程图标转换
+                // $('#auditors-list2 li i').removeClass('fa-stop-circle').addClass('fa-chevron-circle-down');
+                // // 添加新审批人
+                // auditorshtml2.push('<li class="list-group-item" data-auditid="' + data.aid + '">');
+                // auditorshtml2.push('<h5 class="card-title"><i class="fa fa-stop-circle"></i> ');
+                // auditorshtml2.push(data.name + ' <small class="text-muted">' + data.role + '</small>');
+                // auditorshtml2.push('<span class="pull-right">终审</span>');
+                // auditorshtml2.push('</h5></li>');
+                // $('#auditors-list2').append(auditorshtml2.join(''));
+            });
+        }
+    });
+    // 删除审批人
+    $('body').on('click', '#auditors li>a', function () {
+        const li = $(this).parent();
+        const data = {
+            auditorId: parseInt(li.attr('auditorId')),
+        };
+        postData(preUrl +  '/audit/delete', data, (result) => {
+            li.remove();
+            for (const rst of result) {
+                const aLi = $('li[auditorId=' + rst.aid + ']');
+                $('span', aLi).text(rst.order + ' ' + rst.name + ' ');
+            }
+
+            // 如果是重新上报
+            // 令最后一个图标转换
+            $('#auditors-list li[data-auditorid="' + data.auditorId + '"]').remove();
+            if ($('#auditors-list li').length !== 0 && !$('#auditors-list li i').hasClass('fa-stop-circle')) {
+                $('#auditors-list li').eq($('#auditors-list li').length-1).children('i')
+                    .removeClass('fa-chevron-circle-down').addClass('fa-stop-circle');
+            }
+            // $('#auditors-list2 li[data-auditid="' + data.auditorId + '"]').remove();
+            // if ($('#auditors-list2 li').length !== 0 && !$('#auditors-list2 li i').hasClass('fa-stop-circle')) {
+            //     $('#auditors-list2 li').eq($('#auditors-list2 li').length-1).children('i')
+            //         .removeClass('fa-chevron-circle-down').addClass('fa-stop-circle');
+            // }
+            for (let i = 0; i < $('#auditors-list li').length; i++) {
+                $('#auditors-list li').eq(i).find('.pull-right').text(i === 0 ? '原报' : (i+1 === $('#auditors-list li').length ? '终' : transFormToChinese(i)) + '审');
+                // $('#auditors-list2').eq(i).find('.pull-right').text((i+1 === $('#auditors-list2').length ? '终' : transFormToChinese(i+1)) + '审');
+            }
+        });
+    });
+    // 退回选择修改审批人流程
+    $('#hideSp').click(function () {
+        $('#sp-list').modal('hide');
+    });
+    $('a[f-target]').click(function () {
+        $($(this).attr('f-target')).modal('show');
+    });
+
+    // 多层modal关闭后的滚动bug修复
+    $('#sp-list').on('hidden.bs.modal', function (e) {
+        $(document.body).addClass('modal-open');
+    });
+});
+// 检查上报情况
+function checkAuditorFrom () {
+    if ($('#auditors li').length === 0) {
+        // if(shenpi_status === shenpiConst.sp_status.gdspl) {
+        //     toastr.error('请联系管理员添加审批人');
+        // } else {
+            toastr.error('请先选择审批人,再上报数据');
+        // }
+        return false;
+    }
+    let flag = false;
+    if (change.code === '') {
+        toastr.error('立项书编号不能为空');
+        flag = true;
+    }
+    if (change.name === '') {
+        toastr.error('工程名称不能为空');
+        flag = true;
+    }
+    if (!change.reason) {
+        toastr.error('变更原因不能为空');
+        flag = true;
+    }
+    if (flag) {
+        return false;
+    }
+    $('#hide-all').show();
+}
+// texterea换行
+function auditCheck(i) {
+    // const inlineRadio1 = $('#inlineRadio1:checked').val()
+    // const inlineRadio2 = $('#inlineRadio2:checked').val()
+    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);
+    if (i === 2) {
+        if ($('textarea[name="opinion"]').eq(i).val() === '') {
+            toastr.error('请输入终止原因');
+            return false;
+        }
+    }
+    // if (i === 1) {
+    //     if (!inlineRadio1 && !inlineRadio2) {
+    //         if (!$('#warning-text').length) {
+    //             $('#reject-process').prepend('<p id="warning-text" style="color: red; margin: 0;">请选择退回流程</p>');
+    //         }
+    //         return false;
+    //     }
+    //     if ($('#warning-text').length) $('#warning-text').remove()
+    // }
+
+    return true;
+}

+ 195 - 0
app/public/js/change_project_information.js

@@ -0,0 +1,195 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author EllisRan
+ * @date 2022/01/21
+ * @version
+ */
+
+$(document).ready(() => {
+    $.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();
+        }
+    });
+
+    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-cancel').click()
+                });
+            }
+        }
+    })
+    function handleFileList(files = []) {
+        $('#file-content').empty();
+        // const { uncheck, checkNo } = auditConst.status
+        const newFiles = files.map(file => {
+            let showDel = false;
+            if (file.uid === cur_uid) {
+                // if (!curAuditor) {
+                //     advance.status === uncheck && cur_uid === advance.uid && (showDel = true)
+                //     advance.status === checkNo && cur_uid === advance.uid && (showDel = true)
+                // } else {
+                //     curAuditor.audit_id === cur_uid && (showDel = true)
+                // }
+                if (change.status === auditConst.status.checked) {
+                    showDel = Boolean(file.extra_upload )
+                } else {
+                    showDel = true
+                }
+            }
+            return {...file, showDel}
+        })
+        let html = change.filePermission ? `<tr><td colspan="5"><a href="#addfujian" data-toggle="modal" class="btn btn-primary btn-sm" data-placement="bottom" title="">上传附件</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="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></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);
+            })
+        }
+    });
+
+    // 回车提交
+    $('#project-table input').on('keypress', function () {
+        if(window.event.keyCode === 13) {
+            $(this).blur();
+        }
+    });
+
+    $('#project-table input').blur(function () {
+        const val_name = $(this).data('name');
+        let val = _.trim($(this).val()) !== '' ? _.trim($(this).val()) : null;
+        switch(val_name) {
+            case 'code':
+                if(!val) {
+                    toastr.error('立项书编号不能为空');
+                    $(this).val(change[val_name]);
+                    return false;
+                }
+                break;
+            case 'name':
+                if(!val) {
+                    toastr.error('工程名称不能为空');
+                    $(this).val(change[val_name]);
+                    return false;
+                } else if(val.length > 100) {
+                    toastr.error('名称超过100个字,请缩减名称');
+                    $(this).val(change[val_name]);
+                    return false;
+                }
+                break;
+            case 'org_price':
+            case 'change_price':
+            case 'crease_price':
+                val = val ? parseFloat(val) : null;
+                if(val && !_.isNumber(val)) {
+                    toastr.error('请输入数字');
+                    $(this).val(change[val_name]);
+                    return false;
+                }
+                break;
+            default:
+                if(val && val.length > 255) {
+                    toastr.error('超出字段范围,请缩减');
+                    $(this).val(change[val_name]);
+                    return false;
+                }
+                break;
+        }
+        if(change[val_name] !== val) {
+            const _self = $(this);
+            postData(preUrl + '/save', { name: val_name, val}, function (result) {
+                change[val_name] = val;
+                _self.val(change[val_name]);
+                if (val_name === 'code') {
+                    $('#change-project-code').text(change[val_name]);
+                }
+            }, function () {
+                _self.val(change[val_name]);
+            })
+        } else {
+            $(this).val(change[val_name]);
+        }
+    })
+
+    $('#project-table textarea').blur(function () {
+        const val_name = $(this).data('name');
+        let val = _.trim($(this).val()) !== '' ? _.trim($(this).val()) : null;
+        if(change[val_name] !== val) {
+            const _self = $(this);
+            postData(preUrl + '/save', { name: val_name, val}, function (result) {
+                change[val_name] = val;
+                _self.val(change[val_name]);
+            }, function () {
+                _self.val(change[val_name]);
+            })
+        } else {
+            $(this).val(change[val_name]);
+        }
+    })
+});
+
+/**
+ * 校验文件大小、格式
+ * @param {Array} files 文件数组
+ */
+function validateFiles(files) {
+    if (files.length > 10) {
+        toastr.error('至多同时上传10个文件');
+        return false
+    }
+    return files.every(file => {
+        if (file.size > 1024 * 1024 * 30) {
+            toastr.error('文件大小限制为30MB');
+            return false
+        }
+        if (whiteList.indexOf('.' + file.ext) === -1) {
+            toastr.error('请上传正确的格式文件');
+            return false
+        }
+        return true
+    })
+}

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

@@ -116,6 +116,78 @@ $(function(){
             $(this).attr('href', $(this).attr('href') + '?sort=' + orders[0] + '&order=' + orders[1]);
         }
     });
+    $('.change_project_sort_link').each(function () {
+        const tender_id = $(this).attr('href').split('/')[2];
+        let orderSetting = getLocalCache('change-project-'+ tender_id +'-list-order');
+        if(orderSetting) {
+            const orders = orderSetting.split('|');
+            $(this).attr('href', $(this).attr('href') + '?sort=' + orders[0] + '&order=' + orders[1]);
+        }
+    });
+    $('.change_apply_sort_link').each(function () {
+        const tender_id = $(this).attr('href').split('/')[2];
+        let orderSetting = getLocalCache('change-apply-'+ tender_id +'-list-order');
+        if(orderSetting) {
+            const orders = orderSetting.split('|');
+            $(this).attr('href', $(this).attr('href') + '?sort=' + orders[0] + '&order=' + orders[1]);
+        }
+    });
+
+    $('#nav_management').click(function(e) {
+      e.preventDefault()
+      showWaitingView();
+      $.ajax({
+        type: 'GET',
+        url: '/management/proxy/project/vertify',
+        dataType: 'json',
+        cache: false,
+        timeout: 60000,
+        success: function ({code = -1, data: { exist, is_admin, redirect } = { exist: 0, is_admin: false}}) {
+          if (code === 0) {
+              if (!exist && !is_admin) {
+                toastr.error('「项目管理」系统不存在当前项目,请联系管理员进行处理。');
+              } else if(!exist && is_admin) {
+                $('#add-management').modal('show')
+              } else {
+                redirect && window.open(redirect)
+              }
+            }
+            closeWaitingView()
+          },
+          error: function(jqXHR, textStatus, errorThrown){
+            toastr.error('error: ' + textStatus + " " + errorThrown);
+            closeWaitingView();
+          }
+        })
+      });
+    $('#add-management .btn-primary').click(function() {
+
+      $('#add-management').modal('hide')
+      $('#process-management').modal('show')
+      $.ajax({
+        tpye: 'post',
+        url: '/management/proxy/project/add',
+        dataType: 'json',
+        cache: false,
+        timeout: 60000,
+        // beforeSend: function(xhr) {
+        //   let csrfToken = Cookies.get('csrfToken_j');
+        //   xhr.setRequestHeader('x-csrf-token', csrfToken);
+        // },
+        success: function ({code = -1, data = {}}) {
+          if (code === 0) {
+            timer = setTimeout(() => {
+              $('#process-management .process-child').css('width', '100%')
+              setTimeout(() => {
+                const { redirect } = data
+                redirect && window.open(redirect)
+                $('#process-management').modal('hide')
+              }, 150);
+            }, 500);
+          }
+        }
+      })
+    })
 });
 
 function checkShowLast (count) {
@@ -935,7 +1007,6 @@ const checkUtils = {
                 for (const p of posRange) {
                     if (checkUtils.posOver(p)) return true;
                 }
-                return false;
             }
             if (data.is_tp) {
                 if (!data.total_price) return !!data.end_contract_tp;

+ 7 - 3
app/public/js/ledger.js

@@ -879,16 +879,19 @@ $(document).ready(function() {
                 removeLocalCache(copyBlockTag);
             }, null, true);
         },
+        loadRelaData: function () {
+            posOperationObj.loadCurPosData();
+            posSearch.search($('#pos-keyword').val());
+            treeOperationObj.loadExprToInput(ledgerSpread.getActiveSheet());
+        },
         selectionChanged: function (e, info) {
             if (!info.oldSelections || !info.oldSelections[0] || info.newSelections[0].row !== info.oldSelections[0].row) {
                 SpreadJsObj.resetTopAndSelect(posSpread.getActiveSheet());
-                posOperationObj.loadCurPosData();
-                posSearch.search($('#pos-keyword').val());
+                treeOperationObj.loadRelaData();
                 // 全选去除
                 $('#dqjiedian').find('.check-all-file').prop('checked', false);
             }
             SpreadJsObj.saveTopAndSelect(info.sheet, ckBillsSpread);
-            treeOperationObj.loadExprToInput(info.sheet);
         },
         topRowChanged(e, info) {
             SpreadJsObj.saveTopAndSelect(info.sheet, ckBillsSpread);
@@ -1221,6 +1224,7 @@ $(document).ready(function() {
         selector: '#ledger-spread',
         build: function ($trigger, e) {
             const target = SpreadJsObj.safeRightClickSelection($trigger, e, ledgerSpread);
+            treeOperationObj.loadRelaData();
             return target.hitTestType === spreadNS.SheetArea.viewport || target.hitTestType === spreadNS.SheetArea.rowHeader;
         },
         items: {},

+ 37 - 45
app/public/js/material.js

@@ -72,15 +72,15 @@ DatePickerCellType.prototype.updateEditor = function (editorContext, cellStyle,
 };
 
 function resetTpTable() {
-    $('#tp_set').find('td').eq(1).text(ZhCalc.round(m_tp, 2));
-    $('#tp_set').find('td').eq(2).text(ZhCalc.round(ZhCalc.add(pre_tp, m_tp), 2));
+    $('#tp_set').find('td').eq(1).text(ZhCalc.round(m_tp, materialDecimal.tp));
+    $('#tp_set').find('td').eq(2).text(ZhCalc.round(ZhCalc.add(pre_tp, m_tp), materialDecimal.tp));
     if (materialTax) {
-        $('#tax_rate_set').find('td').eq(1).text(ZhCalc.round(m_tax_tp, 2));
-        $('#tax_rate_set').find('td').eq(2).text(ZhCalc.round(ZhCalc.add(m_tax_pre_tp, m_tax_tp), 2));
+        $('#tax_rate_set').find('td').eq(1).text(ZhCalc.round(m_tax_tp, materialDecimal.tp));
+        $('#tax_rate_set').find('td').eq(2).text(ZhCalc.round(ZhCalc.add(m_tax_pre_tp, m_tax_tp), materialDecimal.tp));
     } else {
         const rate = $('#changeRate').val();
-        const bqhs = ZhCalc.round(ZhCalc.mul(m_tp, 1+rate/100), 2);
-        const jzbqhs = ZhCalc.round(ZhCalc.add(pre_tp_hs, bqhs), 2);
+        const bqhs = ZhCalc.round(ZhCalc.mul(m_tp, 1+rate/100), materialDecimal.tp);
+        const jzbqhs = ZhCalc.round(ZhCalc.add(pre_tp_hs, bqhs), materialDecimal.tp);
         $('#rate_set').find('td').eq(1).text(bqhs !== 0 ? bqhs : '');
         $('#rate_set').find('td').eq(2).text(jzbqhs !== 0 ? jzbqhs : '');
     }
@@ -129,7 +129,7 @@ $(document).ready(() => {
         {title: '基准时间', colSpan: '1', rowSpan: '2', field: 'basic_times', hAlign: 0, width: 70, formatter: '@', readOnly: 'readOnly.isEdit'},
         {title: '本期信息价|单价', colSpan: '3|1', rowSpan: '1|1', field: 'msg_tp', hAlign: 2, width: 60, type: 'Number', readOnly: 'readOnly.msg_tp'},
         {title: '|时间', colSpan: '|1', rowSpan: '|1', field: 'msg_times', hAlign: 0, width: 80, formatter: '@', readOnly: 'readOnly.remark'},
-        {title: '|价差', colSpan: '|1', rowSpan: '|1', field: 'msg_spread', hAlign: 2, width: 60, type: 'Number', readOnly: true, getValue: 'getValue.msg_spread'},]
+        {title: '|价差', colSpan: '|1', rowSpan: '|1', field: 'msg_spread', hAlign: 2, width: 60, type: 'Number', readOnly: true, getValue: 'getValue.msg_spread'},]
     );
     if (materialTax) {
         materialSpreadSettingCols = _.concat(materialSpreadSettingCols, [
@@ -209,19 +209,19 @@ $(document).ready(() => {
     const materialCol = {
         getValue: {
             msg_spread: function (data) {
-                return ZhCalc.round(ZhCalc.sub(data.msg_tp, data.basic_price), 3);
+                return ZhCalc.round(ZhCalc.sub(data.msg_tp, data.basic_price), materialDecimal.up);
             },
             m_spread : function (data) {
                 const msg_spread = materialCol.getValue.msg_spread(data);
                 const cor = msg_spread >= 0 ? ZhCalc.mul(data.basic_price, ZhCalc.div(data.m_up_risk, 100)) : ZhCalc.mul(data.basic_price, ZhCalc.div(data.m_down_risk, 100));
-                return Math.abs(msg_spread) > Math.abs(cor) ? (msg_spread > 0 ? ZhCalc.round(ZhCalc.sub(msg_spread, cor), 3) : ZhCalc.round(ZhCalc.add(msg_spread, cor), 3)) : 0;
+                return Math.abs(msg_spread) > Math.abs(cor) ? (msg_spread > 0 ? ZhCalc.round(ZhCalc.sub(msg_spread, cor), materialDecimal.up) : ZhCalc.round(ZhCalc.add(msg_spread, cor), materialDecimal.up)) : 0;
             },
             m_tp: function (data) {
-                return ZhCalc.round(ZhCalc.mul(materialCol.getValue.m_spread(data), data.quantity), 2);
+                return ZhCalc.round(ZhCalc.mul(materialCol.getValue.m_spread(data), data.quantity), materialDecimal.tp);
             },
             m_tax_tp: function (data) {
-                const m_tp = ZhCalc.round(ZhCalc.mul(materialCol.getValue.m_spread(data), data.quantity), 2);
-                return data.m_tax ? ZhCalc.round(ZhCalc.mul(m_tp, (1+ZhCalc.div(data.m_tax, 100))), 2) : m_tp;
+                const m_tp = ZhCalc.round(ZhCalc.mul(materialCol.getValue.m_spread(data), data.quantity), materialDecimal.tp);
+                return data.m_tax ? ZhCalc.round(ZhCalc.mul(m_tp, (1+ZhCalc.div(data.m_tax, 100))), materialDecimal.tp) : m_tp;
             }
         },
         readOnly: {
@@ -401,12 +401,10 @@ $(document).ready(() => {
                         return;
                     }
                     let num = parseFloat(validText);
-                    if (validText !== null && (num < 0 || !/^\d+(\.\d{1,3})?$/.test(num))) {
-                        toastr.warning('已保留3位小数');
-                        validText = ZhCalc.round(num, 3);
-                        // toastr.error('请输入大于0并且小于3位小数的浮点数');
-                        // SpreadJsObj.reLoadRowData(info.sheet, info.row);
-                        // return;
+                    const reg = materialDecimal.up ? new RegExp("^\\d+(\\.\\d{1,"+ materialDecimal.up +"})?$") : new RegExp("^\\d+?$");
+                    if (validText !== null && (num < 0 || !reg.test(num))) {
+                        toastr.warning('已保留'+ materialDecimal.up +'位小数');
+                        validText = ZhCalc.round(num, materialDecimal.up);
                     }
                 }
                 if (col.field === 'msg_tp') {
@@ -416,12 +414,10 @@ $(document).ready(() => {
                         return;
                     }
                     const num = parseFloat(validText);
-                    if (validText !== null && (num < 0 || !/^\d+(\.\d{1,3})?$/.test(num))) {
-                        toastr.warning('已保留3位小数');
-                        validText = ZhCalc.round(num, 3);
-                        // toastr.error('请输入大于0并且小于3位小数的浮点数');
-                        // SpreadJsObj.reLoadRowData(info.sheet, info.row);
-                        // return;
+                    const reg = materialDecimal.up ? new RegExp("^\\d+(\\.\\d{1,"+ materialDecimal.up +"})?$") : new RegExp("^\\d+?$");
+                    if (validText !== null && (num < 0 || !reg.test(num))) {
+                        toastr.warning('已保留'+ materialDecimal.up +'位小数');
+                        validText = ZhCalc.round(num, materialDecimal.up);
                     }
                 }
                 if (col.field === 'm_up_risk' || col.field === 'm_down_risk' || col.field === 'm_tax') {
@@ -512,7 +508,7 @@ $(document).ready(() => {
                 codeError: {type: 'error', msg: '编号为纯数字时,不能为小数'},
                 numberExpr: {type: 'error', msg: '不能粘贴其它非数字类型字符'},
                 riskCan: {type: 'error', msg: '只能粘贴0-100的正整数'},
-                numberCan: {type: 'warning', msg: '已保留3位小数'},
+                numberCan: {type: 'warning', msg: '已保留'+ materialDecimal.up +'位小数'},
             };
             const range = info.cellRange;
             const sortData = info.sheet.zh_data || [];
@@ -581,11 +577,10 @@ $(document).ready(() => {
                         }
                         const num = parseFloat(validText);
                         if (colSetting.field === 'basic_price' || colSetting.field === 'msg_tp') {
-                            if (validText !== null && (num < 0 || !/^\d+(\.\d{1,3})?$/.test(num))) {
+                            const reg = materialDecimal.up ? new RegExp("^\\d+(\\.\\d{1,"+ materialDecimal.up +"})?$") : new RegExp("^\\d+?$");
+                            if (validText !== null && (num < 0 || !reg.test(num))) {
                                 toastMessageUniq(getPasteHint(hint.numberCan, hintRow));
-                                validText = ZhCalc.round(num, 3);
-                                // bPaste = false;
-                                // continue;
+                                validText = ZhCalc.round(num, materialDecimal.up);
                             }
                         } else if (colSetting.field === 'm_up_risk' || colSetting.field === 'm_down_risk' || colSetting.field === 'm_tax') {
                             if (validText !== null && (num < 0 || num > 100 || !/^\d+$/.test(num))) {
@@ -766,7 +761,7 @@ $(document).ready(() => {
                         hadnum++;
                     }
                 }
-                const average_tp = hadnum !== 0 ? ZhCalc.round(ZhCalc.div(msg_tp, hadnum), 3) : ZhCalc.round(ZhCalc.div(msg_tp, months.length), 3);
+                const average_tp = hadnum !== 0 ? ZhCalc.round(ZhCalc.div(msg_tp, hadnum), materialDecimal.up) : ZhCalc.round(ZhCalc.div(msg_tp, months.length), materialDecimal.up);
                 return average_tp;
             },
         },
@@ -812,12 +807,10 @@ $(document).ready(() => {
                         return;
                     }
                     const num = parseFloat(validText);
-                    if (validText !== null && (num < 0 || !/^\d+(\.\d{1,3})?$/.test(num))) {
-                        // toastr.error('请输入大于0并且小于3位小数的浮点数');
-                        // SpreadJsObj.reLoadRowData(info.sheet, info.row);
-                        // return;
-                        toastr.warning('已保留3位小数');
-                        validText = ZhCalc.round(num, 3);
+                    const reg = materialDecimal.up ? new RegExp("^\\d+(\\.\\d{1,"+ materialDecimal.up +"})?$") : new RegExp("^\\d+?$");
+                    if (validText !== null && (num < 0 || !reg.test(num))) {
+                        toastr.warning('已保留'+ materialDecimal.up +'位小数');
+                        validText = ZhCalc.round(num, materialDecimal.up);
                     }
                     select[col.field] = validText;
 
@@ -874,7 +867,7 @@ $(document).ready(() => {
             const hint = {
                 cellError: {type: 'error', msg: '粘贴内容超出了表格范围'},
                 numberExpr: {type: 'error', msg: '不能粘贴其它非数字类型字符'},
-                numberCan: {type: 'warning', msg: '已保留3位小数'},
+                numberCan: {type: 'warning', msg: '已保留'+ materialDecimal.up +'位小数'},
             };
             const range = info.cellRange;
             const sortData = info.sheet.zh_data || [];
@@ -922,11 +915,10 @@ $(document).ready(() => {
                             bPaste = false;
                             continue;
                         }
-                        if (validText !== null && (num < 0 || !/^\d+(\.\d{1,3})?$/.test(num))) {
+                        const reg = materialDecimal.up ? new RegExp("^\\d+(\\.\\d{1,"+ materialDecimal.up +"})?$") : new RegExp("^\\d+?$");
+                        if (validText !== null && (num < 0 || !reg.test(num))) {
                             toastMessageUniq(getPasteHint(hint.numberCan, hintRow));
-                            validText = ZhCalc.round(num, 3);
-                            // bPaste = false;
-                            // continue;
+                            validText = ZhCalc.round(num, materialDecimal.up);
                         }
                     }
                     materialMonthData[colSetting.field] = validText;
@@ -1021,10 +1013,10 @@ $(document).ready(() => {
         $('#changeRate').change(function () {
             const rate = parseInt($(this).val());
             postData(window.location.pathname + '/save', { type:'rate', rate: rate }, function (result) {
-                const bqhs = ZhCalc.round(ZhCalc.mul(m_tp, 1+rate/100), 2);
-                const exbqhs = ZhCalc.round(ZhCalc.mul(ex_tp, 1+rate/100), 2);
-                const jzbqhs = ZhCalc.round(ZhCalc.add(pre_tp_hs, bqhs), 2);
-                const exjzbqhs = ZhCalc.round(ZhCalc.add(ex_pre_tp_hs, exbqhs), 2);
+                const bqhs = ZhCalc.round(ZhCalc.mul(m_tp, 1+rate/100), materialDecimal.tp);
+                const exbqhs = ZhCalc.round(ZhCalc.mul(ex_tp, 1+rate/100), materialDecimal.tp);
+                const jzbqhs = ZhCalc.round(ZhCalc.add(pre_tp_hs, bqhs), materialDecimal.tp);
+                const exjzbqhs = ZhCalc.round(ZhCalc.add(ex_pre_tp_hs, exbqhs), materialDecimal.tp);
                 $('#rate_set').find('td').eq(1).text(bqhs !== 0 ? bqhs : '');
                 $('#rate_set').find('td').eq(2).text(jzbqhs !== 0 ? jzbqhs : '');
                 $('#rate_set').find('td').eq(3).text(exbqhs !== 0 ? exbqhs : '');

+ 928 - 0
app/public/js/material_checklist.js

@@ -0,0 +1,928 @@
+'use strict';
+
+/**
+ * 材料调差 - 调差清单设置
+ *
+ * @author EllisRan
+ * @date 2022/1/7
+ * @version
+ */
+
+function getStageId() {
+    return window.location.pathname.split('/')[5];
+}
+
+function findNotJoinLeafXmj(x, type = '') {
+    if (type === 'index') {
+        return notJoinList.findIndex(function (item) {
+            return item.gcl_id === x.gcl_id && item.xmj_id === x.id && (x.mx_id === undefined || (x.mx_id !== undefined && x.mx_id === item.mx_id));
+        });
+    }
+    return notJoinList.find(function (item) {
+        return item.gcl_id === x.gcl_id && item.xmj_id === x.id && (x.mx_id === undefined || (x.mx_id !== undefined && x.mx_id === item.mx_id));
+    });
+}
+
+function getPasteHint (str, row = '') {
+    let returnObj = str;
+    if (row) {
+        returnObj.msg = '清单第' + (row+1) + '行' + (str.msg ? str.msg : str);
+    }
+    return returnObj;
+}
+
+function makeChecklistData(lists, checklists) {
+    let html = '';
+    if (lists.length > 0) {
+        for(const [i,l] of lists.entries()) {
+            const checklistInfo = _.find(checklists, { b_code: l.b_code, name: l.name, unit: l.unit, unit_price: l.unit_price });
+            const isChecked = checklistInfo ? ' checked' : '';
+            const isDisabled = checklistInfo && checklistInfo.had_bills === 1 ? ' disabled' : '';
+            html += '<tr>\n' +
+                '                                    <td><div class="text-center custom-control custom-checkbox mb-2">\n' +
+                '                                            <input type="checkbox" id="lists_'+ i +'" value="'+ i +'" name="customCheckbox" class="custom-control-input"'+ isChecked + isDisabled +'>\n' +
+                '                                            <label class="custom-control-label" for="lists_'+ i +'"></label>\n' +
+                '                                        </div></td>\n' +
+                '                                    <td class="text-center">'+ (i+1) +'</td>\n' +
+                '                                    <td>'+ l.b_code +'</td>\n' +
+                '                                    <td>'+ l.name +'</td>\n' +
+                '                                    <td class="text-center">'+ (l.unit ? l.unit : '') +'</td>\n' +
+                '                                    <td class="text-right">'+ (l.unit_price ? l.unit_price : '') +'</td>\n' +
+                '                                    <td class="text-right">'+ (l.quantity ? l.quantity : '') +'</td>\n' +
+                '                                    <td class="text-right">'+ (l.total_price ? l.total_price : '') +'</td>\n' +
+                '                                </tr>';
+        }
+    }
+    $('#lists_data').html(html);
+}
+
+// 清单搜索隐藏清单table部分值
+function remakeChecklistData(lists, showListData = lists) {
+    // 先加载台账数据
+    if (lists.length > 0) {
+        for (const [index,gcl] of lists.entries()) {
+            const isShow = _.find(showListData, gcl);
+            $('#lists_data tr').eq(index).css('display', (isShow ? 'table-row' : 'none'));
+        }
+    }
+}
+
+$(document).ready(() => {
+    function TipCellType()
+    {
+    }
+    TipCellType.prototype = new GC.Spread.Sheets.CellTypes.ColumnHeader();
+    TipCellType.prototype.getHitInfo = function (x, y, cellStyle, cellRect, context) {
+        return { x: x, y: y, row: context.row, col: context.col, cellRect: cellRect, sheetArea: context.sheetArea, sheet: context.sheet };
+    };
+    TipCellType.prototype.processMouseEnter = function (hitInfo){
+        if (!this._toolTipElement) {
+            var div = document.createElement("div");
+            $(div).css("position", "absolute")
+                .css("border", "1px #C0C0C0 solid")
+                .css("box-shadow", "1px 2px 5px rgba(0,0,0,0.4)")
+                .css("font", "9pt Arial")
+                .css("background", "#fff")
+                // .css("color", "#fff")
+                .css("z-index", "1000")
+                .css("padding", 5);
+            this._toolTipElement = div;
+        }
+        $(this._toolTipElement).text("单位数量:每一单位清单下所需工料消耗量。")
+            .css("top", hitInfo.y + 15)
+            .css("left", hitInfo.x - 15);
+        $(this._toolTipElement).hide();
+        // document.body.insertBefore(this._toolTipElement, null);
+        $('#material-spread-div').append(this._toolTipElement, null);
+        $(this._toolTipElement).show("fast");
+    };
+    TipCellType.prototype.processMouseLeave = function (hitInfo) {
+        if (this._toolTipElement) {
+            this._toolTipElement.remove();
+            this._toolTipElement = null;
+        }
+    };
+    autoFlashHeight();
+    // 清单table
+    const ledgerSpread = SpreadJsObj.createNewSpread($('#ledger-spread')[0]);
+    const ledgerSpreadSetting = {
+        cols: [
+            {title: '清单编号', colSpan: '1', rowSpan: '2', field: 'b_code', hAlign: 0, width: 80, formatter: '@'},
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 220, formatter: '@'},
+            {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 60, formatter: '@'},
+            {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 70, type: 'Number'},
+            {title: '工程量', colSpan: '1', rowSpan: '2', field: 'quantity', hAlign: 2, width: 90, type: 'Number'},
+            {title: '台账金额', colSpan: '1', rowSpan: '2', field: 'total_price', hAlign: 2, width: 110, type: 'Number'},
+        ],
+        emptyRows: 0,
+        headRows: 1,
+        headRowHeight: [25, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: true,
+    };
+    SpreadJsObj.initSheet(ledgerSpread.getActiveSheet(), ledgerSpreadSetting);
+
+    // 加载清单数据 - 暂时统一加载,如有需要,切换成动态加载并缓存
+    postData(window.location.pathname + '/load', {}, function (result) {
+        ledger = result.ledger;
+        curLedgerData = result.curLedgerData;
+        pos = result.pos;
+        curPosData = result.curPosData;
+        materialListData = result.materialListData;
+        notJoinList = result.materialNotJoinListData;
+        materialChecklistData = result.materialChecklistData;
+        // 解析清单汇总数据
+        gclGatherModel.loadLedgerData(ledger, curLedgerData);
+        gclGatherModel.loadPosData(pos, curPosData);
+        gclGatherData = gclGatherModel.gatherGclData();
+        console.log(gclGatherData);
+        const hadBillsidList = _.uniq(_.map(materialListData, 'gcl_id'));
+        console.log(hadBillsidList);
+        // 对比清单设置和调差清单,还要和台账对比,显示已选清单列表 不同则更新到清单设置页中
+        const pushData = [];
+        const updateData = [];
+        for (const hb of hadBillsidList) {
+            const gcl = _.find(gclGatherData, function (item) {
+                return item.leafXmjs && item.leafXmjs.length > 0 && _.findIndex(item.leafXmjs, { gcl_id : hb }) !== -1;
+            });
+            if (gcl) {
+                const mc = _.find(materialChecklistData, { b_code: gcl.b_code, name: gcl.name, unit: gcl.unit, unit_price: gcl.unit_price });
+                // 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 });
+                }
+            }
+        }
+        const removeData = [];
+        for (const mc of materialChecklistData) {
+            const gcl = _.find(gclGatherData, { b_code: mc.b_code, name: mc.name, unit: mc.unit, unit_price: mc.unit_price });
+            // 判断是否已不存在工料清单,台账修改过后删除之
+            if (!gcl) {
+                removeData.push(mc.id);
+            }
+            // 更新had_bills值
+            if (mc.had_bills === 1) {
+                if (_.indexOf(hadBillsidList, gcl.leafXmjs ? gcl.leafXmjs[0].gcl_id : null) === -1) {
+                    updateData.push({ id: mc.id, mid: materialID, had_bills: 0 });
+                }
+            }
+        }
+        setChecklistData(pushData, removeData, updateData, true);
+    });
+    function setChecklistData(pushData, removeData, updateData = [], sendmsg = false) {
+        if (pushData.length > 0 || removeData.length > 0 || updateData.length > 0) {
+            postData(window.location.pathname + '/save', { type: 'resetChecklist', pushData, removeData, updateData }, function (result2) {
+                if (sendmsg && pushData.length > 0) {
+                    toastr.success('已同步历史调差清单数据至本页中');
+                }
+                if (sendmsg && removeData.length > 0) {
+                    toastr.warning('已删除部分与台账清单不匹配的清单数据');
+                }
+                materialChecklistData = result2;
+                showSjsData();
+            })
+        } else {
+            showSjsData();
+        }
+    }
+    function showSjsData() {
+        SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), SpreadJsObj.DataType.Data, materialChecklistData);
+        SpreadJsObj.resetTopAndSelect(ledgerSpread.getActiveSheet());
+        if (materialChecklistData.length > 0) {
+            const index = _.findIndex(gclGatherData, { b_code: materialChecklistData[0].b_code, name: materialChecklistData[0].name, unit: materialChecklistData[0].unit, unit_price: materialChecklistData[0].unit_price });
+            loadMaterialData(index, 0);
+        } else {
+            loadMaterialData(-1, 0);
+        }
+        const sheet = materialSpread.getActiveSheet();
+        sheet.suspendPaint();
+        sheet.setCellType(1, 3, new TipCellType(), spreadNS.SheetArea.colHeader);
+        sheet.resumePaint();
+    }
+    // 调差清单工料table
+    const materialSpread = SpreadJsObj.createNewSpread($('#material-spread')[0]);
+    const materialSpreadSetting = {
+        cols: [
+            {title: '清单工料含量|编号', colSpan: '5|1', rowSpan: '1|1', field: 'code', hAlign: 0, width: 80, formatter: '@', readOnly: true},
+            {title: '|名称', colSpan: '|1', rowSpan: '|1', field: 'name', hAlign: 0, width: 100, formatter: '@', readOnly: true},
+            {title: '|单位', colSpan: '|1', rowSpan: '|1', field: 'unit', hAlign: 1, width: 60, formatter: '@', readOnly: true},
+            {title: '|数量 �', colSpan: '|1', rowSpan: '|1', field: 'quantity', hAlign: 2, width: 80, type: 'Number', readOnly: 'readOnly.isEdit'},
+            {title: '|计算式', colSpan: '1', rowSpan: '|1', field: 'expr', hAlign: 2, width: 120, formatter: '@', readOnly: 'readOnly.isEdit'},
+        ],
+        emptyRows: 0,
+        headRows: 2,
+        headRowHeight: [25, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+    };
+
+    const materialBase = {
+        isEdit: function (data) {
+            // 是否本期添加的工料
+            return data.order === stage_order;
+        }
+    };
+
+    const materialCol = {
+        readOnly: {
+            isEdit: function (data) {
+                return !(!readOnly && materialBase.isEdit(data));
+            },
+        },
+    };
+    SpreadJsObj.initSpreadSettingEvents(materialSpreadSetting, materialCol);
+    // 获取项目节数据
+    let materialList = [];
+    function loadMaterialData(iGclRow, iLXmjRow) {
+        const gcl = gclGatherData[iGclRow];
+        if (gcl && gcl.leafXmjs[iLXmjRow]) {
+            const xmj = gcl.leafXmjs[iLXmjRow];
+            materialList = [];
+            materialList = _.filter(materialListData, function (m) {
+                return m.gcl_id === xmj.gcl_id && m.xmj_id === xmj.id && ((xmj.mx_id !==undefined && m.mx_id === xmj.mx_id) || xmj.mx_id === undefined);
+            });
+            // for (const m of materialListData) {
+            //     if (m.gcl_id === xmj.gcl_id && m.xmj_id === xmj.id && ((xmj.mx_id !==undefined && m.mx_id === xmj.mx_id) || xmj.mx_id === undefined)) {
+            //         materialList.push(m);
+            //     }
+            // }
+            // 对清单调差工料table的单位数量进行改变
+            materialSpreadSetting.cols[materialSpreadSetting.cols.length - 2].title = '|' + gcl.unit + '数量 �';
+            SpreadJsObj.initSheet(materialSpread.getActiveSheet(), materialSpreadSetting);
+            SpreadJsObj.loadSheetData(materialSpread.getActiveSheet(), SpreadJsObj.DataType.Data, materialList);
+        } else {
+            materialSpreadSetting.cols[materialSpreadSetting.cols.length - 2].title = '数量 �';
+            SpreadJsObj.initSheet(materialSpread.getActiveSheet(), materialSpreadSetting);
+            SpreadJsObj.loadSheetData(materialSpread.getActiveSheet(), SpreadJsObj.DataType.Data, []);
+        }
+        SpreadJsObj.resetTopAndSelect(materialSpread.getActiveSheet());
+    }
+
+    // 对添加工料表格赋值
+    function changeMaterialTable() {
+        $('#materialBills tr').removeClass('table-secondary');
+        $('#materialBills').find('input').removeAttr('disabled');
+        $('#materialBills').find('input').prop('checked', false);
+        for (const ml of materialList) {
+            const mbIndex = _.findIndex(materialBillsData, {id : ml.mb_id });
+            if (mbIndex !== -1) {
+                $('#materialBills tr').eq(mbIndex).addClass('table-secondary');
+                $('#materialBills').find('input').eq(mbIndex).attr('disabled', true);
+                $('#materialBills').find('input').eq(mbIndex).prop('checked', true);
+            }
+        }
+    }
+    // 选中清单并添加
+    $('#set_checklist_btn').click(function () {
+        const select_checklist = $('#lists_data').find('input:checked:not(:disabled)');
+        const pushData = [];
+        for (const sc of select_checklist) {
+            const order = parseInt($(sc).val());
+            const checklistInfo = _.find(materialChecklistData, { b_code: gclGatherData[order].b_code, name: gclGatherData[order].name, unit: gclGatherData[order].unit, unit_price: gclGatherData[order].unit_price });
+            if (!checklistInfo) {
+                pushData.push({
+                    b_code: gclGatherData[order].b_code,
+                    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,
+                    had_bills: 0,
+                })
+            }
+        }
+        const notSelect_checklist = $('#lists_data').find('input:not(:checked):not(:disabled)');
+        const removeData = [];
+        for (const nsc of notSelect_checklist) {
+            const order = parseInt($(nsc).val());
+            // const order = parseInt($(nsc).attr('data-index'));
+            const checklistInfo = _.find(materialChecklistData, { b_code: gclGatherData[order].b_code, name: gclGatherData[order].name, unit: gclGatherData[order].unit, unit_price: gclGatherData[order].unit_price });
+            if (checklistInfo) {
+               removeData.push(checklistInfo.id);
+            }
+        }
+        setChecklistData(pushData, removeData);
+        $('#addtclist').modal('hide');
+    });
+    // 筛选无调差工料清单
+    $('#notBills_checkList').click(function () {
+        const isCheck = $(this).is(':checked');
+        let newMaterialChecklistData = materialChecklistData;
+        if (isCheck) {
+            $('#bills0_checkList').prop('checked', false);
+            newMaterialChecklistData = _.filter(materialChecklistData, { had_bills: 0 });
+        }
+        SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), SpreadJsObj.DataType.Data, newMaterialChecklistData);
+        SpreadJsObj.resetTopAndSelect(ledgerSpread.getActiveSheet());
+        if (newMaterialChecklistData.length > 0) {
+            const index = _.findIndex(gclGatherData, { b_code: newMaterialChecklistData[0].b_code, name: newMaterialChecklistData[0].name, unit: newMaterialChecklistData[0].unit, unit_price: newMaterialChecklistData[0].unit_price });
+            loadMaterialData(index, 0);
+        } else {
+            loadMaterialData(-1, 0);
+        }
+    });
+    // 筛选调差工料清单为0
+    $('#bills0_checkList').click(function () {
+        const isCheck = $(this).is(':checked');
+        let newMaterialChecklistData = materialChecklistData;
+        if (isCheck) {
+            newMaterialChecklistData = [];
+            $('#notBills_checkList').prop('checked', false);
+            const materialList0 = _.uniq(_.map(_.filter(materialListData, { quantity: 0 }), 'gcl_id'));
+            if (materialList0.length > 0) {
+                const hadMaterialChecklistData = _.filter(materialChecklistData, { had_bills: 1 });
+                for (const h of hadMaterialChecklistData) {
+                    const gcl = _.find(gclGatherData, { b_code: h.b_code, name: h.name, unit: h.unit, unit_price: h.unit_price });
+                    if (gcl && gcl.leafXmjs.length > 0 && _.indexOf(materialList0, gcl.leafXmjs[0].gcl_id) !== -1) {
+                        newMaterialChecklistData.push(h);
+                    }
+                }
+            }
+        }
+        SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), SpreadJsObj.DataType.Data, newMaterialChecklistData);
+        SpreadJsObj.resetTopAndSelect(ledgerSpread.getActiveSheet());
+        if (newMaterialChecklistData.length > 0) {
+            const index = _.findIndex(gclGatherData, { b_code: newMaterialChecklistData[0].b_code, name: newMaterialChecklistData[0].name, unit: newMaterialChecklistData[0].unit, unit_price: newMaterialChecklistData[0].unit_price });
+            loadMaterialData(index, 0);
+        } else {
+            loadMaterialData(-1, 0);
+        }
+    });
+    // 添加调差工料
+    $('#add_material_bill').click(function () {
+        // 获取已选工料
+        $('#materialBills').find('input:disabled').prop('checked', false);
+        const selectList = $('#materialBills').find('input:checked');
+        if (selectList.length === 0) {
+            toastr.warning('请选择调差工料');
+            $('#materialBills').find('input:disabled').prop('checked', true);
+            return false;
+        }
+        const mb_id = [];
+        for (let s = 0; s < selectList.length; s++) {
+            mb_id.push($('#materialBills').find('input:checked').eq(s).val());
+        }
+        // 获取当前项目节或部位明细id
+        const sheet = ledgerSpread.getActiveSheet();
+        const select = SpreadJsObj.getSelectObject(sheet);
+        const gclIndex = _.findIndex(gclGatherData, { b_code: select.b_code, name: select.name, unit: select.unit, unit_price: select.unit_price });
+        const gcl = gclGatherData[gclIndex].leafXmjs;
+        const index = materialChecklistData.indexOf(select);
+        const datas = [];
+        for (const xmj of gcl) {
+            const notx = findNotJoinLeafXmj(xmj);
+            const data = {
+                xmj_id: xmj.id,
+                gcl_id: xmj.gcl_id,
+                mx_id: xmj.mx_id !== undefined ? xmj.mx_id : '',
+                gather_qty: xmj.gather_qty,
+                is_join: notx === undefined ? 1 : 0,
+            };
+            datas.push(data);
+        }
+        // 上传到数据库
+        console.log(datas, gcl);
+        postData(window.location.pathname + '/save', {type: 'adds', checklist: { id: select.id, had_bills: 1 }, postData: {xmjs: datas, mbIds: mb_id}}, function (result) {
+            materialListData = result;
+            materialChecklistData[index].had_bills = 1;
+            loadMaterialData(gclIndex, 0);
+            // SpreadJsObj.reLoadRowData(ledgerSpread.getActiveSheet(), index);
+            $('#addgl').modal('hide');
+        });
+        $('#materialBills').find('input:disabled').prop('checked', true);
+    });
+    if (!readOnly) {
+        // material-spread右键功能
+        const materialSpreadObj = {
+            del: function () {
+                const materialSheet = materialSpread.getActiveSheet();
+                const materialSelect = SpreadJsObj.getSelectObject(materialSheet);
+                const sheet = ledgerSpread.getActiveSheet();
+                const select = SpreadJsObj.getSelectObject(sheet);
+                const index = materialChecklistData.indexOf(select);
+                const gclIndex = _.findIndex(gclGatherData, { b_code: select.b_code, name: select.name, unit: select.unit, unit_price: select.unit_price });
+                const gcl = gclGatherData[gclIndex].leafXmjs;
+                const datas = [];
+                for (const xmj of gcl) {
+                    const data = {
+                        xmj_id: xmj.id,
+                        gcl_id: xmj.gcl_id,
+                        mx_id: xmj.mx_id !== undefined ? xmj.mx_id : '',
+                    };
+                    datas.push(data);
+                }
+                const xmj = gcl[0];
+                const materialCount = _.size(_.filter(materialListData, function (m) {
+                    return m.gcl_id === xmj.gcl_id && m.xmj_id === xmj.id && ((xmj.mx_id !==undefined && m.mx_id === xmj.mx_id) || xmj.mx_id === undefined);
+                }));
+                let checklist = false;
+                if (materialCount === 1) {
+                    checklist = {
+                        id: select.id,
+                        had_bills: 0,
+                    }
+                }
+                console.log(datas, materialSelect.mb_id, checklist);
+                postData(window.location.pathname + '/save', {type: 'dels', checklist, postData: { xmjs: datas, mb_id: materialSelect.mb_id }}, function (result) {
+                    materialListData = result;
+                    if (checklist) materialChecklistData[index].had_bills = checklist.had_bills;
+                    loadMaterialData(gclIndex, 0);
+                    // SpreadJsObj.reLoadRowData(ledgerSpread.getActiveSheet(), index);
+                });
+            },
+            deletePress: function (sheet) {
+                return;
+            },
+            editStarting: function (e, info) {
+                const col = info.sheet.zh_setting.cols[info.col];
+                const select = SpreadJsObj.getSelectObject(info.sheet);
+                if (col.field === 'quantity') {
+                    if (select.expr && select.expr !== '') {
+                        info.sheet.getCell(info.row, info.col).text(select.expr);
+                    }
+                }
+            },
+            editEnded: function (e, info) {
+                if (info.sheet.zh_setting) {
+                    const select = SpreadJsObj.getSelectObject(info.sheet);
+                    const col = info.sheet.zh_setting.cols[info.col];
+                    const validText = info.editingText ? info.editingText.replace('\n', '') : null;
+                    let orgValue;
+                    if (col.field === 'quantity') {
+                        orgValue = validText && validText !== ''
+                            ? _.toNumber(validText) ? select.quantity : select.expr
+                            : (select.expr && select.expr !== '') ? select.expr : select.quantity;
+                    } else {
+                        orgValue = select[col.field];
+                    }
+                    if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === '' || validText === null))) {
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        return;
+                    }
+                    const exprQuantity = {
+                        expr: '',
+                        quantity: 0,
+                    };
+                    const [valid, msg] = materialSpreadObj._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;
+                    }
+                    const num = parseFloat(exprQuantity.quantity);
+                    if (num < 0 || !/^\d+(\.\d{1,6})?$/.test(num)) {
+                        toastr.warning('已保留6位小数');
+                        exprQuantity.quantity = ZhCalc.round(num, 6);
+                    }
+                    // 更新至服务器
+                    const ledgerSheet = ledgerSpread.getActiveSheet();
+                    const ledgerSelect = SpreadJsObj.getSelectObject(ledgerSheet);
+                    const gclIndex = _.findIndex(gclGatherData, { b_code: ledgerSelect.b_code, name: ledgerSelect.name, unit: ledgerSelect.unit, unit_price: ledgerSelect.unit_price });
+                    const gcl = gclGatherData[gclIndex].leafXmjs;
+                    const datas = [];
+                    for (const xmj of gcl) {
+                        const data = {
+                            xmj_id: xmj.id,
+                            gcl_id: xmj.gcl_id,
+                            mx_id: xmj.mx_id !== undefined ? xmj.mx_id : '',
+                        };
+                        datas.push(data);
+                    }
+                    console.log(exprQuantity, datas, select.mb_id);
+                    postData(window.location.pathname + '/save', { type:'updates', updateData: { xmjs: datas, expr: exprQuantity.expr, quantity: exprQuantity.quantity, mb_id: select.mb_id } }, function (result) {
+                        materialListData = result;
+                        loadMaterialData(gclIndex, 0);
+                        materialSpread.getActiveSheet().setSelection(info.row + 1, info.col, 1, 1);
+                    }, function () {
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    });
+                }
+            },
+            clipboardPasted(e, info) {
+                const hint = {
+                    cellError: {type: 'error', msg: '粘贴内容超出了表格范围'},
+                    numberExpr: {type: 'error', msg: '不能粘贴其它非数字类型字符'},
+                    numberCan: {type: 'warning', msg: '已保留6位小数'},
+                };
+                const range = info.cellRange;
+                const sortData = info.sheet.zh_data || [];
+                if (range.row + range.rowCount > sortData.length) {
+                    toastMessageUniq(hint.cellError);
+                    SpreadJsObj.reLoadSheetHeader(materialSpread.getActiveSheet());
+                    SpreadJsObj.reLoadSheetData(materialSpread.getActiveSheet());
+                    return;
+                }
+                if (sortData.length > 0 && range.col + range.colCount > 5) {
+                    toastMessageUniq(hint.cellError);
+                    SpreadJsObj.reLoadSheetHeader(materialSpread.getActiveSheet());
+                    SpreadJsObj.reLoadSheetData(materialSpread.getActiveSheet());
+                    return;
+                }
+                const data = [];
+                for (let iRow = 0; iRow < range.rowCount; iRow++) {
+                    let bPaste = true;
+                    const curRow = range.row + iRow;
+                    const materialData = { id: sortData[curRow].id, mb_id: sortData[curRow].mb_id };
+                    const hintRow = range.rowCount > 1 ? curRow : '';
+                    let sameCol = 0;
+                    for (let iCol = 0; iCol < range.colCount; iCol++) {
+                        const curCol = range.col + iCol;
+                        const colSetting = info.sheet.zh_setting.cols[curCol];
+                        if (!colSetting) continue;
+
+                        const validText = info.sheet.getText(curRow, curCol).replace('\n', '');
+                        const orgValue = sortData[curRow][colSetting.field];
+                        if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === ''))) {
+                            sameCol++;
+                            if (range.colCount === sameCol)  {
+                                bPaste = false;
+                            }
+                            continue;
+                        }
+                        const exprQuantity = {
+                            expr: '',
+                            quantity: 0,
+                        };
+                        const [valid, msg] = materialSpreadObj._checkExpr(validText, exprQuantity);
+                        if (!valid) {
+                            toastMessageUniq(getPasteHint(msg, hintRow));
+                            bPaste = false;
+                            continue;
+                        }
+                        if (isNaN(exprQuantity.quantity)) {
+                            toastMessageUniq(getPasteHint(hint.numberExpr, hintRow));
+                            bPaste = false;
+                            continue;
+                        }
+                        const num = parseFloat(exprQuantity.quantity);
+                        if (num < 0 || !/^\d+(\.\d{1,6})?$/.test(num)) {
+                            toastMessageUniq(getPasteHint(hint.numberCan, hintRow));
+                            exprQuantity.quantity = ZhCalc.round(num, 6);
+                        }
+                        materialData.expr = exprQuantity.expr;
+                        materialData.quantity = exprQuantity.quantity;
+                    }
+                    if (bPaste) {
+                        data.push(materialData);
+                    } else {
+                        SpreadJsObj.reLoadRowData(info.sheet, curRow);
+                    }
+                }
+                if (data.length === 0) {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
+                    return;
+                }
+                const ledgerSheet = ledgerSpread.getActiveSheet();
+                const ledgerSelect = SpreadJsObj.getSelectObject(ledgerSheet);
+                const gclIndex = _.findIndex(gclGatherData, { b_code: ledgerSelect.b_code, name: ledgerSelect.name, unit: ledgerSelect.unit, unit_price: ledgerSelect.unit_price });
+                const gcl = gclGatherData[gclIndex].leafXmjs;
+                const datas = [];
+                for (const xmj of gcl) {
+                    const data2 = {
+                        xmj_id: xmj.id,
+                        gcl_id: xmj.gcl_id,
+                        mx_id: xmj.mx_id !== undefined ? xmj.mx_id : '',
+                    };
+                    datas.push(data2);
+                }
+                console.log(data, datas);
+                // 更新至服务器
+                postData(window.location.pathname + '/save', { type:'pastes', updateData: { xmjs: datas, pasteData: data } }, function (result) {
+                    materialListData = result;
+                    loadMaterialData(gclIndex, 0);
+                    materialSpread.getActiveSheet().setSelection(info.cellRange.row, info.cellRange.col, info.cellRange.rowCount, info.cellRange.colCount);
+                }, function () {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                });
+            },
+            _checkExprValid(expr) {
+                if (!expr) return [true, null];
+                const param = [];
+                let num = '', base = '';
+                for (let i = 0, iLen = expr.length; i < iLen; i++) {
+                    if (/^[\d\.%]+/.test(expr[i])) {
+                        if (base !== '') {
+                            param.push({type: 'base', value: base});
+                            base = '';
+                        }
+                        num = num + expr[i];
+                    } else if (expr[i] === '(') {
+                        if (num !== '') {
+                            param.push({type: 'num', value: num});
+                            num = '';
+                        }
+                        if (base !== '') {
+                            param.push({type: 'base', value: base});
+                            base = '';
+                        }
+                        param.push({type: 'left', value: '('});
+                    } else if (expr[i] === ')') {
+                        if (num !== '') {
+                            param.push({type: 'num', value: num});
+                            num = '';
+                        }
+                        if (base !== '') {
+                            param.push({type: 'base', value: base});
+                            base = '';
+                        }
+                        param.push({type: 'right', value: ')'});
+                    } else if (/^[\+\-*\/]/.test(expr[i])) {
+                        if (num !== '') {
+                            param.push({type: 'num', value: num});
+                            num = '';
+                        }
+                        if (base !== '') {
+                            param.push({type: 'base', value: base});
+                            base = '';
+                        }
+                        param.push({type: 'calc', value: expr[i]});
+                    } else {
+                        return [false, '输入的表达式含有非法字符: ' + expr[i]];
+                    }
+                }
+                if (num !== '') {
+                    param.push({type: 'num', value: num});
+                    num = '';
+                }
+                if (base !== '') {
+                    param.push({type: 'base', value: base});
+                    base = '';
+                }
+                if (param.length === 0) return true;
+                if (param.length > 1) {
+                    if (param[0].value === '-') {
+                        param[1].value = '-' + param[1];
+                    }
+                    param.unshift();
+                }
+                const iLen = param.length;
+                let iLeftCount = 0, iRightCount = 0;
+                for (const [i, p] of param.entries()) {
+                    if (p.type === 'calc') {
+                        if (i === 0 || i === iLen - 1)
+                            return [false, '输入的表达式非法:计算符号' + p.value + '前后应有数字'];
+                    }
+                    if (p.type === 'num') {
+                        num = p.value.replace('%', '');
+                        if (p.value.length - num.length > 1)
+                            return [false, '输入的表达式非法:' + p.value + '不是一个有效的数字'];
+                        num = _.toNumber(num);
+                        if (num === undefined || num === null || _.isNaN(num))
+                            return [false, '输入的表达式非法:' + p.value + '不是一个有效的数字'];
+                        if (i > 0) {
+                            if (param[i - 1].type !== 'calc' && param[i - 1].type !== 'left') {
+                                return [false, '输入的表达式非法:' + p.value + '前应有运算符'];
+                            } else if (param[i - 1].value === '/' && num === 0) {
+                                return [false, '输入的表达式非法:请勿除0'];
+                            }
+                        }
+                    }
+                    if (p.type === 'base') {
+                        if (i > 0 && (param[i - 1].type === 'num' || param[i - 1].type === 'right'))
+                            return [false, '输入的表达式非法:' + p.value + '前应有运算符'];
+                    }
+                    if (p.type === 'left') {
+                        iLeftCount += 1;
+                        if (i !== 0 && param[i-1].type !== 'calc')
+                            return [false, '输入的表达式非法:(前应有运算符'];
+                    }
+                    if (p.type === 'right') {
+                        iRightCount += 1;
+                        if (i !== iLen - 1 && param[i+1].type !== 'calc')
+                            return [false, '输入的表达式非法:)后应有运算符'];
+                        if (iRightCount > iLeftCount)
+                            return [false, '输入的表达式非法:")"前无对应的"("'];
+                    }
+                }
+                if (iLeftCount > iRightCount)
+                    return [false, '输入的表达式非法:"("后无对应的")"'];
+                return [true, ''];
+            },
+            _checkExpr: function (text, data) {
+                if (text) {
+                    const num = _.toNumber(text);
+                    if (num) {
+                        data.quantity = num;
+                        data.expr = '';
+                    } else {
+                        const expr = $.trim(text).replace('\t', '').replace('=', '').toLowerCase();
+                        const [valid, msg] = this._checkExprValid(expr);
+                        if (!valid) return [valid, msg];
+                        data.expr = expr;
+                        data.quantity = ZhCalc.calcExpr.calcExprStrRpn(expr);
+                    }
+                } else {
+                    data.quantity = 0;
+                    data.expr = '';
+                }
+                return [true, ''];
+            },
+        };
+        materialSpread.bind(spreadNS.Events.EditStarting, materialSpreadObj.editStarting);
+        materialSpread.bind(spreadNS.Events.EditEnded, materialSpreadObj.editEnded);
+        materialSpread.bind(spreadNS.Events.ClipboardPasted, materialSpreadObj.clipboardPasted);
+        SpreadJsObj.addDeleteBind(materialSpread, materialSpreadObj.deletePress);
+        $.contextMenu({
+            selector: '#material-spread',
+            build: function ($trigger, e) {
+                const target = SpreadJsObj.safeRightClickSelection($trigger, e, materialSpread);
+                return target.hitTestType === GC.Spread.Sheets.SheetArea.viewport || target.hitTestType === GC.Spread.Sheets.SheetArea.rowHeader;
+            },
+            items: {
+                'create': {
+                    name: '添加工料',
+                    icon: 'fa-sign-in',
+                    callback: function (key, opt) {
+                        // 获取已选清单
+                        changeMaterialTable();
+                        $('#addgl').modal('show');
+                    },
+                    disabled: function (key, opt) {
+                        const sheet = ledgerSpread.getActiveSheet();
+                        const select = SpreadJsObj.getSelectObject(sheet);
+                        if (!select) {
+                            return true;
+                        }
+                        return readOnly;
+                    }
+                },
+                'delete': {
+                    name: '删除工料',
+                    icon: 'fa-remove',
+                    callback: function (key, opt) {
+                        materialSpreadObj.del(materialSpread.getActiveSheet());
+                    },
+                    disabled: function (key, opt) {
+                        const sheet = materialSpread.getActiveSheet();
+                        const selection = sheet.getSelections();
+                        const sel = selection ? selection[0] : sheet.getSelections()[0];
+                        const select = SpreadJsObj.getSelectObject(sheet);
+                        if (!select) {
+                            return true;
+                        }
+                        if (!readOnly && sel.rowCount === 1 && select && materialBase.isEdit(select)) {
+                            return false;
+                        } else {
+                            return true;
+                        }
+                    }
+                },
+            }
+        });
+    }
+
+    // 切换清单行,读取所属项目节数据
+    ledgerSpread.getActiveSheet().bind(spreadNS.Events.SelectionChanged, function (e, info) {
+        if (info.oldSelections !== undefined) {
+            const iOldRow = info.oldSelections[0].row, iNewRow = info.newSelections[0].row;
+            if (iNewRow !== iOldRow) {
+                const sheet = ledgerSpread.getActiveSheet();
+                const select = SpreadJsObj.getSelectObject(sheet);
+                const index = _.findIndex(gclGatherData, { b_code: select.b_code, name: select.name, unit: select.unit, unit_price: select.unit_price });
+                loadMaterialData(index, 0);
+            }
+        }
+    });
+
+    $('#open_addtclist').click(function () {
+        $('#tclist_search').val('');
+        $('#tclist_search').siblings('a').hide();
+        makeChecklistData(gclGatherData, materialChecklistData);
+        $('#addtclist').modal('show');
+    });
+
+    // 回车提交
+    $('#tclist_search').on('keypress', function () {
+        if(window.event.keyCode === 13) {
+            $(this).blur();
+        }
+    });
+
+    $('#tclist_search').on('blur', function () {
+        const value = _.trim($(this).val());
+        let showListData = gclGatherData;
+        if (value !== '') {
+            $(this).siblings('a').show();
+            showListData = _.filter(gclGatherData, function (c) {
+                return (c.b_code && c.b_code.indexOf(value) !== -1) || (c.name && c.name.indexOf(value) !== -1);
+            })
+        } else {
+            $(this).siblings('a').hide();
+        }
+        remakeChecklistData(gclGatherData, showListData);
+    });
+
+    $('.remove-btn').on('click', function () {
+        $(this).hide();
+        $(this).siblings('input').val('');
+        remakeChecklistData(gclGatherData);
+    });
+
+    // 显示有调差工料清单
+    // $('#show_material_gcl').click(function () {
+    //     if ($(this).is(':checked')) {
+    //         const hadMaterialGclGatherData = [];
+    //         const hadGclIdList = [];
+    //         for (const ml of materialListData) {
+    //             if (hadGclIdList.indexOf(ml.gcl_id) === -1) {
+    //                 hadGclIdList.push(ml.gcl_id);
+    //             }
+    //         }
+    //         for (const gcl of gclGatherData) {
+    //             for (const index in gcl.leafXmjs) {
+    //                 const gcl_id = gcl.leafXmjs[index].gcl_id;
+    //                 if (hadGclIdList.indexOf(gcl_id) !== -1) {
+    //                     hadMaterialGclGatherData.push(gcl);
+    //                     break;
+    //                 }
+    //             }
+    //         }
+    //         gclGatherData = hadMaterialGclGatherData;
+    //     } else {
+    //         gclGatherModel.loadLedgerData(ledger, curLedgerData);
+    //         gclGatherModel.loadPosData(pos, curPosData);
+    //         gclGatherData = gclGatherModel.gatherGclData().filter(item => {
+    //             return item.qc_qty || item.contract_qty
+    //         });
+    //     }
+    //     SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), SpreadJsObj.DataType.Data, gclGatherData);
+    //     loadLeafXmjData(0);
+    //     loadMaterialData(0, 0);
+    //     SpreadJsObj.resetTopAndSelect(ledgerSpread.getActiveSheet());
+    //     SpreadJsObj.resetTopAndSelect(leafXmjSpread.getActiveSheet());
+    //     SpreadJsObj.resetTopAndSelect(materialSpread.getActiveSheet());
+    // });
+
+    $.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();
+            ledgerSpread.refresh();
+            materialSpread.refresh();
+        }
+    });
+
+    $.divResizer({
+        select: '#right-spr',
+        callback: function () {
+            ledgerSpread.refresh();
+            materialSpread.refresh();
+            const width = (($('#right-view').width()/$('#right-view').parent('div').width())*100).toFixed();
+            setLocalCache('material_checklist_' + materialID, width);
+        }
+    });
+
+    // 展开收起工料并浏览器记住本期展开收起
+    $('a', '.right-nav').bind('click', function () {
+        const tab = $(this), tabPanel = $(tab.attr('content'));
+        if (!tab.hasClass('active')) {
+            $('a', '.side-menu').removeClass('active');
+            $('.tab-content .tab-select-show').removeClass('active');
+            tab.addClass('active');
+            tabPanel.addClass('active');
+            showSideTools(tab.hasClass('active'));
+            if (tab.attr('content') === '#material-tab') {
+                const width = (($('#right-view').width()/$('#right-view').parent('div').width())*100).toFixed();
+                setLocalCache('material_checklist_' + materialID, width);
+            }
+        } else {
+            removeLocalCache('material_checklist_' + materialID);
+            tab.removeClass('active');
+            tabPanel.removeClass('active');
+            showSideTools(tab.hasClass('active'));
+        }
+        ledgerSpread.refresh();
+        materialSpread.refresh();
+    });
+    // 根据浏览器记录展开收起
+    if (getLocalCache('material_checklist_' + materialID)) {
+        const tab = $('.right-nav a[content="#material-tab"]'), tabPanel = $(tab.attr('content'));
+        $('a', '.side-menu').removeClass('active');
+        $('.tab-content .tab-select-show').removeClass('active');
+        tab.addClass('active');
+        tabPanel.addClass('active');
+        $('#right-view').width(getLocalCache('material_checklist_' + materialID) + '%');
+        showSideTools(tab.hasClass('active'));
+        ledgerSpread.refresh();
+        materialSpread.refresh();
+    }
+});

+ 9 - 9
app/public/js/material_exponent.js

@@ -14,10 +14,10 @@ function getPasteHint (str, row = '') {
 }
 function resetExTpTable() {
     const rate = $('#changeRate').val();
-    const bqhs = ZhCalc.round(ZhCalc.mul(ex_tp, 1+rate/100), 2);
-    const jzbqhs = ZhCalc.round(ZhCalc.add(ex_pre_tp_hs, bqhs), 2);
-    $('#tp_set').find('td').eq(3).text(ZhCalc.round(ex_tp, 2));
-    $('#tp_set').find('td').eq(4).text(ZhCalc.round(ZhCalc.add(ex_pre_tp, ex_tp), 2));
+    const bqhs = ZhCalc.round(ZhCalc.mul(ex_tp, 1+rate/100), materialDecimal.tp);
+    const jzbqhs = ZhCalc.round(ZhCalc.add(ex_pre_tp_hs, bqhs), materialDecimal.tp);
+    $('#tp_set').find('td').eq(3).text(ZhCalc.round(ex_tp, materialDecimal.tp));
+    $('#tp_set').find('td').eq(4).text(ZhCalc.round(ZhCalc.add(ex_pre_tp, ex_tp), materialDecimal.tp));
     $('#rate_set').find('td').eq(3).text(bqhs !== 0 ? bqhs : '');
     $('#rate_set').find('td').eq(4).text(jzbqhs !== 0 ? jzbqhs : '');
     // $('#ex_expr').html(ex_expr);
@@ -504,7 +504,7 @@ $(document).ready(() => {
         $('#calc_zdy').on('blur', function () {
             let newValue = parseFloat($(this).val());
             // 判断输入位数,并自动四舍五入
-            newValue = ZhCalc.round(newValue, decimal.tp);
+            newValue = ZhCalc.round(newValue, materialDecimal.tp);
             $('#calc_zdy').val(newValue);
             if (isNaN(newValue)) {
                 toastr.error('请输入正确的金额');
@@ -527,11 +527,11 @@ $(document).ready(() => {
         $('#changeRate').change(function () {
             const rate = parseInt($(this).val());
             postData(window.location.pathname + '/save', { type:'rate', rate: rate }, function (result) {
-                const exbqhs = ZhCalc.round(ZhCalc.mul(ex_tp, 1+rate/100), 2);
-                const exjzbqhs = ZhCalc.round(ZhCalc.add(ex_pre_tp_hs, exbqhs), 2);
+                const exbqhs = ZhCalc.round(ZhCalc.mul(ex_tp, 1+rate/100), materialDecimal.tp);
+                const exjzbqhs = ZhCalc.round(ZhCalc.add(ex_pre_tp_hs, exbqhs), materialDecimal.tp);
                 if (!materialTax) {
-                    const bqhs = ZhCalc.round(ZhCalc.mul(m_tp, 1+rate/100), 2);
-                    const jzbqhs = ZhCalc.round(ZhCalc.add(pre_tp_hs, bqhs), 2);
+                    const bqhs = ZhCalc.round(ZhCalc.mul(m_tp, 1+rate/100), materialDecimal.tp);
+                    const jzbqhs = ZhCalc.round(ZhCalc.add(pre_tp_hs, bqhs), materialDecimal.tp);
                     $('#rate_set').find('td').eq(1).text(bqhs !== 0 ? bqhs : '');
                     $('#rate_set').find('td').eq(2).text(jzbqhs !== 0 ? jzbqhs : '');
                 }

+ 89 - 61
app/public/js/material_list.js

@@ -49,7 +49,7 @@ function calcOneBQJC(xmj) {
             jiacha = ZhCalc.add(jiacha, ZhCalc.mul(ZhCalc.mul(xmj.gather_qty, l.quantity), getMpSpreadByMBData(l.mb_id)));
         }
     }
-    return ZhCalc.round(jiacha, 2);
+    return ZhCalc.round(jiacha, materialDecimal.tp);
 }
 
 function getPasteHint (str, row = '') {
@@ -71,7 +71,7 @@ function calculateJiaCha(data, index) {
             gcld.leafXmjs[index].jiacha = jiacha !== 0 ? jiacha : null;
             total_jiacha += jiacha;
         }
-        gcld.total_jiacha = ZhCalc.round(total_jiacha, 2)
+        gcld.total_jiacha = ZhCalc.round(total_jiacha, materialDecimal.tp)
     } else {
         for(const gcld of data) {
             let total_jiacha = 0;
@@ -80,7 +80,7 @@ function calculateJiaCha(data, index) {
                 gcld.leafXmjs[index].jiacha = jiacha !== 0 ? jiacha : null;
                 total_jiacha += jiacha;
             }
-            gcld.total_jiacha = ZhCalc.round(total_jiacha, 2)
+            gcld.total_jiacha = ZhCalc.round(total_jiacha, materialDecimal.tp)
         }
     }
 }
@@ -160,7 +160,7 @@ $(document).ready(() => {
         if (gcl) {
             for (const [index, xmj] of gcl.leafXmjs.entries()) {
                 const jiacha = calcOneBQJC(xmj);
-                gcl.leafXmjs[index].jiacha = jiacha !== 0 ? ZhCalc.round(jiacha, 2) : null;
+                gcl.leafXmjs[index].jiacha = jiacha !== 0 ? ZhCalc.round(jiacha, materialDecimal.tp) : null;
             }
             const leafXmjs = gcl.leafXmjs.filter(item => {
                 return item.qc_qty || item.contract_qty
@@ -209,12 +209,19 @@ $(document).ready(() => {
         curPosData = result.curPosData;
         materialListData = result.materialListData;
         notJoinList = result.materialNotJoinListData;
+        materialChecklistData = result.materialChecklistData;
         // 解析清单汇总数据
         gclGatherModel.loadLedgerData(ledger, curLedgerData);
         gclGatherModel.loadPosData(pos, curPosData);
         gclGatherData = gclGatherModel.gatherGclData().filter(item => {
             return item.qc_qty || item.contract_qty
         });
+        if (openMaterialChecklist) {
+            // 取交集
+            gclGatherData = _.filter(gclGatherData, function (item) {
+                return _.find(materialChecklistData, { b_code: item.b_code, name: item.name, unit: item.unit, unit_price: item.unit_price });
+            });
+        }
         calculateJiaCha(gclGatherData);
         SpreadJsObj.initSheet(leafXmjSpread.getActiveSheet(), leafXmjSpreadSetting);
         // 加载清单数据
@@ -266,7 +273,7 @@ $(document).ready(() => {
     const materialBase = {
         isEdit: function (data) {
             // 是否本期添加的工料
-            return data.order === stage_order;
+            return data.order === stage_order && !openMaterialChecklist;
         }
     };
 
@@ -286,6 +293,10 @@ $(document).ready(() => {
     let materialList = [];
     function loadMaterialData(iGclRow, iLXmjRow) {
         const gcl = gclGatherData[iGclRow];
+        // const leafXmjs = gcl.leafXmjs.filter(item => {
+        //     return item.qc_qty || item.contract_qty
+        // });
+        // console.log(iLXmjRow, leafXmjs, materialListData);
         if (gcl && gcl.leafXmjs[iLXmjRow]) {
             const xmj = gcl.leafXmjs[iLXmjRow];
             materialList = [];
@@ -294,6 +305,7 @@ $(document).ready(() => {
                     materialList.push(m);
                 }
             }
+            console.log(materialList);
             SpreadJsObj.loadSheetData(materialSpread.getActiveSheet(), SpreadJsObj.DataType.Data, materialList);
         } else {
             SpreadJsObj.loadSheetData(materialSpread.getActiveSheet(), SpreadJsObj.DataType.Data, []);
@@ -314,7 +326,9 @@ $(document).ready(() => {
         const select = SpreadJsObj.getSelectObject(sheet);
         const index = gclGatherData.indexOf(select);
         if (index !== -1) {
-            const xmj = gclGatherData[index].leafXmjs;
+            const xmj = gclGatherData[index].leafXmjs.filter(item => {
+                return item.qc_qty || item.contract_qty
+            });
             const leafXmjSheet = leafXmjSpread.getActiveSheet();
             for (const [iRow,x] of xmj.entries()) {
                 const notx = findNotJoinLeafXmj(x);
@@ -416,15 +430,21 @@ $(document).ready(() => {
                 const leafXmjSheet = leafXmjSpread.getActiveSheet();
                 const leafXmjSelect = SpreadJsObj.getSelectObject(leafXmjSheet);
                 const iRow = gclGatherData[index].leafXmjs.indexOf(leafXmjSelect);
-                return [index, iRow, leafXmjSheet, leafXmjSelect];
+                const leafXmjs = gclGatherData[index].leafXmjs.filter(item => {
+                    return item.qc_qty || item.contract_qty
+                });
+                const nRow = leafXmjs.indexOf(leafXmjSelect);
+                return [index, iRow, nRow, leafXmjSheet, leafXmjSelect];
             },
             checkJoinMaterial: function (type) {
-                const [iGclRow, iRow, sheet, select] = leafXmjSpreadObj.getSelect();
+                const [iGclRow, iRow, nRow, sheet, select] = leafXmjSpreadObj.getSelect();
                 const color = type === 'join' ? '' : '#d6d8db';
                 const data = {
                     type: type,
                     select: type === 'join' ? findNotJoinLeafXmj(select) : select,
-                }
+                };
+                console.log(iGclRow, iRow, nRow, select);
+                console.log(materialList);
                 // 添加到
                 postData(window.location.pathname + '/save', data, function (result) {
                     if (type === 'join') {
@@ -434,10 +454,10 @@ $(document).ready(() => {
                         notJoinList.push(result);
                     }
                     gclGatherData[iGclRow].leafXmjs[iRow].jiacha = calcOneBQJC(select);
-                    calculateJiaCha(gclGatherData, iGclRow)
-                    SpreadJsObj.reLoadRowData(sheet, iRow);
-                    sheet.getRange(iRow, -1, 1, -1).backColor(color);
-                    loadMaterialData(iGclRow, iRow);
+                    calculateJiaCha(gclGatherData, iGclRow);
+                    SpreadJsObj.reLoadRowData(sheet, nRow);
+                    sheet.getRange(nRow, -1, 1, -1).backColor(color);
+                    loadMaterialData(iGclRow, 0);
                     SpreadJsObj.reLoadRowData(ledgerSpread.getActiveSheet(), iGclRow);
                 });
             },
@@ -930,57 +950,59 @@ $(document).ready(() => {
             //     SpreadJsObj.reLoadRowData(ledgerSpread.getActiveSheet(), index);
             // });
         });
-        $.contextMenu({
-            selector: '#material-spread',
-            build: function ($trigger, e) {
-                const target = SpreadJsObj.safeRightClickSelection($trigger, e, materialSpread);
-                return target.hitTestType === GC.Spread.Sheets.SheetArea.viewport || target.hitTestType === GC.Spread.Sheets.SheetArea.rowHeader;
-            },
-            items: {
-                'create': {
-                    name: '添加工料',
-                    icon: 'fa-sign-in',
-                    callback: function (key, opt) {
-                        // 获取已选清单
-                        changeMaterialTable();
-                        $('#addgl').modal('show');
-                    },
-                    disabled: function (key, opt) {
-                        const sheet = leafXmjSpread.getActiveSheet();
-                        const select = SpreadJsObj.getSelectObject(sheet);
-                        // const notx = findNotJoinLeafXmj(select);
-                        if (!select) {
-                            return true;
-                        }
-                        // if (!readOnly && notx === undefined) {
-                        //     return false;
-                        // } else {
-                        //     return true;
-                        // }
-                        return readOnly;
-                    }
+        if (!openMaterialChecklist) {
+            $.contextMenu({
+                selector: '#material-spread',
+                build: function ($trigger, e) {
+                    const target = SpreadJsObj.safeRightClickSelection($trigger, e, materialSpread);
+                    return target.hitTestType === GC.Spread.Sheets.SheetArea.viewport || target.hitTestType === GC.Spread.Sheets.SheetArea.rowHeader;
                 },
-                'delete': {
-                    name: '删除工料',
-                    icon: 'fa-remove',
-                    callback: function (key, opt) {
-                        materialSpreadObj.del(materialSpread.getActiveSheet());
-                    },
-                    disabled: function (key, opt) {
-                        const sheet = materialSpread.getActiveSheet();
-                        const select = SpreadJsObj.getSelectObject(sheet);
-                        if (!select) {
-                            return true;
+                items: {
+                    'create': {
+                        name: '添加工料',
+                        icon: 'fa-sign-in',
+                        callback: function (key, opt) {
+                            // 获取已选清单
+                            changeMaterialTable();
+                            $('#addgl').modal('show');
+                        },
+                        disabled: function (key, opt) {
+                            const sheet = leafXmjSpread.getActiveSheet();
+                            const select = SpreadJsObj.getSelectObject(sheet);
+                            // const notx = findNotJoinLeafXmj(select);
+                            if (!select) {
+                                return true;
+                            }
+                            // if (!readOnly && notx === undefined) {
+                            //     return false;
+                            // } else {
+                            //     return true;
+                            // }
+                            return readOnly;
                         }
-                        if (!readOnly && select && materialBase.isEdit(select)) {
-                            return false;
-                        } else {
-                            return true;
+                    },
+                    'delete': {
+                        name: '删除工料',
+                        icon: 'fa-remove',
+                        callback: function (key, opt) {
+                            materialSpreadObj.del(materialSpread.getActiveSheet());
+                        },
+                        disabled: function (key, opt) {
+                            const sheet = materialSpread.getActiveSheet();
+                            const select = SpreadJsObj.getSelectObject(sheet);
+                            if (!select) {
+                                return true;
+                            }
+                            if (!readOnly && select && materialBase.isEdit(select)) {
+                                return false;
+                            } else {
+                                return true;
+                            }
                         }
-                    }
-                },
-            }
-        });
+                    },
+                }
+            });
+        }
     }
 
     // 切换清单行,读取所属项目节数据
@@ -1036,6 +1058,12 @@ $(document).ready(() => {
             gclGatherData = gclGatherModel.gatherGclData().filter(item => {
                 return item.qc_qty || item.contract_qty
             });
+            if (openMaterialChecklist) {
+                // 取交集
+                gclGatherData = _.filter(gclGatherData, function (item) {
+                    return _.find(materialChecklistData, { b_code: item.b_code, name: item.name, unit: item.unit, unit_price: item.unit_price });
+                });
+            }
         }
         calculateJiaCha(gclGatherData);
         SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), SpreadJsObj.DataType.Data, gclGatherData);

+ 14 - 15
app/public/js/path_tree.js

@@ -337,22 +337,21 @@ const createNewPathTree = function (type, setting) {
             });
             for (const data of datas) {
                 const keyName = itemsPre + data[this.setting.id];
-                if (!this.items[keyName]) {
-                    const item = JSON.parse(JSON.stringify(data));
-                    item.children = [];
-                    item.expanded = true;
-                    item.visible = true;
-                    this.items[keyName] = item;
-                    this.datas.push(item);
-                    if (item[setting.pid] === setting.rootId) {
-                        this.children.push(item);
-                    } else {
-                        const parent = this.getParent(item);
-                        if (parent) {
-                            parent.children.push(item);
-                        }
-                    }
+                if (this.items[keyName]) throw '数据错误';
+
+                const item = JSON.parse(JSON.stringify(data));
+                item.children = [];
+                item.expanded = true;
+                item.visible = true;
+                if (item[setting.pid] === setting.rootId) {
+                    this.children.push(item);
+                } else {
+                    const parent = this.getParent(item);
+                    if (!parent) continue;
+                    parent.children.push(item);
                 }
+                this.items[keyName] = item;
+                this.datas.push(item);
             }
             this.children.sort((a, b) => { return a[self.setting.order] - b[self.setting.order]; });
             this.sortTreeNode(true);

+ 10 - 6
app/public/js/revise.js

@@ -358,14 +358,17 @@ $(document).ready(() => {
                 }
             });
         },
+        loadRelaData: function() {
+            billsTreeSpreadObj.refreshOperationValid(billsSheet);
+            SpreadJsObj.resetTopAndSelect(posSheet);
+            posSpreadObj.loadCurPosData();
+            SpreadJsObj.saveTopAndSelect(billsSheet, ckBillsSpread);
+            posSearch.search($('#pos-keyword').val());
+        },
         selectionChanged: function (e, info) {
             if (info.newSelections) {
                 if (!info.oldSelections || info.newSelections[0].row !== info.oldSelections[0].row) {
-                    billsTreeSpreadObj.refreshOperationValid(info.sheet);
-                    SpreadJsObj.resetTopAndSelect(posSheet);
-                    posSpreadObj.loadCurPosData();
-                    SpreadJsObj.saveTopAndSelect(billsSheet, ckBillsSpread);
-                    posSearch.search($('#pos-keyword').val());
+                    billsTreeSpreadObj.loadRelaData();
                 }
             }
             billsTreeSpreadObj.loadExprToInput(info.sheet);
@@ -1062,6 +1065,7 @@ $(document).ready(() => {
         selector: '#bills-spread',
         build: function ($trigger, e) {
             const target = SpreadJsObj.safeRightClickSelection($trigger, e, billsSpread);
+            billsTreeSpreadObj.loadRelaData();
             return target.hitTestType === spreadNS.SheetArea.viewport || target.hitTestType === spreadNS.SheetArea.rowHeader;
         },
         items: {}
@@ -2644,7 +2648,7 @@ $(document).ready(() => {
         // 修订详情 保存
         $('#save').click(function () {
             const content = $('#content').val();
-            postData('save', { content: content }, function () {
+            postData(`/tender/${window.location.pathname.split('/')[2]}/revise/save`, { content: content }, function () {
                 $('#content').attr('org-value', content);
             });
         });

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

@@ -253,7 +253,7 @@ $(document).ready(() => {
     };
 
     // 加载清单&计量单元数据
-    postData('/tender/' + window.location.pathname.split('/')[2] + '/revise/load', {filter: 'bills;pos;reviseBills;revisePos'}, function (result) {
+    postData('load', {filter: 'bills;pos;reviseBills;revisePos'}, function (result) {
         const tenderTreeSetting = {
             id: 'ledger_id',
             pid: 'ledger_pid',

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

@@ -149,7 +149,7 @@ $(document).ready(() => {
         $('#chapter-list').html(html.join(''));
     }
 
-    postData('/tender/' + window.location.pathname.split('/')[2] + '/revise/load', {filter: 'bills;pos;reviseBills;revisePos;dealBills;spec'}, function (data) {
+    postData('load', {filter: 'bills;pos;reviseBills;revisePos;dealBills;spec'}, function (data) {
         const setting = {
             tree: {
                 id: 'ledger_id',

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

@@ -155,15 +155,15 @@ $(document).ready(() => {
         },
     };
     // 加载清单&计量单元数据
-    postData(window.location.pathname + '/load', {}, function (result) {
-        billsTree.loadDatas(result.bills);
+    postData('load', {filter:'reviseBills;revisePos'}, function (result) {
+        billsTree.loadDatas(result.reviseBills);
         treeCalc.calculateAll(billsTree);
         SpreadJsObj.loadSheetData(billsSheet, SpreadJsObj.DataType.Tree, billsTree);
         SpreadJsObj.loadTopAndSelect(billsSheet, ckBillsSpread);
 
-        checkShowLast(result.bills.length);
+        checkShowLast(result.reviseBills.length);
 
-        pos.loadDatas(result.pos);
+        pos.loadDatas(result.revisePos);
         posSpreadObj.loadCurPosData();
         SpreadJsObj.resetTopAndSelect(posSheet);
     }, null);   
@@ -300,7 +300,7 @@ $(document).ready(() => {
     })('a[name=showLevel]', billsSheet);
 
     $('#reviseHistory').change(function () {
-        postData(window.location.pathname + '/info', { rid: this.value }, function (result) {
+        postData('info', { rid: this.value }, function (result) {
             $('#user-name').val(result.user_name);
             $('#content')[0].textContent = result.content;
             $('#end-time').val(result.end_time_str);

+ 11 - 2
app/public/js/setting_datacollect_tender.js

@@ -16,6 +16,12 @@ function findNode (key, value, arr) {
         }
     }
 }
+// 分类数据排序
+function sortCategory() {
+    category.sort(function (a, b) {
+        return a.level ? (b.level ? a.level - b.level : -1) : a.id - b.id;
+    });
+}
 // 初始化TenderTree数据
 function initTenderTree () {
     const levelCategory = category.filter(function (c) {
@@ -81,6 +87,7 @@ function initTenderTree () {
             tenderTree.push(t);
         }
     }
+    sortTenderTree();
 }
 function recursiveGetTenderNodeHtml (node, arr, pid) {
     // console.log(node, tender)
@@ -133,10 +140,12 @@ function getTenderTreeHtml () {
     }
 }
 $(document).ready(function () {
+    sortCategory();
     initTenderTree();
     $('#tenderBtn').click(() => {
-        const html = getTenderTreeHtml();
-        $('#copyModalContent').html(html);
+        tenderListOrder.reOrderTenders('', '#copyModalContent', false);
+        // const html = getTenderTreeHtml();
+        // $('#copyModalContent').html(html);
         $('#sort input[value="'+ addDataCollect +'"]').prop('checked', true);
         if($('#copyModalContent input[type="checkbox"]:not(:checked)').length === 0) {
             $('#select-tender-all').prop('checked', true);

+ 3 - 3
app/public/js/shares/tender_list_order.js

@@ -28,7 +28,7 @@ const tenderListOrder = (function () {
         // }
         return pinyin.compareWord(x, y);
     }
-    function reOrderTenders (orderStr) {
+    function reOrderTenders (orderStr, htmlClass = '.c-body', loadHide = true) {
         if (orderStr) {
             orderSetting = orderStr;
             setLocalCache('zh-calc-tender-list-order', orderStr);
@@ -51,8 +51,8 @@ const tenderListOrder = (function () {
             })
         }
         initTenderTree();
-        $('.c-body').html(getTenderTreeHtml());
-        localHideList();
+        $(htmlClass).html(getTenderTreeHtml());
+        if (loadHide) localHideList();
     }
     function getOrderButton(field) {
         const orders = orderSetting.split('|');

+ 14 - 32
app/public/js/stage.js

@@ -603,7 +603,9 @@ $(document).ready(() => {
             this.displayChanges = [];
             for (const c of this.changes) {
                 const filterVisible = filterEmpty ? (c.vamount ? !checkZero(c.vamount) : false) : true;
-                const matchVisible = matchPosName && this.callData.pos ? c.b_bwmx === this.callData.pos.name : true;
+                const matchVisible = matchPosName && this.callData.pos
+                    ? (c.gcl_id === this.callData.bills.id && c.b_bwmx === this.callData.pos.name)
+                    : true;
                 if ((filterVisible && matchVisible) || (c.org_uamount)) {
                     this.displayChanges.push(c);
                 }
@@ -961,17 +963,19 @@ $(document).ready(() => {
                 });
             }
         },
+        loadRelaData: function () {
+            const billsSheet = spSpread.getActiveSheet();
+            SpreadJsObj.resetTopAndSelect(billsSheet);
+            stagePosSpreadObj.loadCurPosData();
+            if (posSearch) posSearch.search();
+            SpreadJsObj.saveTopAndSelect(billsSheet, ckBillsSpread);
+        },
         selectionChanged: function (e, info) {
             if (!info.oldSelections || !info.oldSelections[0] || info.newSelections[0].row !== info.oldSelections[0].row) {
-                SpreadJsObj.resetTopAndSelect(spSpread.getActiveSheet());
-                stagePosSpreadObj.loadCurPosData();
-                if (posSearch) {
-                    posSearch.search();
-                }
+                stageTreeSpreadObj.loadRelaData();
                 // 全选去除
                 $('#dqjiedian').find('.check-all-file').prop('checked', false);
             }
-            SpreadJsObj.saveTopAndSelect(info.sheet, ckBillsSpread);
             stageTreeSpreadObj.loadExprToInput(info.sheet);
         },
         deletePress(sheet) {
@@ -1471,6 +1475,7 @@ $(document).ready(() => {
         selector: '#stage-ledger',
         build: function ($trigger, e) {
             const target = SpreadJsObj.safeRightClickSelection($trigger, e, slSpread);
+            stageTreeSpreadObj.loadRelaData();
             return target.hitTestType === spreadNS.SheetArea.viewport || target.hitTestType === spreadNS.SheetArea.rowHeader;
         },
         items: {
@@ -1617,7 +1622,7 @@ $(document).ready(() => {
             const sheet = slSpread.getActiveSheet();
             const node = SpreadJsObj.getSelectObject(sheet);
             if (node) {
-                const posReadOnly = node.lock || (node.children && node.children.length > 0);
+                const posReadOnly = node.lock || (node.children && node.children.length > 0) || !node.b_code;
                 spSpread.getActiveSheet().zh_setting.readOnly = posReadOnly;
                 const posData = stagePos.ledgerPos[itemsPre + node.id] || [];
                 SpreadJsObj.loadSheetData(spSpread.getActiveSheet(), 'data', posData, posReadOnly);
@@ -2021,23 +2026,11 @@ $(document).ready(() => {
 
     // 加载计量单元数据 - 暂时统一加载,如有需要,切换成动态加载并缓存
     postData(window.location.pathname + '/load', { filter: 'ledger;pos;detail;change;tag;cooperation' }, function (result) {
-        result.ledgerData && console.log('ledger: ' + result.ledgerData.length);
-        result.posData && console.log('pos: ' + result.posData.length);
-        result.detailData && console.log('detail: ' + result.detailData.length);
-        result.detailAtt && console.log('detailAtt: ' + result.detailAtt.length);
-        result.changeData && console.log('change: ' + result.changeData.length);
-        result.tags && console.log('tag: ' + result.tags.length);
-        result.cooperation && console.log('cooperation: ' + result.cooperation.length);
         // 加载树结构
-        console.time('loadLedger');
         stageTree.loadDatas(result.ledgerData);
-        console.timeEnd('loadLedger');
         checkShowLast(result.ledgerData.length);
-        console.time('calcLedger');
         treeCalc.calculateAll(stageTree);
-        console.timeEnd('calcLedger');
         // 加载解锁相关
-        console.time('loadCooperation');
         if (result.cooperation) {
             stageTree.loadPwd(result.cooperation, 'bills-p-' + userID + '-' + window.location.pathname.split('/')[2], result.cooperationConfirm);
             $('#cooperationCount').html(stageTree.pwd.length || '');
@@ -2045,31 +2038,20 @@ $(document).ready(() => {
             setCooperationSelectHtml();
             reloadCooperationHtml();
         }
-        console.timeEnd('loadCooperation');
-        console.time('loadTag');
         for (const t of result.tags) {
             t.node = stageTree.datas.find(x => {return x.id === t.lid});
         }
         billsTag.loadDatas(result.tags);
-        console.timeEnd('loadTag');
         // 加载部位明细
-        console.time('loadPos');
         stagePos.loadDatas(result.posData);
-        console.timeEnd('loadPos');
         stagePos.calculateAll();
-        console.time('showLedger');
         SpreadJsObj.loadSheetData(slSpread.getActiveSheet(), 'tree', stageTree);
         SpreadJsObj.loadTopAndSelect(slSpread.getActiveSheet(), ckBillsSpread);
-        console.timeEnd('showLedger');
-        console.time('showPos');
         stagePosSpreadObj.loadCurPosData();
         SpreadJsObj.resetTopAndSelect(spSpread.getActiveSheet());
-        console.timeEnd('showPos');
         // 加载中间计量
-        console.time('loadIm');
         stageIm.init(stage, imType, tenderInfo.decimal);
         stageIm.loadData(result.ledgerData, result.posData, result.detailData, result.changeData, result.detailAtt);
-        console.timeEnd('loadIm');
 
         errorList.loadHisErrorData();
         checkList.loadHisCheckData();
@@ -3757,7 +3739,7 @@ $(document).ready(() => {
                             const changeBills = SpreadJsObj.getSelectObject(self.changeBillsSheet);
                             if (changeBills.gcl_id) {
                                 const node = stageTree.nodes.find(x => {return x.id === changeBills.gcl_id});
-                                SpreadJsObj.locateTreeNode(slSpread.getActiveSheet(), node.ledger_id);
+                                SpreadJsObj.locateTreeNode(slSpread.getActiveSheet(), node.ledger_id, true);
                                 stagePosSpreadObj.loadCurPosData();
                             } else {
                                 const cb = {

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

@@ -150,6 +150,7 @@ $(document).ready(function () {
         selector: '#ledger-spread',
         build: function ($trigger, e) {
             const target = SpreadJsObj.safeRightClickSelection($trigger, e, ledgerSpread);
+            loadPosData();
             return target.hitTestType === spreadNS.SheetArea.viewport || target.hitTestType === spreadNS.SheetArea.rowHeader;
         },
         items: {
@@ -194,7 +195,9 @@ $(document).ready(function () {
 
     // 获取部位明细数据
     function loadPosData(iRow) {
-        const node = ledgerSpread.getActiveSheet().zh_tree.nodes[iRow];
+        const node = iRow
+            ? (ledgerSpread.getActiveSheet().zh_tree ? ledgerSpread.getActiveSheet().zh_tree.nodes[iRow] : null)
+            : (SpreadJsObj.getSelectObject(ledgerSpread.getActiveSheet()));
         if (node) {
             SpreadJsObj.loadSheetData(posSpread.getActiveSheet(), SpreadJsObj.DataType.Data, scPos.getLedgerPos(node.id) || []);
         } else {

+ 85 - 91
app/public/js/stage_pay.js

@@ -139,7 +139,8 @@ $(document).ready(() => {
                 title: '附件', colSpan: '1', rowSpan: '1', field: 'attachment', hAlign: 0, width: 60, readOnly: true, cellType: 'imageBtn',
                 normalImg: '#rela-file-icon', hoverImg: '#rela-file-hover', getValue: 'getValue.attachment'
             },
-            {title: '状态', colSpan: '1', rowSpan: '1', field: '', hAlign: 0, width: 120, readOnly: true, getValue: 'getValue.state', foreColor: 'red', font: '8px 宋体'},
+            {title: '本期批注', colSpan: '1', rowSpan: '1', field: 'postil', hAlign: 0, width: 120},
+            // {title: '状态', colSpan: '1', rowSpan: '1', field: '', hAlign: 0, width: 120, readOnly: true, getValue: 'getValue.state', foreColor: 'red', font: '8px 宋体'},
         ],
         emptyRows: 0,
         headRows: 1,
@@ -643,49 +644,46 @@ $(document).ready(() => {
                 }
                 // 获取更新信息
                 const data = {
-                    type: (col.field === 'tp' || col.field === 'name') ? 'stage' : 'info',
-                    updateData: {}
+                    type: ['tp', 'name', 'postil'].indexOf(col.field) >= 0 ? 'stage' : 'info',
                 };
+                data.updateData = data.type === 'stage' ? { pid: select.pid } : { id: select.pid };
                 // 获取更新数据
-                if (col.field === 'tp') {
-                    data.updateData.pid = select.pid;
-                    const [valid, msg] = payBase.isSF(select)
-                        ? paySpreadObj._checkSfExpr(validText, data.updateData)
-                        : paySpreadObj._checkExpr(validText, data.updateData);
-                    if (!valid) {
-                        toastr.warning(msg);
-                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
-                        return;
-                    }
-                    // if (payBase.isSF(select)) {
-                    //     data.updateData.expr = data.updateData.tp;
-                    // }
-                } else if (col.field === 'name') {
-                    data.updateData.pid = select.pid;
-                    data.updateData.name = validText;
-                } else {
-                    data.updateData.id = select.pid;
-                    if (col.field === 'sprice') {
-                        const [valid, msg] = paySpreadObj._checkSExpr(select, validText, data.updateData);
-                        if (!valid) {
-                            toastr.warning(msg);
+                switch(col.field) {
+                    case 'tp':
+                        const [tpValid, tpMsg] = payBase.isSF(select)
+                            ? paySpreadObj._checkSfExpr(validText, data.updateData)
+                            : paySpreadObj._checkExpr(validText, data.updateData);
+                        if (!tpValid) {
+                            toastr.warning(tpMsg);
                             SpreadJsObj.reLoadRowData(info.sheet, info.row);
                             return;
                         }
-                    } else if (col.field === 'rprice') {
-                        const [valid, msg] = paySpreadObj._checkRExpr(select, validText, data.updateData);
-                        if (!valid) {
-                            toastr.warning(msg);
+                        // if (payBase.isSF(select)) {
+                        //     data.updateData.expr = data.updateData.tp;
+                        // }
+                        break;
+                    case 'name':
+                        data.updateData.name = validText;
+                        break;
+                    case 'sprice':
+                        const [sValid, sMsg] = paySpreadObj._checkSExpr(select, validText, data.updateData);
+                        if (!sValid) {
+                            toastr.warning(sMsg);
                             SpreadJsObj.reLoadRowData(info.sheet, info.row);
                             return;
                         }
-                    } else {
-                        if (validText) {
-                            data.updateData[col.field] = validText;
-                        } else {
-                            data.updateData[col.field] = null;
+                        break;
+                    case 'rprice':
+                        const [rValid, rMsg] = paySpreadObj._checkRExpr(select, validText, data.updateData);
+                        if (!rValid) {
+                            toastr.warning(rMsg);
+                            SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                            return;
                         }
-                    }
+                        break;
+                    default:
+                        data.updateData[col.field] = validText || '';
+
                 }
                 // 更新至服务器
                 postData(window.location.pathname + '/save', data, function (result) {
@@ -758,32 +756,32 @@ $(document).ready(() => {
                     toast('请勿同时删除多列数据', 'warning');
                 }
 
-                const data = {type: col.field === 'tp' ? 'stage' : 'info', updateData: []};
+                const data = {type: ['tp', 'postil'].indexOf(col.field) >= 0 ? 'stage' : 'info', updateData: []};
                 for (let iRow = sel.row; iRow < sel.row + sel.rowCount; iRow++) {
                     const node = sheet.zh_data[iRow];
-                    if (node && (node.ptype === 1 || node.ptype === 3)) {
-                        const updateData = {};
-                        if (col.field === 'tp') {
-                            updateData.pid = node.pid;
-                            updateData.tp = null;
-                            updateData.expr = null;
-                        } else {
-                            updateData.id = node.pid;
-                            if (col.field === 'sprice') {
-                                const [valid, msg] = paySpreadObj._checkSExpr(node, null, updateData);
-                                if (!valid) {
-                                    toastr.warning(msg);
+                    if (node && (node.ptype === 1 || node.ptype === 3 || col.field === 'postil')) {
+                        const updateData = data.type === 'stage' ? { pid: node.pid } : { id: node.pid };
+                        switch (col.field) {
+                            case 'tp':
+                                updateData.tp = null;
+                                updateData.expr = null;
+                                break;
+                            case 'sprice':
+                                const [sValid, sMsg] = paySpreadObj._checkSExpr(node, null, updateData);
+                                if (!sValid) {
+                                    toastr.warning(sMsg);
                                     return;
                                 }
-                            } else if (col.field === 'rprice') {
-                                const [valid, msg] = paySpreadObj._checkRExpr(node, null, updateData);
-                                if (!valid) {
-                                    toastr.warning(msg);
+                                break;
+                            case 'rprice':
+                                const [rValid, rMsg] = paySpreadObj._checkRExpr(node, null, updateData);
+                                if (!rValid) {
+                                    toastr.warning(rMsg);
                                     return;
                                 }
-                            } else {
-                                updateData[col.field] = null;
-                            }
+                                break;
+                            default:
+                                updateData[col.field] = col.type === 'Number' ? 0 : '';
                         }
                         data.updateData.push(updateData);
                     }
@@ -813,56 +811,52 @@ $(document).ready(() => {
 
                 const sortData = info.sheet.zh_data;
                 const data = {
-                    type: (col.field === 'tp' || col.field === 'name') ? 'stage' : 'info',
+                    type: ['tp', 'name', 'postil'].indexOf(col.field) >= 0 ? 'stage' : 'info',
                     updateData: []
                 };
 
                 for (let iRow = 0; iRow < info.cellRange.rowCount; iRow++) {
                     const curRow = info.cellRange.row + iRow;
                     const node = sortData[curRow];
-                    if (node && (node.ptype === 1 || node.ptype === 3)) {
+                    if (node && (node.ptype === 1 || node.ptype === 3 || col.field === 'postil')) {
                         const validText = info.sheet.getText(curRow, info.cellRange.col).replace('\n', '');
-                        const updateData = {};
-                        if (col.field === 'tp') {
-                            updateData.pid = node.pid;
-                            const [valid, msg] = payBase.isSF(node)
-                                ? paySpreadObj._checkSfExpr(validText, updateData)
-                                : paySpreadObj._checkExpr(validText, updateData);
-                            if (!valid) {
-                                toastr.warning(msg);
-                                SpreadJsObj.reLoadSheetData(paySpread.getActiveSheet());
-                                return;
-                            }
-                            if (payBase.isSF(node)) {
-                                data.updateData.expr = data.updateData.tp;
-                            }
-                        } else if (col.field === 'name') {
-                            updateData.pid = node.pid;
-                            updateData.name = validText;
-                        } else {
-                            updateData.id = node.pid;
-
-                            if (col.field === 'sprice') {
-                                const [valid, msg] = paySpreadObj._checkSExpr(node, validText, updateData);
-                                if (!valid) {
-                                    toastr.warning(msg);
+                        const updateData = data.type === 'stage' ? { pid: node.pid } : { id: node.pid };
+                        switch (col.field) {
+                            case 'tp':
+                                const [tpValid, tpMsg] = payBase.isSF(node)
+                                    ? paySpreadObj._checkSfExpr(validText, updateData)
+                                    : paySpreadObj._checkExpr(validText, updateData);
+                                if (!tpValid) {
+                                    toastr.warning(tpMsg);
                                     SpreadJsObj.reLoadSheetData(paySpread.getActiveSheet());
                                     return;
                                 }
-                            } else if (col.field === 'rprice') {
-                                const [valid, msg] = paySpreadObj._checkRExpr(node, validText, updateData);
-                                if (!valid) {
-                                    toastr.warning(msg);
+                                // if (payBase.isSF(node)) {
+                                //     data.updateData.expr = data.updateData.tp;
+                                // }
+                                break;
+                            case 'name':
+                                updateData.name = validText;
+                                break;
+                            case 'sprice':
+                                const [sValid, sMsg] = paySpreadObj._checkSExpr(node, validText, updateData);
+                                if (!sValid) {
+                                    toastr.warning(sMsg);
                                     SpreadJsObj.reLoadSheetData(paySpread.getActiveSheet());
                                     return;
                                 }
-                            } else {
-                                if (validText) {
-                                    updateData[col.field] = validText;
-                                } else {
-                                    updateData[col.field] = null;
+                                break;
+                            case 'rprice':
+                                const [rValid, rMsg] = paySpreadObj._checkRExpr(node, validText, updateData);
+                                if (!rValid) {
+                                    toastr.warning(rMsg);
+                                    SpreadJsObj.reLoadSheetData(paySpread.getActiveSheet());
+                                    return;
                                 }
-                            }
+                                break;
+                            default:
+                                updateData[col.field] = validText || '';
+
                         }
                         data.updateData.push(updateData);
                     }

+ 12 - 3
app/public/js/tender_copy_setting.js

@@ -16,6 +16,12 @@ function findNode (key, value, arr) {
         }
     }
 }
+// 分类数据排序
+function sortCategory() {
+    category.sort(function (a, b) {
+        return a.level ? (b.level ? a.level - b.level : -1) : a.id - b.id;
+    });
+}
 // 初始化TenderTree数据
 function initTenderTree () {
     const levelCategory = category.filter(function (c) {
@@ -93,6 +99,7 @@ function initTenderTree () {
             tenderTree.push(t);
         }
     }
+    sortTenderTree();
 }
 function recursiveGetTenderNodeHtml (node, arr, pid) {
     // console.log(node, tender)
@@ -147,10 +154,12 @@ function getTenderTreeHtml () {
     }
 }
 $(document).ready(function () {
-    initTenderTree()
+    sortCategory();
+    initTenderTree();
     $('#copyBtn').click(() => {
-        const html = getTenderTreeHtml();
-        $('#copyModalContent').html(html);
+        tenderListOrder.reOrderTenders('', '#copyModalContent', false);
+        // const html = getTenderTreeHtml();
+        // $('#copyModalContent').html(html);
         $('#bd-set-8').modal('show');
     });
 

+ 0 - 1
app/public/js/zh_calc.js

@@ -272,7 +272,6 @@ const ZhCalc = (function () {
                     return Number(expr);
                 } else {
                     const rpnArr = this.parse2Rpn(expr);
-                    console.log(rpnArr);
                     const result = this.evalRpn(rpnArr);
                     return result === Infinity ? null : result;
                 }

+ 19 - 0
app/public/report/js/rpt_main.js

@@ -995,6 +995,25 @@ function downloadPDFReport(pageDataArr, pageSize, rpt_names, signatureRelArr, si
             let pageData = newPageDataArr[rptControlObj.currentDownloadIdx];
             let rptName = new_rpt_rames[rptControlObj.currentDownloadIdx];
             rptControlObj.currentDownloadIdx++;
+            /*
+            // 临时代码,为大批量草图PDF下载用(强制分页)
+            const MAX_PAGES_AMT  = 100;
+            const orgItems = pageData.items;
+            let ttlSec = parseInt(orgItems.length / MAX_PAGES_AMT);
+            if (orgItems.length % MAX_PAGES_AMT > 0) {
+                ttlSec++;
+            }
+            for (let idx = 0; idx < ttlSec; idx++) {
+                let indPages = [];
+                for (let pi = idx * MAX_PAGES_AMT; pi < Math.min((idx + 1) * MAX_PAGES_AMT, orgItems.length); pi++ ) {
+                    indPages.push(orgItems[pi]);
+                }
+                pageData.items = indPages;
+                JpcJsPDFHelper.outputAsPdf(pageData, pageSize, rptName + idx, singleSignatureRelArr, STAGE_AUDIT);
+            }
+            pageData.items = orgItems;
+            //*/
+
             JpcJsPDFHelper.outputAsPdf(pageData, pageSize, rptName, singleSignatureRelArr, STAGE_AUDIT); // 精确控制签名
             if (rptControlObj.currentDownloadIdx < newPageDataArr.length) setTimeout(private_download(newPageDataArr, new_rpt_rames), 2000);
         }

+ 48 - 0
app/reports/rpt_component/jpc_cross_tab.js

@@ -388,6 +388,17 @@ JpcCrossTabSrv.prototype.createNew = function() {
                         }
                         for (let colIdx = 0; colIdx < colSplitCnt; colIdx++) {
                             pageStatus[JV.STATUS_CROSS_COL_END] = (colIdx === (colSplitCnt - 1));
+                            let needOneMoreRptEnd = false;
+                            if ((segIdx === segCnt - 1) && pageStatus[JV.STATUS_CROSS_ROW_END] && pageStatus[JV.STATUS_CROSS_COL_END]) {
+                                pageStatus[JV.STATUS_REPORT_END] = true;
+                                private_resetBandArea();
+                                let hasAdHocColEnd = !JpcCrossTabHelper.chkTabEnd(JV.NODE_CROSS_COL_SUM, rptTpl, bands, me.sortedColSequence, segIdx, (colSplitCnt - 1) * orgMaxColRec, maxColRec);
+                                let hasAdHocRowEnd = !JpcCrossTabHelper.chkTabEnd(JV.NODE_CROSS_ROW_SUM, rptTpl, bands, me.sortedRowSequence, segIdx, (rowSplitCnt - 1) * orgMaxRowRec, maxRowRec);
+                                if (hasAdHocColEnd || hasAdHocRowEnd) {
+                                    needOneMoreRptEnd = true;
+                                    pageStatus[JV.STATUS_REPORT_END] = false;
+                                }
+                            }
                             private_resetBandArea();
                             counterColRec = orgMaxColRec * colIdx;
                             let currentSortedColSequence = me.sortedColSequence;
@@ -401,6 +412,19 @@ JpcCrossTabSrv.prototype.createNew = function() {
                             private_addTabValue(me.dispValueIdxLst_Row, currentSortedRowSequence, segIdx, counterRowRec, maxRowRec, me.dispSerialIdxLst_Row, me.col_sum_fields_value_total, me.dispSumValueLst_Col);
                             private_addTabValue(me.dispValueIdxLst_Col, currentSortedColSequence, segIdx, counterColRec, maxColRec, null, me.row_sum_fields_value_total, me.dispSumValueLst_Row);
                             private_addContentValue(me.dispValueIdxLst_Content, currentSortedContentSequence, segIdx, counterRowRec, maxRowRec, counterColRec, maxColRec, me.page_seg_map, pageIdx);
+                            // 处理report end情况
+                            if (needOneMoreRptEnd) {
+                                pageStatus[JV.STATUS_REPORT_END] = true;
+                                private_resetBandArea();
+                                currentSortedColSequence = null;
+                                currentSortedContentSequence = null;
+                                counterColRec = 0;
+                                me.pageStatusLst.push(pageStatus.slice(0));
+                                pageIdx++;
+                                private_addTabValue(me.dispValueIdxLst_Row, currentSortedRowSequence, segIdx, counterRowRec, maxRowRec, me.dispSerialIdxLst_Row, me.col_sum_fields_value_total, me.dispSumValueLst_Col);
+                                private_addTabValue(me.dispValueIdxLst_Col, currentSortedColSequence, segIdx, counterColRec, maxColRec, null, me.row_sum_fields_value_total, me.dispSumValueLst_Row);
+                                private_addContentValue(me.dispValueIdxLst_Content, currentSortedContentSequence, segIdx, counterRowRec, maxRowRec, counterColRec, maxColRec, me.page_seg_map, pageIdx);
+                            }
                         }
                     }
                 } else {
@@ -417,6 +441,17 @@ JpcCrossTabSrv.prototype.createNew = function() {
                         }
                         for (let rowIdx = 0; rowIdx < rowSplitCnt; rowIdx++) {
                             pageStatus[JV.STATUS_CROSS_ROW_END] = (rowIdx === (rowSplitCnt - 1));
+                            let needOneMoreRptEnd = false;
+                            if ((segIdx === segCnt - 1) && pageStatus[JV.STATUS_CROSS_ROW_END] && pageStatus[JV.STATUS_CROSS_COL_END]) {
+                                pageStatus[JV.STATUS_REPORT_END] = true;
+                                private_resetBandArea();
+                                let hasAdHocColEnd = !JpcCrossTabHelper.chkTabEnd(JV.NODE_CROSS_COL_SUM, rptTpl, bands, me.sortedColSequence, segIdx, (colSplitCnt - 1) * orgMaxColRec, maxColRec);
+                                let hasAdHocRowEnd = !JpcCrossTabHelper.chkTabEnd(JV.NODE_CROSS_ROW_SUM, rptTpl, bands, me.sortedRowSequence, segIdx, (rowSplitCnt - 1) * orgMaxRowRec, maxRowRec);
+                                if (hasAdHocColEnd || hasAdHocRowEnd) {
+                                    needOneMoreRptEnd = true;
+                                    pageStatus[JV.STATUS_REPORT_END] = false;
+                                }
+                            }
                             private_resetBandArea();
                             me.pageStatusLst.push(pageStatus.slice(0));
                             pageIdx++;
@@ -430,6 +465,19 @@ JpcCrossTabSrv.prototype.createNew = function() {
                             private_addTabValue(me.dispValueIdxLst_Row, currentSortedRowSequence, segIdx, counterRowRec, maxRowRec, me.dispSerialIdxLst_Row, me.col_sum_fields_value_total, me.dispSumValueLst_Col);
                             private_addTabValue(me.dispValueIdxLst_Col, currentSortedColSequence, segIdx, counterColRec, maxColRec, null, me.row_sum_fields_value_total, me.dispSumValueLst_Row);
                             private_addContentValue(me.dispValueIdxLst_Content, currentSortedContentSequence, segIdx, counterRowRec, maxRowRec, counterColRec, maxColRec, me.page_seg_map, pageIdx);
+                            // 处理report end情况
+                            if (needOneMoreRptEnd) {
+                                pageStatus[JV.STATUS_REPORT_END] = true;
+                                private_resetBandArea();
+                                currentSortedRowSequence = null;
+                                currentSortedContentSequence = null;
+                                counterRowRec = 0;
+                                me.pageStatusLst.push(pageStatus.slice(0));
+                                pageIdx++;
+                                private_addTabValue(me.dispValueIdxLst_Row, currentSortedRowSequence, segIdx, counterRowRec, maxRowRec, me.dispSerialIdxLst_Row, me.col_sum_fields_value_total, me.dispSumValueLst_Col);
+                                private_addTabValue(me.dispValueIdxLst_Col, currentSortedColSequence, segIdx, counterColRec, maxColRec, null, me.row_sum_fields_value_total, me.dispSumValueLst_Row);
+                                private_addContentValue(me.dispValueIdxLst_Content, currentSortedContentSequence, segIdx, counterRowRec, maxRowRec, counterColRec, maxColRec, me.page_seg_map, pageIdx);
+                            }
                         }
                     }
                 }

+ 17 - 5
app/reports/rpt_component/jpc_ex.js

@@ -174,10 +174,10 @@ JpcExSrv.prototype.createNew = function() {
         // 1. data object
         let dataHelper = JpcData.createNew();
         // console.log(JSON.stringify(rptTpl));
-        me.executeFormulas($CTX_HELPER, JV.RUN_TYPE_BEFORE_ANALYZING, rptTpl, dataObj, me, $CUSTOM_DEFINE); // 在分析前运行,主要是增加灵活性,比如:重新编排数据的主从关系
+        me.executeFormulas($CTX_HELPER, JV.RUN_TYPE_BEFORE_ANALYZING, rptTpl, dataObj, me, $CUSTOM_DEFINE, defProperties); // 在分析前运行,主要是增加灵活性,比如:重新编排数据的主从关系
         // console.log(JSON.stringify(rptTpl));
         if (me.crossTab) {
-            me.executeFormulas($CTX_HELPER, JV.RUN_TYPE_BEFORE_PAGING, rptTpl, dataObj, me, $CUSTOM_DEFINE);
+            me.executeFormulas($CTX_HELPER, JV.RUN_TYPE_BEFORE_PAGING, rptTpl, dataObj, me, $CUSTOM_DEFINE, defProperties);
             dataHelper.analyzeData(rptTpl, dataObj);
             me.crossTab.sorting(rptTpl, dataObj, dataHelper.dataSeq.slice(0), me);
         } else {
@@ -197,7 +197,7 @@ JpcExSrv.prototype.createNew = function() {
             // let dt2 = new Date();
             // alert(dt2 - dt1);
             // 3. formulas
-            me.executeFormulas($CTX_HELPER, JV.RUN_TYPE_BEFORE_PAGING, rptTpl, dataObj, me, $CUSTOM_DEFINE);
+            me.executeFormulas($CTX_HELPER, JV.RUN_TYPE_BEFORE_PAGING, rptTpl, dataObj, me, $CUSTOM_DEFINE, defProperties);
         }
         // 4. paging
         me.paging(rptTpl, dataObj, defProperties, dftPagingOption, outputType);
@@ -226,7 +226,19 @@ JpcExSrv.prototype.createNew = function() {
             me.totalPages = me.billTab.paging(rptTpl, dataObj);
         }
     };
-    JpcResult.executeFormulas = function($CTX_HELPER, runType, $CURRENT_TEMPLATE, $CURRENT_DATA, $CURRENT_RPT, $CUSTOM_DEFINE) {
+    JpcResult.getDftRowsPerPage = function(rptTpl, defProperties) {
+        const me = this;
+        let rst = 1;
+        if (me.flowTab) {
+            rst = me.flowTab.getDftRowsPerPage(rptTpl, defProperties, me);
+        } else if (me.crossTab) {
+            // 实际业务可以暂时不提供
+        } else if (me.billTab) {
+            // 实际业务可以暂时不提供
+        }
+        return rst;
+    };
+    JpcResult.executeFormulas = function($CTX_HELPER, runType, $CURRENT_TEMPLATE, $CURRENT_DATA, $CURRENT_RPT, $CUSTOM_DEFINE, $PAGE_PROPERTIES) {
         const execFmlMe = this;
         for (let execFmlIdx = 0; execFmlIdx < execFmlMe.formulas.length; execFmlIdx++) {
             // remark: 搞这么复杂的变量名是为了防止与表达式起冲突(如循环变量i,j,k,容易造成变量冲突且不容易看出问题)
@@ -328,7 +340,7 @@ JpcExSrv.prototype.createNew = function() {
             try {
                 for (let page = startPage; page <= endPage; page++) {
                     me.runTimePageData.currentPage = page;
-                    me.executeFormulas($CTX_HELPER, JV.RUN_TYPE_BEFORE_OUTPUT, rptTpl, dataObj, me, $CUSTOM_DEFINE);
+                    me.executeFormulas($CTX_HELPER, JV.RUN_TYPE_BEFORE_OUTPUT, rptTpl, dataObj, me, $CUSTOM_DEFINE, defProperties);
                     rst.items.push(me.outputAsSimpleJSONPage(rptTpl, dataObj, bands, page, rst[JV.NODE_CONTROL_COLLECTION], customizeCfg));
                 }
                 if (bands[JV.BAND_PROP_MERGE_BAND]) {

+ 10 - 0
app/reports/rpt_component/jpc_flow_tab.js

@@ -497,6 +497,16 @@ JpcFlowTabSrv.prototype.createNew = function() {
         }
     };
 
+    JpcFlowTabResult.getDftRowsPerPage = function(rptTpl, defProperties, $CURRENT_RPT) {
+        const me = this;
+        let rst = 1;
+        const bands = JpcBand.createNew(rptTpl, defProperties);
+        const pageStatus = [true, true, true, true, true, true, true, true];
+        JpcBandHelper.setBandArea(bands, rptTpl, pageStatus, true, false);
+        rst = JpcFlowTabHelper.getMaxRowsPerPage(bands, rptTpl, false);
+        return rst;
+    };
+
     JpcFlowTabResult.preSetupPages = function(rptTpl, dataObj, defProperties, option, $CURRENT_RPT, followTabEx, outputType) {
         // 换一种思路来整理流水式数据
         const me = this;

+ 2 - 1
app/reports/util/rpt_excel_util.js

@@ -1290,7 +1290,8 @@ function resetDummuySignature(pageData, roleRel) {
             for (const signature of page[JV.PROP_SIGNATURE_CELLS]) {
                 if (signature.signature_name.indexOf(JV.SIGNATURE_NAME_DUMMY) >= 0) {
                     // 表示这是一个其他类型的非原生电子签名图,只是借用signature的处理机制,每个图都是唯一的,所以需要重新给个唯一的新signature_name
-                    signature.signature_name = signature.signature_name + '_' + pageIdx + '_' + dummySignIdx;
+                    // signature.signature_name = signature.signature_name + '_' + pageIdx + '_' + dummySignIdx;
+                    signature.signature_name = signature.signature_name + '_' + page.page_seq + '_' + dummySignIdx; // page_seq在分页后都不会变动
                     dummySignIdx++;
                     const roleRelItem = { type: '用户', sign_path: signature.path, signature_name: signature.signature_name };
                     roleRel.push(roleRelItem);

+ 50 - 13
app/router.js

@@ -19,12 +19,16 @@ module.exports = app => {
     const materialCheck = app.middlewares.materialCheck();
     // 第三方接口认证判断中间件
     const api2otherCheck = app.middlewares.api2otherCheck();
+    // 项目管理接口认证中间件
+    const api3managementCheck = app.middlewares.api3managementCheck();
     // 微信验证登录中间件
     const wechatAuth = app.middlewares.wechatAuth();
     // 预付款中间件
     const advanceCheck = app.middlewares.advanceCheck();
     // 变更令中间件
     const changeCheck = app.middlewares.changeCheck();
+    // 变更立项书中间件
+    const changeProjectCheck = app.middlewares.changeProjectCheck();
     // 投资进度中间件
     const scheduleCheck = app.middlewares.scheduleCheck();
     // 修订
@@ -37,12 +41,19 @@ module.exports = app => {
     app.get('/logout', 'loginController.logout');
     app.post('/login', 'loginController.login');
     app.post('/login/port', 'loginController.loginPort');
+    app.get('/login/management', api3managementCheck, 'loginController.management');
     app.get('/project/name', 'loginController.projectName');
 
     app.get('/sign', 'signController.index');
     app.post('/sign/save', 'signController.save');
     app.post('/reset/password', 'loginController.resetPassword');
 
+    // 项目管理对计量接口相关
+    app.get('/management/account', api3managementCheck, 'loginController.account');
+    app.get('/management/account/sync', api3managementCheck, 'loginController.syncProjectAccount');
+    app.get('/management/project', api3managementCheck, 'loginController.project');
+    app.get('/management/proxy/project/vertify', sessionAuth, 'loginController.vertifyProject');
+    app.get('/management/proxy/project/add', sessionAuth, 'loginController.addProject');
     // 用户信息初始化相关
     app.get('/boot', sessionAuth, 'bootController.index');
     app.post('/boot', sessionAuth, 'bootController.boot');
@@ -208,21 +219,22 @@ module.exports = app => {
     // app.post('/tender/:id/revise/deal2sgfh', sessionAuth, tenderCheck, uncheckTenderCheck, 'reviseController.deal2sgfh');
 
     // 台账修订页面
-    app.get('/tender/:id/revise/info', sessionAuth, tenderCheck, uncheckTenderCheck, reviseAuditCheck, 'reviseController.info');
-    app.post('/tender/:id/revise/auditors', sessionAuth, tenderCheck, uncheckTenderCheck, 'reviseController.reviseAuditors');
-    app.post('/tender/:id/revise/info/load', sessionAuth, tenderCheck, uncheckTenderCheck, 'reviseController.loadInfoData');
-    app.post('/tender/:id/revise/info/update', sessionAuth, tenderCheck, uncheckTenderCheck, 'reviseController.update');
-    app.post('/tender/:id/revise/info/upload-excel/:ueType', sessionAuth, tenderCheck, uncheckTenderCheck, 'reviseController.uploadExcel');
-    app.post('/tender/:id/revise/info/check', sessionAuth, tenderCheck, uncheckTenderCheck, 'reviseController.checkData');
+    app.get('/tender/:id/revise/:rid/info', sessionAuth, tenderCheck, uncheckTenderCheck, reviseCheck, reviseAuditCheck, 'reviseController.info');
+    app.post('/tender/:id/revise/auditors', sessionAuth, tenderCheck, uncheckTenderCheck, reviseCheck, 'reviseController.reviseAuditors');
+    app.post('/tender/:id/revise/:rid/info/load', sessionAuth, tenderCheck, uncheckTenderCheck, reviseCheck, 'reviseController.loadInfoData');
+    app.post('/tender/:id/revise/:rid/info/update', sessionAuth, tenderCheck, uncheckTenderCheck, reviseCheck, 'reviseController.update');
+    app.post('/tender/:id/revise/:rid/info/upload-excel/:ueType', sessionAuth, tenderCheck, uncheckTenderCheck, reviseCheck, 'reviseController.uploadExcel');
+    app.post('/tender/:id/revise/:rid/info/check', sessionAuth, tenderCheck, uncheckTenderCheck, reviseCheck, 'reviseController.checkData');
 
-    app.get('/tender/:id/revise/compare', sessionAuth, tenderCheck, uncheckTenderCheck, reviseAuditCheck, 'reviseController.compare');
-    app.get('/tender/:id/revise/gcl-compare', sessionAuth, tenderCheck, uncheckTenderCheck, reviseAuditCheck, 'reviseController.gclCompare');
-    app.post('/tender/:id/revise/load', sessionAuth, tenderCheck, uncheckTenderCheck, reviseAuditCheck, 'reviseController.loadData');
+    app.get('/tender/:id/revise/:rid/compare', sessionAuth, tenderCheck, uncheckTenderCheck, reviseCheck, reviseAuditCheck, 'reviseController.compare');
+    app.get('/tender/:id/revise/:rid/gcl-compare', sessionAuth, tenderCheck, uncheckTenderCheck, reviseCheck, reviseAuditCheck, 'reviseController.gclCompare');
+    app.post('/tender/:id/revise/:rid/load', sessionAuth, tenderCheck, uncheckTenderCheck, reviseCheck, reviseAuditCheck, 'reviseController.loadData');
 
     // 查看修订数据
-    app.get('/tender/:id/revise/history', sessionAuth, tenderCheck, uncheckTenderCheck, 'reviseController.history');
-    app.post('/tender/:id/revise/history/load', sessionAuth, tenderCheck, uncheckTenderCheck, 'reviseController.loadHistoryData');
-    app.post('/tender/:id/revise/history/info', sessionAuth, tenderCheck, uncheckTenderCheck, 'reviseController.historyInfo');
+    app.get('/tender/:id/revise/history/:rid/info', sessionAuth, tenderCheck, uncheckTenderCheck, reviseCheck, 'reviseController.history');
+    app.post('/tender/:id/revise/history/:rid/load', sessionAuth, tenderCheck, uncheckTenderCheck, reviseCheck, 'reviseController.loadData');
+    app.get('/tender/:id/revise/history/:rid/gcl-compare', sessionAuth, tenderCheck, uncheckTenderCheck, reviseCheck, 'reviseController.gclCompare');
+    app.get('/tender/:id/revise/history/:rid/compare', sessionAuth, tenderCheck, uncheckTenderCheck, reviseCheck, 'reviseController.compare');
 
     // 修订审批
     app.post('/tender/:id/revise/audit/add', sessionAuth, tenderCheck, uncheckTenderCheck, 'reviseController.addAuditor');
@@ -438,11 +450,33 @@ module.exports = app => {
     // 变更新增部位页
     app.get('/tender/:id/change/:cid/information/revise', sessionAuth, tenderCheck, uncheckTenderCheck, changeCheck, 'changeController.reviseInfo');
     app.post('/tender/:id/change/:cid/information/revise/update', sessionAuth, tenderCheck, uncheckTenderCheck, changeCheck, 'changeController.updateRevise');
+    // 变更立项
+    app.get('/tender/:id/change/project', sessionAuth, tenderCheck, uncheckTenderCheck, 'changeController.project');
+    app.get('/tender/:id/change/project/status/:status', sessionAuth, tenderCheck, uncheckTenderCheck, 'changeController.projectStatus');
+    app.post('/tender/:id/change/project/add', sessionAuth, tenderCheck, uncheckTenderCheck, 'changeController.projectAdd');
+    app.get('/tender/:id/change/project/:cpid/information', sessionAuth, tenderCheck, uncheckTenderCheck, changeProjectCheck, 'changeController.projectInformation');
+    app.post('/tender/:id/change/project/:cpid/information/save', sessionAuth, tenderCheck, uncheckTenderCheck, changeProjectCheck, 'changeController.projectInformationSave');
+    app.post('/tender/:id/change/project/:cpid/information/file/upload', sessionAuth, tenderCheck, uncheckTenderCheck, changeProjectCheck, 'changeController.uploadProjectFile');
+    app.post('/tender/:id/change/project/:cpid/information/file/delete', sessionAuth, tenderCheck, uncheckTenderCheck, changeProjectCheck, 'changeController.deleteProjectFile');
+    app.get('/tender/:id/change/project/:cpid/information/file/:fid/download', sessionAuth, tenderCheck, uncheckTenderCheck, changeProjectCheck, 'changeController.downloadProjectFile');
+    app.post('/tender/:id/change/project/:cpid/information/audit/add', sessionAuth, tenderCheck, uncheckTenderCheck, changeProjectCheck, 'changeController.addProjectAudit');
+    app.post('/tender/:id/change/project/:cpid/information/audit/delete', sessionAuth, tenderCheck, uncheckTenderCheck, changeProjectCheck, 'changeController.deleteProjectAudit');
+    app.post('/tender/:id/change/project/:cpid/information/audit/start', sessionAuth, tenderCheck, uncheckTenderCheck, changeProjectCheck, 'changeController.startProjectAudit');
+    app.post('/tender/:id/change/project/:cpid/information/audit/check', sessionAuth, tenderCheck, uncheckTenderCheck, changeProjectCheck, 'changeController.checkProjectAudit');
+    // // 变更申请
+    // app.get('/tender/:id/change/apply', sessionAuth, tenderCheck, uncheckTenderCheck, 'changeController.apply');
+    // app.get('/tender/:id/change/apply/:cid/information', sessionAuth, tenderCheck, uncheckTenderCheck, 'changeController.applyInformation');
+    // app.post('/tender/:id/change/apply/:cid/information/save', sessionAuth, tenderCheck, uncheckTenderCheck, 'changeController.applyInformationSave');
+    // app.post('/tender/:id/change/apply/:cid/information/file/upload', sessionAuth, 'changeController.uploadFile');
+    // app.post('/tender/:id/change/apply/:cid/information/file/delete', sessionAuth, 'changeController.deleteFile');
+    // app.post('/tender/:id/change/apply/:cid/information/audit/add', sessionAuth, tenderCheck, uncheckTenderCheck, 'changeController.addAudit');
+    // app.post('/tender/:id/change/apply/:cid/information/audit/delete', sessionAuth, tenderCheck, uncheckTenderCheck, 'changeController.deleteAudit');
     // 材料调差
     app.get('/tender/:id/measure/material', sessionAuth, tenderCheck, uncheckTenderCheck, 'materialController.index');
     app.post('/tender/:id/measure/material/add', sessionAuth, tenderCheck, uncheckTenderCheck, 'materialController.add');
     app.post('/tender/:id/measure/material/delete', sessionAuth, tenderCheck, uncheckTenderCheck, 'materialController.delete');
     app.post('/tender/:id/measure/material/auditors', sessionAuth, tenderCheck, uncheckTenderCheck, 'materialController.materialAuditors');
+    app.post('/tender/:id/measure/material/:order/save/decimal', sessionAuth, tenderCheck, uncheckTenderCheck, materialCheck, 'materialController.saveDecimal');
     // 审批
     app.post('/tender/:id/measure/material/:order/audit/add', sessionAuth, tenderCheck, uncheckTenderCheck, materialCheck, 'materialController.addAudit');
     app.post('/tender/:id/measure/material/:order/audit/delete', sessionAuth, tenderCheck, uncheckTenderCheck, materialCheck, 'materialController.deleteAudit');
@@ -460,7 +494,10 @@ module.exports = app => {
     app.get('/tender/:id/measure/material/:order/list', sessionAuth, tenderCheck, uncheckTenderCheck, materialCheck, 'materialController.list');
     app.post('/tender/:id/measure/material/:order/list/save', sessionAuth, tenderCheck, uncheckTenderCheck, materialCheck, 'materialController.saveListsData');
     app.post('/tender/:id/measure/material/:order/list/load', sessionAuth, tenderCheck, uncheckTenderCheck, materialCheck, 'materialController.loadListsData');
-
+    // 调差清单设置页
+    app.get('/tender/:id/measure/material/:order/checklist', sessionAuth, tenderCheck, uncheckTenderCheck, materialCheck, 'materialController.checklist');
+    app.post('/tender/:id/measure/material/:order/checklist/load', sessionAuth, tenderCheck, uncheckTenderCheck, materialCheck, 'materialController.loadListsData');
+    app.post('/tender/:id/measure/material/:order/checklist/save', sessionAuth, tenderCheck, uncheckTenderCheck, materialCheck, 'materialController.saveChecklistData');
     // 附件
     app.get('/tender/:id/measure/material/:order/file', sessionAuth, tenderCheck, uncheckTenderCheck, materialCheck, 'materialController.file');
     app.post('/tender/:id/measure/material/:order/file/upload', sessionAuth, tenderCheck, uncheckTenderCheck, materialCheck, 'materialController.upload');

+ 2 - 2
app/service/change.js

@@ -1141,7 +1141,7 @@ module.exports = app => {
             const sql =
                 'SELECT c.cid, c.code, c.name, c.w_code, c.p_code, c.peg, c.org_name, c.org_code, c.new_name, c.new_code,' +
                 '    c.content, c.basis, c.memo, c.type, c.class, c.quality, c.company, c.charge, ' +
-                '    cb.id As cbid, cb.code As b_code, cb.name As b_name, cb.unit As b_unit, cb.samount As b_amount, cb.detail As b_detail, cb.bwmx As b_bwmx, ' +
+                '    cb.id As cbid, cb.code As b_code, cb.name As b_name, cb.unit As b_unit, cb.samount As b_amount, cb.detail As b_detail, cb.bwmx As b_bwmx, cb.gcl_id, ' +
                 '    scb.used_amount' +
                 '  FROM ' + this.tableName + ' As c ' +
                 '  Left Join ' + this.ctx.service.changeAuditList.tableName + ' As cb On c.cid = cb.cid ' +
@@ -1189,7 +1189,7 @@ module.exports = app => {
             const sql =
                 'SELECT c.cid, c.code, c.name, c.w_code, c.p_code, c.peg, c.org_name, c.org_code, c.new_name, c.new_code,' +
                 '    c.content, c.basis, c.memo, c.type, c.class, c.quality, c.company, c.charge, ' +
-                '    cb.id As cbid, cb.code As b_code, cb.name As b_name, cb.unit As b_unit, cb.samount As b_amount, cb.detail As b_detail, cb.bwmx As b_bwmx, ' +
+                '    cb.id As cbid, cb.code As b_code, cb.name As b_name, cb.unit As b_unit, cb.samount As b_amount, cb.detail As b_detail, cb.bwmx As b_bwmx, cb.gcl_id, ' +
                 '    scb.used_amount' +
                 '  FROM ' + this.tableName + ' As c ' +
                 '  Left Join ' + this.ctx.service.changeAuditList.tableName + ' As cb On c.cid = cb.cid ' +

+ 9 - 5
app/service/change_audit_list.js

@@ -814,7 +814,7 @@ module.exports = app => {
                     await this._findParents(transaction, tid, r, result);
                 }
                 // 插入到计量单元表,并删除变更的计量单元数据, 插入清单表,并删除变更的清单表
-                await this._insertByChangeRevise(transaction, tid, result, result2);
+                await this._insertByChangeRevise(transaction, tid, cid, result, result2);
                 // 更新标段总金额
                 const sumSql = 'SELECT Sum(total_price) As total_price, Sum(deal_tp) As deal_tp' +
                     '  FROM ' + this.ctx.service.ledger.tableName + this.ctx.helper.whereSql({ tender_id: tid });
@@ -829,7 +829,7 @@ module.exports = app => {
 
         async _findParents(transaction, tid, id, result) {
             const info = await transaction.get(this.ctx.service.changeLedger.tableName, { tender_id: tid, ledger_id: id });
-            if (info && this._.findIndex(result, { ledger_id: info.id }) === -1) {
+            if (info && this._.findIndex(result, { ledger_id: info.ledger_id }) === -1) {
                 result.push(info);
                 await this._findParents(transaction, tid, info.ledger_pid, result);
             } else {
@@ -837,7 +837,7 @@ module.exports = app => {
             }
         }
 
-        async _insertByChangeRevise(transaction, tid, ledgerList, posList) {
+        async _insertByChangeRevise(transaction, tid, cid, ledgerList, posList) {
             if (ledgerList.length > 0) {
                 const insertLedgerArr = [];
                 for (const l of ledgerList) {
@@ -851,6 +851,8 @@ module.exports = app => {
                     ];
                     insertLedgerArr.push('(' + this.ctx.helper.getInArrStrSqlFilter(insertL) + ')');
                     await transaction.delete(this.ctx.service.changeLedger.tableName, { id: l.id });
+                    // 日志添加
+                    await transaction.insert(this.ctx.service.changeReviseLog.tableName, { tid, cid, lid: l.id, name: l.name, create_time: new Date() });
                 }
                 const bSql = 'Insert Into ' +
                     this.ctx.service.ledger.tableName +
@@ -866,7 +868,7 @@ module.exports = app => {
                 const insertPosArr = [];
                 for (const p of posList) {
                     const insertp = [
-                        p.id, p.tid, p.lid, p.name, p.drawing_code, p.quantity, p.add_stage, p.add_times,
+                        p.id, p.tid, p.lid, p.name, p.drawing_code, p.quantity, p.add_stage, p.add_stage_order, p.add_times,
                         p.add_user, p.sgfh_qty, p.sjcl_qty, p.qtcl_qty, p.crid, p.porder, p.position,
                         p.sgfh_expr, p.sjcl_expr, p.qtcl_expr, p.real_qty,
                         p.gxby_status, p.dagl_status, p.dagl_url, p.gxby_limit, p.dagl_limit,
@@ -874,11 +876,13 @@ module.exports = app => {
                     ];
                     insertPosArr.push('(' + this.ctx.helper.getInArrStrSqlFilter(insertp) + ')');
                     await transaction.delete(this.ctx.service.changePos.tableName, { id: p.id });
+                    // 日志添加
+                    await transaction.insert(this.ctx.service.changeReviseLog.tableName, { tid, cid, pid: p.id, name: p.name, create_time: new Date() });
                 }
                 const pSql =
                     'Insert Into ' +
                     this.ctx.service.pos.tableName +
-                    '  (id, tid, lid, name, drawing_code, quantity, add_stage, add_times, add_user,' +
+                    '  (id, tid, lid, name, drawing_code, quantity, add_stage, add_stage_order, add_times, add_user,' +
                     '     sgfh_qty, sjcl_qty, qtcl_qty, crid, porder, position, ' +
                     '     sgfh_expr, sjcl_expr, qtcl_expr, real_qty,' +
                     '     gxby_status, dagl_status, dagl_url, gxby_limit,  dagl_limit,' +

+ 1 - 0
app/service/change_ledger.js

@@ -1220,6 +1220,7 @@ module.exports = app => {
                                 sjcl_expr: pos.sjcl_expr ? pos.sjcl_expr : '',
                                 qtcl_expr: pos.qtcl_expr ? pos.qtcl_expr : '',
                                 add_stage: 0,
+                                add_stage_order: 0,
                                 add_times: 0,
                                 ex_memo1: pos.ex_memo1,
                                 ex_memo2: pos.ex_memo2,

+ 2 - 0
app/service/change_pos.js

@@ -40,6 +40,7 @@ module.exports = app => {
             data.tid = tid;
             // todo 新增期
             data.add_stage = 0;
+            data.add_stage_order = 0;
             data.add_times = 0;
             data.in_time = new Date();
             data.add_user = this.ctx.session.sessionUser.accountId;
@@ -382,6 +383,7 @@ module.exports = app => {
             data.tid = tid;
             // todo 新增期
             data.add_stage = 0;
+            data.add_stage_order = 0;
             data.add_times = 0;
             data.in_time = new Date();
             data.add_user = this.ctx.session.sessionUser.accountId;

+ 363 - 0
app/service/change_project.js

@@ -0,0 +1,363 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2018/8/14
+ * @version
+ */
+
+const audit = require('../const/audit').changeProject;
+// const smsTypeConst = require('../const/sms_type');
+// const SMS = require('../lib/sms');
+// const SmsAliConst = require('../const/sms_alitemplate');
+const wxConst = require('../const/wechat_template');
+const pushType = require('../const/audit').pushType;
+const projectLogConst = require('../const/project_log');
+const codeRuleConst = require('../const/code_rule');
+
+module.exports = app => {
+    class ChangeProject extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'change_project';
+        }
+
+        async add(tenderId, userId, code, name) {
+            const type = userId === this.ctx.tender.data.user_id ? codeRuleConst.ruleType.suggestion : codeRuleConst.ruleType.will;
+            const sql = 'SELECT COUNT(*) as count FROM ?? WHERE `tid` = ? AND `code` = ? AND type = ?';
+            const sqlParam = [this.tableName, tenderId, code, audit.status.checked, code, audit.status.checked, type];
+            const codeCount = await this.db.queryOne(sql, sqlParam);
+            const count = codeCount.count;
+            if (count > 0) {
+                throw '立项书编号重复';
+            }
+
+            // 初始化事务
+            this.transaction = await this.db.beginTransaction();
+            let result = false;
+            try {
+                const change = {
+                    tid: tenderId,
+                    uid: userId,
+                    status: audit.status.uncheck,
+                    times: 1,
+                    type,
+                    in_time: new Date(),
+                    code,
+                    name,
+                };
+                const operate = await this.transaction.insert(this.tableName, change);
+
+                if (operate.affectedRows <= 0) {
+                    throw '新建变更令数据失败';
+                }
+                change.id = operate.insertId;
+                // 先找出标段最近存在的变更令审批人的变更令info
+                const preChangeInfo = await this.getHaveAuditLastInfo(tenderId, type);
+                if (preChangeInfo) {
+                    // 并把之前存在的变更令审批人添加到zh_change_audit
+                    const auditResult = await this.ctx.service.changeProjectAudit.copyPreChangeProjectAuditors(this.transaction, preChangeInfo, change);
+                    if (!auditResult) {
+                        throw '复制上一次审批流程失败';
+                    }
+                }
+                result = change;
+                this.transaction.commit();
+            } catch (error) {
+                console.log(error);
+                // 回滚
+                await this.transaction.rollback();
+            }
+
+            return result;
+        }
+
+        async getHaveAuditLastInfo(tenderId, type) {
+            const sql = 'SELECT a.* FROM ?? as a LEFT JOIN ?? as b ON a.`id` = b.`cpid` WHERE a.`tid` = ? AND a.type = ? ORDER BY a.`in_time` DESC';
+            const sqlParam = [this.tableName, this.ctx.service.changeProjectAudit.tableName, tenderId, type];
+            return await this.db.queryOne(sql, sqlParam);
+        }
+
+        /**
+         * 获取变更立项列表
+         * @param {int} tenderId - 标段id
+         * @param {int} status - 状态
+         * @param {int} hadlimit - 分页
+         * @return {object} list - 列表
+         */
+        async getListByStatus(tenderId, status = 0, hadlimit = 1, sortBy = '', orderBy = '') {
+            let sql = '';
+            let sqlParam = '';
+            if (this.ctx.tender.isTourist && status === 0) {
+                sql = 'SELECT a.*, p.name as account_name FROM ?? As a LEFT JOIN ?? AS p On a.uid = p.id WHERE a.tid = ?';
+                sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, tenderId];
+            } else {
+                switch (status) {
+                    case 0: // 包含你的所有变更立项
+                        sql =
+                            'SELECT a.*, p.name as account_name FROM ?? AS a LEFT JOIN ?? AS p On a.uid = p.id WHERE a.tid = ? AND ' +
+                            '(a.uid = ? OR (a.status != ? AND a.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.cpid)) OR a.status = ? )';
+                        sqlParam = [
+                            this.tableName,
+                            this.ctx.service.projectAccount.tableName,
+                            tenderId,
+                            this.ctx.session.sessionUser.accountId,
+                            audit.status.uncheck,
+                            this.ctx.service.changeProjectAudit.tableName,
+                            this.ctx.session.sessionUser.accountId,
+                            audit.status.checked,
+                        ];
+                        break;
+                    case 1: // 待处理(你的)
+                        sql = 'SELECT a.*, p.name as account_name FROM ?? as a LEFT JOIN ?? AS p On a.uid = p.id WHERE a.id in(SELECT b.cpid FROM ?? as b WHERE b.tid = ? AND b.aid = ? AND b.status = ?) OR (a.uid = ? AND (a.status = ? OR a.status = ?))';
+                        sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, this.ctx.service.changeProjectAudit.tableName, tenderId, this.ctx.session.sessionUser.accountId, audit.status.checking, this.ctx.session.sessionUser.accountId, audit.status.uncheck, audit.status.back];
+                        break;
+                    case 5: // 待上报(所有的)PS:取未上报,退回,修订的变更令
+                        sql =
+                            'SELECT a.*, p.name as account_name FROM ?? AS a LEFT JOIN ?? AS p On a.uid = p.id WHERE ' +
+                            // 'a.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.cpid) AND ' +
+                            'a.uid = ? AND a.tid = ? AND (a.status = ? OR a.status = ?)';
+                        sqlParam = [
+                            this.tableName,
+                            this.ctx.service.projectAccount.tableName,
+                            // this.ctx.service.changeProjectAudit.tableName,
+                            this.ctx.session.sessionUser.accountId,
+                            tenderId,
+                            audit.status.uncheck,
+                            audit.status.back,
+                        ];
+                        break;
+                    case 2: // 进行中(所有的)
+                    case 4: // 终止(所有的)
+                        sql =
+                            'SELECT a.*, p.name as account_name FROM ?? AS a LEFT JOIN ?? AS p On a.uid = p.id WHERE ' +
+                            'a.status = ? AND a.tid = ? AND (a.uid = ? OR a.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.cpid))';
+                        sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, status, tenderId, this.ctx.session.sessionUser.accountId, this.ctx.service.changeProjectAudit.tableName, this.ctx.session.sessionUser.accountId];
+                        break;
+                    case 3: // 已完成(所有的)
+                        sql = 'SELECT a.*, p.name as account_name FROM ?? AS a LEFT JOIN ?? AS p On a.uid = p.id WHERE a.status = ? AND a.tid = ?';
+                        sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, status, tenderId];
+                        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.in_time ' + orderBy;
+                }
+            } else {
+                sql += ' ORDER BY a.in_time DESC';
+            }
+            if (hadlimit) {
+                const limit = 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} tenderId - 标段id
+         * @param {int} status - 状态
+         * @return {void}
+         */
+        async getCountByStatus(tenderId, status) {
+            if (this.ctx.tender.isTourist && status === 0) {
+                const sql5 = 'SELECT count(*) AS count FROM ?? WHERE tid = ? ORDER BY in_time DESC';
+                const sqlParam5 = [this.tableName, tenderId];
+                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.cpid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.cpid)) OR a.status = ? )';
+                    const sqlParam = [
+                        this.tableName,
+                        tenderId,
+                        this.ctx.session.sessionUser.accountId,
+                        audit.status.uncheck,
+                        this.ctx.service.changeProjectAudit.tableName,
+                        this.ctx.session.sessionUser.accountId,
+                        audit.status.checked,
+                    ];
+                    const result = await this.db.query(sql, sqlParam);
+                    return result[0].count;
+                case 1: // 待处理(你的)
+                    // return await this.ctx.service.changeAudit.count({
+                    //     tid: tenderId,
+                    //     uid: this.ctx.session.sessionUser.accountId,
+                    //     status: 2,
+                    // });
+                    const sql6 = 'SELECT count(*) AS count FROM ?? as a WHERE a.id in(SELECT b.cpid FROM ?? as b WHERE b.tid = ? AND b.aid = ? AND b.status = ?) OR (a.uid = ? AND (a.status = ? OR a.status = ?))';
+                    const sqlParam6 = [this.tableName, this.ctx.service.changeProjectAudit.tableName, tenderId, this.ctx.session.sessionUser.accountId, audit.status.checking, this.ctx.session.sessionUser.accountId, audit.status.uncheck, audit.status.back];
+                    const result6 = await this.db.query(sql6, sqlParam6);
+                    return result6[0].count;
+                case 5: // 待上报(所有的)PS:取未上报,退回的变更立项
+                    const sql2 =
+                        'SELECT count(*) AS count FROM ?? AS a WHERE ' +
+                        // 'a.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.cpid) ' +
+                        'a.uid = ? AND a.tid = ? AND (a.status = ? OR a.status = ?)';
+                    const sqlParam2 = [
+                        this.tableName,
+                        // this.ctx.service.changeProjectAudit.tableName,
+                        this.ctx.session.sessionUser.accountId,
+                        tenderId,
+                        audit.status.uncheck,
+                        audit.status.back,
+                    ];
+                    const result2 = await this.db.query(sql2, sqlParam2);
+                    return result2[0].count;
+                case 2: // 进行中(所有的)
+                case 4: // 终止(所有的)
+                    const sql3 =
+                        'SELECT count(*) AS count FROM ?? AS a WHERE ' +
+                        'a.status = ? AND a.tid = ? AND (a.uid = ? OR a.id IN (SELECT b.cpid FROM ?? AS b WHERE b.aid = ? AND a.times = b.times GROUP BY b.cpid))';
+                    const sqlParam3 = [this.tableName, status, tenderId, this.ctx.session.sessionUser.accountId, this.ctx.service.changeProjectAudit.tableName, this.ctx.session.sessionUser.accountId];
+                    const result3 = await this.db.query(sql3, sqlParam3);
+                    return result3[0].count;
+                case 3: // 已完成(所有的)
+                    const sql4 = 'SELECT count(*) AS count FROM ?? WHERE status = ? AND tid = ?';
+                    const sqlParam4 = [this.tableName, status, tenderId];
+                    const result4 = await this.db.query(sql4, sqlParam4);
+                    return result4[0].count;
+                default:
+                    break;
+            }
+        }
+
+        /**
+         * 保存变更信息
+         * @param {int} postData - 表单提交的数据
+         * @param {int} tenderId - 标段id
+         * @return {void}
+         */
+        async saveInfo(cpId, postData) {
+            // 初始化事务
+            const transaction = await this.db.beginTransaction();
+            let result = false;
+            try {
+                const updateData = {
+                    id: cpId,
+                };
+                updateData[postData.name] = postData.val;
+                await transaction.update(this.tableName, updateData);
+                await transaction.commit();
+                result = true;
+            } catch (error) {
+                await transaction.rollback();
+                result = false;
+            }
+            return result;
+        }
+
+
+        /**
+         * 审批终止
+         * @param {int} postData - 表单提交的数据
+         * @return {void}
+         */
+        async approvalStop(postData) {
+            // 初始化事务
+            this.transaction = await this.db.beginTransaction();
+            let result = false;
+            try {
+                // 设置审批人终止
+                const audit_update = {
+                    id: postData.audit_id,
+                    sdesc: postData.sdesc,
+                    status: 4,
+                    sin_time: new Date(),
+                };
+                await this.transaction.update(this.ctx.service.changeAudit.tableName, audit_update);
+                // 设置变更令终止
+                const change_update = {
+                    w_code: postData.w_code,
+                    status: 4,
+                    cin_time: Date.parse(new Date()) / 1000,
+                };
+                const options = {
+                    where: {
+                        cid: postData.change_id,
+                    },
+                };
+                await this.transaction.update(this.tableName, change_update, options);
+                await this.transaction.commit();
+                result = true;
+            } catch (error) {
+                await this.transaction.rollback();
+                result = false;
+            }
+            return result;
+        }
+
+
+        /**
+         * 查询可用的变更令
+         * @param { string } cid - 查询的清单
+         * @return {Promise<*>} - 可用的变更令列表
+         */
+        async delete(cid) {
+            // 初始化事务
+            this.transaction = await this.db.beginTransaction();
+            let result = false;
+            try {
+                const changeInfo = await this.getDataByCondition({ cid });
+                // 先删除清单,审批人列表
+                await this.transaction.delete(this.ctx.service.changeAuditList.tableName, { cid });
+                await this.transaction.delete(this.ctx.service.changeAudit.tableName, { cid });
+                // 再删除附件和附件文件ni zuo
+                const attList = await this.ctx.service.changeAtt.getAllDataByCondition({ where: { cid } });
+                await this.ctx.helper.delFiles(attList);
+                await this.transaction.delete(this.ctx.service.changeAtt.tableName, { cid });
+                // if (attList.length !== 0) {
+                //     for (const att of attList) {
+                //         await fs.unlinkSync(path.join(this.app.baseDir, att.filepath));
+                //     }
+                //     await this.transaction.delete(this.ctx.service.changeAtt.tableName, { cid });
+                // }
+                // 最后删除变更令
+                await this.transaction.delete(this.tableName, { cid });
+                // 记录删除日志
+                await this.ctx.service.projectLog.addProjectLog(this.transaction, projectLogConst.type.change, projectLogConst.status.delete, changeInfo.code);
+                await this.transaction.commit();
+                result = true;
+            } catch (e) {
+                await this.transaction.rollback();
+                result = false;
+            }
+            return result;
+        }
+
+        /**
+         * 判断是否有重名的变更立项
+         * @param cpid
+         * @param code
+         * @param tid
+         * @return {Promise<void>}
+         */
+        async isRepeat(cpId, code, tid, type) {
+            const sql = 'SELECT COUNT(*) as count FROM ?? WHERE `code` = ? AND `id` != ? AND `tid` = ? AND `type` = ?';
+            const sqlParam = [this.tableName, code, cpId, tid, type];
+            const result = await this.db.queryOne(sql, sqlParam);
+            return result.count !== 0;
+        }
+    }
+
+    return ChangeProject;
+};

+ 137 - 0
app/service/change_project_att.js

@@ -0,0 +1,137 @@
+'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 ChangeProjectFile extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'change_project_attachment';
+        }
+
+        /**
+         * 获取当前标段(期)所有上传的附件
+         * @param {Number} tid 标段id
+         * @param {Number?} mid 期id
+         * @return {Promise<void>} 数据库查询实例
+         */
+        async getAllChangeProjectAtt(tid, cpid) {
+            const { ctx } = this;
+            // const where = { tid };
+            // if (cpid) where.cpid = cpid;
+            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.cpid = ? ORDER BY upload_time DESC';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, tid, cpid];
+            const result = await this.db.query(sql, sqlParam);
+            // const result = await this.db.select(this.tableName, {
+            //     where,
+            //     orders: [['upload_time', 'desc']],
+            // });
+            // for (const r of result) {
+            //     const userInfo = await this.ctx.service.projectAccount.getDataById(r.uid);
+            //     r.username = userInfo ? userInfo.name : '';
+            // }
+            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/project/${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);
+        }
+
+        /**
+         * 将文件压缩成zip,并返回zip文件的路径
+         * @param {array} fileIds - 文件数组id
+         * @param {string} zipPath - 压缩文件存储路径
+         * @return {string} 压缩后的zip文件路径
+         */
+        async compressedFile(fileIds, zipPath) {
+            this.initSqlBuilder();
+            this.sqlBuilder.setAndWhere('id', {
+                value: fileIds,
+                operate: 'in',
+            });
+            const [sql, sqlParam] = this.sqlBuilder.build(this.tableName);
+            const files = await this.db.query(sql, sqlParam);
+            // const paths = files.map(item => {
+            //     return { name: item.filename + item.fileext, path: item.filepath }
+            // })
+            return new Promise((resolve, reject) => {
+                // 每次开一个新的archiver
+                const ziparchiver = archiver('zip');
+                const outputPath = fs.createWriteStream(path.resolve(this.app.baseDir, zipPath));
+                outputPath.on('error', err => {
+                    return reject(err);
+                });
+
+                ziparchiver.pipe(outputPath);
+                files.forEach(item => {
+                    ziparchiver.file(path.resolve(this.app.baseDir, 'app', item.filepath), { name: item.file_name });
+                });
+
+                // 存档警告
+                ziparchiver.on('warning', function(err) {
+                    // if (err.code === 'ENOENT') {
+                    //     console.warn('stat故障和其他非阻塞错误');
+                    // }
+                    return reject(err);
+                });
+
+                // 存档出错
+                ziparchiver.on('error', function(err) {
+                    // console.log(err);
+                    return reject(err);
+                });
+                ziparchiver.finalize();
+                outputPath.on('close', () => {
+                    return resolve(ziparchiver.pointer());
+                });
+            });
+        }
+    }
+    return ChangeProjectFile;
+};
+

+ 571 - 0
app/service/change_project_audit.js

@@ -0,0 +1,571 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2018/8/14
+ * @version
+ */
+
+const auditConst = require('../const/audit').changeProject;
+const pushType = require('../const/audit').pushType;
+const shenpiConst = require('../const/shenpi');
+const smsTypeConst = require('../const/sms_type');
+const SMS = require('../lib/sms');
+const SmsAliConst = require('../const/sms_alitemplate');
+const wxConst = require('../const/wechat_template');
+
+module.exports = app => {
+    class ChangeProjectAudit extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'change_project_audit';
+        }
+
+        /**
+         * 获取 审核列表信息
+         *
+         * @param {Number} cpId - 变更立项id
+         * @param {Number} times - 第几次审批
+         * @return {Promise<*>}
+         */
+        async getAuditors(cpId, times = 1) {
+            const sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time`, g.`sort` ' +
+                'FROM ?? AS la, ?? AS pa, (SELECT `aid`,(@i:=@i+1) as `sort` FROM ??, (select @i:=0) as it WHERE `cpid` = ? AND `times` = ? GROUP BY `aid`) as g ' +
+                'WHERE la.`cpid` = ? and la.`times` = ? and la.`aid` = pa.`id` and g.`aid` = la.`aid` order by la.`order`';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, this.tableName, cpId, times, cpId, times];
+            const result = await this.db.query(sql, sqlParam);
+            const sql2 = 'SELECT COUNT(a.`aid`) as num FROM (SELECT `aid` FROM ?? WHERE `cpid` = ? AND `times` = ? GROUP BY `aid`) as a';
+            const sqlParam2 = [this.tableName, cpId, times];
+            const count = await this.db.queryOne(sql2, sqlParam2);
+            for (const i in result) {
+                result[i].max_sort = count.num;
+            }
+            return result;
+        }
+
+        /**
+         * 获取 当前审核人
+         *
+         * @param {Number} cpId - 变更立项id
+         * @param {Number} times - 第几次审批
+         * @return {Promise<*>}
+         */
+        async getCurAuditor(cpId, times = 1) {
+            const sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, la.`times`, la.`order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time` ' +
+                '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id` ' +
+                '  WHERE la.`cpid` = ? and la.`status` = ? and la.`times` = ?';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, cpId, auditConst.status.checking, times];
+            return await this.db.queryOne(sql, sqlParam);
+        }
+
+        /**
+         * 获取审核人流程列表
+         *
+         * @param auditorId
+         * @return {Promise<*>}
+         */
+        async getAuditGroupByList(changeId, times) {
+            const sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`cpid`, la.`aid`, la.`order` ' +
+                '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id`' +
+                '  WHERE la.`cpid` = ? and la.`times` = ? GROUP BY la.`aid` ORDER BY la.`order`';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, changeId, times];
+            return await this.db.query(sql, sqlParam);
+        }
+
+        /**
+         * 移除审核人
+         *
+         * @param {Number} materialId - 材料调差期id
+         * @param {Number} status - 期状态
+         * @param {Number} status - 期次数
+         * @return {Promise<boolean>}
+         */
+        async getAuditorByStatus(cpId, status, times = 1) {
+            let auditor = null;
+            let sql = '';
+            let sqlParam = '';
+            switch (status) {
+                case auditConst.status.checking :
+                case auditConst.status.checked :
+                    sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`cpid`, la.`aid`, la.`order` ' +
+                        '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id` ' +
+                        '  WHERE la.`cpid` = ? and la.`status` = ? ' +
+                        '  ORDER BY la.`times` desc, la.`order` desc';
+                    sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, cpId, status];
+                    auditor = await this.db.queryOne(sql, sqlParam);
+                    break;
+                case auditConst.status.checkNo :
+                    sql = 'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`cpid`, la.`aid`, la.`order` ' +
+                        '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id`' +
+                        '  WHERE la.`cpid` = ? and la.`status` = ? and la.`times` = ?' +
+                        '  ORDER BY la.`times` desc, la.`order` desc';
+                    sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, cpId, auditConst.status.checkNo, parseInt(times) - 1];
+                    auditor = await this.db.queryOne(sql, sqlParam);
+                    break;
+                case auditConst.status.uncheck :
+                    break;
+                case auditConst.status.back :
+                default:break;
+            }
+            return auditor;
+        }
+
+        /**
+         * 获取审核人流程列表(包括原报)
+         * @param {Number} materialId 调差id
+         * @param {Number} times 审核次数
+         * @return {Promise<Array>} 查询结果集(包括原报)
+         */
+        async getAuditorsWithOwner(cpId, times = 1) {
+            const result = await this.getAuditGroupByList(cpId, times);
+            const sql =
+                'SELECT pa.`id` As aid, pa.`name`, pa.`company`, pa.`role`, ? As times, ? As cpid, 0 As `order`' +
+                '  FROM ' +
+                this.ctx.service.changeProject.tableName +
+                ' As s' +
+                '  LEFT JOIN ' +
+                this.ctx.service.projectAccount.tableName +
+                ' As pa' +
+                '  ON s.uid = pa.id' +
+                '  WHERE s.id = ?';
+            const sqlParam = [times, cpId, cpId];
+            const user = await this.db.queryOne(sql, sqlParam);
+            result.unshift(user);
+            return result;
+        }
+
+        /**
+         * 新增审核人
+         *
+         * @param {Number} cpId - 立项书id
+         * @param {Number} auditorId - 审核人id
+         * @param {Number} times - 第几次审批
+         * @return {Promise<number>}
+         */
+        async addAuditor(cpId, auditorId, times = 1, is_gdzs = 0) {
+            const transaction = await this.db.beginTransaction();
+            let flag = false;
+            try {
+                let newOrder = await this.getNewOrder(cpId, times);
+                // 判断是否存在固定终审,存在则newOrder - 1并使终审order+1
+                newOrder = is_gdzs === 1 ? newOrder - 1 : newOrder;
+                if (is_gdzs) await this._syncOrderByDelete(transaction, cpId, newOrder, times, '+');
+                const data = {
+                    tid: this.ctx.tender.id,
+                    cpid: cpId,
+                    aid: auditorId,
+                    times,
+                    order: newOrder,
+                    status: auditConst.status.uncheck,
+                };
+                const result = await transaction.insert(this.tableName, data);
+                await transaction.commit();
+                flag = result.effectRows = 1;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+            return flag;
+        }
+
+        /**
+         * 获取 最新审核顺序
+         *
+         * @param {Number} cpId - 立项书id
+         * @param {Number} times - 第几次审批
+         * @return {Promise<number>}
+         */
+        async getNewOrder(cpId, times = 1) {
+            const sql = 'SELECT Max(??) As max_order FROM ?? Where `cpid` = ? and `times` = ?';
+            const sqlParam = ['order', this.tableName, cpId, times];
+            const result = await this.db.queryOne(sql, sqlParam);
+            return result && result.max_order ? result.max_order + 1 : 1;
+        }
+
+        /**
+         * 移除审核人
+         *
+         * @param {Number} cpId - 变更立项书id
+         * @param {Number} auditorId - 审核人id
+         * @param {Number} times - 第几次审批
+         * @return {Promise<boolean>}
+         */
+        async deleteAuditor(cpId, auditorId, times = 1) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                const condition = { cpid: cpId, aid: auditorId, times };
+                const auditor = await this.getDataByCondition(condition);
+                if (!auditor) {
+                    throw '该审核人不存在';
+                }
+                await this._syncOrderByDelete(transaction, cpId, auditor.order, times);
+                await transaction.delete(this.tableName, condition);
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+            return true;
+        }
+
+        /**
+         * 移除审核人时,同步其后审核人order
+         * @param transaction - 事务
+         * @param {Number} cpId - 变更立项书id
+         * @param {Number} auditorId - 审核人id
+         * @param {Number} times - 第几次审批
+         * @return {Promise<*>}
+         * @private
+         */
+        async _syncOrderByDelete(transaction, cpId, order, times, selfOperate = '-') {
+            this.initSqlBuilder();
+            this.sqlBuilder.setAndWhere('cpid', {
+                value: cpId,
+                operate: '=',
+            });
+            this.sqlBuilder.setAndWhere('order', {
+                value: order,
+                operate: '>=',
+            });
+            this.sqlBuilder.setAndWhere('times', {
+                value: times,
+                operate: '=',
+            });
+            this.sqlBuilder.setUpdateData('order', {
+                value: 1,
+                selfOperate,
+            });
+            const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'update');
+            const data = await transaction.query(sql, sqlParam);
+
+            return data;
+        }
+
+        /**
+         * 开始审批
+         * @param {Number} cpId - 立项书id
+         * @param {Number} times - 第几次审批
+         * @return {Promise<boolean>}
+         */
+        async start(cpId, times = 1) {
+            const audit = await this.getDataByCondition({ cpid: cpId, times, order: 1 });
+            if (!audit) {
+                // if (this.ctx.tender.info.shenpi.material === shenpiConst.sp_status.gdspl) {
+                //     throw '请联系管理员添加审批人';
+                // } else {
+                throw '请先选择审批人,再上报数据';
+                // }
+            }
+
+            const transaction = await this.db.beginTransaction();
+            try {
+                await transaction.update(this.tableName, { id: audit.id, status: auditConst.status.checking, begin_time: new Date() });
+                await transaction.update(this.ctx.service.changeProject.tableName, {
+                    id: cpId, status: auditConst.status.checking,
+                });
+                // 微信模板通知
+                // const materialInfo = await this.ctx.service.material.getDataById(materialId);
+                // const wechatData = {
+                //     qi: materialInfo.order,
+                //     status: wxConst.status.check,
+                //     tips: wxConst.tips.check,
+                //     begin_time: Date.parse(new Date()),
+                //     m_tp: this.ctx.helper.add(this.ctx.helper.round(materialInfo.m_tp, 2), this.ctx.helper.round(materialInfo.ex_tp, 2)),
+                //     hs_m_tp: this.ctx.helper.add(this.ctx.helper.round(this.ctx.helper.mul(materialInfo.m_tp, 1+materialInfo.rate/100), 2), this.ctx.helper.round(this.ctx.helper.mul(materialInfo.ex_tp, 1+materialInfo.rate/100), 2)),
+                // };
+                // await this.ctx.helper.sendWechat(audit.aid, smsTypeConst.const.TC, smsTypeConst.judge.approval.toString(), wxConst.template.material, wechatData);
+
+                // todo 更新标段tender状态 ?
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+            return true;
+        }
+
+        /**
+         * 获取审核人需要审核的期列表
+         *
+         * @param auditorId
+         * @return {Promise<*>}
+         */
+        async getAuditChangeProject(auditorId) {
+            const sql = 'SELECT ma.`aid`, ma.`times`, ma.`order`, ma.`begin_time`, ma.`end_time`, ma.`tid`, ma.`cpid`,' +
+                '    m.`status` As `mstatus`, m.`code` As `mcode`,' +
+                '    t.`name`, t.`project_id`, t.`type`, t.`user_id` ' +
+                '  FROM ?? AS ma, ?? AS m, ?? As t ' +
+                '  WHERE ((ma.`aid` = ? and ma.`status` = ?) OR (m.`uid` = ? and ma.`status` = ? and m.`status` = ? and ma.`times` = (m.`times`-1)))' +
+                '    and ma.`cpid` = m.`id` and ma.`tid` = t.`id` ORDER BY ma.`begin_time` DESC';
+            const sqlParam = [this.tableName, this.ctx.service.changeProject.tableName, this.ctx.service.tender.tableName, auditorId, auditConst.status.checking, auditorId, auditConst.status.back, auditConst.status.back];
+            return await this.db.query(sql, sqlParam);
+        }
+
+        /**
+         * 用于添加推送所需的content内容
+         * @param {Number} pid 项目id
+         * @param {Number} tid 台账id
+         * @param {Number} cpId 立项书id
+         * @param {Number} uid 审批人id
+         */
+        async getNoticeContent(pid, tid, cpId, uid) {
+            const noticeSql = 'SELECT * FROM (SELECT ' +
+                '  t.`id` As `tid`, ma.`cpid`, t.`name`, pa.`name` As `su_name`, pa.role As `su_role`' +
+                '  FROM (SELECT * FROM ?? WHERE `id` = ? ) As t' +
+                '  LEFT JOIN ?? As m On t.`id` = m.`tid` AND m.`id` = ?' +
+                '  LEFT JOIN ?? As ma ON m.`id` = ma.`cpid`' +
+                '  LEFT JOIN ?? As pa ON pa.`id` = ?' +
+                '  WHERE  t.`project_id` = ? ) as new_t GROUP BY new_t.`tid`';
+            const noticeSqlParam = [this.ctx.service.tender.tableName, tid, this.ctx.service.changeProject.tableName, cpId, this.tableName, this.ctx.service.projectAccount.tableName, uid, pid];
+            const content = await this.db.query(noticeSql, noticeSqlParam);
+            return content.length ? JSON.stringify(content[0]) : '';
+        }
+
+        /**
+         * 审批
+         * @param {Number} cpId - 立项书id
+         * @param {auditConst.status.checked|auditConst.status.checkNo} checkType - 审批结果
+         * @param {Number} times - 第几次审批
+         * @return {Promise<void>}
+         */
+        async check(cpId, checkData, times = 1) {
+            if (checkData.checkType !== auditConst.status.checked && checkData.checkType !== auditConst.status.checkNo && checkData.checkType !== auditConst.status.back) {
+                throw '提交数据错误';
+            }
+            const pid = this.ctx.session.sessionProject.id;
+            switch (checkData.checkType) {
+                case auditConst.status.checked:
+                    await this._checked(pid, cpId, checkData, times);
+                    break;
+                case auditConst.status.back:
+                    await this._back(pid, cpId, checkData, times);
+                    break;
+                case auditConst.status.checkNo:
+                    await this._checkNo(pid, cpId, checkData, times);
+                    break;
+                default:
+                    throw '无效审批操作';
+            }
+        }
+
+        async _checked(pid, cpId, checkData, times) {
+            const time = new Date();
+
+            // 整理当前流程审核人状态更新
+            const audit = await this.getDataByCondition({ cpid: cpId, times, status: auditConst.status.checking });
+            if (!audit) {
+                throw '审核数据错误';
+            }
+
+            // 获取审核人列表
+            const sql = 'SELECT `tid`, `cpid`, `aid`, `order` FROM ?? WHERE `cpid` = ? and `times` = ? GROUP BY `aid` ORDER BY `id` ASC';
+            const sqlParam = [this.tableName, cpId, times];
+            const auditors = await this.db.query(sql, sqlParam);
+
+            const nextAudit = await this.getDataByCondition({ cpid: cpId, times, order: audit.order + 1 });
+
+
+            const transaction = await this.db.beginTransaction();
+            try {
+                await transaction.update(this.tableName, { id: audit.id, status: checkData.checkType, opinion: checkData.opinion, end_time: time });
+
+                // 获取推送必要信息
+                const noticeContent = await this.getNoticeContent(pid, audit.tid, cpId, audit.aid);
+                // 添加推送
+                const records = [{ pid, type: pushType.changeProject, uid: this.ctx.change.uid, status: auditConst.status.checked, content: noticeContent }];
+                auditors.forEach(audit => {
+                    records.push({ pid, type: pushType.changeProject, uid: audit.aid, status: auditConst.status.checked, content: noticeContent });
+                });
+                await transaction.insert('zh_notice', records);
+
+                // 无下一审核人表示,审核结束
+                if (nextAudit) {
+                    // 流程至下一审批人
+                    await transaction.update(this.tableName, { id: nextAudit.id, status: auditConst.status.checking, begin_time: time });
+
+                    // 同步 期信息
+                    await transaction.update(this.ctx.service.changeProject.tableName, {
+                        id: cpId, status: auditConst.status.checking,
+                    });
+
+                    // 微信模板通知
+                    // const wechatData = {
+                    //     qi: materialInfo.order,
+                    //     status: wxConst.status.check,
+                    //     tips: wxConst.tips.check,
+                    //     begin_time: Date.parse(begin_audit.begin_time),
+                    //     m_tp: this.ctx.helper.add(this.ctx.helper.round(materialInfo.m_tp, 2), this.ctx.helper.round(materialInfo.ex_tp, 2)),
+                    //     hs_m_tp: this.ctx.helper.add(this.ctx.helper.round(this.ctx.helper.mul(materialInfo.m_tp, 1+materialInfo.rate/100), 2), this.ctx.helper.round(this.ctx.helper.mul(materialInfo.ex_tp, 1+materialInfo.rate/100), 2)),
+                    // };
+                    // await this.ctx.helper.sendWechat(nextAudit.aid, smsTypeConst.const.TC, smsTypeConst.judge.approval.toString(), wxConst.template.material, wechatData);
+                } else {
+                    // 本期结束
+                    // 生成截止本期数据 final数据
+                    // 同步 期信息
+                    await transaction.update(this.ctx.service.changeProject.tableName, {
+                        id: cpId, status: checkData.checkType,
+                    });
+
+                    // 微信模板通知
+                    // const users = this._.uniq(this._.concat(this._.map(auditors, 'aid'), materialInfo.user_id));
+                    // const wechatData = {
+                    //     qi: materialInfo.order,
+                    //     status: wxConst.status.success,
+                    //     tips: wxConst.tips.success,
+                    //     begin_time: Date.parse(begin_audit.begin_time),
+                    //     m_tp: this.ctx.helper.add(this.ctx.helper.round(materialInfo.m_tp, 2), this.ctx.helper.round(materialInfo.ex_tp, 2)),
+                    //     hs_m_tp: this.ctx.helper.add(this.ctx.helper.round(this.ctx.helper.mul(materialInfo.m_tp, 1+materialInfo.rate/100), 2), this.ctx.helper.round(this.ctx.helper.mul(materialInfo.ex_tp, 1+materialInfo.rate/100), 2)),
+                    // };
+                    // await this.ctx.helper.sendWechat(users, smsTypeConst.const.TC, smsTypeConst.judge.result.toString(), wxConst.template.material, wechatData);
+                }
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        async _back(pid, cpId, checkData, times) {
+            const time = new Date();
+            // 整理当前流程审核人状态更新
+            const audit = await this.getDataByCondition({ cpid: cpId, times, status: auditConst.status.checking });
+            if (!audit) {
+                throw '审核数据错误';
+            }
+            const sql = 'SELECT `tid`, `cpid`, `aid`, `order` FROM ?? WHERE `cpid` = ? and `times` = ? GROUP BY `aid` ORDER BY `id` ASC';
+            const sqlParam = [this.tableName, cpId, times];
+            const auditors = await this.db.query(sql, sqlParam);
+            let order = 1;
+            for (const a of auditors) {
+                a.times = times + 1;
+                a.order = order;
+                a.status = auditConst.status.uncheck;
+                order++;
+            }
+            const transaction = await this.db.beginTransaction();
+            try {
+                await transaction.update(this.tableName, { id: audit.id, status: checkData.checkType, opinion: checkData.opinion, end_time: time });
+                // 添加到消息推送表
+                const noticeContent = await this.getNoticeContent(pid, audit.tid, cpId, audit.aid);
+                const records = [{ pid, type: pushType.changeProject, uid: this.ctx.change.uid, status: auditConst.status.back, content: noticeContent }];
+                auditors.forEach(audit => {
+                    records.push({ pid, type: pushType.changeProject, uid: audit.aid, status: auditConst.status.back, content: noticeContent });
+                });
+                await transaction.insert(this.ctx.service.noticePush.tableName, records);
+                // 同步期信息
+                await transaction.update(this.ctx.service.changeProject.tableName, {
+                    id: cpId, status: checkData.checkType,
+                    times: times + 1,
+                });
+                // 拷贝新一次审核流程列表
+                await transaction.insert(this.tableName, auditors);
+                // 微信模板通知
+                // const begin_audit = await this.getDataByCondition({
+                //     mid: materialId,
+                //     order: 1,
+                // });
+                // const materialInfo = await this.ctx.service.material.getDataById(materialId);
+                // const users = this._.uniq(this._.concat(this._.map(auditors, 'aid'), materialInfo.user_id));
+                // const wechatData = {
+                //     qi: materialInfo.order,
+                //     status: wxConst.status.back,
+                //     tips: wxConst.tips.back,
+                //     begin_time: Date.parse(begin_audit.begin_time),
+                //     m_tp: this.ctx.helper.add(this.ctx.helper.round(materialInfo.m_tp, 2), this.ctx.helper.round(materialInfo.ex_tp, 2)),
+                //     hs_m_tp: this.ctx.helper.add(this.ctx.helper.round(this.ctx.helper.mul(materialInfo.m_tp, 1+materialInfo.rate/100), 2), this.ctx.helper.round(this.ctx.helper.mul(materialInfo.ex_tp, 1+materialInfo.rate/100), 2)),
+                // };
+                // await this.ctx.helper.sendWechat(users, smsTypeConst.const.TC, smsTypeConst.judge.result.toString(), wxConst.template.material, wechatData);
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        async _checkNo(pid, cpId, checkData, times) {
+            const time = new Date();
+
+            // 整理当前流程审核人状态更新
+            const audit = await this.getDataByCondition({ cpid: cpId, times, status: auditConst.status.checking });
+            if (!audit) {
+                throw '审核数据错误';
+            }
+
+            // 获取审核人列表
+            const sql = 'SELECT `tid`, `cpid`, `aid`, `order` FROM ?? WHERE `cpid` = ? and `times` = ? GROUP BY `aid` ORDER BY `id` ASC';
+            const sqlParam = [this.tableName, cpId, times];
+            const auditors = await this.db.query(sql, sqlParam);
+            const transaction = await this.db.beginTransaction();
+            try {
+                await transaction.update(this.tableName, { id: audit.id, status: checkData.checkType, opinion: checkData.opinion, end_time: time });
+
+                // 获取推送必要信息
+                const noticeContent = await this.getNoticeContent(pid, audit.tid, cpId, audit.aid);
+                // 添加推送
+                const records = [{ pid, type: pushType.changeProject, uid: this.ctx.change.uid, status: auditConst.status.checkNo, content: noticeContent }];
+                auditors.forEach(audit => {
+                    records.push({ pid, type: pushType.changeProject, uid: audit.aid, status: auditConst.status.checkNo, content: noticeContent });
+                });
+                await transaction.insert('zh_notice', records);
+                // 本期结束
+                // 生成截止本期数据 final数据
+                // 同步 期信息
+                await transaction.update(this.ctx.service.changeProject.tableName, {
+                    id: cpId, status: checkData.checkType,
+                });
+
+                // 微信模板通知
+                // const users = this._.uniq(this._.concat(this._.map(auditors, 'aid'), materialInfo.user_id));
+                // const wechatData = {
+                //     qi: materialInfo.order,
+                //     status: wxConst.status.success,
+                //     tips: wxConst.tips.success,
+                //     begin_time: Date.parse(begin_audit.begin_time),
+                //     m_tp: this.ctx.helper.add(this.ctx.helper.round(materialInfo.m_tp, 2), this.ctx.helper.round(materialInfo.ex_tp, 2)),
+                //     hs_m_tp: this.ctx.helper.add(this.ctx.helper.round(this.ctx.helper.mul(materialInfo.m_tp, 1+materialInfo.rate/100), 2), this.ctx.helper.round(this.ctx.helper.mul(materialInfo.ex_tp, 1+materialInfo.rate/100), 2)),
+                // };
+                // await this.ctx.helper.sendWechat(users, smsTypeConst.const.TC, smsTypeConst.judge.result.toString(), wxConst.template.material, wechatData);
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        /**
+         * 复制上一期的审批人列表给最新一期
+         *
+         * @param transaction - 新增一期的事务
+         * @param {Object} preMaterial - 上一期
+         * @param {Object} newaMaterial - 最新一期
+         * @return {Promise<*>}
+         */
+        async copyPreChangeProjectAuditors(transaction, preChange, newChange) {
+            const auditors = await this.getAuditGroupByList(preChange.id, preChange.times);
+            const newAuditors = [];
+            for (const a of auditors) {
+                const na = {
+                    tid: preChange.tid,
+                    cpid: newChange.id,
+                    aid: a.aid,
+                    times: newChange.times,
+                    order: newAuditors.length + 1,
+                    status: auditConst.status.uncheck,
+                };
+                newAuditors.push(na);
+            }
+            const result = await transaction.insert(this.tableName, newAuditors);
+            return result.affectedRows === auditors.length;
+        }
+    }
+
+    return ChangeProjectAudit;
+};

+ 27 - 0
app/service/change_revise_log.js

@@ -0,0 +1,27 @@
+'use strict';
+
+/**
+ * 变更新增部位插入记录表
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+module.exports = app => {
+
+    class ChangePos extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'change_revise_log';
+        }
+    }
+
+    return ChangePos;
+};

+ 2 - 2
app/service/ledger.js

@@ -520,7 +520,7 @@ module.exports = app => {
                     for (const [j, p] of data[i].pos.entries()) {
                         const inD = {
                             id: this.uuid.v4(), tid: tenderId, lid: qd.id,
-                            add_stage: 0, add_times: 0, add_user: this.ctx.session.sessionUser.accountId,
+                            add_stage: 0, add_stage_order: 0, add_times: 0, add_user: this.ctx.session.sessionUser.accountId,
                             in_time, porder: j + 1,
                             name: p.name, drawing_code: p.drawing_code,
                         };
@@ -609,7 +609,7 @@ module.exports = app => {
                     for (const [j, p] of data[i].pos.entries()) {
                         const inD = {
                             id: this.uuid.v4(), tid: tenderId, lid: qd.id,
-                            add_stage: 0, add_times: 0, add_user: this.ctx.session.sessionUser.accountId,
+                            add_stage: 0, add_stage_order: 0, add_times: 0, add_user: this.ctx.session.sessionUser.accountId,
                             in_time, porder: j + 1,
                             name: p.name, drawing_code: p.drawing_code,
                         };

+ 26 - 0
app/service/ledger_audit.js

@@ -242,6 +242,26 @@ module.exports = app => {
         }
 
         /**
+         * 备份
+         * @param {Object} revise - 修订
+         * @returns {Promise<void>}
+         * @private
+         */
+        async backupLedgerHistoryFile() {
+            const timestamp = (new Date()).getTime();
+
+            const billsHis = `${this.ctx.session.sessionProject.id}/${this.ctx.tender.id}/bills${timestamp}.json`;
+            const bills = await this.ctx.service.ledger.getData(this.ctx.tender.id);
+            await this.ctx.app.hisOss.put(this.ctx.app.config.hisOssPath + billsHis, Buffer.from(JSON.stringify(bills), 'utf8'));
+
+            const posHis = `${this.ctx.session.sessionProject.id}/${this.ctx.tender.id}/pos${timestamp}.json`;
+            const pos = await this.ctx.service.pos.getAllDataByCondition({tid: this.ctx.tender.id});
+            await this.ctx.app.hisOss.put(this.ctx.app.config.hisOssPath + posHis, Buffer.from(JSON.stringify(pos), 'utf8'));
+
+            return [billsHis, posHis];
+        }
+
+        /**
          * 开始审批
          *
          * @param {Number} tenderId - 标段id
@@ -258,6 +278,11 @@ module.exports = app => {
                 }
             }
             const sum = await this.ctx.service.ledger.addUp({ tender_id: tenderId /* , is_leaf: true*/ });
+            // 拷贝备份台账数据
+            const his_id = await this.ctx.service.ledgerHistory.backupLedgerHistory(this.ctx.tender.data);
+
+            // 拷贝备份台账数据
+            const [billsHis, posHis] = await this.backupLedgerHistoryFile();
 
             const transaction = await this.db.beginTransaction();
             try {
@@ -271,6 +296,7 @@ module.exports = app => {
                     ledger_status: auditConst.status.checking,
                     total_price: sum.total_price,
                     deal_tp: sum.deal_tp,
+                    his_id: his_id,
                 });
 
                 // 添加短信通知-需要审批提醒功能

+ 130 - 0
app/service/ledger_history.js

@@ -0,0 +1,130 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+module.exports = app => {
+
+    class LedgerTag extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'ledger_history';
+        }
+
+        /** 获取最新数据
+         *
+         * @param {Number}tid - 标段id
+         * @return {Promise<*>} 最新数据
+         */
+        async getLatestHistory(tid) {
+            const his = await this.db.select(this.tableName, {
+                where: { tid },
+                orders: [['in_time', 'desc']],
+                limit: 1, offset: 0,
+            });
+            return his[0];
+        }
+
+        /**
+         * 备份
+         * @param {Object} tender - 标段
+         * @return {Promise<void>} - 新增备份id
+         * @private
+         */
+        async backupLedgerHistory(tender) {
+            const now = new Date();
+            const timestamp = (now).getTime();
+
+            const billsHis = `${this.ctx.session.sessionProject.id}/${tender.id}/ledger/bills${timestamp}.json`;
+            const bills = await this.ctx.service.ledger.getData(tender.id);
+            await this.ctx.hisOss.put(this.ctx.hisOssPath + billsHis, Buffer.from(JSON.stringify(bills), 'utf8'));
+
+            const posHis = `${this.ctx.session.sessionProject.id}/${tender.id}/ledger/pos${timestamp}.json`;
+            const pos = await this.ctx.service.pos.getPosData({ tid: tender.id });
+            await this.ctx.hisOss.put(this.ctx.hisOssPath + posHis, Buffer.from(JSON.stringify(pos), 'utf8'));
+
+            const result = await this.db.insert(this.tableName, {
+                pid: this.ctx.session.sessionProject.id, tid: tender.id,
+                in_time: now,
+                bills_file: billsHis, pos_file: posHis,
+            });
+
+            return result.insertId;
+        }
+
+        /**
+         * 备份
+         * @param {Object} revise - 修订
+         * @return {Promise<void>} - 新增备份id
+         * @private
+         */
+        async backupReviseLedgerHistory(revise) {
+            const now = new Date();
+            const timestamp = (now).getTime();
+
+            const billsHis = `${this.ctx.session.sessionProject.id}/${revise.tid}/ledger/bills${timestamp}-r.json`;
+            const bills = await this.ctx.service.reviseBills.getData(revise.tid);
+            await this.ctx.hisOss.put(this.ctx.hisOssPath + billsHis, Buffer.from(JSON.stringify(bills), 'utf8'));
+
+            const posHis = `${this.ctx.session.sessionProject.id}/${revise.tid}/ledger/pos${timestamp}-r.json`;
+            const pos = await this.ctx.service.revisePos.getData(revise.tid);
+            await this.ctx.hisOss.put(this.ctx.hisOssPath + posHis, Buffer.from(JSON.stringify(pos), 'utf8'));
+
+            const result = await this.db.insert(this.tableName, {
+                pid: this.ctx.session.sessionProject.id, tid: revise.tid,
+                rid: revise.id, rorder: revise.corder,
+                in_time: now,
+                bills_file: billsHis, pos_file: posHis,
+            });
+
+            return result.insertId;
+        }
+
+        /**
+         * 备份 (预留功能)
+         * @param {Object} transaction - 事务
+         * @param {Object} change - 工程变更
+         * @param {Array} newBillsNode - 新增项目节节点
+         * @param {Array} newPosNode - 新增计量单元节点
+         * @return {Promise<void>} - 新增备份id
+         * @private
+         */
+        async backupChangeHistory(transaction, change, newBillsNodes, newPosNodes) {
+            if ((newBillsNodes || newBillsNodes === 0) && (newPosNodes || newPosNodes.length === 0)) return;
+            const now = new Date();
+            const timestamp = (now).getTime();
+
+            const billsHis = `${this.ctx.session.sessionProject.id}/${change.tid}/ledger/bills${timestamp}-c.json`;
+            const bills = await this.ctx.service.ledger.getData(change.tid);
+            if (newBillsNodes.length > 0) bills.push(...newBillsNodes);
+            await this.ctx.hisOss.put(this.ctx.hisOssPath + billsHis, Buffer.from(JSON.stringify(bills), 'utf8'));
+
+            const posHis = `${this.ctx.session.sessionProject.id}/${change.tid}/ledger/pos${timestamp}-c.json`;
+            const pos = await this.ctx.service.pos.getPosData({ tid: change.tid });
+            if (newPosNodes.length > 0) pos.push(...newPosNodes);
+            await this.ctx.hisOss.put(this.ctx.hisOssPath + posHis, Buffer.from(JSON.stringify(pos), 'utf8'));
+
+            const result = await transaction.insert(this.tableName, {
+                pid: this.ctx.session.sessionProject.id, tid: change.tid,
+                cid: change.cid,
+                in_time: now,
+                bills_file: billsHis, pos_file: posHis,
+            });
+
+            return result.insertId;
+        }
+    }
+
+    return LedgerTag;
+};

+ 8 - 25
app/service/ledger_revise.js

@@ -30,7 +30,7 @@ module.exports = app => {
          * @returns {Promise<*>} - ledger_change下所有数据,并关联 project_account(读取提交人名称、单位、公司)
          */
         async getReviseList (tid) {
-            const sql = 'SELECT lc.id, lc.tid, lc.corder, lc.in_time, lc.uid, lc.begin_time, lc.end_time, lc.times, lc.status, lc.valid, lc.content,' +
+            const sql = 'SELECT lc.id, lc.tid, lc.corder, lc.in_time, lc.uid, lc.begin_time, lc.end_time, lc.times, lc.status, lc.valid, lc.content, lc.pre_his_id, lc.his_id,' +
                 '    pa.name As user_name, pa.role As user_role, pa.company As user_company' +
                 '  FROM ' + this.tableName + ' As lc' +
                 '  INNER JOIN ' + this.ctx.service.projectAccount.tableName + ' As pa ON lc.uid = pa.id' +
@@ -48,7 +48,7 @@ module.exports = app => {
          * @returns {Promise<*>}
          */
         async getAllReviseList (tid) {
-            const sql = 'SELECT lc.id, lc.tid, lc.corder, lc.in_time, lc.uid, lc.begin_time, lc.end_time, lc.times, lc.status, lc.valid,' +
+            const sql = 'SELECT lc.id, lc.tid, lc.corder, lc.in_time, lc.uid, lc.begin_time, lc.end_time, lc.times, lc.status, lc.valid, lc.pre_his_id, lc.his_id,' +
                 '    pa.name As user_name, pa.role As user_role, pa.company As user_company' +
                 '  FROM ' + this.tableName + ' As lc' +
                 '  INNER JOIN ' + this.ctx.service.projectAccount.tableName + ' As pa ON lc.uid = pa.id' +
@@ -150,26 +150,6 @@ module.exports = app => {
         }
 
         /**
-         * 备份
-         * @param {Object} revise - 修订
-         * @returns {Promise<void>}
-         * @private
-         */
-        async backupReviseHistoryFile(revise) {
-            const timestamp = (new Date()).getTime();
-
-            const billsHis = '/revise/bills' + timestamp + '.json';
-            const bills = await this.ctx.service.reviseBills.getData(revise.tid);
-            await this.ctx.helper.saveBufferFile(JSON.stringify(bills), this.ctx.app.config.filePath + billsHis);
-
-            const posHis = '/revise/pos' + timestamp + '.json';
-            const pos = await this.ctx.service.revisePos.getData(revise.tid);
-            await this.ctx.helper.saveBufferFile(JSON.stringify(pos), this.ctx.app.config.filePath + posHis);
-
-            return [billsHis, posHis];
-        }
-
-        /**
          * 新增修订
          * @param {Number}tid - 标段id
          * @param {Number}uid - 提交人id
@@ -180,9 +160,11 @@ module.exports = app => {
                 throw '数据错误';
             }
             const maxOrder = await this.getNewOrder(tid);
+            const latest = await this.ctx.service.ledgerHistory.getLatestHistory(tid);
+            if (!latest) throw '台账历史数据有误';
             const data = {
                 id: this.uuid.v4(), tid: tid, uid: uid,
-                corder: maxOrder + 1, in_time: new Date(), status: audit.status.uncheck,
+                corder: maxOrder + 1, in_time: new Date(), status: audit.status.uncheck, pre_his_id: latest.id,
             };
             const transaction = await this.db.beginTransaction();
             try {
@@ -209,13 +191,14 @@ module.exports = app => {
          * @returns {Promise<void>}
          */
         async cancelRevise(revise) {
+            const his_id = await this.ctx.service.ledgerHistory.backupReviseLedgerHistory(revise);
             const transaction = await this.db.beginTransaction();
             try {
-                const [billsHis, posHis] = await this.backupReviseHistoryFile(revise);
                 const result = await transaction.update(this.tableName, {
                     id: revise.id, valid: false, end_time: new Date(),
-                    bills_file: billsHis, pos_file: posHis,
+                    his_id,
                 });
+                await transaction.update(this.ctx.service.ledgerHistory.tableName, { id: his_id, valid: 0 });
                 // 投资进度改变状态
                 await transaction.update(this.ctx.service.schedule.tableName, { revising: 0 }, { where: { tid: this.ctx.tender.id } });
                 await transaction.commit();

+ 1 - 0
app/service/manager.js

@@ -22,6 +22,7 @@ module.exports = app => {
             super(ctx);
             this.tableName = 'manager';
         }
+
     }
 
     return Manager;

+ 39 - 6
app/service/material.js

@@ -10,6 +10,7 @@
 
 const auditConst = require('../const/audit').material;
 const projectLogConst = require('../const/project_log');
+const materialConst = require('../const/material');
 module.exports = app => {
     class Material extends app.BaseService {
         /**
@@ -145,6 +146,7 @@ module.exports = app => {
                 stage_id: data.stage_id.join(','),
                 s_order: data.s_order,
                 material_tax: this.ctx.session.sessionProject.page_show.openMaterialTax,
+                decimal: preMaterial && preMaterial.decimal ? preMaterial.decimal : JSON.stringify(materialConst.decimal),
             };
             const transaction = await this.db.beginTransaction();
             try {
@@ -173,9 +175,9 @@ module.exports = app => {
                     // 复制调差清单工料关联表
                     await this.ctx.service.materialList.copyPreMaterialList(transaction, preMaterial, newMaterial);
                     // 修改本期应耗数量值和有效价差,需要剔除不参与调差的清单数据,并返回总金额
-                    const [m_tp, m_tax_tp] = await this.ctx.service.materialBills.updateNewMaterial(transaction, this.ctx.tender.id, newMaterial.id, this.ctx, newMaterial.stage_id);
+                    const [m_tp, m_tax_tp] = await this.ctx.service.materialBills.updateNewMaterial(transaction, this.ctx.tender.id, newMaterial.id, this.ctx, newMaterial.stage_id, JSON.parse(newMaterial.decimal));
                     // 修改现行价格指数,并返回调差基数json
-                    const ex_calc = await this.ctx.service.materialExponent.updateNewMaterial(transaction, newMaterial.id, this.ctx, newMaterial.stage_id, preMaterial.ex_calc);
+                    const ex_calc = await this.ctx.service.materialExponent.updateNewMaterial(transaction, newMaterial.id, this.ctx, newMaterial.stage_id, preMaterial.ex_calc, JSON.parse(newMaterial.decimal));
                     // 计算得出本期总金额
                     const updateMaterialData = {
                         id: newMaterial.id,
@@ -312,8 +314,8 @@ module.exports = app => {
          * @param {int} order 调差期数
          * @return {Promise<*>}
          */
-        async getPreTpHs(tid, order) {
-            const sql = 'SELECT SUM(ROUND(`m_tp`*(1+ `rate`/100),2)) AS `pre_tp_hs` FROM ?? WHERE `tid` = ? AND `material_tax` = ? AND `order` < ?';
+        async getPreTpHs(tid, order, tp) {
+            const sql = 'SELECT SUM(ROUND(`m_tp`*(1+ `rate`/100),' + tp + ')) AS `pre_tp_hs` FROM ?? WHERE `tid` = ? AND `material_tax` = ? AND `order` < ?';
             const sqlParam = [this.tableName, tid, 0, order];
             const result = await this.db.queryOne(sql, sqlParam);
             return result.pre_tp_hs;
@@ -338,8 +340,8 @@ module.exports = app => {
          * @param {int} order 调差期数
          * @return {Promise<*>}
          */
-        async getExPreTpHs(tid, order) {
-            const sql = 'SELECT SUM(ROUND(`ex_tp`*(1+ `rate`/100),2)) AS `ex_pre_tp_hs` FROM ?? WHERE `tid` = ? AND `order` < ?';
+        async getExPreTpHs(tid, order, tp) {
+            const sql = 'SELECT SUM(ROUND(`ex_tp`*(1+ `rate`/100),' + tp + ')) AS `ex_pre_tp_hs` FROM ?? WHERE `tid` = ? AND `order` < ?';
             const sqlParam = [this.tableName, tid, order];
             const result = await this.db.queryOne(sql, sqlParam);
             return result.ex_pre_tp_hs;
@@ -370,6 +372,37 @@ module.exports = app => {
             const result = await this.db.queryOne(sql, sqlParam);
             return result && result.count !== 0;
         }
+
+        async saveDecimal(newUp, newTp) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                await this.ctx.service.materialBills.resetDecimal(transaction, newUp, newTp);
+                this.ctx.material.decimal.up = newUp;
+                this.ctx.material.decimal.tp = newTp;
+                const m_tp = await this.ctx.service.materialBills.calcMaterialMTp(transaction);
+                let update_calc = false;
+                if (this.ctx.material.ex_calc) {
+                    const ex_calc = JSON.parse(this.ctx.material.ex_calc);
+                    const zdy = this._.find(ex_calc, { code: 'zdy' });
+                    zdy.value = this.ctx.helper.round(zdy.value, newTp);
+                    this.ctx.material.ex_calc = JSON.stringify(ex_calc);
+                    update_calc = true;
+                }
+                const [ex_tp, ex_expr] = await this.ctx.service.materialExponent.calcMaterialExTp(transaction);
+                const updateData = {
+                    id: this.ctx.material.id,
+                    decimal: JSON.stringify(this.ctx.material.decimal),
+                };
+                if (update_calc) updateData.ex_calc = this.ctx.material.ex_calc;
+                await transaction.update(this.tableName, updateData);
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                console.log(err);
+                await transaction.rollback();
+                return false;
+            }
+        }
     }
 
     return Material;

+ 64 - 59
app/service/material_audit.js

@@ -231,55 +231,6 @@ module.exports = app => {
                 if (materialBillsData.length === 0 && this.ctx.material.ex_expr === null) {
                     throw '请至少使用一种调差方式';
                 }
-                const mbhList = [];
-                for (const mb of materialBillsData) {
-                    if (mb.code === '') {
-                        throw '调差工料编号不能为空';
-                    }
-                    const newMbh = {
-                        tid: this.ctx.tender.id,
-                        mid: this.ctx.material.id,
-                        order: this.ctx.material.order,
-                        mb_id: mb.id,
-                        quantity: mb.quantity,
-                        expr: mb.expr,
-                        msg_tp: mb.msg_tp,
-                        msg_times: mb.msg_times,
-                        msg_spread: mb.msg_spread,
-                        m_up_risk: mb.m_up_risk,
-                        m_down_risk: mb.m_down_risk,
-                        m_spread: mb.m_spread,
-                        m_tp: mb.m_tp,
-                        pre_tp: mb.pre_tp,
-                        m_tax_tp: mb.m_tax_tp,
-                        tax_pre_tp: mb.tax_pre_tp,
-                        origin: mb.origin,
-                        is_summary: mb.is_summary,
-                        m_tax: mb.m_tax,
-                    };
-                    mbhList.push(newMbh);
-                }
-                if(mbhList.length !== 0) await transaction.insert(this.ctx.service.materialBillsHistory.tableName, mbhList);
-
-                const materialExponentData = await this.ctx.service.materialExponent.getAllDataByCondition({ where: { tid: this.ctx.tender.id } });
-                const mehList = [];
-                for (const me of materialExponentData) {
-                    const newMeh = {
-                        tid: this.ctx.tender.id,
-                        mid: this.ctx.material.id,
-                        order: this.ctx.material.order,
-                        me_id: me.id,
-                        type: me.type,
-                        weight_num: me.weight_num,
-                        basic_price: me.basic_price,
-                        basic_times: me.basic_times,
-                        m_price: me.m_price,
-                        calc_num: me.calc_num,
-                        is_summary: me.is_summary,
-                    };
-                    mehList.push(newMeh);
-                }
-                if(mehList.length !== 0) await transaction.insert(this.ctx.service.materialExponentHistory.tableName, mehList);
 
                 // 微信模板通知
                 const materialInfo = await this.ctx.service.material.getDataById(materialId);
@@ -401,6 +352,60 @@ module.exports = app => {
                         id: materialId, status: checkData.checkType,
                     });
 
+                    // 处理旧数据,防止重复插入到历史表
+                    await transaction.delete(this.ctx.service.materialBillsHistory.tableName, { tid: this.ctx.tender.id, order: this.ctx.material.order});
+                    const mbhList = [];
+                    const materialBillsData = await this.ctx.service.materialBills.getAllDataByCondition({ where: { tid: this.ctx.tender.id } });
+                    for (const mb of materialBillsData) {
+                        if (mb.code === '') {
+                            throw '调差工料编号不能为空';
+                        }
+                        const newMbh = {
+                            tid: this.ctx.tender.id,
+                            mid: this.ctx.material.id,
+                            order: this.ctx.material.order,
+                            mb_id: mb.id,
+                            quantity: mb.quantity,
+                            expr: mb.expr,
+                            msg_tp: mb.msg_tp,
+                            msg_times: mb.msg_times,
+                            msg_spread: mb.msg_spread,
+                            m_up_risk: mb.m_up_risk,
+                            m_down_risk: mb.m_down_risk,
+                            m_spread: mb.m_spread,
+                            m_tp: mb.m_tp,
+                            pre_tp: mb.pre_tp,
+                            m_tax_tp: mb.m_tax_tp,
+                            tax_pre_tp: mb.tax_pre_tp,
+                            origin: mb.origin,
+                            is_summary: mb.is_summary,
+                            m_tax: mb.m_tax,
+                        };
+                        mbhList.push(newMbh);
+                    }
+                    if(mbhList.length !== 0) await transaction.insert(this.ctx.service.materialBillsHistory.tableName, mbhList);
+                    // 处理旧数据,防止重复插入到历史表
+                    await transaction.delete(this.ctx.service.materialExponentHistory.tableName, { tid: this.ctx.tender.id, order: this.ctx.material.order});
+                    const materialExponentData = await this.ctx.service.materialExponent.getAllDataByCondition({ where: { tid: this.ctx.tender.id } });
+                    const mehList = [];
+                    for (const me of materialExponentData) {
+                        const newMeh = {
+                            tid: this.ctx.tender.id,
+                            mid: this.ctx.material.id,
+                            order: this.ctx.material.order,
+                            me_id: me.id,
+                            type: me.type,
+                            weight_num: me.weight_num,
+                            basic_price: me.basic_price,
+                            basic_times: me.basic_times,
+                            m_price: me.m_price,
+                            calc_num: me.calc_num,
+                            is_summary: me.is_summary,
+                        };
+                        mehList.push(newMeh);
+                    }
+                    if(mehList.length !== 0) await transaction.insert(this.ctx.service.materialExponentHistory.tableName, mehList);
+
                     // 微信模板通知
                     const users = this._.uniq(this._.concat(this._.map(auditors, 'aid'), materialInfo.user_id));
                     const wechatData = {
@@ -482,16 +487,16 @@ module.exports = app => {
                 });
                 // 拷贝新一次审核流程列表
                 await transaction.insert(this.tableName, auditors);
-                // 删除material_bills_history信息
-                await transaction.delete(this.ctx.service.materialBillsHistory.tableName, {
-                    tid: this.ctx.tender.id,
-                    order: this.ctx.material.order,
-                });
-                // 删除material_exponent_history信息
-                await transaction.delete(this.ctx.service.materialExponentHistory.tableName, {
-                    tid: this.ctx.tender.id,
-                    order: this.ctx.material.order,
-                });
+                // // 删除material_bills_history信息
+                // await transaction.delete(this.ctx.service.materialBillsHistory.tableName, {
+                //     tid: this.ctx.tender.id,
+                //     order: this.ctx.material.order,
+                // });
+                // // 删除material_exponent_history信息
+                // await transaction.delete(this.ctx.service.materialExponentHistory.tableName, {
+                //     tid: this.ctx.tender.id,
+                //     order: this.ctx.material.order,
+                // });
 
                 // 微信模板通知
                 const begin_audit = await this.getDataByCondition({

+ 72 - 22
app/service/material_bills.js

@@ -222,13 +222,13 @@ module.exports = app => {
          * @param mid
          * @returns {Promise<number>}
          */
-        async updateNewMaterial(transaction, tid, mid, ctx, stage_id) {
+        async updateNewMaterial(transaction, tid, mid, ctx, stage_id, decimal) {
             const materialBillsData = await this.getAllDataByCondition({ where: { tid } });
             let m_tp = 0;
             let m_tax_tp = 0;
             const materialCalculator = new MaterialCalculator(ctx, stage_id, ctx.tender.info);
             for (const mb of materialBillsData) {
-                const [one_tp, one_tax_tp] = await this.calcQuantityByMB(transaction, mid, mb, materialCalculator);
+                const [one_tp, one_tax_tp] = await this.calcQuantityByMB(transaction, mid, mb, materialCalculator, decimal);
                 m_tp = this.ctx.helper.add(m_tp, one_tp);
                 m_tax_tp = this.ctx.helper.add(m_tax_tp, one_tax_tp);
             }
@@ -242,16 +242,16 @@ module.exports = app => {
          * @param mb
          * @returns {Promise<*>}
          */
-        async calcQuantityByMB(transaction, mid, mb, materialCalculator) {
-            const [newmsg_spread, newm_spread] = await this.getSpread(mb, null);
+        async calcQuantityByMB(transaction, mid, mb, materialCalculator, decimal) {
+            const [newmsg_spread, newm_spread] = await this.getSpread(mb, null, decimal.up);
             if (mb.t_type === materialConst.t_type[0].value) {
                 const sql = 'SELECT SUM(`gather_qty`*`quantity`) as quantity FROM ' + this.ctx.service.materialList.tableName + ' WHERE `mid`=? AND `mb_id`=? AND `is_join`=1';
                 const sqlParam = [mid, mb.id];
                 const mb_quantity = await transaction.queryOne(sql, sqlParam);
                 console.log(mb_quantity);
                 // 取历史期记录获取截止上期调差金额,并清空本期单价和时间,来源地,重新计算价差和有效价差
-                const newQuantity = this.ctx.helper.round(mb_quantity.quantity, 3);
-                const newTp = this.ctx.helper.round(this.ctx.helper.mul(newQuantity, newm_spread), 2);
+                const newQuantity = this.ctx.helper.round(mb_quantity.quantity, decimal.qty);
+                const newTp = this.ctx.helper.round(this.ctx.helper.mul(newQuantity, newm_spread), decimal.tp);
                 const updateData = {
                     id: mb.id,
                     quantity: newQuantity,
@@ -261,33 +261,33 @@ module.exports = app => {
                     m_spread: newm_spread,
                     origin: null,
                     m_tp: newTp,
-                    pre_tp: mb.m_tp !== null ? this.ctx.helper.round(this.ctx.helper.add(mb.pre_tp, mb.m_tp), 2) : mb.pre_tp,
-                    m_tax_tp: this.ctx.helper.round(this.ctx.helper.mul(newTp, (1 + this.ctx.helper.div(mb.m_tax, 100))), 2),
-                    tax_pre_tp: mb.m_tax_tp !== null ? this.ctx.helper.round(this.ctx.helper.add(mb.tax_pre_tp, mb.m_tax_tp), 2) : mb.tax_pre_tp,
+                    pre_tp: mb.m_tp !== null ? this.ctx.helper.round(this.ctx.helper.add(mb.pre_tp, mb.m_tp), decimal.tp) : mb.pre_tp,
+                    m_tax_tp: this.ctx.helper.round(this.ctx.helper.mul(newTp, (1 + this.ctx.helper.div(mb.m_tax, 100))), decimal.tp),
+                    tax_pre_tp: mb.m_tax_tp !== null ? this.ctx.helper.round(this.ctx.helper.add(mb.tax_pre_tp, mb.m_tax_tp), decimal.tp) : mb.tax_pre_tp,
                 };
                 await transaction.update(this.tableName, updateData);
-                const m_tp = mb.is_summary === 1 ? await this.ctx.helper.round(this.ctx.helper.mul(mb_quantity.quantity, newm_spread), 2) : 0;
-                const m_tax_tp = this.ctx.helper.round(this.ctx.helper.mul(m_tp, (1 + this.ctx.helper.div(mb.m_tax, 100))), 2);
+                const m_tp = mb.is_summary === 1 ? await this.ctx.helper.round(this.ctx.helper.mul(mb_quantity.quantity, newm_spread), decimal.tp) : 0;
+                const m_tax_tp = this.ctx.helper.round(this.ctx.helper.mul(m_tp, (1 + this.ctx.helper.div(mb.m_tax, 100))), decimal.tp);
                 return [m_tp, m_tax_tp];
             } else if (mb.t_type === materialConst.t_type[1].value) {
                 const quantity = await materialCalculator.calculateExpr(mb.expr);
-                const newTp = quantity !== 0 && quantity !== null ? this.ctx.helper.round(this.ctx.helper.mul(this.ctx.helper.round(quantity, 3), newm_spread), 2) : null;
+                const newTp = quantity !== 0 && quantity !== null ? this.ctx.helper.round(this.ctx.helper.mul(this.ctx.helper.round(quantity, decimal.qty), newm_spread), decimal.tp) : null;
                 const updateData = {
                     id: mb.id,
-                    quantity: quantity !== 0 && quantity !== null ? this.ctx.helper.round(quantity, 3) : null,
+                    quantity: quantity !== 0 && quantity !== null ? this.ctx.helper.round(quantity, decimal.qty) : null,
                     msg_tp: null,
                     msg_times: null,
                     msg_spread: newmsg_spread,
                     m_spread: newm_spread,
                     origin: null,
                     m_tp: newTp,
-                    pre_tp: mb.m_tp !== null ? this.ctx.helper.round(this.ctx.helper.add(mb.pre_tp, mb.m_tp), 2) : mb.pre_tp,
-                    m_tax_tp: this.ctx.helper.round(this.ctx.helper.mul(newTp, (1 + this.ctx.helper.div(mb.m_tax, 100))), 2),
-                    tax_pre_tp: mb.m_tax_tp !== null ? this.ctx.helper.round(this.ctx.helper.add(mb.tax_pre_tp, mb.m_tax_tp), 2) : mb.tax_pre_tp,
+                    pre_tp: mb.m_tp !== null ? this.ctx.helper.round(this.ctx.helper.add(mb.pre_tp, mb.m_tp), decimal.tp) : mb.pre_tp,
+                    m_tax_tp: this.ctx.helper.round(this.ctx.helper.mul(newTp, (1 + this.ctx.helper.div(mb.m_tax, 100))), decimal.tp),
+                    tax_pre_tp: mb.m_tax_tp !== null ? this.ctx.helper.round(this.ctx.helper.add(mb.tax_pre_tp, mb.m_tax_tp), decimal.tp) : mb.tax_pre_tp,
                 };
                 await transaction.update(this.tableName, updateData);
-                const m_tp = mb.is_summary === 1 ? await this.ctx.helper.round(this.ctx.helper.mul(quantity, newm_spread), 2) : 0;
-                const m_tax_tp = this.ctx.helper.round(this.ctx.helper.mul(m_tp, (1 + this.ctx.helper.div(mb.m_tax, 100))), 2);
+                const m_tp = mb.is_summary === 1 ? await this.ctx.helper.round(this.ctx.helper.mul(quantity, newm_spread), decimal.tp) : 0;
+                const m_tax_tp = this.ctx.helper.round(this.ctx.helper.mul(m_tp, (1 + this.ctx.helper.div(mb.m_tax, 100))), decimal.tp);
                 return [m_tp, m_tax_tp];
             }
         }
@@ -297,11 +297,11 @@ module.exports = app => {
          * @param data
          * @returns {Promise<void>}
          */
-        async getSpread(data, msg_tp) {
+        async getSpread(data, msg_tp, newDecimalUp = this.ctx.material.decimal.up) {
             data.msg_tp = msg_tp;
-            const msg_spread = this.ctx.helper.round(this.ctx.helper.sub(data.msg_tp, data.basic_price), 3);
+            const msg_spread = this.ctx.helper.round(this.ctx.helper.sub(data.msg_tp, data.basic_price), newDecimalUp);
             const cor = msg_spread >= 0 ? this.ctx.helper.mul(data.basic_price, this.ctx.helper.div(data.m_up_risk, 100)) : this.ctx.helper.mul(data.basic_price, this.ctx.helper.div(data.m_down_risk, 100));
-            const m_spread = Math.abs(msg_spread) > Math.abs(cor) ? (msg_spread > 0 ? this.ctx.helper.round(this.ctx.helper.sub(msg_spread, cor), 3) : this.ctx.helper.round(this.ctx.helper.add(msg_spread, cor), 3)) : 0;
+            const m_spread = Math.abs(msg_spread) > Math.abs(cor) ? (msg_spread > 0 ? this.ctx.helper.round(this.ctx.helper.sub(msg_spread, cor), newDecimalUp) : this.ctx.helper.round(this.ctx.helper.add(msg_spread, cor), newDecimalUp)) : 0;
             return [msg_spread, m_spread];
         }
 
@@ -317,7 +317,8 @@ module.exports = app => {
             const transaction = await this.db.beginTransaction();
             try {
                 const mbInfo = await this.getDataById(data.id);
-                data.m_tp = this.ctx.helper.round(this.ctx.helper.mul(data.quantity, mbInfo.m_spread), 2);
+                data.m_tp = this.ctx.helper.round(this.ctx.helper.mul(data.quantity, mbInfo.m_spread), this.ctx.material.decimal.tp);
+                data.m_tax_tp = this.ctx.helper.round(this.ctx.helper.mul(data.m_tp, (1 + this.ctx.helper.div(mbInfo.m_tax, 100))), this.ctx.material.decimal.tp);
                 await transaction.update(this.tableName, data);
                 let m_tp = this.ctx.material.m_tp;
                 if (mbInfo.quantity !== data.quantity) {
@@ -353,6 +354,55 @@ module.exports = app => {
             await transaction.update(this.ctx.service.material.tableName, updateData2);
             return tp.total_price;
         }
+
+        // 小数位变化更新单价和金额
+        async resetDecimal(transaction, newDecimalUp, newDecimalTp) {
+            const mbList = await transaction.select(this.tableName, { where: { tid: this.ctx.tender.id }, orders: [['order', 'asc']] });
+            const updateList = [];
+            const material_month = this.ctx.material.months ? this.ctx.material.months.split(',') : [];
+            const updateMonthList = [];
+            for (const mb of mbList) {
+                const updateData = {
+                    id: mb.id,
+                };
+                if (newDecimalUp !== this.ctx.material.decimal.up) {
+                    let newmsg_tp = this.ctx.helper.round(mb.msg_tp, newDecimalUp);
+                    mb.msg_tp = newmsg_tp;
+                    // 判断是否有月信息价,如果有则msg_tp值由月信息价的平均单价获得,并更新月信息价单价
+                    if (material_month.length > 0) {
+                        const monthList = await transaction.select(this.ctx.service.materialMonth.tableName, { where: { mb_id: mb.id, mid: this.ctx.material.id } });
+                        if (monthList.length !== 0) {
+                            for (const m of monthList) {
+                                // 更新月信息单价小数位
+                                const newMonthMsgTP = this.ctx.helper.round(m.msg_tp, newDecimalUp);
+                                if (m.msg_tp && newMonthMsgTP !== m.msg_tp) {
+                                    m.msg_tp = newMonthMsgTP;
+                                    updateMonthList.push({ id: m.id, msg_tp: m.msg_tp });
+                                }
+                            }
+                            const mb_msg_tp_sum = this._.sumBy(monthList, 'msg_tp');
+                            const month_num = material_month.length - this.ctx.helper.arrayCount(this._.map(monthList, 'msg_tp'), [null, '', 0]);
+                            newmsg_tp = month_num !== 0 ? this.ctx.helper.round(this.ctx.helper.div(mb_msg_tp_sum, month_num), newDecimalUp) : null;
+                            mb.msg_tp = newmsg_tp;
+                        }
+                    }
+                    const newbasic_price = this.ctx.helper.round(mb.basic_price, newDecimalUp);
+                    mb.basic_price = newbasic_price;
+                    const [newmsg_spread, newm_spread] = await this.getSpread(mb, mb.msg_tp, newDecimalUp);
+                    mb.m_spread = newm_spread;
+                    updateData.basic_price = newbasic_price;
+                    updateData.msg_tp = newmsg_tp;
+                    updateData.msg_spread = newmsg_spread;
+                    updateData.m_spread = newm_spread;
+                }
+                const newTp = this.ctx.helper.round(this.ctx.helper.mul(mb.quantity, mb.m_spread), newDecimalTp);
+                updateData.m_tp = newTp;
+                updateData.m_tax_tp = this.ctx.helper.round(this.ctx.helper.mul(newTp, (1 + this.ctx.helper.div(mb.m_tax, 100))), newDecimalTp);
+                updateList.push(updateData);
+            }
+            if (updateMonthList.length > 0) await transaction.updateRows(this.ctx.service.materialMonth.tableName, updateMonthList);
+            if (updateList.length > 0) await transaction.updateRows(this.tableName, updateList);
+        }
     }
 
     return MaterialBills;

+ 67 - 0
app/service/material_checklist.js

@@ -0,0 +1,67 @@
+'use strict';
+/**
+ * 清单设置 数据模型
+ * @author LanJianRong
+ * @date 2020/6/30
+ * @version
+ */
+
+module.exports = app => {
+    class MaterialChecklist extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'material_checklist';
+        }
+
+        async resetData(pushData, removeData, updateData) {
+            if (!this.ctx.tender || !this.ctx.material) {
+                throw '数据错误';
+            }
+            const transaction = await this.db.beginTransaction();
+            try {
+                if (pushData.length > 0) {
+                    const insertDatas = [];
+                    for (const p of pushData) {
+                        p.mid = this.ctx.material.id;
+                        p.tid = this.ctx.tender.id;
+                        insertDatas.push(p);
+                    }
+                    await transaction.insert(this.tableName, insertDatas);
+                }
+                if (removeData.length > 0) {
+                    for (const r of removeData) {
+                        await transaction.delete(this.tableName, { id: r });
+                    }
+                }
+                if (updateData.length > 0) {
+                    await transaction.updateRows(this.tableName, updateData);
+                }
+                await transaction.commit();
+                const materialChecklistData = await this.getAllDataByCondition({ where: { tid: this.ctx.tender.id } });
+                const self = this;
+                return await materialChecklistData.sort(function(a, b) {
+                    return self.ctx.helper.compareCode(a.b_code, b.b_code);
+                });
+            } catch (err) {
+                console.log(err);
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        async updateHadBills(transaction, id, had_bills) {
+            if (!this.ctx.tender || !this.ctx.material) {
+                throw '数据错误';
+            }
+            return await transaction.update(this.tableName, { id, had_bills });
+        }
+    }
+    return MaterialChecklist;
+};
+

+ 4 - 4
app/service/material_exponent.js

@@ -188,7 +188,7 @@ module.exports = app => {
         }
 
         // 更改计算总指数金额并返回值和公式
-        async calcMaterialExTp(transaction, ex_calc = (this.ctx.material.ex_calc ? JSON.parse(this.ctx.material.ex_calc) : null), mid = null) {
+        async calcMaterialExTp(transaction, ex_calc = (this.ctx.material.ex_calc ? JSON.parse(this.ctx.material.ex_calc) : null), mid = null, decimal = (this.ctx.material.decimal ? this.ctx.material.decimal : materialConst.decimal)) {
             let basic_calc = 0;
             if (ex_calc) {
                 for (const calc of ex_calc) {
@@ -213,7 +213,7 @@ module.exports = app => {
             }
             expr += '-1]';
             expr = constant !== 0 ? expr : null;
-            const ex_tp = constant !== 0 ? this.ctx.helper.round(this.ctx.helper.mul(basic_calc, this.ctx.helper.sub(this.ctx.helper.add(constant, sumByChange), 1)), 2) : null;
+            const ex_tp = constant !== 0 ? this.ctx.helper.round(this.ctx.helper.mul(basic_calc, this.ctx.helper.sub(this.ctx.helper.add(constant, sumByChange), 1)), decimal.tp) : null;
             await transaction.update(this.ctx.service.material.tableName, {
                 id: mid ? mid : this.ctx.material.id,
                 ex_tp,
@@ -230,7 +230,7 @@ module.exports = app => {
          * @param mid
          * @returns {Promise<number>}
          */
-        async updateNewMaterial(transaction, mid, ctx, stage_id, ex_calc) {
+        async updateNewMaterial(transaction, mid, ctx, stage_id, ex_calc, decimal) {
             const stage_list = await ctx.service.stage.getStageMsgByStageId(stage_id);
             const calcBase = await ctx.service.stage.getMaterialCalcBase(stage_list, ctx.tender.info);
             const old_ex_calc = ex_calc ? JSON.parse(ex_calc) : null;
@@ -241,7 +241,7 @@ module.exports = app => {
                 bq.value = calc.value;
                 bq.select = oldcalc ? oldcalc.select : false;
             }
-            await this.calcMaterialExTp(transaction, new_ex_calc, mid);
+            await this.calcMaterialExTp(transaction, new_ex_calc, mid, decimal);
             return new_ex_calc;
         }
     }

+ 12 - 6
app/service/material_list.js

@@ -216,13 +216,13 @@ module.exports = app => {
             const sqlParam = [this.ctx.material.id, mb_id];
             const mb_quantity = await transaction.queryOne(sql, sqlParam);
             console.log(mb_quantity);
-            const newQuantity = this.ctx.helper.round(mb_quantity.quantity, 3);
-            const newTp = this.ctx.helper.round(this.ctx.helper.mul(newQuantity, mbInfo.m_spread), 2);
+            const newQuantity = this.ctx.helper.round(mb_quantity.quantity, this.ctx.material.decimal.qty);
+            const newTp = this.ctx.helper.round(this.ctx.helper.mul(newQuantity, mbInfo.m_spread), this.ctx.material.decimal.tp);
             const updateData = {
                 id: mb_id,
-                quantity: this.ctx.helper.round(mb_quantity.quantity, 3),
+                quantity: this.ctx.helper.round(mb_quantity.quantity, this.ctx.material.decimal.qty),
                 m_tp: newTp,
-                m_tax_tp: this.ctx.helper.round(this.ctx.helper.mul(newTp, (1 + this.ctx.helper.div(mbInfo.m_tax, 100))), 2),
+                m_tax_tp: this.ctx.helper.round(this.ctx.helper.mul(newTp, (1 + this.ctx.helper.div(mbInfo.m_tax, 100))), this.ctx.material.decimal.tp),
             };
             await transaction.update(this.ctx.service.materialBills.tableName, updateData);
             // 计算本期总金额
@@ -296,7 +296,7 @@ module.exports = app => {
          * 添加工料清单关联(多清单对应)
          * @return {void}
          */
-        async adds(datas) {
+        async adds(datas, checklist = false) {
             if (!this.ctx.tender || !this.ctx.material) {
                 throw '数据错误';
             }
@@ -344,6 +344,9 @@ module.exports = app => {
                         throw '新增工料数据失败';
                     }
                 }
+                if (checklist) {
+                    await this.ctx.service.materialChecklist.updateHadBills(transaction, checklist.id, checklist.had_bills);
+                }
                 // 重算工料和总金额
                 // const calcMBIdList = this._.uniq(mb_idList);
                 // if (calcMBIdList.length > 0) {
@@ -364,7 +367,7 @@ module.exports = app => {
          * @param {int} id 工料id
          * @return {void}
          */
-        async dels(datas) {
+        async dels(datas, checklist = false) {
             if (!this.ctx.tender || !this.ctx.material) {
                 throw '数据错误';
             }
@@ -376,6 +379,9 @@ module.exports = app => {
                 }
                 // await transaction.delete(this.tableName, { id });
                 await this.calcQuantityByML(transaction, datas.mb_id);
+                if (checklist) {
+                    await this.ctx.service.materialChecklist.updateHadBills(transaction, checklist.id, checklist.had_bills);
+                }
                 await transaction.commit();
                 // console.log(datas);
                 return await this.getMaterialData(this.ctx.tender.id, this.ctx.material.id);

+ 12 - 12
app/service/material_month.js

@@ -61,16 +61,16 @@ module.exports = app => {
                         if (monthList.length !== 0) {
                             const mb_msg_tp_sum = this._.sumBy(this._.filter(monthList, { mb_id: mb.id }), 'msg_tp');
                             const month_num = material_month.length - this.ctx.helper.arrayCount(this._.concat(this._.map(this._.filter(monthList, { mb_id: mb.id }), 'msg_tp'), one_month.msg_tp), [null, '', 0]);
-                            const new_msg_tp = this.ctx.helper.round(this.ctx.helper.div(this.ctx.helper.add(mb_msg_tp_sum, one_month.msg_tp), month_num), 3);
+                            const new_msg_tp = this.ctx.helper.round(this.ctx.helper.div(this.ctx.helper.add(mb_msg_tp_sum, one_month.msg_tp), month_num), this.ctx.material.decimal.up);
                             const [newmsg_spread, newm_spread] = await this.ctx.service.materialBills.getSpread(mb, new_msg_tp);
-                            const newTp = this.ctx.helper.round(this.ctx.helper.mul(mb.quantity, newm_spread), 2);
+                            const newTp = this.ctx.helper.round(this.ctx.helper.mul(mb.quantity, newm_spread), this.ctx.material.decimal.tp);
                             updateArray.push({
                                 id: mb.id,
                                 msg_tp: new_msg_tp,
                                 msg_spread: newmsg_spread,
                                 m_spread: newm_spread,
                                 m_tp: newTp,
-                                m_tax_tp: this.ctx.helper.round(this.ctx.helper.mul(newTp, (1 + this.ctx.helper.div(mb.m_tax, 100))), 2),
+                                m_tax_tp: this.ctx.helper.round(this.ctx.helper.mul(newTp, (1 + this.ctx.helper.div(mb.m_tax, 100))), this.ctx.material.decimal.tp),
                             });
                         }
                     }
@@ -111,16 +111,16 @@ module.exports = app => {
                         });
                         const mb_msg_tp_sum = this._.sumBy(this._.filter(monthList, { mb_id: mb.id }), 'msg_tp');
                         const month_num = material_month.length - this.ctx.helper.arrayCount(this._.map(this._.filter(monthList, { mb_id: mb.id }), 'msg_tp'), [null, '', 0]);
-                        const new_msg_tp = month_num !== 0 ? this.ctx.helper.round(this.ctx.helper.div(mb_msg_tp_sum, month_num), 3) : null;
+                        const new_msg_tp = month_num !== 0 ? this.ctx.helper.round(this.ctx.helper.div(mb_msg_tp_sum, month_num), this.ctx.material.decimal.up) : null;
                         const [newmsg_spread, newm_spread] = await this.ctx.service.materialBills.getSpread(mb, new_msg_tp);
-                        const newTp = this.ctx.helper.round(this.ctx.helper.mul(mb.quantity, newm_spread), 2);
+                        const newTp = this.ctx.helper.round(this.ctx.helper.mul(mb.quantity, newm_spread), this.ctx.material.decimal.tp);
                         updateArray.push({
                             id: mb.id,
                             msg_tp: new_msg_tp,
                             msg_spread: newmsg_spread,
                             m_spread: newm_spread,
                             m_tp: newTp,
-                            m_tax_tp: this.ctx.helper.round(this.ctx.helper.mul(newTp, (1 + this.ctx.helper.div(mb.m_tax, 100))), 2),
+                            m_tax_tp: this.ctx.helper.round(this.ctx.helper.mul(newTp, (1 + this.ctx.helper.div(mb.m_tax, 100))), this.ctx.material.decimal.tp),
                         });
                     }
                     if (updateArray.length !== 0) await transaction.updateRows(this.ctx.service.materialBills.tableName, updateArray);
@@ -153,16 +153,16 @@ module.exports = app => {
                 if (monthList.length !== 0) {
                     const mb_msg_tp_sum = this._.sumBy(monthList, 'msg_tp');
                     const month_num = material_month.length - this.ctx.helper.arrayCount(this._.map(monthList, 'msg_tp'), [null, '', 0]);
-                    const new_msg_tp = month_num !== 0 ? this.ctx.helper.round(this.ctx.helper.div(mb_msg_tp_sum, month_num), 3) : null;
+                    const new_msg_tp = month_num !== 0 ? this.ctx.helper.round(this.ctx.helper.div(mb_msg_tp_sum, month_num), this.ctx.material.decimal.up) : null;
                     const [newmsg_spread, newm_spread] = await this.ctx.service.materialBills.getSpread(mbInfo, new_msg_tp);
-                    const newTp = this.ctx.helper.round(this.ctx.helper.mul(mbInfo.quantity, newm_spread), 2);
+                    const newTp = this.ctx.helper.round(this.ctx.helper.mul(mbInfo.quantity, newm_spread), this.ctx.material.decimal.tp);
                     await transaction.update(this.ctx.service.materialBills.tableName, {
                         id: mbInfo.id,
                         msg_tp: new_msg_tp,
                         msg_spread: newmsg_spread,
                         m_spread: newm_spread,
                         m_tp: newTp,
-                        m_tax_tp: this.ctx.helper.round(this.ctx.helper.mul(newTp, (1 + this.ctx.helper.div(mbInfo.m_tax, 100))), 2),
+                        m_tax_tp: this.ctx.helper.round(this.ctx.helper.mul(newTp, (1 + this.ctx.helper.div(mbInfo.m_tax, 100))), this.ctx.material.decimal.tp),
                     });
                 }
                 const m_tp = await this.ctx.service.materialBills.calcMaterialMTp(transaction);
@@ -207,16 +207,16 @@ module.exports = app => {
                     for (const mb of mbList) {
                         const mb_msg_tp_sum = this._.sumBy(this._.filter(monthList, { mb_id: mb.id }), 'msg_tp');
                         const month_num = material_month.length - this.ctx.helper.arrayCount(this._.map(this._.filter(monthList, { mb_id: mb.id }), 'msg_tp'), [null, '', 0]);
-                        const new_msg_tp = month_num !== 0 ? this.ctx.helper.round(this.ctx.helper.div(mb_msg_tp_sum, month_num), 3) : null;
+                        const new_msg_tp = month_num !== 0 ? this.ctx.helper.round(this.ctx.helper.div(mb_msg_tp_sum, month_num), this.ctx.material.decimal.up) : null;
                         const [newmsg_spread, newm_spread] = await this.ctx.service.materialBills.getSpread(mb, new_msg_tp);
-                        const newTp = this.ctx.helper.round(this.ctx.helper.mul(mb.quantity, newm_spread), 2);
+                        const newTp = this.ctx.helper.round(this.ctx.helper.mul(mb.quantity, newm_spread), this.ctx.material.decimal.tp);
                         mbUpdateArray.push({
                             id: mb.id,
                             msg_tp: new_msg_tp,
                             msg_spread: newmsg_spread,
                             m_spread: newm_spread,
                             m_tp: newTp,
-                            m_tax_tp: this.ctx.helper.round(this.ctx.helper.mul(newTp, (1 + this.ctx.helper.div(mb.m_tax, 100))), 2),
+                            m_tax_tp: this.ctx.helper.round(this.ctx.helper.mul(newTp, (1 + this.ctx.helper.div(mb.m_tax, 100))), this.ctx.material.decimal.tp),
                         });
                     }
                     if (mbUpdateArray.length !== 0) await transaction.updateRows(this.ctx.service.materialBills.tableName, mbUpdateArray);

+ 7 - 8
app/service/pos.js

@@ -37,14 +37,11 @@ module.exports = app => {
 
         async getPosDataWithAddStageOrder(condition) {
             if (!condition.tid) throw '查询计量单元缺少必要信息';
-            const sql = 'SELECT p.id, p.tid, p.lid, p.name, p.quantity, p.position, p.drawing_code,' +
-                '    p.sgfh_qty, p.sjcl_qty, p.qtcl_qty, p.porder, p.add_stage, p.add_times, p.add_user, s.order As add_stage_order,' +
-                '    p.sgfh_expr, p.sjcl_expr, p.qtcl_expr, p.real_qty, p.gxby_status, p.gxby_url, p.dagl_status, p.dagl_url,' +
-                '    p.gxby_limit, p.dagl_limit, p.ex_memo1, p.ex_memo2, p.ex_memo3' +
-                '  FROM ' + this.departTableName(condition.tid) + ' p ' +
-                '  LEFT JOIN ' + this.ctx.service.stage.tableName + ' s' +
-                '  ON p.add_stage = s.id'
-                + this.ctx.helper.whereSql(condition, 'p');
+            const sql = 'SELECT id, tid, lid, name, quantity, position, drawing_code,' +
+                '    sgfh_qty, sjcl_qty, qtcl_qty, porder, add_stage, add_times, add_user, add_stage_order,' +
+                '    sgfh_expr, sjcl_expr, qtcl_expr, real_qty, gxby_status, gxby_url, dagl_status, dagl_url,' +
+                '    gxby_limit, dagl_limit, ex_memo1, ex_memo2, ex_memo3' +
+                '  FROM ' + this.departTableName(condition.tid) + this.ctx.helper.whereSql(condition);
             return await this.db.query(sql);
         }
 
@@ -84,6 +81,7 @@ module.exports = app => {
             data.tid = tid;
             // todo 新增期
             data.add_stage = 0;
+            data.add_stage_order = 0;
             data.add_times = 0;
             data.in_time = new Date();
             data.add_user = this.ctx.session.sessionUser.accountId;
@@ -104,6 +102,7 @@ module.exports = app => {
             data.tid = tid;
             // todo 新增期
             data.add_stage = 0;
+            data.add_stage_order = 0;
             data.add_times = 0;
             data.in_time = new Date();
             data.add_user = this.ctx.session.sessionUser.accountId;

+ 52 - 0
app/service/project.js

@@ -191,6 +191,58 @@ module.exports = app => {
             });
             return result.affectedRows === 1;
         }
+
+        /**
+         * 校验项目是否存在(项目管理)
+         * @param {String} token - token
+         * @return {Promise} Promise
+         */
+        verifyManagementProject(token) {
+            return new Promise((resolve, reject) => {
+                this.ctx.curl(`${app.config.managementProxyPath}/api/external/jl/calibration`, {
+                    method: 'POST',
+                    // dateType: 'json',
+                    encoding: 'utf8',
+                    data: {
+                        token,
+                    },
+                    // timeout: 2000,
+                }).then(({ status, data }) => {
+                    if (status === 200) {
+                        const result = JSON.parse(data.toString()).data;
+                        return resolve(result);
+                    }
+                    return reject(new Error('校验失败'));
+                }).catch(err => {
+                    console.log(err);
+                    return reject(err);
+                });
+            });
+        }
+
+        /**
+         * 创建项目和账号(项目管理)
+         * @return {Promise} Promise
+         */
+        async addProjectFromManagement() {
+            const token = this.ctx.helper.createJWT({ account: this.ctx.session.sessionUser.account, code: this.ctx.session.sessionProject.code });
+            return new Promise((resolve, reject) => {
+                this.ctx.curl(`${app.config.managementProxyPath}/api/external/jl/project/add`, {
+                    method: 'POST',
+                    encoding: 'utf8',
+                    data: { token },
+                }).then(({ status, data }) => {
+                    if (status === 200) {
+                        const result = JSON.parse(data.toString());
+                        return resolve(result);
+                    }
+                    return reject(new Error('添加失败'));
+                }).catch(err => {
+                    console.log(err);
+                    return reject(err);
+                });
+            });
+        }
     }
 
     return Project;

+ 64 - 0
app/service/project_account.js

@@ -369,6 +369,24 @@ module.exports = app => {
             return info;
         }
 
+        async getAccountInfoByAccountWithPid(account, project_id) {
+            if (!account || !project_id) throw new Error('参数错误');
+            this.initSqlBuilder();
+            this.sqlBuilder.columns = ['account', 'name', 'company', 'role', 'is_admin', 'enable', 'telephone', 'mobile', 'account_group'];
+            this.sqlBuilder.setAndWhere('account', {
+                operate: '=',
+                value: `"${account}"`,
+            });
+            this.sqlBuilder.setAndWhere('project_id', {
+                operate: '=',
+                value: project_id,
+            });
+
+            const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'select');
+            const info = await this.db.queryOne(sql, sqlParam);
+
+            return info;
+        }
         async getListByProjectId(columns = '', pid) {
             this.initSqlBuilder();
             this.sqlBuilder.columns = columns !== '' ? columns : ['id', 'account', 'name', 'company', 'role', 'mobile', 'auth_mobile', 'telephone', 'enable', 'is_admin', 'account_group'];
@@ -565,6 +583,10 @@ module.exports = app => {
                 if (accountData.account === undefined) {
                     throw '不存在对应账号';
                 }
+                const projectData = await this.ctx.service.project.getProjectById(accountData.project_id);
+                if (!projectData) {
+                    throw '不存在对应项目';
+                }
                 // 加密密码
                 const encryptPassword = account ? crypto.createHmac('sha1', account).update(password)
                     .digest().toString('base64') : crypto.createHmac('sha1', accountData.account).update(password)
@@ -597,6 +619,11 @@ module.exports = app => {
                     }, SmsAliConst.template.mmcz);
                 }
 
+                // 判断是否更改了账号
+                if (accountData.account !== account) {
+                    await this.syncAccount(projectData.code, accountData.account, account);
+                }
+
                 this.transaction.commit();
             } catch (error) {
                 this.transaction.rollback();
@@ -795,6 +822,43 @@ module.exports = app => {
 
             return result;
         }
+
+        /**
+         * 获取项目下所有账号
+         * @param {String} project_id - 项目id
+         * @return {Promise<Array>} - 账号
+         */
+        async getAllProjectAccountByPid(project_id) {
+            const sql = 'Select `account`, `name`, `company`, `role`, `mobile`, `telephone`, `is_admin` as `isAdmin`, `account_group` as `accountGroup` From ' + this.tableName + ' where project_id = ?';
+            return await this.db.query(sql, [project_id]);
+        }
+
+        /**
+         * 同步修改项目管理的账号
+         * @param {String} code - 项目编号
+         * @param {String} account - 旧账号
+         * @param {String} newAccount - 新账号
+         * @return {Promise} -
+         */
+        async syncAccount(code, account, newAccount) {
+            return new Promise(resolve => {
+                this.ctx.curl(`${app.config.managementProxyPath}/api/external/jl/account/update`, {
+                    method: 'POST',
+                    data: {
+                        token: this.ctx.helper.createJWT({ code, account, newAccount }),
+                    },
+                }).then(({ status, data }) => {
+                    if (status === 200) {
+                        const result = JSON.parse(data.toString());
+                        if (!result || result.code !== 0) {
+                            return resolve();
+                        }
+                        return resolve();
+                    }
+                    return resolve();
+                });
+            });
+        }
     }
 
     return ProjectAccount;

+ 5 - 6
app/service/revise_audit.js

@@ -231,7 +231,7 @@ module.exports = app => {
             const time = new Date();
 
             // 拷贝备份台账数据
-            const [billsHis, posHis] = await this.ctx.service.ledgerRevise.backupReviseHistoryFile(revise);
+            const his_id = await this.ctx.service.ledgerHistory.backupReviseLedgerHistory(revise);
 
             const transaction = await this.db.beginTransaction();
             try {
@@ -245,8 +245,7 @@ module.exports = app => {
                 const reviseData = {
                     id: revise.id,
                     status: auditConst.status.checking,
-                    bills_file: billsHis,
-                    pos_file: posHis,
+                    his_id,
                 };
                 if (revise.times === 1) {
                     reviseData.begin_time = time;
@@ -322,12 +321,12 @@ module.exports = app => {
             const pSql =
                 'Insert Into ' +
                 this.ctx.service.pos.tableName +
-                '  (id, tid, lid, name, drawing_code, quantity, add_stage, add_times, add_user,' +
+                '  (id, tid, lid, name, drawing_code, quantity, add_stage, add_stage_order, add_times, add_user,' +
                 '     sgfh_qty, sjcl_qty, qtcl_qty, crid, porder, position, ' +
                 '     sgfh_expr, sjcl_expr, qtcl_expr, real_qty,' +
                 '     gxby_status, dagl_status, gxby_url, dagl_url, gxby_limit,  dagl_limit,' +
                 '     ex_memo1, ex_memo2, ex_memo3)' +
-                '  Select id, tid, lid, name, drawing_code, quantity, add_stage, add_times, add_user,' +
+                '  Select id, tid, lid, name, drawing_code, quantity, add_stage, add_stage_order, add_times, add_user,' +
                 '     sgfh_qty, sjcl_qty, qtcl_qty, crid, porder, position,' +
                 '     sgfh_expr, sjcl_expr, qtcl_expr, real_qty,' +
                 '     gxby_status, dagl_status, gxby_url, dagl_url, gxby_limit,  dagl_limit,' +
@@ -653,7 +652,7 @@ module.exports = app => {
         async getNoticeContent(pid, tid, rid, uid) {
             const noticeSql =
                 'SELECT * FROM (SELECT ' +
-                '  t.`id` As `tid`, t.`name`, r.`corder`, pa.`name` As `su_name`, pa.role As `su_role`' +
+                '  t.`id` As `tid`, t.`name`, r.`corder`, r.`id` As rid, pa.`name` As `su_name`, pa.role As `su_role`' +
                 '  FROM (SELECT * FROM ?? WHERE `id` = ? ) As t' +
                 '  LEFT JOIN ?? As r On r.`id` = ? ' +
                 '  LEFT JOIN ?? As pa ON pa.`id` = ? ' +

+ 2 - 2
app/service/revise_bills.js

@@ -95,7 +95,7 @@ module.exports = app => {
                 for (const p of data[i].pos) {
                     const inP = {
                         id: this.uuid.v4(), tid: tid, lid: qd.id, crid: rid,
-                        add_stage: 0, add_times: 0, add_user: this.ctx.session.sessionUser.accountId,
+                        add_stage: 0, add_stage_order: 0, add_times: 0, add_user: this.ctx.session.sessionUser.accountId,
                         name: p.name, drawing_code: p.drawing_code, porder: p.porder,
                     };
                     if (p.quantity) {
@@ -195,7 +195,7 @@ module.exports = app => {
                 for (const p of data[i].pos) {
                     const inP = {
                         id: this.uuid.v4(), tid: tid, lid: qd.id, crid: rid,
-                        add_stage: 0, add_times: 0, add_user: this.ctx.session.sessionUser.accountId,
+                        add_stage: 0, add_stage_order: 0, add_times: 0, add_user: this.ctx.session.sessionUser.accountId,
                         name: p.name, drawing_code: p.drawing_code, porder: p.porder,
                     };
                     if (p.quantity) {

+ 7 - 8
app/service/revise_pos.js

@@ -24,14 +24,11 @@ module.exports = app => {
         }
 
         async getPosData(condition) {
-            const sql = 'SELECT p.id, p.tid, p.lid, p.name, p.quantity, p.position, p.drawing_code, p.sgfh_qty, ' +
-                '    p.sjcl_qty, p.qtcl_qty, p.porder, p.add_stage, p.add_times, p.add_user, s.order As add_stage_order,' +
-                '    p.sgfh_expr, p.sjcl_expr, p.qtcl_expr, p.ex_memo1, p.ex_memo2, p.ex_memo3 ' +
-                '  FROM ' + this.tableName + ' p ' +
-                '  LEFT JOIN ' + this.ctx.service.stage.tableName + ' s' +
-                '  ON add_stage = s.id'
-                + this.ctx.helper.whereSql(condition, 'p') +
-                '  ORDER By p.porder ASC';
+            const sql = 'SELECT id, tid, lid, name, quantity, position, drawing_code, sgfh_qty, ' +
+                '    sjcl_qty, qtcl_qty, porder, add_stage, add_times, add_user, add_stage_order,' +
+                '    sgfh_expr, sjcl_expr, qtcl_expr, ex_memo1, ex_memo2, ex_memo3 ' +
+                '  FROM ' + this.tableName + this.ctx.helper.whereSql(condition) +
+                '  ORDER By porder ASC';
             return await this.db.query(sql);
         }
 
@@ -80,6 +77,7 @@ module.exports = app => {
             data.tid = tid;
             // todo 新增期
             data.add_stage = 0;
+            data.add_stage_order = 0;
             data.add_times = 0;
             data.in_time = new Date();
             data.add_user = this.ctx.session.sessionUser.accountId;
@@ -97,6 +95,7 @@ module.exports = app => {
             data.tid = tid;
             // todo 新增期
             data.add_stage = 0;
+            data.add_stage_order = 0;
             data.add_times = 0;
             data.in_time = new Date();
             data.add_user = this.ctx.session.sessionUser.accountId;

+ 4 - 0
app/service/rpt_gather_memory.js

@@ -25,12 +25,14 @@ const gatherUtils = {
             for (const cd of completeDatas) {
                 data[cd.prefix + 'id'] = cd.id;
                 data[cd.prefix + 'name'] = cd.name;
+                data[cd.prefix + 'category'] = cd.category;
             }
         }
     },
     gatherStage: function (tender, gatherNode, sourceNode, prefix, helper) {
         gatherNode[prefix + 'id'] = tender.id;
         gatherNode[prefix + 'name'] = tender.name;
+        gatherNode[prefix + 'category'] = tender.category;
 
         gatherNode[prefix + "qty"] = helper.add(gatherNode[prefix + "qty"], sourceNode.quantity);
         gatherNode[prefix + "tp"] = helper.add(gatherNode[prefix + "tp"], sourceNode.total_price);
@@ -99,6 +101,7 @@ const gatherUtils = {
     gatherZone: function (tender, gatherNode, sourceNode, prefix, helper) {
         gatherNode[prefix + 'id'] = tender.id;
         gatherNode[prefix + 'name'] = tender.name;
+        gatherNode[prefix + 'category'] = tender.category;
 
         gatherNode[prefix + "qty"] = helper.add(gatherNode[prefix + "qty"], sourceNode.quantity);
         gatherNode[prefix + "tp"] = helper.add(gatherNode[prefix + "tp"], sourceNode.total_price);
@@ -131,6 +134,7 @@ const gatherUtils = {
     gatherLedger: function (tender, gatherNode, sourceNode, prefix, helper) {
         gatherNode[prefix + 'id'] = tender.id;
         gatherNode[prefix + 'name'] = tender.name;
+        gatherNode[prefix + 'category'] = tender.category;
 
         gatherNode[prefix + "qty"] = helper.add(gatherNode[prefix + "qty"], sourceNode.quantity);
         gatherNode[prefix + "tp"] = helper.add(gatherNode[prefix + "tp"], sourceNode.total_price);

+ 2 - 0
app/service/stage_audit.js

@@ -470,6 +470,7 @@ module.exports = app => {
                     await this.ctx.helper.sendWechat(nextAudit.aid, smsTypeConst.const.JL, smsTypeConst.judge.approval.toString(), wxConst.template.stage, wechatData);
                 } else {
                     await this.ctx.service.tenderTag.saveTenderTag(this.ctx.tender.id, {stage_time: new Date()}, transaction);
+                    const ledgerHis = await this.ctx.service.ledgerHistory.getLatestHistory(this.ctx.tender.id);
                     // 本期结束
                     // 生成截止本期数据 final数据
                     await this.ctx.service.stageBillsFinal.generateFinalData(transaction, this.ctx.tender, this.ctx.stage);
@@ -485,6 +486,7 @@ module.exports = app => {
                         sf_tp: sfPay.tp,
                         tp_history: JSON.stringify(this.ctx.stage.tp_history),
                         cache_time_r: this.ctx.stage.cache_time_l,
+                        his_id: ledgerHis.id,
                     });
 
                     // 添加短信通知-审批通过提醒功能

+ 3 - 3
app/service/stage_pay.js

@@ -207,6 +207,7 @@ module.exports = app => {
                 if (data.expr !== undefined) { updateData.expr = data.expr }
                 if (data.tp !== undefined) { updateData.tp = data.tp }
                 if (data.pause !== undefined) { updateData.pause = data.pause }
+                if (data.postil !== undefined) updateData.postil = data.postil;
                 updateDatas.push(updateData);
             }
             const result = await this.db.updateRows(this.tableName, updateDatas);
@@ -229,7 +230,6 @@ module.exports = app => {
             const PayCalculator = require('../lib/pay_calc');
             const payCalculator = new PayCalculator(this.ctx, stage, this.ctx.tender.info);
             await payCalculator.calculateAll(stagePays);
-            console.log(stagePays);
             const srUpdate = [], update = [];
             for (const sp of stagePays) {
                 update.push({
@@ -289,9 +289,9 @@ module.exports = app => {
                 throw '数据错误';
             }
             const sql = 'INSERT INTO ?? (`tid`, `sid`, `pid`, `stimes`, `sorder`, `name`, `tp`, `expr`, `pause`,' +
-                        '    `pre_tp`, `end_tp`, `pre_used`, `pre_finish`, `start_stage_order`) ' +
+                        '    `pre_tp`, `end_tp`, `pre_used`, `pre_finish`, `start_stage_order`, `postil`) ' +
                         '  SELECT SP.`tid`, SP.`sid`, SP.`pid`, ?, ?, SP.name, SP.`tp`, SP.`expr`, SP.`pause`,' +
-                        '     SP.`pre_tp`, SP.`end_tp`, SP.`pre_used`, SP.`pre_finish`, SP.`start_stage_order` ' +
+                        '     SP.`pre_tp`, SP.`end_tp`, SP.`pre_used`, SP.`pre_finish`, SP.`start_stage_order`, SP.`postil` ' +
                         '  FROM ?? As SP' +
                         '  WHERE SP.`sid` = ? AND SP.`stimes` = ? AND SP.`sorder` = ?';
             const sqlParam = [this.tableName, times, order, this.tableName, stage.id, stage.curTimes, stage.curOrder];

+ 1 - 1
app/service/stage_pos.js

@@ -164,7 +164,7 @@ module.exports = app => {
             for (const d of datas) {
                 const p = {
                     id: this.uuid.v4(), tid: this.ctx.tender.id, lid: d.lid, name: d.name, porder: d.porder, position: d.position,
-                    add_stage: this.ctx.stage.id,
+                    add_stage: this.ctx.stage.id, add_stage_order: this.ctx.stage.order,
                     add_times: this.ctx.stage.curTimes,
                     add_user: this.ctx.session.sessionUser.accountId,
                 };

+ 5 - 1
app/service/tender.js

@@ -15,7 +15,7 @@ const fs = require('fs');
 const path = require('path');
 const commonQueryColumns = [
     'id', 'project_id', 'name', 'status', 'category', 'ledger_times', 'ledger_status', 'measure_type', 'user_id', 'valuation',
-    'total_price', 'deal_tp', 'copy_id', 's2b_gxby_check', 's2b_gxby_limit', 's2b_dagl_check', 's2b_dagl_limit', 'has_rela',
+    'total_price', 'deal_tp', 'copy_id', 's2b_gxby_check', 's2b_gxby_limit', 's2b_dagl_check', 's2b_dagl_limit', 'has_rela', 'his_id',
 ];
 
 module.exports = app => {
@@ -111,6 +111,8 @@ module.exports = app => {
                 // 根据用户权限查阅标段
                 // tender 163条数据,project_account 68条数据测试
                 // 查询两张表耗时0.003s,查询tender左连接project_account耗时0.002s
+                const changeProjectSql = this.ctx.session.sessionProject.openChangeProject ? '    OR (t.`ledger_status` = ' + auditConst.ledger.status.checked + ' AND ' +
+                    '        t.id IN ( SELECT cpa.`tid` FROM ' + this.ctx.service.changeProjectAudit.tableName + ' AS cpa WHERE cpa.`aid` = ' + session.sessionUser.accountId + ' GROUP BY cpa.`tid`))' : '';
                 sql = 'SELECT t.`id`, t.`project_id`, t.`name`, t.`status`, t.`category`, t.`ledger_times`, t.`ledger_status`, t.`measure_type`, t.`user_id`, t.`create_time`, t.`total_price`, t.`deal_tp`,' +
                     '    pa.`name` As `user_name`, pa.`role` As `user_role`, pa.`company` As `user_company` ' +
                     // '  FROM ?? As t, ?? As pa ' +
@@ -138,6 +140,8 @@ module.exports = app => {
                     '        t.id IN ( SELECT ma.`tid` FROM ?? AS ma WHERE ma.`aid` = ? GROUP BY ma.`tid`))' +
                     // 参与审批 预付款 的标段
                     '    OR (t.id IN ( SELECT ad.`tid` FROM ?? AS ad WHERE ad.`audit_id` = ? GROUP BY ad.`tid`))' +
+                    // 参与审批 变更立项书 的标段
+                    changeProjectSql +
                     // 游客权限的标段
                     '    OR (t.id IN ( SELECT tt.`tid` FROM ?? AS tt WHERE tt.`user_id` = ?))' +
                     // 未参与,但可见的标段

+ 1 - 1
app/view/budget/list.ejs

@@ -1,7 +1,7 @@
 <div class="panel-content">
     <div class="panel-title fluid">
         <div class="title-main  d-flex justify-content-between">
-            <div>概算投资</div>
+            <div>动态决算</div>
             <% if (ctx.session.sessionUser.is_admin) { %>
             <div class="ml-auto">
                 <a href="#add-budget" name="add" data-toggle="modal" data-target="#add-budget" class="btn btn-sm btn-primary pull-right">新建项目</a>

+ 3 - 3
app/view/change/information.ejs

@@ -448,7 +448,7 @@
     let changeInfo = Object.assign({}, back_changeInfo);
     let changeUsedData = JSON.parse(unescape('<%- escape(JSON.stringify(changeUsedData)) %>'));
 </script>
-<script src="/public/js/change_information_set.js"></script>
+<script src="/public/js/change_information_set.js?202001181"></script>
 <script src="/public/js/change_audit.js"></script>
 <% } else if (auditStatus === 3 || auditStatus === 4 || auditStatus === 5 || auditStatus === 7 || auditStatus === 8) { %>
 <script>
@@ -456,7 +456,7 @@
     const aidList = _.map(auditList2, 'uid');
     aidList.splice(0, 1);
 </script>
-<script src="/public/js/change_information_show.js"></script>
+<script src="/public/js/change_information_show.js?202001181"></script>
 <% } else if (auditStatus === 6) { %>
 <script>
     const auditList2 = JSON.parse(unescape('<%- escape(JSON.stringify(auditList2)) %>'));
@@ -466,5 +466,5 @@
     const changeLedgerList = JSON.parse(unescape('<%- escape(JSON.stringify(changeLedgerList)) %>'));
     const changePosList = JSON.parse(unescape('<%- escape(JSON.stringify(changePosList)) %>'));
 </script>
-<script src="/public/js/change_information_approval.js"></script>
+<script src="/public/js/change_information_approval.js?202001181"></script>
 <% } %>

+ 110 - 0
app/view/change/project.ejs

@@ -0,0 +1,110 @@
+<% include ../tender/tender_sub_menu.ejs %>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main d-flex">
+            <% include ../tender/tender_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" 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>
+            <% if (tender.user_id === uid || (userPermission !== null && userPermission.tender !== undefined && userPermission.tender.indexOf('5') !== -1)) { %>
+            <div class="ml-auto">
+                <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>
+                    <tr>
+                        <th width="20%" id="sort_change">变更立项书编号</th><th width="30%">变更立项书名称</th>
+                        <th width="10%">发起人</th><th width="10%">发起类型</th><th width="10%">发起时间</th>
+                        <th width="10%">状态</th><th width="10%">操作</th>
+                    </tr>
+                    </thead>
+                    <tbody id="changeList">
+                    <% for (const c of changes) { %>
+                        <tr><td><a href="/tender/<%- tender.id %>/change/project/<%- c.id %>/information"><%- c.code %></a></td>
+                            <td><%- c.name %></td><td><%- c.account_name %></td><td><%- changeConst.project_type[c.type] %></td><td><%- ctx.helper.formatFullDate(c.in_time) %></td>
+                            <td><span class="<%- auditConst.statusClass[c.status] %>"><%- auditConst.statusString[c.status] %></span></td>
+                            <td>
+                                <% if ((c.status === auditConst.status.uncheck || c.status === auditConst.status.back) && c.uid === ctx.session.sessionUser.accountId) { %>
+                                    <a href="/tender/<%- tender.id %>/change/project/<%- c.id %>/information" class="btn <%- auditConst.statusButtonClass[c.status] %> btn-sm"><%- auditConst.statusButton[c.status] %></a>
+                                <% } else if (c.status === auditConst.status.checking && c.curAuditor && c.curAuditor.aid === ctx.session.sessionUser.accountId) { %>
+                                    <a href="/tender/<%- tender.id %>/change/project/<%- c.id %>/information" class="btn <%- auditConst.statusButtonClass[c.status] %> btn-sm"><%- auditConst.statusButton[c.status] %></a>
+                                <% } %>
+                                <% if (c.uid === uid && (c.status === auditConst.status.uncheck || c.status === auditConst.status.back)) { %><a href="#del-bg" data-toggle="modal" data-target="#del-bg" class="btn btn-outline-danger btn-sm">删除</a><% } %>
+                            </td></tr>
+                    <% } %>
+                    <!--<tr><td><a href="biangeng-lixiang-detail.html">BGYX-TJ01-001</a></td><td>关于XX</td><td>仁温书</td><td>变更意向</td><td>2021-10-7</td><td>草稿</td><td><a href="#del-bg" data-toggle="modal" data-target="#del-bg" class="btn btn-outline-danger btn-sm">删除</a></td></tr>-->
+                    <!--<tr><td><a href="#">BGJY-TJ01-002</a></td><td>关于XX</td><td>张云铭</td><td>变更建议</td><td>2021-9-18</td><td><span class="text-success">已同意</span></td><td></td></tr>-->
+                    <!--<tr><td><a href="#">BGJY-TJ01-003</a></td><td>关于XX</td><td>张云铭</td><td>变更建议</td><td>2021-9-18</td><td><span class="text-danger">不同意</span></td><td></td></tr>-->
+                    </tbody>
+                </table>
+                <!--翻页-->
+                <% include ../layout/page.ejs %>
+            </div>
+        </div>
+    </div>
+</div>
+<script>
+    autoFlashHeight();
+    const tenderId = parseInt('<%- 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 %>';
+</script>

+ 135 - 0
app/view/change/project_information.ejs

@@ -0,0 +1,135 @@
+<% include ../tender/tender_sub_menu.ejs %>
+<div class="panel-content">
+    <div class="panel-title"><!--收起详解目录添加类名 fluid -->
+        <div class="title-main d-flex"><!--工具-->
+            <% include ../tender/tender_sub_mini_menu.ejs %>
+            <div>
+                <div class="d-inline-block">
+                    <a href="/tender/<%- tender.id %>/change/project"><i class="fa fa-chevron-left mr-2"></i><span>返回</span></a>
+                </div>
+                <div class="d-inline-block" id="change-project-code">
+                    <%- change.code %>
+                </div>
+            </div>
+            <div class="ml-auto" id="sp-btn">
+                <% if (ctx.change.status === auditConst.status.uncheck) { %>
+                    <% if (ctx.session.sessionUser.accountId === ctx.change.uid) { %>
+                        <a id="sub-sp-btn" href="javascript: void(0);" data-toggle="modal" data-target="#sub-sp" class="btn btn-primary btn-sm">上报审批</a>
+                    <% } else { %>
+                        <a id="sub-sp-btn" href="javascript: void(0);" data-toggle="modal" data-target="#sub-sp" class="btn btn-outline-secondary btn-sm">上报中</a>
+                    <% } %>
+                <% } else if (ctx.change.status === auditConst.status.checking) { %>
+                    <% if (ctx.change.curAuditor && ctx.change.curAuditor.aid === ctx.session.sessionUser.accountId) { %>
+                        <a href="#stop" data-toggle="modal" data-target="#stop" class="btn btn-sm btn-danger mr-2">终止</a>
+                        <a id="sp-done-btn" href="javascript: void(0);" data-toggle="modal" data-target="#sp-done" class="btn btn-success btn-sm">审批通过</a>
+                        <a href="#sp-back" data-toggle="modal" data-target="#sp-back" class="btn btn-warning btn-sm">审批退回</a>
+                    <% } else { %>
+                        <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-secondary btn-sm">审批中</a>
+                    <% } %>
+                <% } else if (ctx.change.status === auditConst.status.checked) { %>
+                    <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-secondary btn-sm">审批完成</a>
+                <% } else if (ctx.change.status === auditConst.status.back) { %>
+                    <a href="#sp-list"  data-type="hide" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-warning btn-sm text-muted sp-list-btn">审批退回</a>
+                    <% if (ctx.session.sessionUser.accountId === ctx.change.uid) { %>
+                        <a href="#sp-list" data-type="show" data-toggle="modal" data-target="#sp-list"  class="btn btn-primary btn-sm sp-list-btn">重新上报</a>
+                    <% } %>
+                <% } else if (ctx.change.status === auditConst.status.checkNo) { %>
+                    <a href="#sp-list" data-toggle="modal" data-target="#sp-list" class="btn btn-outline-danger btn-sm">审批终止</a>
+                <% } %>
+            </div>
+        </div>
+    </div>
+    <div class="content-wrap">
+        <div class="c-body">
+            <div class="sjs-height-0">
+                <div class="col-xl-8 mx-auto">
+                    <h4 class="text-center py-2">变更建议/意向报告书</h4>
+                    <table class="table table-bordered" id="project-table">
+                        <tr>
+                            <th width="120" class="text-center">立项编号<b class="text-danger">*&nbsp;</b></th>
+                            <td><input class="form-control form-control-sm" value="<%- change.code %>" data-name="code" <% if (change.readOnly) { %>readonly<% } %> type="text" placeholder=""></td>
+                        </tr>
+                        <tr>
+                            <th width="120" class="text-center">变更工程名称<b class="text-danger">*&nbsp;</b></th>
+                            <td colspan="3"><input class="form-control form-control-sm" value="<%- change.name %>" data-name="name" <% if (change.readOnly) { %>readonly<% } %> type="text" placeholder=""></td>
+                        </tr>
+                        <tr>
+                            <th width="120" class="text-center">原设计图名称</th>
+                            <td  colspan="3"><input class="form-control form-control-sm" value="<%- change.org_name %>" data-name="org_name" <% if (change.readOnly) { %>readonly<% } %> type="text" placeholder=""></td>
+                        </tr>
+                        <tr>
+                            <th width="120" class="text-center">桩号</th>
+                            <td><input class="form-control form-control-sm" type="text" value="<%- change.peg %>" data-name="peg" <% if (change.readOnly) { %>readonly<% } %> placeholder=""></td>
+                            <th width="120" class="text-center">图号</th>
+                            <td><input class="form-control form-control-sm" type="text" value="<%- change.new_code %>" data-name="new_code" <% if (change.readOnly) { %>readonly<% } %> placeholder=""></td>
+                        </tr>
+                        <tr>
+                            <th width="120" class="text-center">工程变更类别</th>
+                            <td><input class="form-control form-control-sm" type="text" value="<%- change.class %>" data-name="class" <% if (change.readOnly) { %>readonly<% } %> placeholder=""></td>
+                            <th width="120" class="text-center">工程变更性质</th>
+                            <td><input class="form-control form-control-sm" type="text" value="<%- change.quality %>" data-name="quality" <% if (change.readOnly) { %>readonly<% } %> placeholder=""></td>
+                        </tr>
+                        <tr>
+                            <th width="120" class="text-center">原工程造价(元)</th>
+                            <td><input class="form-control form-control-sm" type="text" value="<%- change.org_price %>" data-name="org_price" <% if (change.readOnly) { %>readonly<% } %> placeholder=""></td>
+                        </tr>
+                        <tr>
+                            <th width="120" class="text-center">预计变更造价(元)</th>
+                            <td><input class="form-control form-control-sm" type="text" value="<%- change.change_price %>" data-name="change_price" <% if (change.readOnly) { %>readonly<% } %> placeholder=""></td>
+                            <th width="120" class="text-center">预计造价增减(元)</th>
+                            <td><input class="form-control form-control-sm" type="text" value="<%- change.crease_price %>" data-name="crease_price" <% if (change.readOnly) { %>readonly<% } %> placeholder=""></td>
+                        </tr>
+                        <tr>
+                            <th width="120" class="text-center">变更原因<b class="text-danger">*&nbsp;</b></th>
+                            <td colspan="3"><textarea class="form-control form-control-sm" id="exampleFormControlTextarea1" data-name="reason" <% if (change.readOnly) { %>readonly<% } %> rows="3"><%- change.reason %></textarea></td>
+                        </tr>
+                        <tr>
+                            <th width="120" class="text-center">内容摘要</th>
+                            <td colspan="3"><textarea class="form-control form-control-sm" id="exampleFormControlTextarea1" data-name="content" <% if (change.readOnly) { %>readonly<% } %> rows="3"><%- change.content %></textarea></td>
+                        </tr>
+                    </table>
+                    <table class="table table-bordered">
+                        <thead>
+                        <tr>
+                            <th></th>
+                            <th>附件</th>
+                            <th>上传者</th>
+                            <th>上传时间</th>
+                            <th>操作</th>
+                        </tr>
+                        </thead>
+                        <tbody>
+                        <!--<tr>-->
+                            <!--<td colspan="5"><button type="button" class="btn btn-primary btn-sm"  data-toggle="modal" data-target="#upload-fj">上传附件</button></td>-->
+                        <!--</tr>-->
+                        <tbody id="file-content">
+                        </tbody>
+                        <!--<tr>-->
+                            <!--<td>1</td>-->
+                            <!--<td>XXX设计图纸</td>-->
+                            <!--<td>仁温书</td>-->
+                            <!--<td>2021-12-09 16:58:47</td>-->
+                            <!--<td><a href="#" class="mr-2"><i class="fa fa-download"></i></a><a href="#" class="text-danger"><i class="fa fa-remove"></i></a></td>-->
+                        <!--</tr>-->
+                        <!--<tr>-->
+                            <!--<td>1</td>-->
+                            <!--<td>XXX资料说明</td>-->
+                            <!--<td>仁温书</td>-->
+                            <!--<td>2021-12-09 16:58:47</td>-->
+                            <!--<td><a href="#" class="mr-2"><i class="fa fa-download"></i></a><a href="#" class="text-danger"><i class="fa fa-remove"></i></a></td>-->
+                        <!--</tr>-->
+                        </tbody>
+                    </table>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+<script>
+    autoFlashHeight();
+    const auditConst = JSON.parse('<%- JSON.stringify(auditConst) %>');
+    const fileList = JSON.parse(unescape('<%- escape(JSON.stringify(fileList)) %>')) || [];
+    const whiteList = JSON.parse('<%- JSON.stringify(whiteList) %>');
+    const preUrl = '<%- preUrl %>';
+    const change = JSON.parse(unescape('<%- escape(JSON.stringify(change)) %>'));
+</script>

+ 765 - 0
app/view/change/project_information_modal.ejs

@@ -0,0 +1,765 @@
+<!--添加附件-->
+<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">
+                <p>大小限制:30MB,支持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>
+            <div class="modal-footer">
+                <button id="file-cancel" type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
+                <button id="file-ok" type="button" class="btn btn-primary">添加</button>
+            </div>
+        </div>
+    </div>
+</div>
+<% if ((ctx.change.status === auditConst.status.uncheck || ctx.change.status === auditConst.status.back) && (ctx.session.sessionUser.accountId === ctx.change.uid || ctx.tender.isTourist)) { %>
+    <!--上报审批-->
+    <div class="modal fade" id="sub-sp" data-backdrop="static">
+        <div class="modal-dialog" role="document">
+            <div class="modal-content">
+                <div class="modal-header">
+                    <h5 class="modal-title">上报审批</h5>
+                </div>
+                <div class="modal-body">
+                    <div class="dropdown text-right">
+                            <button class="btn btn-outline-primary btn-sm dropdown-toggle" type="button"
+                                    id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true"
+                                    aria-expanded="false">
+                                添加审批流程
+                            </button>
+                            <div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton"
+                                 style="width:220px">
+                                <div class="mb-2 p-2"><input class="form-control form-control-sm"
+                                                             placeholder="姓名/手机 检索" id="gr-search" 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 !== ctx.session.sessionUser.accountId) { %>
+                                                    <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 class="card mt-3">
+                        <div class="card-header">
+                            审批流程
+                        </div>
+                        <div class="modal-height-500" style="overflow: auto">
+                            <ul class="list-group list-group-flush" id="auditors">
+                                <% for (let i = 0, iLen = ctx.change.auditorList.length; i < iLen; i++) { %>
+                                    <li class="list-group-item" auditorId="<%- ctx.change.auditorList[i].aid %>">
+                                        <% if (ctx.session.sessionUser.accountId === ctx.change.uid && !ctx.tender.isTourist) { %>
+                                            <a href="javascript: void(0)" class="text-danger pull-right">移除</a>
+                                        <% } %>
+                                        <span><%- ctx.change.auditorList[i].order %> <%- ctx.change.auditorList[i].name %></span>
+                                        <small class="text-muted"><%- ctx.change.auditorList[i].role %></small>
+                                    </li>
+                                <% } %>
+                            </ul>
+                        </div>
+                    </div>
+                </div>
+                <form class="modal-footer" method="post" action="<%- preUrl %>/audit/start" onsubmit="return checkAuditorFrom()">
+                    <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                    <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>">
+                    <% if (ctx.session.sessionUser.accountId === ctx.change.uid) { %>
+                        <button class="btn btn-primary btn-sm" type="submit">确认上报</button>
+                    <% } %>
+                </form>
+            </div>
+        </div>
+    </div>
+<% } %>
+
+<!--审批流程/结果-->
+<div class="modal fade" id="sp-list" data-backdrop="static">
+    <div class="modal-dialog modal-lg" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title"><%- ctx.change.status === auditConst.status.checking ? '审批流程' : '重新上报' %></h5>
+            </div>
+            <div class="modal-body">
+                <div class="row">
+                    <div class="col-4">
+                        <% if(ctx.change.status === auditConst.status.back) { %>
+                            <a class="sp-list-item" href="#sub-sp" data-toggle="modal" data-target="#sub-sp" id="hideSp">修改审批流程</a>
+                        <% } %>
+                        <div class="card mt-3">
+                            <ul class="list-group list-group-flush" id="auditors-list">
+                                <% ctx.change.auditors2.forEach((item, idx) => { %>
+                                    <% if (idx === 0) { %>
+                                        <li class="list-group-item" data-auditorId="<%- item.aid %>">
+                                            <i class="fa fa fa-play-circle fa-rotate-90"></i> <%- item.name %>
+                                            <small class="text-muted"><%- item.role %></small>
+                                            <span class="pull-right">原报</span>
+                                        </li>
+                                    <% } else if(idx === ctx.change.auditors2.length -1 && idx !== 0) { %>
+                                        <li class="list-group-item" data-auditorId="<%- item.aid %>">
+                                            <i class="fa fa fa-stop-circle"></i> <%- item.name %>
+                                            <small class="text-muted"><%- item.role %></small>
+                                            <span class="pull-right">终审</span>
+                                        </li>
+                                    <% } else {%>
+                                        <li class="list-group-item" data-auditorId="<%- item.aid %>">
+                                            <i class="fa fa-chevron-circle-down"></i> <%- item.name %>
+                                            <small class="text-muted"><%- item.role %></small>
+                                            <span class="pull-right"><%= ctx.helper.transFormToChinese(idx) %>审</span>
+                                        </li>
+                                    <% } %>
+                                <% }) %>
+                            </ul>
+                        </div>
+                    </div>
+                    <div class="col-8 modal-height-500" style="overflow: auto">
+                        <% ctx.change.auditHistory.forEach((auditors, idx) => { %>
+                            <!-- 展开/收起历史流程 -->
+                            <% if(idx === ctx.change.auditHistory.length - 1 && ctx.change.auditHistory.length !== 1) { %>
+                                <div class="text-right">
+                                    <a href="javascript: void(0);" id="fold-btn" data-target="show">展开历史审批流程</a>
+                                </div>
+                            <% } %>
+                            <div class="<%- idx < ctx.change.auditHistory.length - 1 ? 'fold-card' : '' %>">
+                                <div class="text-center text-muted"><%- idx+1 %>#</div>
+                                <ul class="timeline-list list-unstyled mt-2">
+                                    <% auditors.forEach((auditor, index) => { %>
+                                        <% if (index === 0) { %>
+                                            <li class="timeline-list-item pb-2">
+                                                <div class="timeline-item-date">
+                                                    <%- ctx.helper.formatDate(auditor.begin_time) %>
+                                                </div>
+                                                <div class="timeline-item-tail"></div>
+                                                <div class="timeline-item-icon bg-success text-light">
+                                                    <i class="fa fa-caret-down"></i>
+                                                </div>
+                                                <div class="timeline-item-content">
+                                                    <div class="card">
+                                                        <div class="card-body p-3">
+                                                            <div class="card-text">
+                                                                <p class="mb-1"><span
+                                                                            class="h5"><%- ctx.change.user.name %></span><span
+                                                                            class="pull-right text-success"><%- idx !== 0 ? '重新' : '' %>上报审批</span>
+                                                                </p>
+                                                                <p class="text-muted mb-0"><%- ctx.change.user.role %></p>
+                                                            </div>
+                                                        </div>
+                                                    </div>
+                                                </div>
+                                            </li>
+                                            <li class="timeline-list-item pb-2">
+                                                <div class="timeline-item-date">
+                                                    <%- ctx.helper.formatDate(auditor.end_time) %>
+                                                </div>
+                                                <% if(index < auditors.length - 1) { %>
+                                                    <div class="timeline-item-tail"></div>
+                                                <% } %>
+                                                <% if(auditor.status === auditConst.status.checked) { %>
+                                                    <div class="timeline-item-icon bg-success text-light">
+                                                        <i class="fa fa-check"></i>
+                                                    </div>
+                                                <% } else if(auditor.status === auditConst.status.back) {%>
+                                                    <div class="timeline-item-icon bg-warning text-light">
+                                                        <i class="fa fa-level-up"></i>
+                                                    </div>
+                                                <% } else if(auditor.status === auditConst.status.checking) { %>
+                                                    <div class="timeline-item-icon bg-warning text-light">
+                                                        <i class="fa fa-ellipsis-h"></i>
+                                                    </div>
+                                                <% } else if(auditor.status === auditConst.status.checkNo) { %>
+                                                    <div class="timeline-item-icon bg-danger text-light">
+                                                        <i class="fa fa-ellipsis-h"></i>
+                                                    </div>
+                                                <% } else {%>
+                                                    <div class="timeline-item-icon bg-secondary text-light">
+                                                    </div>
+                                                <% } %>
+                                                <div class="timeline-item-content">
+                                                    <div class="card">
+                                                        <div class="card-body p-3">
+                                                            <div class="card-text">
+                                                                <p class="mb-1"><span class="h5"><%- auditor.name %></span><span
+                                                                            class="pull-right <%- auditConst.statusClass[auditor.status] %>"><%- auditConst.statusString[auditor.status] %></span>
+                                                                </p>
+                                                                <p class="text-muted mb-0"><%- auditor.role %></p>
+                                                            </div>
+                                                        </div>
+
+                                                        <!--审批意见-->
+                                                        <% if (auditor.opinion) { %>
+                                                            <div class="card-body p-3 border-top">
+                                                                <p style="margin: 0;"><%- auditor.opinion %></p>
+                                                            </div>
+                                                        <% } %>
+                                                    </div>
+                                                </div>
+                                            </li>
+                                        <% } else {%>
+                                            <li class="timeline-list-item pb-2">
+                                                <div class="timeline-item-date">
+                                                    <%- ctx.helper.formatDate(auditor.end_time) %>
+                                                </div>
+                                                <% if(index < auditors.length - 1) { %>
+                                                    <div class="timeline-item-tail"></div>
+                                                <% } %>
+                                                <% if(auditor.status === auditConst.status.checked) { %>
+                                                    <div class="timeline-item-icon bg-success text-light">
+                                                        <i class="fa fa-check"></i>
+                                                    </div>
+                                                <% } else if(auditor.status === auditConst.status.back) {%>
+                                                    <div class="timeline-item-icon bg-warning text-light">
+                                                        <i class="fa fa-level-up"></i>
+                                                    </div>
+                                                <% } else if(auditor.status === auditConst.status.checking) { %>
+                                                    <div class="timeline-item-icon bg-warning text-light">
+                                                        <i class="fa fa-ellipsis-h"></i>
+                                                    </div>
+                                                <% } else if(auditor.status === auditConst.status.checkNo) { %>
+                                                    <div class="timeline-item-icon bg-danger text-light">
+                                                        <i class="fa fa-ellipsis-h"></i>
+                                                    </div>
+                                                <% } else { %>
+                                                    <div class="timeline-item-icon bg-secondary text-light">
+                                                    </div>
+                                                <% } %>
+                                                <div class="timeline-item-content">
+                                                    <div class="card">
+                                                        <div class="card-body p-3">
+                                                            <div class="card-text">
+                                                                <p class="mb-1"><span class="h5"><%- auditor.name %></span>
+                                                                    <span
+                                                                            class="pull-right
+                                                                            <%- auditConst.statusClass[auditor.status] %>"><%- auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''%>
+                                                                        <%- auditor.status === auditConst.status.back ? ctx.change.user.name : '' %>
+                                                        </span>
+                                                                </p>
+                                                                <p class="text-muted mb-0"><%- auditor.role %></p>
+                                                            </div>
+                                                        </div>
+                                                        <!--审批意见-->
+                                                        <% if (auditor.opinion) { %>
+                                                            <div class="card-body p-3 border-top">
+                                                                <p style="margin: 0;"><%- auditor.opinion %></p>
+                                                            </div>
+                                                        <% } %>
+                                                    </div>
+                                                </div>
+                                            </li>
+                                        <% } %>
+                                    <% }) %>
+                                </ul>
+                            </div>
+
+                        <% }) %>
+                    </div>
+                </div>
+            </div>
+            <form class="modal-footer" method="post" action="<%- preUrl %>/audit/start" onsubmit="return checkAuditorFrom()">
+                <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>">
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                <% if(ctx.change.status === auditConst.status.back && ctx.session.sessionUser.accountId === ctx.change.uid) { %>
+                    <button class="btn btn-primary btn-sm sp-list-item" type="submit">确认上报</button>
+                <% } %>
+            </form>
+        </div>
+    </div>
+</div>
+<% if (ctx.change.status === auditConst.status.checking) { %>
+    <% if (ctx.change.curAuditor && ctx.change.curAuditor.aid === ctx.session.sessionUser.accountId) { %>
+        <!--审批通过-->
+        <div class="modal fade sp-location-list" id="sp-done" data-backdrop="static">
+            <div class="modal-dialog modal-lg" role="document">
+                <form class="modal-content" action="<%- preUrl %>/audit/check" method="post" onsubmit="return auditCheck(0);">
+                    <div class="modal-header">
+                        <h5 class="modal-title">审批通过</h5>
+                    </div>
+                    <div class="modal-body">
+                        <div class="row">
+                            <div class="col-4">
+                                <div class="card mt-3">
+                                    <ul class="list-group list-group-flush">
+                                        <% ctx.change.auditors2.forEach((item, idx) => { %>
+                                            <% if (idx === 0) { %>
+                                                <li class="list-group-item" data-auditorId="<%- item.aid %>">
+                                                    <i class="fa fa fa-play-circle fa-rotate-90"></i> <%- item.name %>
+                                                    <small class="text-muted"><%- item.role %></small>
+                                                    <span class="pull-right">原报</span>
+                                                </li>
+                                            <% } else if(idx === ctx.change.auditors2.length -1 && idx !== 0) { %>
+                                                <li class="list-group-item" data-auditorId="<%- item.aid %>">
+                                                    <i class="fa fa fa-stop-circle"></i> <%- item.name %>
+                                                    <small class="text-muted"><%- item.role %></small>
+                                                    <span class="pull-right">终审</span>
+                                                </li>
+                                            <% } else {%>
+                                                <li class="list-group-item" data-auditorId="<%- item.aid %>">
+                                                    <i class="fa fa-chevron-circle-down"></i> <%- item.name %>
+                                                    <small class="text-muted"><%- item.role %></small>
+                                                    <span class="pull-right"><%= ctx.helper.transFormToChinese(idx) %>审</span>
+                                                </li>
+                                            <% } %>
+                                        <% }) %>
+                                    </ul>
+                                </div>
+                            </div>
+                            <div class="col-8 modal-height-500" style="overflow: auto">
+                                <% ctx.change.auditHistory.forEach((auditors, idx) => { %>
+                                    <!-- 展开/收起历史流程 -->
+                                    <% if(idx === ctx.change.auditHistory.length - 1 && ctx.change.auditHistory.length !== 1) { %>
+                                        <div class="text-right">
+                                            <a href="javascript: void(0);" id="fold-btn" data-target="show">展开历史审批流程</a>
+                                        </div>
+                                    <% } %>
+                                    <div class="<%- idx < ctx.change.auditHistory.length - 1 ? 'fold-card' : '' %>">
+                                        <div class="text-center text-muted"><%- idx+1 %>#</div>
+                                        <ul class="timeline-list list-unstyled mt-2">
+                                            <% auditors.forEach((auditor, index) => { %>
+                                                <% if (index === 0) { %>
+                                                    <li class="timeline-list-item pb-2">
+                                                        <div class="timeline-item-date">
+                                                            <%- ctx.helper.formatDate(auditor.begin_time) %>
+                                                        </div>
+                                                        <div class="timeline-item-tail"></div>
+                                                        <div class="timeline-item-icon bg-success text-light">
+                                                            <i class="fa fa-caret-down"></i>
+                                                        </div>
+                                                        <div class="timeline-item-content">
+                                                            <div class="card">
+                                                                <div class="card-body p-3">
+                                                                    <div class="card-text">
+                                                                        <p class="mb-1"><span
+                                                                                    class="h5"><%- ctx.change.user.name %></span><span
+                                                                                    class="pull-right text-success"><%- idx !== 0 ? '重新' : '' %>上报审批</span>
+                                                                        </p>
+                                                                        <p class="text-muted mb-0"><%- ctx.change.user.role %></p>
+                                                                    </div>
+                                                                </div>
+                                                            </div>
+                                                        </div>
+                                                    </li>
+                                                    <li class="timeline-list-item pb-2">
+                                                        <div class="timeline-item-date">
+                                                            <%- ctx.helper.formatDate(auditor.end_time) %>
+                                                        </div>
+                                                        <% if(index < auditors.length - 1) { %>
+                                                            <div class="timeline-item-tail"></div>
+                                                        <% } %>
+                                                        <% if(auditor.status === auditConst.status.checked) { %>
+                                                            <div class="timeline-item-icon bg-success text-light">
+                                                                <i class="fa fa-check"></i>
+                                                            </div>
+                                                        <% } else if(auditor.status === auditConst.status.back) {%>
+                                                            <div class="timeline-item-icon bg-warning text-light">
+                                                                <i class="fa fa-level-up"></i>
+                                                            </div>
+                                                        <% } else if(auditor.status === auditConst.status.checking) { %>
+                                                            <div class="timeline-item-icon bg-warning text-light">
+                                                                <i class="fa fa-ellipsis-h"></i>
+                                                            </div>
+                                                        <% } else {%>
+                                                            <div class="timeline-item-icon bg-secondary text-light">
+                                                            </div>
+                                                        <% } %>
+                                                        <div class="timeline-item-content">
+                                                            <div class="card">
+                                                                <div class="card-body p-3">
+                                                                    <div class="card-text">
+                                                                        <p class="mb-1"><span class="h5"><%- auditor.name %></span><span
+                                                                                    class="pull-right <%- auditConst.statusClass[auditor.status] %>"><%- auditConst.statusString[auditor.status] %></span>
+                                                                        </p>
+                                                                        <p class="text-muted mb-0"><%- auditor.role %></p>
+                                                                    </div>
+                                                                </div>
+                                                                <!--审批意见-->
+                                                                <% if(auditor.status !== auditConst.status.uncheck) { %>
+                                                                    <div class="card-body p-3 border-top">
+                                                                        <% if (ctx.change.times === idx + 1 && auditor.status === auditConst.status.checking) { %>
+                                                                            <label>审批意见<b class="text-danger">*</b></label>
+                                                                            <textarea class="form-control form-control-sm"
+                                                                                      name="opinion">同意</textarea>
+                                                                        <% } else { %>
+                                                                            <p style="margin: 0;"><%- auditor.opinion %></p>
+                                                                        <% } %>
+                                                                    </div>
+                                                                <% } %>
+                                                            </div>
+                                                        </div>
+                                                    </li>
+                                                <% } else {%>
+                                                    <li class="timeline-list-item pb-2">
+                                                        <div class="timeline-item-date">
+                                                            <%- ctx.helper.formatDate(auditor.end_time) %>
+                                                        </div>
+                                                        <% if(index < auditors.length - 1) { %>
+                                                            <div class="timeline-item-tail"></div>
+                                                        <% } %>
+                                                        <% if(auditor.status === auditConst.status.checked) { %>
+                                                            <div class="timeline-item-icon bg-success text-light">
+                                                                <i class="fa fa-check"></i>
+                                                            </div>
+                                                        <% } else if(auditor.status === auditConst.status.back) {%>
+                                                            <div class="timeline-item-icon bg-warning text-light">
+                                                                <i class="fa fa-level-up"></i>
+                                                            </div>
+                                                        <% } else if(auditor.status === auditConst.status.checking) { %>
+                                                            <div class="timeline-item-icon bg-warning text-light">
+                                                                <i class="fa fa-ellipsis-h"></i>
+                                                            </div>
+                                                        <% } else { %>
+                                                            <div class="timeline-item-icon bg-secondary text-light">
+                                                            </div>
+                                                        <% } %>
+                                                        <div class="timeline-item-content">
+                                                            <div class="card">
+                                                                <div class="card-body p-3">
+                                                                    <div class="card-text">
+                                                                        <p class="mb-1"><span class="h5"><%- auditor.name %></span>
+                                                                            <span
+                                                                                    class="pull-right
+                                                                                    <%- auditConst.statusClass[auditor.status] %>"><%- auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''%>
+                                                                                <%- auditor.status === auditConst.status.back ? ctx.change.user.name : '' %>
+                                                                </span>
+                                                                        </p>
+                                                                        <p class="text-muted mb-0"><%- auditor.role %></p>
+                                                                    </div>
+                                                                </div>
+                                                                <!--审批意见-->
+                                                                <% if(auditor.status !== auditConst.status.uncheck) { %>
+                                                                    <div class="card-body p-3 border-top">
+                                                                        <% if (ctx.change.times === idx + 1 && auditor.status === auditConst.status.checking) { %>
+                                                                            <label>审批意见<b class="text-danger">*</b></label>
+                                                                            <textarea class="form-control form-control-sm"
+                                                                                      name="opinion">同意</textarea>
+                                                                        <% } else { %>
+                                                                            <p style="margin: 0;"><%- auditor.opinion %></p>
+                                                                        <% } %>
+                                                                    </div>
+                                                                <% } %>
+                                                            </div>
+                                                        </div>
+                                                    </li>
+                                                <% } %>
+                                            <% }) %>
+                                        </ul>
+                                    </div>
+                                <% }) %>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="modal-footer">
+                        <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                        <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>" />
+                        <input type="hidden" name="checkType" value="<%= auditConst.status.checked %>" />
+                        <button type="submit" class="btn btn-success btn-sm">确认通过</button>
+                    </div>
+                </form>
+            </div>
+        </div>
+        <!--审批退回-->
+        <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);">
+                    <div class="modal-header">
+                        <h5 class="modal-title">审批退回</h5>
+                    </div>
+                    <div class="modal-body">
+                        <div class="row">
+                            <div class="col-4">
+                                <div class="card mt-3">
+                                    <ul class="list-group list-group-flush">
+                                        <% ctx.change.auditors2.forEach((item, idx) => { %>
+                                            <% if (idx === 0) { %>
+                                                <li class="list-group-item" data-auditorId="<%- item.aid %>">
+                                                    <i class="fa fa fa-play-circle fa-rotate-90"></i> <%- item.name %>
+                                                    <small class="text-muted"><%- item.role %></small>
+                                                    <span class="pull-right">原报</span>
+                                                </li>
+                                            <% } else if(idx === ctx.change.auditors2.length -1 && idx !== 0) { %>
+                                                <li class="list-group-item" data-auditorId="<%- item.aid %>">
+                                                    <i class="fa fa fa-stop-circle"></i> <%- item.name %>
+                                                    <small class="text-muted"><%- item.role %></small>
+                                                    <span class="pull-right">终审</span>
+                                                </li>
+                                            <% } else {%>
+                                                <li class="list-group-item" data-auditorId="<%- item.aid %>">
+                                                    <i class="fa fa-chevron-circle-down"></i> <%- item.name %>
+                                                    <small class="text-muted"><%- item.role %></small>
+                                                    <span class="pull-right"><%= ctx.helper.transFormToChinese(idx) %>审</span>
+                                                </li>
+                                            <% } %>
+                                        <% }) %>
+                                    </ul>
+                                </div>
+                            </div>
+                            <div class="col-8 modal-height-500" style="overflow: auto">
+                                <% ctx.change.auditHistory.forEach((auditors, idx) => { %>
+                                    <!-- 展开/收起历史流程 -->
+                                    <% if(idx === ctx.change.auditHistory.length - 1 && ctx.change.auditHistory.length !== 1) { %>
+                                        <div class="text-right">
+                                            <a href="javascript: void(0);" id="fold-btn" data-target="show">展开历史审批流程</a>
+                                        </div>
+                                    <% } %>
+                                    <div class="<%- idx < ctx.change.auditHistory.length - 1 ? 'fold-card' : '' %>">
+                                        <div class="text-center text-muted"><%- idx+1 %>#</div>
+                                        <ul class="timeline-list list-unstyled mt-2">
+                                            <% auditors.forEach((auditor, index) => { %>
+                                                <% if (index === 0) { %>
+                                                    <li class="timeline-list-item pb-2">
+                                                        <div class="timeline-item-date">
+                                                            <%- ctx.helper.formatDate(auditor.begin_time) %>
+                                                        </div>
+                                                        <div class="timeline-item-tail"></div>
+                                                        <div class="timeline-item-icon bg-success text-light">
+                                                            <i class="fa fa-caret-down"></i>
+                                                        </div>
+                                                        <div class="timeline-item-content">
+                                                            <div class="card">
+                                                                <div class="card-body p-3">
+                                                                    <div class="card-text">
+                                                                        <p class="mb-1"><span
+                                                                                    class="h5"><%- ctx.change.user.name %></span><span
+                                                                                    class="pull-right text-success"><%- idx !== 0 ? '重新' : '' %>上报审批</span>
+                                                                        </p>
+                                                                        <p class="text-muted mb-0"><%- ctx.change.user.role %></p>
+                                                                    </div>
+                                                                </div>
+                                                            </div>
+                                                        </div>
+                                                    </li>
+                                                    <li class="timeline-list-item pb-2">
+                                                        <div class="timeline-item-date">
+                                                            <%- ctx.helper.formatDate(auditor.end_time) %>
+                                                        </div>
+                                                        <% if(index < auditors.length - 1) { %>
+                                                            <div class="timeline-item-tail"></div>
+                                                        <% } %>
+                                                        <% if(auditor.status === auditConst.status.checked) { %>
+                                                            <div class="timeline-item-icon bg-success text-light">
+                                                                <i class="fa fa-check"></i>
+                                                            </div>
+                                                        <% } else if(auditor.status === auditConst.status.back) {%>
+                                                            <div class="timeline-item-icon bg-warning text-light">
+                                                                <i class="fa fa-level-up"></i>
+                                                            </div>
+                                                        <% } else if(auditor.status === auditConst.status.checking) { %>
+                                                            <div class="timeline-item-icon bg-warning text-light">
+                                                                <i class="fa fa-ellipsis-h"></i>
+                                                            </div>
+                                                        <% } else {%>
+                                                            <div class="timeline-item-icon bg-secondary text-light">
+                                                            </div>
+                                                        <% } %>
+                                                        <div class="timeline-item-content">
+                                                            <div class="card">
+                                                                <div class="card-body p-3">
+                                                                    <div class="card-text">
+                                                                        <p class="mb-1"><span class="h5"><%- auditor.name %></span><span
+                                                                                    class="pull-right <%- auditConst.statusClass[auditor.status] %>"><%- auditConst.statusString[auditor.status] %></span>
+                                                                        </p>
+                                                                        <p class="text-muted mb-0"><%- auditor.role %></p>
+                                                                    </div>
+                                                                </div>
+
+                                                                <!--审批意见-->
+                                                                <% if(auditor.times === ctx.change.times && auditor.status !== auditConst.status.uncheck) { %>
+                                                                    <div class="card-body p-3 border-top">
+                                                                        <% if (ctx.change.times === idx + 1 && auditor.status === auditConst.status.checking) { %>
+                                                                            <label>审批意见<b class="text-danger">*</b></label>
+                                                                            <textarea class="form-control form-control-sm"
+                                                                                      name="opinion">不同意</textarea>
+                                                                            <% if (ctx.change.curAuditor.aid === auditor.aid) { %>
+                                                                                <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.back %>" checked>
+                                                                                        <label class="form-check-label" for="inlineRadio1">退回原报
+                                                                                            <%- ctx.change.user.name %></label>
+                                                                                    </div>
+                                                                                </div>
+                                                                            <% } %>
+                                                                        <% } else if(auditor.status === auditConst.status.checked){ %>
+                                                                            <p style="margin: 0;"><%- auditor.opinion %></p>
+                                                                        <% } %>
+                                                                    </div>
+                                                                <% } %>
+                                                            </div>
+                                                        </div>
+                                                    </li>
+                                                <% } else {%>
+                                                    <li class="timeline-list-item pb-2">
+                                                        <div class="timeline-item-date">
+                                                            <%- ctx.helper.formatDate(auditor.end_time) %>
+                                                        </div>
+                                                        <% if(index < auditors.length - 1) { %>
+                                                            <div class="timeline-item-tail"></div>
+                                                        <% } %>
+                                                        <% if(auditor.status === auditConst.status.checked) { %>
+                                                            <div class="timeline-item-icon bg-success text-light">
+                                                                <i class="fa fa-check"></i>
+                                                            </div>
+                                                        <% } else if(auditor.status === auditConst.status.back) {%>
+                                                            <div class="timeline-item-icon bg-warning text-light">
+                                                                <i class="fa fa-level-up"></i>
+                                                            </div>
+                                                        <% } else if(auditor.status === auditConst.status.checking) { %>
+                                                            <div class="timeline-item-icon bg-warning text-light">
+                                                                <i class="fa fa-ellipsis-h"></i>
+                                                            </div>
+                                                        <% } else { %>
+                                                            <div class="timeline-item-icon bg-secondary text-light">
+                                                            </div>
+                                                        <% } %>
+                                                        <div class="timeline-item-content">
+                                                            <div class="card">
+                                                                <div class="card-body p-3">
+                                                                    <div class="card-text">
+                                                                        <p class="mb-1"><span class="h5"><%- auditor.name %></span>
+                                                                            <span
+                                                                                    class="pull-right
+                                                                                    <%- auditConst.statusClass[auditor.status] %>"><%- auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''%>
+                                                                                <%- auditor.status === auditConst.status.back ? ctx.change.user.name : '' %>
+                                                                </span>
+                                                                        </p>
+                                                                        <p class="text-muted mb-0"><%- auditor.role %></p>
+                                                                    </div>
+                                                                </div>
+                                                                <!--审批意见-->
+                                                                <% if(auditor.times === ctx.change.times && auditor.status !== auditConst.status.uncheck) { %>
+                                                                    <div class="card-body p-3 border-top">
+                                                                        <% if (ctx.change.times === idx + 1 && auditor.status === auditConst.status.checking) { %>
+                                                                            <label>审批意见<b class="text-danger">*</b></label>
+                                                                            <textarea class="form-control form-control-sm"
+                                                                                      name="opinion">不同意</textarea>
+                                                                            <% if (ctx.change.curAuditor.aid === auditor.aid ) { %>
+                                                                                <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.back %>" checked>
+                                                                                        <label class="form-check-label" for="inlineRadio1">退回原报
+                                                                                            <%- ctx.change.user.name %></label>
+                                                                                    </div>
+                                                                                </div>
+                                                                            <% } %>
+                                                                        <% } else { %>
+                                                                            <p style="margin: 0;"><%- auditor.opinion %></p>
+                                                                        <% } %>
+
+                                                                    </div>
+
+                                                                <% } %>
+                                                            </div>
+                                                        </div>
+                                                    </li>
+                                                <% } %>
+                                            <% }) %>
+                                        </ul>
+                                    </div>
+
+                                <% }) %>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="modal-footer">
+                        <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                        <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>" />
+                        <button type="submit" class="btn btn-warning btn-sm">确认退回</button>
+                    </div>
+                </form>
+            </div>
+        </div>
+        <!-- 终止 -->
+        <div class="modal fade" id="stop" data-backdrop="static">
+            <div class="modal-dialog " role="document">
+                <form class="modal-content" action="<%- preUrl %>/audit/check" method="post" onsubmit="return auditCheck(2);">
+                    <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>
+                            <textarea class="form-control" name="opinion" placeholder="请填写审批意见"></textarea>
+                        </div>
+                        <label class="form-text alert alert-danger m-0">审批终止,将结束本次审批。</label>
+                    </div>
+                    <div class="modal-footer">
+                        <button type="button" class="btn btn-sm btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                        <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>" />
+                        <input type="hidden" name="checkType" value="<%= auditConst.status.checkNo %>" />
+                        <button type="submit" class="btn btn-sm btn-danger btn-sm" >确认</button>
+                    </div>
+                </form>
+            </div>
+        </div>
+    <% } %>
+<% } %>
+<% if (ctx.session.sessionUser.accountId === ctx.change.uid && (ctx.change.status === auditConst.status.uncheck || ctx.change.status === auditConst.status.back)) { %>
+    <script>
+        const accountGroup = JSON.parse(unescape('<%- escape(JSON.stringify(accountGroup)) %>'));
+        const accountList = JSON.parse(unescape('<%- escape(JSON.stringify(accountList)) %>'));
+    </script>
+<% } %>
+<script>const cur_uid = parseInt('<%- ctx.session.sessionUser.accountId %>');</script>
+<script>
+    $('.sp-location-list').on('shown.bs.modal', function () {
+        const scrollBox = $(this).find('div[class="col-8 modal-height-500"]');
+        const bdiv = (scrollBox.offset() && scrollBox.offset().top) || 0;
+        scrollBox.scrollTop(0);
+        const hdiv = divSearch($(this).find('textarea')) ? $(this).find('textarea') : null;
+        const hdheight = hdiv ? hdiv.parents('.timeline-item-content').offset().top : null;
+        if (hdiv && scrollBox.length && scrollBox[0].scrollHeight > 200 && hdheight - bdiv > 200) {
+            scrollBox.scrollTop(hdheight - bdiv);
+        }
+    });
+    function divSearch(div) {
+        if (div.length > 0) {
+            return true;
+        }
+        return false;
+    }
+
+    // 展开历史审核记录
+    $('.modal-body #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('展开历史审核记录')
+            })
+        }
+    });
+
+    $('.sp-list-btn').click(function () {
+        const type = $(this).data('type')
+        if (type === 'hide') {
+            $('.sp-list-item').hide()
+            $('.modal-title').text('审批流程')
+        } else {
+            $('.sp-list-item').show()
+            $('.modal-title').text('重新上报')
+        }
+    });
+</script>

+ 148 - 0
app/view/change/project_modal.ejs

@@ -0,0 +1,148 @@
+<!--删除标段-->
+<div class="modal fade" id="del-bg" 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">
+                <h5>删除后,数据无法恢复,请谨慎操作。</h5>
+            </div>
+            <form class="modal-footer" action="/tender/<%- tender.id %>/change/project/delete" method="post">
+                <input type="hidden" name="cid" id="delete-cid" value="">
+                <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>" />
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">取消</button>
+                <button type="submit" class="btn btn-danger btn-sm">确定删除</button>
+            </form>
+        </div>
+    </div>
+</div>
+
+<% if (tender.user_id === uid || (userPermission !== null && userPermission.tender !== undefined && userPermission.tender.indexOf('5') !== -1)) { %>
+<!--弹出添加变更令-->
+<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="bj-name">
+                    <div class="invalid-feedback" style="display: none" id="name_error_msg">名称超过100个字,请缩减名称。</div>
+                </div>
+                <div class="form-group">
+                    <label>发起类型<b class="text-danger">*</b></label>
+                    <div class="d-flex">
+                        <% if (tender.user_id === uid) { %>
+                        <div class="form-check form-check-inline">
+                            <input class="form-check-input" type="radio" id="inlineRadio1" value="option1" checked>
+                            <label class="form-check-label" for="inlineRadio1">变更建议</label>
+                        </div>
+                        <% } else { %>
+                        <div class="form-check form-check-inline">
+                            <input class="form-check-input" type="radio" id="inlineRadio2" value="option2" checked>
+                            <label class="form-check-label" for="inlineRadio2">变更意向</label>
+                        </div>
+                        <% } %>
+                    </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(tender.c_connector !== null && tender.c_connector !== '3' ? ruleConst.connectorString[tender.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 (tender.c_connector !== null && tender.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>
+<% } %>
+
+

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

@@ -28,7 +28,7 @@
                     <div class="card">
                         <div class="card-header">需要你处理</div>
                         <div class="card-body">
-                            <% if (auditTenders.length !== 0 || auditRevise.length !== 0 || auditStages.length !== 0 || auditChanges.length !== 0 || auditMaterial.length !== 0 || auditAdvance.length !== 0) { %>
+                            <% if (auditTenders.length !== 0 || auditRevise.length !== 0 || auditStages.length !== 0 || auditChanges.length !== 0 || auditMaterial.length !== 0 || auditAdvance.length !== 0 || auditChangeProject.length !== 0) { %>
                                 <ul class="list-unstyled m-0">
                                     <% for (const t of auditTenders) { %>
                                         <% if (t.ledger_status === acLedger.status.checking) { %>
@@ -65,7 +65,7 @@
                                                 <div class="row">
                                                     <div class="col-auto"><span class="badge badge-info">台账修订</span></div>
                                                     <div class="col-6"><a href="/tender/<%- revise.t_id %>"><%- revise.t_name %></a> 台账修订(第<%- revise.corder %>次)</div>
-                                                    <div class="col-3 ml-auto text-right pl-0"><a href="/tender/<%- revise.t_id %>/revise/info" class="btn btn-sm btn-outline-primary"><% if (revise.status === acRevise.status.checking) { %>审批<% } else if (revise.status === acRevise.status.checkNo) { %>重新上报<% } %></a></div>
+                                                    <div class="col-3 ml-auto text-right pl-0"><a href="/tender/<%- revise.t_id %>/revise/<%- revise.id %>/info" class="btn btn-sm btn-outline-primary"><% if (revise.status === acRevise.status.checking) { %>审批<% } else if (revise.status === acRevise.status.checkNo) { %>重新上报<% } %></a></div>
                                                 </div>
                                                 <p class="mt-1 mb-0"><%- revise.audit_name %><small class="ml-1 text-muted"><%- (revise.audit_role ? '- ' + revise.audit_role: '') %></small>
                                                     <span class="pull-right text-muted"><%- (
@@ -120,6 +120,20 @@
                                             </div>
                                         </li>
                                     <% } %>
+                                    <% for (const acp of auditChangeProject) { %>
+                                        <li class="media pb-3 mb-3 border-bottom-1">
+                                            <div class="media-body">
+                                                <div class="row">
+                                                    <div class="col-auto"><span class="badge badge-danger">变更立项</span></div>
+                                                    <div class="col-6"><a href="/tender/<%- acp.tid %>"><%- acp.name %></a> 变更立项 <%- acp.mcode %></div>
+                                                    <div class="col-3 ml-auto text-right pl-0"><a href="/tender/<%- acp.tid %>/change/project/<%- acp.cpid %>/information" class="btn btn-sm btn-outline-primary"><% if (acp.mstatus !== acChangeProject.status.back) { %>审批<% } else { %>重新上报<% } %></a></div>
+                                                </div>
+                                                <p class="mt-1 mb-0"><%- ctx.session.sessionUser.name %><small class="ml-1 text-muted"><%- (role ? '- ' + role : '') %></small>
+                                                    <span class="pull-right text-muted"><%- ctx.moment(acp.begin_time).format('YYYY-MM-DD HH:mm:ss') %></span>
+                                                </p>
+                                            </div>
+                                        </li>
+                                    <% } %>
                                     <% for (const am of auditMaterial) { %>
                                         <% if (am.mstatus !== acMaterial.status.checkNo) { %>
                                             <li class="media pb-3 mb-3 border-bottom-1">
@@ -246,7 +260,7 @@
                                                         <div class="col-auto"><span class="badge badge-info">台账修订</span></div>
                                                         <div class="col-6">
                                                             <a href="/tender/<%- notice.tid %>"><%- notice.name %></a>
-                                                            <a href="/tender/<%- notice.tid %>/revise/info">台账修订(第<%- notice.corder %>次)</a>
+                                                            <a href="/tender/<%- notice.tid %>/revise/<%- notice.rid %>/info">台账修订(第<%- notice.corder %>次)</a>
                                                             <%- acRevise.statusString[notice.status]%>
                                                         </div>
                                                     </div>
@@ -271,6 +285,22 @@
                                                     </p>
                                                 </div>
                                             </li>
+                                        <% } else if(notice.type === pushType.changeProject && ctx.session.sessionProject.page_show.openChangeProject) { %>
+                                            <li class="media pb-3 mb-3 border-bottom-1">
+                                                <div class="media-body">
+                                                    <div class="row">
+                                                        <div class="col-auto"><span class="badge badge-danger">变更立项</span></div>
+                                                        <div class="col-6">
+                                                            <a href="/tender/<%- notice.tid %>"><%- notice.name %></a>
+                                                            <a href="/tender/<%- notice.tid %>/change/project/<%- notice.cpid %>"><%- notice.c_code %> </a>
+                                                            <%- acChangeProject.statusString[notice.status]%>
+                                                        </div>
+                                                    </div>
+                                                    <p class="mt-1 mb-0"><%- notice.su_name %><small class="ml-1 text-muted"><%- (notice.su_role ? '- ' + notice.su_role : '') %></small>
+                                                        <span class="pull-right text-muted"><%- ctx.helper.formatFullDate(notice.create_time) %></span>
+                                                    </p>
+                                                </div>
+                                            </li>
                                         <% } else if(notice.type === pushType.advance) { %>
                                             <li class="media pb-3 mb-3 border-bottom-1">
                                                 <div class="media-body">

+ 13 - 13
app/view/datacollect/index.ejs

@@ -176,7 +176,7 @@
                                         <div class="col-6 pr-0 height-50">
                                             <div class="card text-center bg-dark text-white border-right-0 border-botton-0 height-100">
                                                 <div class="card-body card-small-body height-100">
-                                                    <h5 class="card-title card-approve-title height-50"><span class="month_stage_num">0</span><small class="small-text">期</small></h5>
+                                                    <h5 class="card-title card-approve-title height-50"><span class="card-approve-big month_stage_num">0</span><small class="small-text">期</small></h5>
                                                     <p class="card-text text-muted height-50">计量期</p>
                                                 </div>
                                             </div>
@@ -184,7 +184,7 @@
                                         <div class="col-6 pl-0 height-50">
                                             <div class="card text-center bg-dark text-white border-botton-0 height-100">
                                                 <div class="card-body card-small-body height-100">
-                                                    <h5 class="card-title card-approve-title height-50"><span class="month_change_num">0</span><small  class="small-text">条</small></h5>
+                                                    <h5 class="card-title card-approve-title height-50"><span class="card-approve-big month_change_num">0</span><small  class="small-text">条</small></h5>
                                                     <p class="card-text text-muted height-50">变更令</p>
                                                 </div>
                                             </div>
@@ -192,7 +192,7 @@
                                         <div class="col-6 pr-0 height-50">
                                             <div class="card text-center bg-dark text-white border-right-0 border-top-0 height-100">
                                                 <div class="card-body card-small-body height-100">
-                                                    <h5 class="card-title card-approve-title height-50"><span class="month_revise_num">0</span><small  class="small-text">次</small></h5>
+                                                    <h5 class="card-title card-approve-title height-50"><span class="card-approve-big month_revise_num">0</span><small  class="small-text">次</small></h5>
                                                     <p class="card-text text-muted height-50">台账修订</p>
                                                 </div>
                                             </div>
@@ -200,7 +200,7 @@
                                         <div class="col-6 pl-0 height-50">
                                             <div class="card text-center bg-dark text-white border-top-0 height-100">
                                                 <div class="card-body card-small-body height-100">
-                                                    <h5 class="card-title card-approve-title height-50"><span class="month_material_num">0</span><small  class="small-text">期</small></h5>
+                                                    <h5 class="card-title card-approve-title height-50"><span class="card-approve-big month_material_num">0</span><small  class="small-text">期</small></h5>
                                                     <p class="card-text text-muted height-50">材料调差</p>
                                                 </div>
                                             </div>
@@ -544,7 +544,7 @@
                                                     <div class="col-6 pr-0">
                                                         <div class="card text-center bg-dark text-white border-right-0 border-botton-0 height-100">
                                                             <div class="card-body card-small-body">
-                                                                <h5 class="card-title card-approve-title height-50"><span class="month_stage_num">0</span><small class="small-text">期</small></h5>
+                                                                <h5 class="card-title card-approve-title height-50"><span class="card-approve-big month_stage_num">0</span><small class="small-text">期</small></h5>
                                                                 <p class="card-text text-muted height-50">计量期</p>
                                                             </div>
                                                         </div>
@@ -552,7 +552,7 @@
                                                     <div class="col-6 pl-0">
                                                         <div class="card text-center bg-dark text-white border-botton-0 height-100">
                                                             <div class="card-body card-small-body">
-                                                                <h5 class="card-title card-approve-title height-50"><span class="month_change_num">0</span><small class="small-text">条</small></h5>
+                                                                <h5 class="card-title card-approve-title height-50"><span class="card-approve-big month_change_num">0</span><small class="small-text">条</small></h5>
                                                                 <p class="card-text text-muted height-50">变更令</p>
                                                             </div>
                                                         </div>
@@ -560,7 +560,7 @@
                                                     <div class="col-6 pr-0">
                                                         <div class="card text-center bg-dark text-white border-right-0 border-top-0 height-100">
                                                             <div class="card-body card-small-body">
-                                                                <h5 class="card-title card-approve-title height-50"><span class="month_revise_num">0</span><small class="small-text">次</small></h5>
+                                                                <h5 class="card-title card-approve-title height-50"><span class="card-approve-big month_revise_num">0</span><small class="small-text">次</small></h5>
                                                                 <p class="card-text text-muted height-50">台账修订</p>
                                                             </div>
                                                         </div>
@@ -568,7 +568,7 @@
                                                     <div class="col-6 pl-0">
                                                         <div class="card text-center bg-dark text-white border-top-0 height-100">
                                                             <div class="card-body card-small-body">
-                                                                <h5 class="card-title card-approve-title height-50"><span class="month_material_num">0</span><small class="small-text">期</small></h5>
+                                                                <h5 class="card-title card-approve-title height-50"><span class="card-approve-big month_material_num">0</span><small class="small-text">期</small></h5>
                                                                 <p class="card-text text-muted height-50">材料调差</p>
                                                             </div>
                                                         </div>
@@ -1249,7 +1249,7 @@
                         const sameObject = _.intersectionWith(t.category, cc1, _.isEqual);
                         if(sameObject.length === 1) {
                             const index = _.findIndex(chart_category_data.data1, function (item) {
-                                return _.isEqual(item, sameObject);
+                                return _.isEqual(_.sortBy(item, 'cid'), _.sortBy(sameObject, 'cid'));
                             });
                             if (index !== -1) {
                                 chart_option5_data.data1[index].value = ZhCalc.add(chart_option5_data.data1[index].value, (t.total_price ? t.total_price : 0));
@@ -1265,7 +1265,7 @@
                         const sameObject = _.intersectionWith(t.category, cc2, _.isEqual);
                         if(sameObject.length === 2) {
                             const index = _.findIndex(chart_category_data.data2, function (item) {
-                                return _.isEqual(item, sameObject);
+                                return _.isEqual(_.sortBy(item, 'cid'), _.sortBy(sameObject, 'cid'));
                             })
                             if (index !== -1) {
                                 chart_option5_data.data2[index].value = ZhCalc.add(chart_option5_data.data2[index].value, (t.total_price ? t.total_price : 0));
@@ -1276,7 +1276,7 @@
                         const sameObject = _.intersectionWith(t.category, cc3, _.isEqual);
                         if(sameObject.length === 3) {
                             const index = _.findIndex(chart_category_data.data3, function (item) {
-                                return _.isEqual(item, sameObject);
+                                return _.isEqual(_.sortBy(item, 'cid'), _.sortBy(sameObject, 'cid'));
                             })
                             if (index !== -1) {
                                 chart_option5_data.data3[index].value = ZhCalc.add(chart_option5_data.data3[index].value, (t.total_price ? t.total_price : 0));
@@ -1389,7 +1389,7 @@
                 chart_option2_data.total_price.push(t.total_price ? t.total_price : 0);
                 chart_option2_data.contract_tp.push(t.end_contract_tp ? t.end_contract_tp : 0);
                 chart_option2_data.qc_tp.push(t.end_qc_tp ? t.end_qc_tp : 0);
-                const rate = ZhCalc.mul(ZhCalc.div(ZhCalc.add(t.pre_gather_tp, t.gather_tp), t.total_price, 2), 100, 0);
+                const rate = ZhCalc.mul(ZhCalc.div(ZhCalc.add(t.pre_gather_tp, t.gather_tp), ZhCalc.add(t.total_price, t.end_qc_tp), 2), 100, 0);
                 chart_option3_data.push(rate ? rate : '');
                 if (option1_is_tender) {
                     if(t.total_price) chart_option1_data.push({ value: t.total_price, name: t.name});
@@ -1421,7 +1421,7 @@
             }
             // console.log(tenderList);
             $('.data_tender_num').text(tenderList.length);
-            const rate = total_price ? ZhCalc.round(ZhCalc.div(total_stage_price, total_price) * 100, 2) : 0;
+            const rate = total_price && total_change_price ? ZhCalc.round(ZhCalc.div(total_stage_price, ZhCalc.add(total_price, total_change_price)) * 100, 2) : 0;
             $('.data_tender_rate').text(rate ? rate + '%' : '0%');
             $('.data_total_price').text(formatMoney(total_price));
             $('.data_total_change_price').text(formatMoney(total_change_price));

+ 49 - 1
app/view/layout/layout.ejs

@@ -32,7 +32,7 @@
 </div>
 <div class="main">
     <% include ./menu.ejs %>
-
+    
     <div class="main-panel">
         <%- content %>
     </div>
@@ -44,6 +44,10 @@
 <!-- 遮罩层 -->
 <div id="hide-all" style="display:none;position: absolute;width: 100%;height: 100%;top:0;left: 0;z-index:10000000">
 </div>
+
+<!-- 对接项目管理弹窗 -->
+<% include ./modal.ejs %>
+
 <%- modal %>
 <script type="text/javascript">
     toastr.options = {
@@ -79,6 +83,50 @@
     const is_debug = true;
     <% } %>
 </script>
+<style type="text/css">
+
+  .process-box{
+    width: 100%;
+    height: 12px;
+    border-radius: 10px;
+    background: #999;
+    margin-top: 10px;
+    /* border: 1px solid #ff6780; */
+  }
+  .process-child{
+    position: relative;
+    height:100%;
+    border-radius:inherit;
+  }
+
+  .process-animate{
+    background: #007BFF !important;
+    color: white;
+    text-align: center;
+    position: absolute;
+    left: 0;
+    top: 0;
+    bottom: 0;
+    border-radius:inherit;
+    animation: process 1s linear forwards ;
+  }
+  @keyframes process
+  {
+      0%{
+          left:0;right:100%;
+      }
+      20%{
+          right:80%
+      }
+      40%{
+          right:60%;
+      }
+      60%{right:40%;}
+      80%{right:20%;}
+      100%{right:0;}
+  }
+
+</style>
 </body>
 
 </html>

+ 41 - 4
app/view/layout/modal.ejs

@@ -1,5 +1,5 @@
 <!--弹出编辑权限-->
-<div class="modal fade" id="permission-form" data-backdrop="static">
+<!-- <div class="modal fade" id="permission-form" data-backdrop="static">
     <div class="modal-dialog" role="document">
         <div class="modal-content">
             <div class="modal-header">
@@ -13,10 +13,10 @@
             </div>
         </div>
     </div>
-</div>
+</div> -->
 
 <!--弹出新建台账变更-->
-<div class="modal fade" id="add-bg" data-backdrop="static">
+<!-- <div class="modal fade" id="add-bg" data-backdrop="static">
     <div class="modal-dialog" role="document">
         <div class="modal-content">
             <div class="modal-header">
@@ -38,5 +38,42 @@
             </div>
         </div>
     </div>
-</div>
+</div> -->
 
+<!--对接项目管理新增项目-->
+<div class="modal fade" id="add-management" 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">
+              <span>「项目管理」系统不存在当前项目, 确认新增?</span>
+          </div>
+          <div class="modal-footer">
+              <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">取消</button>
+              <button type="button" class="btn btn-primary btn-sm">确定</button>
+          </div>
+      </div>
+  </div>
+</div>
+<div class="modal fade" id="process-management" 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>
+              <div class="process-box">
+                <div class="process-child" style="width:99%"> 
+                    <div class="process-animate"></div>
+                </div>
+            </div>
+          </div>
+          <div class="modal-footer">
+              <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">取消</button>
+          </div>
+      </div>
+  </div>
+</div>

+ 0 - 0
app/view/login/login.ejs


Vissa filer visades inte eftersom för många filer har ändrats