ソースを参照

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

Tony Kang 2 年 前
コミット
7faa8c3b04
77 ファイル変更2898 行追加3183 行削除
  1. 11 0
      app/const/audit.js
  2. 2 0
      app/controller/change_controller.js
  3. 10 1
      app/controller/report_controller.js
  4. 2 1
      app/controller/revise_controller.js
  5. 61 3
      app/controller/stage_controller.js
  6. 11 0
      app/controller/tender_controller.js
  7. 12 2
      app/lib/budget_final.js
  8. 65 3
      app/lib/pay_calc.js
  9. 7 0
      app/lib/revise_price.js
  10. 59 3
      app/lib/rpt_data_analysis.js
  11. 10 10
      app/lib/sum_load.js
  12. 38 6
      app/middleware/stage_check.js
  13. 12 0
      app/public/css/main.css
  14. 18 1
      app/public/js/change_information_approval.js
  15. 15 3
      app/public/js/change_information_set.js
  16. 12 11
      app/public/js/change_information_show.js
  17. 22 22
      app/public/js/change_revise.js
  18. 6 0
      app/public/js/global.js
  19. 17 0
      app/public/js/ledger.js
  20. 7 0
      app/public/js/ledger_audit.js
  21. 46 4
      app/public/js/ledger_gather.js
  22. 40 36
      app/public/js/material_checklist.js
  23. 1 1
      app/public/js/measure_stage.js
  24. 8 4
      app/public/js/path_tree.js
  25. 4 4
      app/public/js/profile.js
  26. 15 0
      app/public/js/revise.js
  27. 40 10
      app/public/js/revise_price.js
  28. 9 0
      app/public/js/se_bonus.js
  29. 111 1
      app/public/js/shares/cs_tools.js
  30. 1 0
      app/public/js/shenpi.js
  31. 10 11
      app/public/js/spreadjs_rela/spreadjs_zh.js
  32. 17 1
      app/public/js/stage.js
  33. 225 2
      app/public/js/stage_audit.js
  34. 1 1
      app/public/js/stage_im.js
  35. 82 19
      app/public/js/stage_pay.js
  36. 42 36
      app/public/js/tender_list_info.js
  37. 9 3
      app/public/js/tender_list_progress.js
  38. 39 13
      app/public/report/js/jpc_output.js
  39. 2 2
      app/public/report/js/rpt_custom.js
  40. 3 1
      app/public/report/js/rpt_jspdf.js
  41. 192 0
      app/public/report/js/rpt_move_signature.js
  42. 4 0
      app/public/report/js/rpt_print.js
  43. 39 0
      app/public/report/js/rpt_show_level.js
  44. 12 3
      app/public/report/js/rpt_signature.js
  45. 2 0
      app/router.js
  46. 4 4
      app/service/change_audit_list.js
  47. 4 4
      app/service/ledger_revise.js
  48. 1 1
      app/service/material_bills.js
  49. 1 1
      app/service/material_stage.js
  50. 12 32
      app/service/pay.js
  51. 11 0
      app/service/report.js
  52. 6 0
      app/service/report_memory.js
  53. 4 4
      app/service/revise_audit.js
  54. 109 0
      app/service/rpt_gather_memory.js
  55. 61 0
      app/service/shenpi_audit.js
  56. 41 16
      app/service/stage.js
  57. 402 12
      app/service/stage_audit.js
  58. 13 6
      app/service/stage_audit_ass.js
  59. 1 1
      app/service/stage_bills.js
  60. 174 3
      app/service/stage_pay.js
  61. 1 1
      app/service/tender.js
  62. 5 0
      app/view/ledger/gather.ejs
  63. 2 0
      app/view/material/checklist.ejs
  64. 6 2
      app/view/report/index.ejs
  65. 20 0
      app/view/report/rpt_all_popup.ejs
  66. 1 2
      app/view/revise/price_modal.ejs
  67. 1 1
      app/view/setting/fun.ejs
  68. 3 0
      app/view/stage/audit_btn.ejs
  69. 221 31
      app/view/stage/audit_modal.ejs
  70. 17 1
      app/view/stage/index.ejs
  71. 221 1
      app/view/tender/modal.ejs
  72. 0 2839
      builder_report_index_define.js
  73. 2 0
      config/web.js
  74. 1 1
      db_script/baseUtils.js
  75. 42 0
      db_script/pay_order.js
  76. 13 0
      publish.md
  77. 147 2
      sql/update.sql

+ 11 - 0
app/const/audit.js

@@ -115,6 +115,7 @@ const stage = (function() {
         checkNo: 4, // 审批退回原报
         checkNoPre: 5, // 审批退回上一人
         checkAgain: 6, // 重新审批 // 该状态仅可用于,终审退回时,修改原终审的审批状态,并同时新增一条新的终审审批中记录
+        checkCancel: 7, // 撤回 // 该状态为上一审批人可发起,回到它到审批阶段,并同时新增一条新的审批中记录
     };
 
     // 流程状态提示
@@ -125,6 +126,7 @@ const stage = (function() {
     statusString[status.checkNo] = '审批退回';
     statusString[status.checkNoPre] = '审批退回';
     statusString[status.checkAgain] = '重新审批';
+    statusString[status.checkCancel] = '撤回';
 
     // 流程状态样式
     const statusClass = [];
@@ -134,6 +136,7 @@ const stage = (function() {
     statusClass[status.checkNo] = 'text-warning';
     statusClass[status.checkNoPre] = 'text-warning';
     statusClass[status.checkAgain] = 'text-warning';
+    statusClass[status.checkCancel] = 'text-warning';
 
     /**
      * 期列表,审批状态一列
@@ -146,6 +149,7 @@ const stage = (function() {
     statusButton[status.checkNo] = '重新上报';
     statusButton[status.checkNoPre] = '重新审批';
     statusButton[status.checkAgain] = '重新审批';
+    statusButton[status.checkCancel] = '撤回';
     // 按钮样式
     const statusButtonClass = [];
     statusButtonClass[status.uncheck] = 'btn-primary';
@@ -154,6 +158,7 @@ const stage = (function() {
     statusButtonClass[status.checkNo] = 'btn-warning';
     statusButtonClass[status.checkNoPre] = 'btn-warning';
     statusButtonClass[status.checkAgain] = 'btn-warning';
+    statusButtonClass[status.checkCancel] = 'btn-warning';
     // 描述文本
     const auditString = [];
     auditString[status.uncheck] = '';
@@ -162,6 +167,7 @@ const stage = (function() {
     auditString[status.checkNo] = '审批退回';
     auditString[status.checkNoPre] = '审批退回';
     auditString[status.checkAgain] = '重新审批';
+    auditString[status.checkCancel] = '撤回';
     // 文字样式
     const auditStringClass = [];
     auditStringClass[status.uncheck] = '';
@@ -170,6 +176,7 @@ const stage = (function() {
     auditStringClass[status.checkNo] = 'text-warning';
     auditStringClass[status.checkNoPre] = 'text-warning';
     auditStringClass[status.checkAgain] = 'text-warning';
+    auditStringClass[status.checkCancel] = 'text-warning';
     /* ------------------------------------------------------- */
 
     /**
@@ -183,6 +190,7 @@ const stage = (function() {
     auditProgress[status.checkNo] = '审批退回';
     auditProgress[status.checkNoPre] = '审批退回';
     auditProgress[status.checkAgain] = '重新审批';
+    auditProgress[status.checkCancel] = '撤回';
     // 样式
     const auditProgressClass = [];
     auditProgressClass[status.uncheck] = '';
@@ -191,6 +199,7 @@ const stage = (function() {
     auditProgressClass[status.checkNo] = 'text-warning';
     auditProgressClass[status.checkNoPre] = 'text-warning';
     auditProgressClass[status.checkAgain] = 'text-warning';
+    auditProgressClass[status.checkCancel] = 'text-warning';
     /* ------------------------------------------------------- */
 
     const tiStatusString = [];
@@ -200,6 +209,7 @@ const stage = (function() {
     tiStatusString[status.checkNo] = '审批退回';
     tiStatusString[status.checkNoPre] = '审批中';
     tiStatusString[status.checkAgain] = '审批中';
+    tiStatusString[status.checkCancel] = '撤回';
     const tiStatusStringClass = [];
     tiStatusStringClass[status.uncheck] = '';
     tiStatusStringClass[status.checking] = 'text-warning';
@@ -207,6 +217,7 @@ const stage = (function() {
     tiStatusStringClass[status.checkNo] = 'text-warning';
     tiStatusStringClass[status.checkNoPre] = 'text-warning';
     tiStatusStringClass[status.checkAgain] = 'text-warning';
+    tiStatusStringClass[status.checkCancel] = 'text-warning';
     const backType = {
         org: 1,
         pre: 2,

+ 2 - 0
app/controller/change_controller.js

@@ -715,6 +715,7 @@ module.exports = app => {
                             }
                         }
                         cl.changed_amount = (change.status === audit.flow.status.backnew || change.status === audit.flow.status.checking || change.status === audit.flow.status.checked) && audit_amount !== '' ? audit_amount[audit_amount.length - 1] : cl.camount;
+                        // cl.changed_amount = ctx.helper.add(cl.oamount ? parseFloat(cl.oamount) : 0, changed_amount ? parseFloat(changed_amount) : 0);
                     }
                     renderData.changeList = changeList;
                     renderData.auditList2 = auditList2;
@@ -765,6 +766,7 @@ module.exports = app => {
                             }
                         }
                         cl.changed_amount = (change.status === audit.flow.status.backnew || change.status === audit.flow.status.checking || change.status === audit.flow.status.checked) && audit_amount !== '' ? audit_amount[audit_amount.length - 1] : cl.camount;
+                        // cl.changed_amount = ctx.helper.add(cl.oamount ? parseFloat(cl.oamount) : 0, changed_amount ? parseFloat(changed_amount) : 0);
                     }
                     renderData.changeList = changeList;
                     renderData.changeLedgerList = await ctx.service.changeLedger.getAllDataByCondition({ where: { tender_id: ctx.tender.id } });

+ 10 - 1
app/controller/report_controller.js

@@ -1636,6 +1636,12 @@ async function mergeStampSignature(ctx, status, pageData, singleRoleRel, rpt_ids
                                                 dupPicPaths.push(stampPath);
                                                 // await _chkRawPicSizeOSS(ctx, stampPath);
                                             }
+                                            // 保存在签字信息中的签章信息
+                                            let signatureArea;
+                                            if (role_rel.areaData && role_rel.areaData[signType]) {
+                                                signatureArea = role_rel.areaData[signType];
+                                            }
+
                                             const newStampCell = {
                                                 signature_name: JV.SIGNATURE_NAME_DUMMY,
                                                 control: sCell.control,
@@ -1644,7 +1650,10 @@ async function mergeStampSignature(ctx, status, pageData, singleRoleRel, rpt_ids
                                                 isStamp: true,
                                                 maxRect,
                                                 orgArea: sCell.area,
-                                                area: createStampArea(sCell, _page[JV.NODE_CONTROL_COLLECTION], maxRect),
+                                                area: signatureArea || createStampArea(sCell, _page[JV.NODE_CONTROL_COLLECTION], maxRect), // 有签章信息就直接取,没有的话就走原来的逻辑
+                                                signatureName: role_rel.signature_name, // 节点名称
+                                                signType, // 签章信息
+                                                isSaveSignature: !!signatureArea, // 是否有签章位置信息
                                             };
                                             newStampCells.push(newStampCell);
                                             break;

+ 2 - 1
app/controller/revise_controller.js

@@ -921,7 +921,8 @@ module.exports = app => {
         async _loadChange(ctx) {
             const change = await ctx.service.change.getAllDataByCondition({
                 columns: [ 'cid', 'code', 'name', 'w_code' ],
-                where: { tid: ctx.tender.id }
+                where: { tid: ctx.tender.id },
+                orders: [['code', 'asc']],
             });
             const changeBills = await ctx.service.changeAuditList.getAllDataByCondition({
                 columns: [ 'cid', 'code', 'name', 'unit', 'unit_price' ],

+ 61 - 3
app/controller/stage_controller.js

@@ -64,7 +64,7 @@ module.exports = app => {
                 },
                 shenpiConst,
             };
-            if ((ctx.stage.status === auditConst.status.uncheck || ctx.stage.status === auditConst.status.checkNo) && ctx.session.sessionUser.accountId === ctx.stage.user_id) {
+            if (((ctx.stage.status === auditConst.status.uncheck || ctx.stage.status === auditConst.status.checkNo) && ctx.session.sessionUser.accountId === ctx.stage.user_id) || ctx.session.sessionUser.is_admin) {
                 // data.accountGroup = accountGroup;
                 // 获取所有项目参与者
                 const accountList = await ctx.service.projectAccount.getAllDataByCondition({
@@ -1026,13 +1026,13 @@ module.exports = app => {
                         responseData.data = await ctx.service.stagePay.getStagePay(ctx.stage, responseData.data.pid);
                         break;
                     case 'del':
-                        await ctx.service.pay.del(data.id);
+                        await ctx.service.stagePay.del(data.id);
                         responseData.data = await ctx.service.stagePay.getStagePays(ctx.stage);
                         await payCalculator.calculateAll(responseData.data);
                         await this._updateStageCache(ctx, payCalculator);
                         break;
                     case 'changeOrder':
-                        responseData.data = await ctx.service.pay.changeOrder(data.id1, data.id2);
+                        responseData.data = await ctx.service.stagePay.changeOrder(data.id1, data.id2);
                         break;
                     case 'info':
                         responseData.data = await ctx.service.pay.save(data.updateData);
@@ -1400,6 +1400,64 @@ module.exports = app => {
         }
 
         /**
+         * 撤回审批
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async checkAuditCancel(ctx) {
+            try {
+                if (ctx.stage.revising) {
+                    throw '台账修订中,请勿修改提交期数据';
+                }
+                if (ctx.stage.cancancel) {
+                    await ctx.service.stageAudit.checkCancel(ctx.stage.id, ctx.stage.times);
+                    // ctx.redirect(ctx.request.header.referer);
+                    ctx.body = {
+                        err: 0,
+                        url: ctx.request.header.referer,
+                        msg: '',
+                    };
+                } else {
+                    throw '您无权进行该操作';
+                }
+            } catch (err) {
+                this.log(err);
+                // ctx.session.postError = err.toString();
+                // ctx.redirect(ctx.request.header.referer);
+                ctx.body = {
+                    err: 1,
+                    // url: ctx.request.header.referer,
+                    msg: err,
+                };
+            }
+        }
+
+        async saveAudit(ctx) {
+            try {
+                if (ctx.stage.revising) {
+                    throw '台账修订中,请勿修改提交期数据';
+                }
+                const data = JSON.parse(ctx.request.body.data);
+                if (ctx.session.sessionUser.is_admin && ctx.stage.status !== auditConst.status.checked) {
+                    await ctx.service.stageAudit.saveAudit(ctx.stage.id, ctx.stage.times, data);
+                    const auditors = await ctx.service.stageAudit.getAuditGroupByListWithOwner(ctx.stage.id, ctx.stage.times);
+                    ctx.body = { err: 0, msg: '', data: auditors };
+                } else {
+                    throw '您无权进行该操作';
+                }
+            } catch (err) {
+                this.log(err);
+                // ctx.session.postError = err.toString();
+                // ctx.redirect(ctx.request.header.referer);
+                ctx.body = {
+                    err: 1,
+                    // url: ctx.request.header.referer,
+                    msg: err,
+                };
+            }
+        }
+
+        /**
          * 清单汇总 页面 (Get)
          * @param ctx
          * @return {Promise<void>}

+ 11 - 0
app/controller/tender_controller.js

@@ -50,12 +50,14 @@ module.exports = app => {
             };
             if (tender.ledger_status === auditConst.ledger.status.uncheck) {
                 tender.cur_flow.name = tender.user_name;
+                tender.cur_flow.role = tender.user_role;
             } else {
                 const cur = tender.ledger_status === auditConst.ledger.status.checkNo
                     ? await this.ctx.service.ledgerAudit.getLastestAuditor(tender.id, tender.ledger_times - 1, auditConst.ledger.status.checkNo)
                     : await this.ctx.service.ledgerAudit.getLastestAuditor(tender.id, tender.ledger_times, tender.ledger_status);
                 if (cur) {
                     tender.cur_flow.name = cur.name;
+                    tender.cur_flow.role = cur.role;
                     if (cur.audit_order === 1) {
                         tender.pre_flow = { name: tender.user_name, time: cur.begin_time };
                     } else {
@@ -64,6 +66,7 @@ module.exports = app => {
                     }
                 } else {
                     tender.cur_flow.name = '';
+                    tender.cur_flow.role = '';
                 }
             }
         }
@@ -77,9 +80,11 @@ module.exports = app => {
             if (stage.status === auditConst.stage.status.uncheck) {
                 if (tender.user_id === stage.user_id) {
                     tender.cur_flow.name = tender.user_name;
+                    tender.cur_flow.role = tender.user_role;
                 } else {
                     const user = await this.ctx.service.projectAccount.getDataById(stage.user_id);
                     tender.cur_flow.name = user.name;
+                    tender.cur_flow.role = user.role;
                 }
                 if (stage.order > 1) {
                     const preStage = await this.ctx.service.stage.getDataByCondition({ tid: tender.id, order: stage.order - 1 });
@@ -99,6 +104,7 @@ module.exports = app => {
                 }
                 if (cur) {
                     tender.cur_flow.name = cur.name;
+                    tender.cur_flow.role = cur.role;
                     if (cur.order === 1) {
                         tender.pre_flow = {};
                         if (tender.user_id === stage.user_id) {
@@ -114,6 +120,7 @@ module.exports = app => {
                     }
                 } else {
                     tender.cur_flow.name = '';
+                    tender.cur_flow.role = '';
                 }
             }
         }
@@ -1517,6 +1524,9 @@ module.exports = app => {
                                 throw '您无权查看该数据';
                             }
                             break;
+                        case 'show_level':
+                            responseData.data[f] = ctx.tender.data.rpt_show_level;
+                            break;
                         default:
                             throw '未知数据类型';
                     }
@@ -1538,6 +1548,7 @@ module.exports = app => {
                 if (data.change_plan) await this.ctx.service.changePlan.defaultUpdateRows(data.change_plan);
                 if (data.advance) await this.ctx.service.advance.defaultUpdateRows(data.advance);
                 if (data.pm_deal) await this.ctx.service.project.setPmDealCache(this.ctx.session.sessionProject.id, data.pm_deal);
+                if (data.show_level) await this.ctx.service.tender.saveTenderData(ctx.tender.id, { rpt_show_level: data.show_level });
                 ctx.body = responseData;
             } catch (err) {
                 ctx.log(err);

+ 12 - 2
app/lib/budget_final.js

@@ -87,7 +87,7 @@ class BudgetFinal {
     constructor (ctx) {
         this.ctx = ctx;
         this.budgetSetting = { id: 'tree_id', pid: 'tree_pid', order: 'order', level: 'level', rootId: -1, calcFields: ['total_price'] };
-        this.tenderSetting = { id: 'ledger_id', pid: 'ledger_pid', order: 'order', level: 'level', rootId: -1, calcFields: ['total_price', 'end_gather_tp'] };
+        this.tenderSetting = { id: 'ledger_id', pid: 'ledger_pid', order: 'order', level: 'level', rootId: -1, calcFields: ['total_price', 'end_gather_tp', 'end_contract_tp', 'end_qc_tp'] };
         this.finalTree = new FinalTree(this.ctx, { id: 'id', pid: 'pid', order: 'order', level: 'level', fullPath: 'full_path', rootId: -1 });
     }
 
@@ -167,9 +167,11 @@ class BudgetFinal {
                 { data: curBills, fields: ['contract_tp', 'qc_tp'], prefix: '', relaId: 'lid' },
                 { data: preBills, fields: ['contract_tp', 'qc_tp'], prefix: 'pre_', relaId: 'lid' },
                 { data: dgnData, fields: ['deal_dgn_qty1', 'deal_dgn_qty2', 'c_dgn_qty1', 'c_dgn_qty2'], prefix: '', relaId: 'id' },
-                { data: bpcData, fields: ['pc_tp', 'org_price'], prefix: '', relaId: 'lid' },
+                { data: bpcData, fields: ['contract_pc_tp', 'qc_pc_tp', 'pc_tp', 'org_price'], prefix: '', relaId: 'lid' },
             ]);
             bills.forEach(b => {
+                b.end_contract_tp = helper.sum([b.contract_tp, b.pre_contract_tp, b.contract_pc_tp]);
+                b.end_qc_tp = helper.sum([b.qc_tp, b.pre_qc_tp, b.qc_pc_tp]);
                 b.end_gather_tp = helper.sum([b.qc_tp, b.contract_tp, b.pre_qc_tp, b.pre_contract_tp, b.pc_tp]);
             });
             this.final.tender_info.push({ id, stageOrder: stage.order, stageStatus: stage.status, stageFlow: stage.curTimes + '-' + stage.curOrder });
@@ -181,8 +183,14 @@ class BudgetFinal {
             cur.total_price = helper.add(cur.total_price, source.total_price);
             cur.dgn_qty1 = helper.add(cur.dgn_qty1, source.dgn_qty1);
             cur.dgn_qty2 = helper.add(cur.dgn_qty2, source.dgn_qty2);
+            cur.deal_dgn_qty1 = helper.add(cur.deal_dgn_qty1, source.deal_dgn_qty1);
+            cur.deal_dgn_qty2 = helper.add(cur.deal_dgn_qty2, source.deal_dgn_qty2);
+            cur.c_dgn_qty1 = helper.add(cur.c_dgn_qty1, source.c_dgn_qty1);
+            cur.c_dgn_qty2 = helper.add(cur.c_dgn_qty2, source.c_dgn_qty2);
             cur.final_dgn_qty1 = helper.sum([cur.final_dgn_qty1, source.deal_dgn_qty1, source.c_dgn_qty1]);
             cur.final_dgn_qty2 = helper.sum([cur.final_dgn_qty2, source.deal_dgn_qty2, source.c_dgn_qty2]);
+            cur.final_contract_tp = helper.add(cur.final_contract_tp, source.end_contract_tp);
+            cur.final_qc_tp = helper.add(cur.final_qc_tp, source.end_qc_tp);
             cur.final_tp = helper.add(cur.final_tp, source.end_gather_tp);
         });
     }
@@ -241,6 +249,8 @@ class BudgetFinal {
 
                 dgn_qty1: x.dgn_qty1 || 0, dgn_qty2: x.dgn_qty2 || 0, total_price: x.total_price || 0,
                 final_dgn_qty1: x.final_dgn_qty1 || 0, final_dgn_qty2: x.final_dgn_qty2 || 0, final_tp: x.final_tp || 0,
+                deal_dgn_qty1: x.deal_dgn_qty1 || 0, deal_dgn_qty2: x.deal_dgn_qty2 || 0, c_dgn_qty1: x.c_dgn_qty1 || 0, c_dgn_qty2: x.c_dgn_qty2 || 0,
+                final_contract_tp: x.final_contract_tp || 0, final_qc_tp: x.final_qc_tp || 0,
                 dgn_price: x.dgn_price || 0, dgn_qty: x.dgn_qty,
                 final_dgn_price: x.final_dgn_price || 0, final_dgn_qty: x.final_dgn_qty,
                 grow_dgn_qty1: x.grow_dgn_qty1 || 0, grow_dgn_qty2: x.grow_dgn_qty2 || 0, grow_dgn_qty: x.grow_dgn_qty, grow_tp: x.grow_tp || 0,

+ 65 - 3
app/lib/pay_calc.js

@@ -23,6 +23,7 @@ class PayCalculate {
         this.percentReg = /((\d+)|((\d+)(\.\d+)))%/g;
         this.tenderInfo = tenderInfo;
         this.decimal = tenderInfo.decimal.pay ? tenderInfo.decimal.payTp : tenderInfo.decimal.tp;
+        this.orderReg = /f\d+/ig;
         /* 以下变量在调用calculate方法后获得
         this.add;
         this.pre;
@@ -51,8 +52,19 @@ class PayCalculate {
         }
     }
 
-    _calculateTpExpr(pay) {
+    _calculateTpExpr(pay, pays) {
         let formula = pay.expr;
+        const bPrint = (pay.expr === 'f18+f12');
+        const orderParam = pay.expr.match(this.orderReg);
+        if (orderParam) {
+            for (const op of orderParam) {
+                const order = parseInt(op.substring(1, op.length));
+                const orderPay = pays.find(x => { return x.order === order });
+                if (bPrint) console.log(order, orderPay);
+                formula = formula.replace(op, orderPay && orderPay.tp || 0);
+            }
+        }
+        if (bPrint) console.log(formula);
         for (const b of this.bases) {
             if ((b.code === 'bqwc' || b.code === 'bqht') && (!pay.pre_used && pay.sprice)) {
                 switch (b.code) {
@@ -177,6 +189,54 @@ class PayCalculate {
         }
     }
 
+    getLeafOrder(data, pays, parentOrder) {
+        // if (!data) return [];
+        // if (!data.expr) return [`f${data.order}`];
+        // const orderParam = data.expr.match(this.orderReg);
+        // if (!orderParam || orderParam.length === 0) return [`f${data.order}`];
+        //
+        // const result = [...orderParam];
+        // for (const op of orderParam) {
+        //     const order = op.substring(1, op.length);
+        //     if (parseInt(order) === data.order || op === parentOrder) {
+        //         result.push(op);
+        //     } else {
+        //         result.push(...this.getLeafOrder(pays[parseInt(order) -1], pays, `f${data.order}`));
+        //     }
+        // }
+        // return this.ctx.helper._.uniq(result);
+        if (!data || !data.expr) return [];
+        const orderParam = data.expr.match(this.orderReg);
+        if (!orderParam || orderParam.length === 0) return [];
+
+        const result = [...orderParam];
+        for (const op of orderParam) {
+            const order = op.substring(1, op.length);
+            if (parseInt(order) === data.order || op === parentOrder) {
+                result.push(op);
+            } else {
+                const sub = this.getLeafOrder(pays[parseInt(order) -1], pays, `f${data.order}`);
+                if (sub.length > 0) {
+                    result.push(...sub);
+                } else {
+                    result.push(op);
+                }
+            }
+        }
+        return this.ctx.helper._.uniq(result);
+    }
+
+    sortPaysByCalc(pays) {
+        for (const pay of pays) {
+            pay.calcLeaf = this.getLeafOrder(pay, pays);
+        }
+        pays.sort((x, y) => {return x.calcLeaf.length - y.calcLeaf.length; });
+    }
+
+    sortPaysByOrder(pays) {
+        pays.sort((x, y) => { return x.order - y.order; });
+    }
+
     /**
      * 计算本期、截止本期金额
      * @param {Array} pays - (标段&期)合同支付数据
@@ -195,7 +255,7 @@ class PayCalculate {
             if (p.ptype === payType.normal || p.ptype === payType.wc) {
                 if (!p.pause && (!p.sprice || this.add.gather_tp >= p.sprice)) {
                     if (p.expr && p.expr !== '') {
-                        const value = this.ctx.helper.round(this._calculateTpExpr(p), this.decimal);
+                        const value = this.ctx.helper.round(this._calculateTpExpr(p, pays), this.decimal);
                         if (p.rprice) {
                             if (this._checkDeadline(p)) {
                                 p.tp = this.ctx.helper.sub(p.rprice, p.pre_tp);
@@ -248,7 +308,7 @@ class PayCalculate {
                 this.sf.tp = this.yf.tp;
             }
         } else {
-            const value = this.ctx.helper.round(this._calculateTpExpr(this.sf), this.decimal);
+            const value = this.ctx.helper.round(this._calculateTpExpr(this.sf, pays), this.decimal);
             if (this.sf.rprice) {
                 this.sf.tp = Math.min(this.ctx.helper.sub(this.sf.rprice, this.sf.pre_tp), value);
             } else {
@@ -262,7 +322,9 @@ class PayCalculate {
         await this.getCalcBase();
         await this._getAddCalcRela();
         await this.calculateStartRangePrice(pays);
+        this.sortPaysByCalc(pays);
         await this.calculate(pays);
+        this.sortPaysByOrder(pays);
     }
 }
 

+ 7 - 0
app/lib/revise_price.js

@@ -32,6 +32,7 @@ class revisePriceCalc {
                 x.rela_cid = x.rela_cid.split(',');
                 this.rela_price_c.push(x);
             } else {
+                x.his_rela_cid = [];
                 this.common_price_c.push(x);
             }
         });
@@ -209,6 +210,7 @@ class revisePriceCalc {
             if (p) {
                 updateBills.push({ id: b.id, unit_price: p.new_price });
                 bills_tp = this.ctx.helper.mul(p.new_price, b.spamount, change.tp_decimal || decimal.tp);
+                if (!p.rela_cid) p.his_rela_cid.push(change.cid);
             } else {
                 bills_tp = this.ctx.helper.mul(b.unit_price, b.spamount, change.tp_decimal || decimal.tp);
             }
@@ -235,6 +237,11 @@ class revisePriceCalc {
         for (const c of change) {
             await this.calcChange(c, transaction);
         }
+        const revisePriceUpdate = [];
+        for (const p of this.common_price_c) {
+            if (p.his_rela_cid.length > 0) revisePriceUpdate.push({id: p.id, his_rela_cid: p.his_rela_cid.join(',')});
+        }
+        if (revisePriceUpdate.length > 0) await transaction.updateRows(this.ctx.service.revisePrice.tableName, revisePriceUpdate);
     }
     async _calcStage(stage, bills, transaction) {
         // 无单价变更不执行

+ 59 - 3
app/lib/rpt_data_analysis.js

@@ -1528,7 +1528,6 @@ const loadCooperationData = {
         return auditor.sign_path ? 'public/upload/sign/' + auditor.sign_path : '';
     },
     _loadImCooperationData(ctx, data, options, csRela) {
-
         let coSignOrder = [];
         if (csRela && csRela.tplDefine && csRela.tplDefine.audit_select && csRela.cDefine && csRela.cDefine.audit_select) {
             if (csRela.cDefine.audit_select) {
@@ -1569,14 +1568,71 @@ const loadCooperationData = {
             }
         }
     },
+    _findStageAuditAss(relaId, stageAuditAss, auditor) {
+        if (!stageAuditAss || stageAuditAss.length === 0) return null;
+        if (relaId.length > 0) {
+            for (const id of relaId) {
+                const c = stageCooperation.find(x => { return x.ass_ledger_id.indexOf(id) >= 0; });
+                if (c) return c;
+            }
+            return null;
+        }
+        return null;
+
+    },
+    _loadImAssistData(ctx, data, options, csRela) {
+        let coSignOrder = [];
+        if (csRela && csRela.tplDefine && csRela.tplDefine.audit_select && csRela.cDefine && csRela.cDefine.audit_select) {
+            if (csRela.cDefine.audit_select) {
+                for (const asc of csRela.cDefine.audit_select) {
+                    coSignOrder.push(asc.order);
+                }
+            }
+        } else {
+            coSignOrder = options.co_sign || [];
+        }
+
+        const stageAuditAss = [];
+        let finish = true;
+        for (const sa of data.stage_audit) {
+            if (sa.end_time) stageAuditAss.push(data.mem_stage_audit_ass.filter(x => { return x.user_id === sa.aid; }));
+            if (finish && !sa.end_time) finish = false;
+        }
+        for (const d of data[options.table]) {
+            const bills = data.mem_stage_bills.find(x => { return x.id === d.lid; });
+            const relaId = bills ? bills.full_path.split('-').reverse() : [];
+
+            d.ass = [];
+            for (const [i, sa] of data.stage_audit.entries()) {
+                const co = this._findStageAuditAss(relaId, stageAuditAss[i], sa);
+                d.ass.push(co);
+            }
+
+            for (const [i, cs] of coSignOrder.entries()) {
+                if (data.stage_audit[cs] && data.stage_audit[cs].end_time) {
+                    d['co_sign' + (i + 1)] = this._completeSign(d.ass[cs] || data.stage_audit[cs]);
+                    d['co_opinion' + (i + 1)] = data.stage_audit[cs].opinion || '';
+                    d['co_time' + (i + 1)] = data.stage_audit[cs].end_time;
+                    d['co_name' + (i + 1)] = d.ass[cs] ? d.ass[cs].name : data.stage_audit[cs].name;
+                    d['co_company' + (i + 1)] = d.ass[cs] ? d.ass[cs].company : data.stage_audit[cs].company;
+                }
+            }
+        }
+    },
     fun(ctx, data, fieldsKey, options, csRela) {
         if (!options || !options.table) return;
         if (!data[options.table]) return;
         if (!data.mem_stage_bills) return;
         if (!data.stage_audit) return;
-        if (!data.ledger_cooperation) return;
+        if (!data.ledger_cooperation && !data.mem_stage_audit_ass) return;
 
-        if (['mem_stage_im_zl', 'mem_stage_im_tz'].indexOf(options.table) >= 0) { this._loadImCooperationData(ctx, data, options, csRela); }
+        if (['mem_stage_im_zl', 'mem_stage_im_tz'].indexOf(options.table) >= 0) {
+            if (data.mem_stage_audit_ass) {
+                this._loadImAssistData(ctx, data, options, csRela);
+            } else {
+                this._loadImCooperationData(ctx, data, options, csRela);
+            }
+        }
     },
 };
 const signSelect = {

+ 10 - 10
app/lib/sum_load.js

@@ -408,19 +408,19 @@ class gatherStageGclTree extends loadGclBaseTree {
                 if (!bn.is_import && bn.org_qc_qty) {
                     data.qc_qty = bn.org_qc_qty;
                     data.qc_tp = bn.org_qc_tp;
-                    data.qc_minus_qty = bn.org_qc_minus_qty;
-                    data.positive_qc_qty = bn.positive_qc_qty;
-                    data.positive_qc_tp = bn.positive_qc_tp;
-                    data.negative_qc_qty = bn.negative_qc_qty;
-                    data.negative_qc_tp = bn.negative_qc_tp;
+                    data.qc_minus_qty = bn.org_qc_minus_qty || 0;
+                    data.positive_qc_qty = bn.positive_qc_qty || 0;
+                    data.positive_qc_tp = bn.positive_qc_tp || 0;
+                    data.negative_qc_qty = bn.negative_qc_qty || 0;
+                    data.negative_qc_tp = bn.negative_qc_tp || 0;
                 } else {
                     data.qc_qty = bn.qc_qty;
                     data.qc_tp = bn.qc_tp;
-                    data.qc_minus_qty = bn.qc_minus_qty;
-                    data.positive_qc_qty = bn.positive_qc_qty;
-                    data.positive_qc_tp = bn.positive_qc_tp;
-                    data.negative_qc_qty = bn.negative_qc_qty;
-                    data.negative_qc_tp = bn.negative_qc_tp;
+                    data.qc_minus_qty = bn.qc_minus_qty || 0;
+                    data.positive_qc_qty = bn.positive_qc_qty || 0;
+                    data.positive_qc_tp = bn.positive_qc_tp || 0;
+                    data.negative_qc_qty = bn.negative_qc_qty || 0;
+                    data.negative_qc_tp = bn.negative_qc_tp || 0;
                 }
                 result.update.push(data);
             }

+ 38 - 6
app/middleware/stage_check.js

@@ -66,16 +66,16 @@ module.exports = options => {
             // 权限相关
             // todo 校验权限 (标段参与人、分享、游客)
             const accountId = this.session.sessionUser.accountId,
-                auditorIds = _.map(stage.auditors, 'aid'),
-                userAssistIds = _.map(stage.userAssists, 'ass_user_id'),
-                auditAssistIds = _.map(stage.auditAssists, 'ass_user_id'),
-                shareIds = [];
+                auditorIds = _.map(stage.auditors, 'aid');
             let auditAssists = yield this.service.stageAuditAss.getData(stage);
             auditAssists = auditAssists.filter(x => {
                 return x.user_id === stage.user_id || auditorIds.indexOf(x.user_id) >= 0;
             });
             stage.userAssists = auditAssists.filter(x => { return x.user_id === stage.user_id; }); // 原报协同人
             stage.auditAssists = auditAssists.filter(x => { return x.user_id !== stage.user_id; }); // 审批协同人
+            const userAssistIds = _.map(stage.userAssists, 'ass_user_id'),
+                auditAssistIds = _.map(stage.auditAssists, 'ass_user_id'),
+                shareIds = [];
             stage.users = stage.status === status.uncheck ? [stage.user_id, ...userAssistIds] : [stage.user_id, ...userAssistIds, ...auditorIds, ...auditAssistIds];
             stage.relaAssists = auditAssists.filter(x => { return x.user_id === accountId });
             if (stage.status === status.uncheck || stage.status === status.checkNo) {
@@ -86,11 +86,43 @@ module.exports = options => {
             } else {
                 const ass = stage.auditAssists.find(x => { return x.user_id === stage.curAuditor.aid && x.ass_user_id === accountId; });
                 stage.readOnly = stage.curAuditor.aid !== accountId && !ass;
-                if (stage.readOnly) stage.assist = ass;
+                if (!stage.readOnly) stage.assist = ass;
             }
-            if (stage.readOnly)  {
+            if (stage.readOnly) {
                 stage.assist = accountId === stage.user_id || auditorIds.indexOf(accountId) >= 0 ? null : auditAssists.find(x => { return x.ass_user_id === accountId});
             }
+
+            // 获取当前审批人的上一个审批人,判断是否是当前登录人,并赋予撤回功能,(当审批人存在有审批过时,上一人不允许再撤回)
+            stage.cancancel = 0;
+            if (stage.status !== status.checked && stage.status !== status.uncheck) {
+                if (stage.status !== status.checkNo) {
+                    // 找出当前操作人上一个审批人,包括审批完成的和退回上一个审批人的,同时当前操作人为第一人时,就是则为原报
+                    const onAuditor = _.find(stage.auditors, function(item) {
+                        return item.aid === stage.curAuditor.aid && item.status === status.checking;
+                    });
+                    const preAudit = onAuditor.order !== 1 ? _.find(stage.auditors, { order: onAuditor.order - 1 }) : false;
+                    const preAid = preAudit ? (preAudit.status !== status.checkAgain ? preAudit.aid : false) : stage.user_id;// 已发起重审无法撤回
+                    if ((onAuditor.aid === preAid && preAudit.status === status.checkCancel) || preAudit.is_old === 1) {
+                        stage.cancancel = 0;// 不可以多次撤回
+                    } else if (preAid === accountId && preAid !== stage.user_id) {
+                        if (preAudit.status === status.checked) {
+                            stage.cancancel = 2;// 审批人撤回审批通过
+                        } else if (preAudit.status === status.checkNoPre) {
+                            stage.cancancel = 3;// 审批人撤回审批退回上一人
+                        }
+                        stage.preAudit = preAudit;
+                    } else if (preAid === accountId && preAid === stage.user_id) {
+                        stage.cancancel = 1;// 原报撤回
+                    }
+                } else {
+                    const lastAuditors = yield this.service.stageAudit.getAuditors(stage.id, stage.times - 1);
+                    const onAuditor = _.find(lastAuditors, { status: status.checkNo });
+                    if (onAuditor.aid === accountId) {
+                        stage.cancancel = 4;// 审批人撤回退回原报
+                    }
+                }
+            }
+
             const permission = this.session.sessionUser.permission;
             if (accountId === stage.user_id || userAssistIds.indexOf(accountId) >= 0) { // 原报
                 stage.curTimes = stage.times;

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

@@ -2060,4 +2060,16 @@ animation:shake 1s .2s ease both;}
   position: absolute;
   right: 10px;
   top: 10px;
+}
+
+.signatureRptBar{
+  position: absolute;
+  top: 0;
+  bottom: 0;
+  background: transparent;
+  left: 0;
+  right: 0;
+}
+.signatureCavans{
+  background: transparent!important;
 }

+ 18 - 1
app/public/js/change_information_approval.js

@@ -78,6 +78,8 @@ $(document).ready(() => {
         changeSpreadSetting.cols.push(newColcount);
         changeSpreadSetting.cols.push(newColTp);
     }
+    changeSpreadSetting.cols.push({title: '变更后|数量', colSpan: '2|1', rowSpan: '1|1', field: 'samount', hAlign: 2, width: 60, type: 'Number', readOnly: true, getValue: 'getValue.changed_amount'});
+    changeSpreadSetting.cols.push({title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'sa_tp', hAlign: 2, width: 80, type: 'Number', readOnly: true, getValue: 'getValue.changed_tp'});
 
     const changeCol = {
         getValue: {
@@ -96,6 +98,13 @@ $(document).ready(() => {
             camount: function (data) {
                 return ZhCalc.round(data.camount, findDecimal(data.unit));
             },
+            changed_amount: function (data) {
+                return ZhCalc.round(ZhCalc.add(data.oamount, data.spamount), findDecimal(data.unit));
+            },
+            changed_tp: function (data) {
+                return ZhCalc.add(ZhCalc.round(ZhCalc.mul(ZhCalc.round(data.unit_price, unitPriceUnit), ZhCalc.round(data.oamount, findDecimal(data.unit))), totalPriceUnit),
+                    ZhCalc.round(ZhCalc.mul(ZhCalc.round(data.unit_price, unitPriceUnit), ZhCalc.round(data.spamount, findDecimal(data.unit))), totalPriceUnit));
+            },
         },
     };
 
@@ -150,22 +159,30 @@ $(document).ready(() => {
             // changeSpreadSheet.setValue(row, col+1, sum !== 0 ? sum : null);
             const rowCount = changeSpreadSheet.getRowCount();
             // 用户的数据合计
+            // 变更后数据合计
             let audit_sum = 0;
+            let changed_sum = 0;
             for(let i = 0; i < rowCount - 1; i++){
                 audit_sum = ZhCalc.add(audit_sum, changeSpreadSheet.getValue(i, col+1));
+                changed_sum = ZhCalc.add(changed_sum, changeSpreadSheet.getValue(i, (11 + aidList.length*2)));
             }
+
             changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, col+1, audit_sum !== 0 ? audit_sum : null);
+            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, (11 + aidList.length*2), changed_sum !== 0 ? changed_sum : null);
         },
         countSum: function() {
             const rowCount = changeSpreadSheet.getRowCount();
             let oSum = 0,
-                cSum = 0;
+                cSum = 0,
+                cdSum = 0;
             for(let i = 0; i < rowCount - 1; i++){
                 oSum = ZhCalc.add(oSum, changeSpreadSheet.getValue(i, 7));
                 cSum = ZhCalc.add(cSum, changeSpreadSheet.getValue(i, 9));
+                cdSum = ZhCalc.add(cdSum, changeSpreadSheet.getValue(i, (11 + aidList.length*2)));
             }
             changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 7, oSum !== 0 ? oSum : null);
             changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 9, cSum !== 0 ? cSum : null);
+            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, (11 + aidList.length*2), cdSum !== 0 ? cdSum : null);
             // 用户的数据合计
             for (const j in aidList) {
                 let audit_sum = 0;

+ 15 - 3
app/public/js/change_information_set.js

@@ -104,6 +104,8 @@ $(document).ready(() => {
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'oa_tp', hAlign: 2, width: 80, type: 'Number', readOnly: true, getValue: 'getValue.oa_tp'},
             {title: '申请变更增(+)减(-)|数量', colSpan: '2|1', rowSpan: '1|1', field: 'camount', hAlign: 2, width: 60, type: 'Number', readOnly: false, getValue: 'getValue.camount'},
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'ca_tp', hAlign: 2, width: 80, type: 'Number', readOnly: true, getValue: 'getValue.ca_tp'},
+            {title: '变更后|数量', colSpan: '2|1', rowSpan: '1|1', field: 'samount', hAlign: 2, width: 60, type: 'Number', readOnly: true, getValue: 'getValue.changed_amount'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'sa_tp', hAlign: 2, width: 80, type: 'Number', readOnly: true, getValue: 'getValue.changed_tp'},
             {title: '操作', colSpan: '1', rowSpan: '2', field: 'del_list', hAlign: 1, width: 40, readOnly: true, cellType: 'mouseTouch', getValue: 'getValue.del_list'},
         ],
         emptyRows: 0,
@@ -153,6 +155,13 @@ $(document).ready(() => {
             camount: function (data) {
                 return ZhCalc.round(data.camount, findDecimal(data.unit));
             },
+            changed_amount: function (data) {
+                return ZhCalc.round(ZhCalc.add(data.oamount, data.camount), findDecimal(data.unit));
+            },
+            changed_tp: function (data) {
+                return ZhCalc.add(ZhCalc.round(ZhCalc.mul(ZhCalc.round(data.unit_price, unitPriceUnit), ZhCalc.round(data.oamount, findDecimal(data.unit))), totalPriceUnit),
+                    ZhCalc.round(ZhCalc.mul(ZhCalc.round(data.unit_price, unitPriceUnit), ZhCalc.round(data.camount, findDecimal(data.unit))), totalPriceUnit));
+            },
             del_list: function (data) {
                 return !_.find(changeUsedData, { cbid: data.id }) ? '移除' : '';
             }
@@ -179,13 +188,16 @@ $(document).ready(() => {
         countSum: function() {
             const rowCount = changeSpreadSheet.getRowCount();
             let oSum = 0,
-                cSum = 0;
+                cSum = 0,
+                cdSum = 0;
             for(var i = 0; i < rowCount - 1; i++){
                 oSum = ZhCalc.add(oSum, changeSpreadSheet.getValue(i, 7));
                 cSum = ZhCalc.add(cSum, changeSpreadSheet.getValue(i, 9));
+                cdSum = ZhCalc.add(cdSum, changeSpreadSheet.getValue(i, 11));
             }
             changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 7, oSum !== 0 ? oSum : null);
             changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 9, cSum !== 0 ? cSum : null);
+            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 11, cdSum !== 0 ? cdSum : null);
         },
         add: function () {
             let select = null;
@@ -725,14 +737,14 @@ $(document).ready(() => {
         changeSpread.bind(spreadNS.Events.ClipboardPasted, changeSpreadObj.clipboardPasted);
         changeSpread.bind(spreadNS.Events.ValueChanged, changeSpreadObj.valueChanged);
         SpreadJsObj.addDeleteBind(changeSpread, changeSpreadObj.deletePress);
-        changeSpreadSheet.getCell(-1, 10).foreColor('#dc3545');
+        changeSpreadSheet.getCell(-1, 12).foreColor('#dc3545');
         const delCommand = {
             canUndo: false,
             execute: function (context, options, isUndo) {
                 const Commands = GC.Spread.Sheets.Commands;
                 const sel = changeSpreadSheet.getSelections()[0];
                 const col = changeSpreadSheet.zh_setting.cols[sel.col];
-                if (col && col.field !== 'ca_tp') {
+                if (col && col.field !== 'changed_tp') {
                     isUndo = true;
                 }
                 if (isUndo) {

+ 12 - 11
app/public/js/change_information_show.js

@@ -22,8 +22,6 @@ $(document).ready(() => {
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'ca_tp', hAlign: 2, width: 80, type: 'Number', getValue: 'getValue.ca_tp'},
             {title: '审批后变更|数量', colSpan: '2|1', rowSpan: '1|1', field: 'samount', hAlign: 2, width: 60, type: 'Number', getValue: 'getValue.samount'},
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'sa_tp', hAlign: 2, width: 80, type: 'Number', getValue: 'getValue.sa_tp'},
-            {title: '变更后|数量', colSpan: '2|1', rowSpan: '1|1', field: 'samount', hAlign: 2, width: 60, type: 'Number', getValue: 'getValue.changed_amount'},
-            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'sa_tp', hAlign: 2, width: 80, type: 'Number', getValue: 'getValue.changed_tp'},
         ],
         emptyRows: 0,
         headRows: 2,
@@ -68,6 +66,8 @@ $(document).ready(() => {
         changeSpreadSetting.cols.push(newColcount);
         changeSpreadSetting.cols.push(newColTp);
     }
+    changeSpreadSetting.cols.push({title: '变更后|数量', colSpan: '2|1', rowSpan: '1|1', field: 'samount', hAlign: 2, width: 60, type: 'Number', getValue: 'getValue.changed_amount'});
+    changeSpreadSetting.cols.push({title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'sa_tp', hAlign: 2, width: 80, type: 'Number', getValue: 'getValue.changed_tp'});
 
     const changeCol = {
         getValue: {
@@ -93,10 +93,11 @@ $(document).ready(() => {
                 return ZhCalc.round(ZhCalc.mul(ZhCalc.round(data.unit_price, unitPriceUnit), ZhCalc.round(data.samount, findDecimal(data.unit))), totalPriceUnit);
             },
             changed_amount: function (data) {
-                return ZhCalc.round(data.changed_amount, findDecimal(data.unit));
+                return ZhCalc.add(ZhCalc.round(data.oamount, findDecimal(data.unit)), ZhCalc.round(data.changed_amount, findDecimal(data.unit)));
             },
             changed_tp: function (data) {
-                return ZhCalc.round(ZhCalc.mul(ZhCalc.round(data.unit_price, unitPriceUnit), ZhCalc.round(data.changed_amount, findDecimal(data.unit))), totalPriceUnit);
+                return ZhCalc.add(ZhCalc.round(ZhCalc.mul(ZhCalc.round(data.unit_price, unitPriceUnit), ZhCalc.round(data.oamount, findDecimal(data.unit))), totalPriceUnit),
+                    ZhCalc.round(ZhCalc.mul(ZhCalc.round(data.unit_price, unitPriceUnit), ZhCalc.round(data.changed_amount, findDecimal(data.unit))), totalPriceUnit));
             },
         },
     };
@@ -130,10 +131,10 @@ $(document).ready(() => {
                 for(let i = 0; i <= rowCount - 1; i++){
                     const data = {
                         unit_price: changeSpreadSheet.getValue(i, 3),
-                        amount: parseFloat(changeSpreadSheet.getValue(i, 14 + parseInt(j)*2)),
+                        amount: parseFloat(changeSpreadSheet.getValue(i, 12 + parseInt(j)*2)),
                     };
                     const sum = ZhCalc.round(ZhCalc.mul(data.unit_price, data.amount), totalPriceUnit);
-                    changeSpreadSheet.setValue(i, 15 + j*2, sum !== 0 ? sum : null);
+                    changeSpreadSheet.setValue(i, 13 + j*2, sum !== 0 ? sum : null);
                 }
             }
         },
@@ -147,24 +148,24 @@ $(document).ready(() => {
                 oSum = ZhCalc.add(oSum, changeSpreadSheet.getValue(i, 7));
                 cSum = ZhCalc.add(cSum, changeSpreadSheet.getValue(i, 9));
                 sSum = ZhCalc.add(sSum, changeSpreadSheet.getValue(i, 11));
-                cdSum = ZhCalc.add(cdSum, changeSpreadSheet.getValue(i, 13));
+                cdSum = ZhCalc.add(cdSum, changeSpreadSheet.getValue(i, (13 + aidList.length*2)));
             }
             changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 7, oSum !== 0 ? oSum : null);
             changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 9, cSum !== 0 ? cSum : null);
             changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 11, sSum !== 0 ? sSum : null);
-            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 13, cdSum !== 0 ? cdSum : null);
+            changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, (13 + aidList.length*2), cdSum !== 0 ? cdSum : null);
             // 用户的数据合计
             for (const j in aidList) {
                 let audit_sum = 0;
                 for(let i = 0; i < rowCount - 1; i++){
-                    audit_sum = ZhCalc.add(audit_sum, changeSpreadSheet.getValue(i, 15 + j*2));
+                    audit_sum = ZhCalc.add(audit_sum, changeSpreadSheet.getValue(i, 13 + j*2));
                 }
-                changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 15 + j*2, audit_sum !== 0 ? audit_sum : null);
+                changeSpreadSheet.setValue(changeSpreadSheet.getRowCount() - 1, 13 + j*2, audit_sum !== 0 ? audit_sum : null);
             }
         },
         showHideAudit: function (show = false) {
             const count = changeSpreadSetting.cols.length;
-            for (let i = 14; i < count; i++) {
+            for (let i = 12; i < count - 2; i++) {
                 changeSpreadSheet.setColumnVisible(i, show, GC.Spread.Sheets.SheetArea.viewport);
             }
             changeSpreadSheet.setColumnVisible(10, !show, GC.Spread.Sheets.SheetArea.viewport);

+ 22 - 22
app/public/js/change_revise.js

@@ -68,14 +68,14 @@ $(document).ready(() => {
     const billsCol = {
         readOnly: {
             isChangeAdd: function (data) {
-                return !readOnly && !data.ccid;
+                return !readOnly && !data.formc;
             }
         }
     };
     const posCol = {
         readOnly: {
             isChangeAdd: function (data) {
-                return !readOnly && !data.ccid;
+                return !readOnly && !data.formc;
             }
         }
     };
@@ -90,7 +90,7 @@ $(document).ready(() => {
                 indent: 16,
                 getColor: function (index, data) {
                     if (!data) return;
-                    if(!data.ccid && !data.cid) return;
+                    if(!data.formc && !data.cid) return;
                     return '#dc3545';
                 }
             },
@@ -114,7 +114,7 @@ $(document).ready(() => {
                 indent: 16,
                 getColor: function (index, data) {
                     if (!data) return;
-                    if(!data.ccid) return;
+                    if(!data.formc) return;
                     return '#dc3545';
                 }
             },
@@ -237,16 +237,16 @@ $(document).ready(() => {
                     }
 
                     if (col.field.indexOf('dgn') >= 0) {
-                        $('#bills-expr').attr('readOnly', readOnly || cell.locked() || (_.isString(data.b_code) && data.b_code !== '') || !data.ccid);
+                        $('#bills-expr').attr('readOnly', readOnly || cell.locked() || (_.isString(data.b_code) && data.b_code !== '') || !data.formc);
                     } else if (col.field === 'unit_price') {
-                        $('#bills-expr').attr('readOnly', readOnly || cell.locked() || (data.children && data.children.length > 0) || (_.isBoolean(data.used) && data.used === true) || !data.ccid);
+                        $('#bills-expr').attr('readOnly', readOnly || cell.locked() || (data.children && data.children.length > 0) || (_.isBoolean(data.used) && data.used === true) || !data.formc);
                     } else {
                         const nodePos = pos.getLedgerPos(data.id);
                         if (nodePos && nodePos.length > 0) {
                             $('#bills-expr').val('').attr('readOnly', true);
                             $('#bills-expr').removeAttr('data-row');
                         } else {
-                            $('#bills-expr').attr('readOnly', readOnly || cell.locked() || (data.children && data.children.length > 0) || !data.ccid);
+                            $('#bills-expr').attr('readOnly', readOnly || cell.locked() || (data.children && data.children.length > 0) || !data.formc);
                         }
                     }
                     $('#bills-expr').attr('data-row', sel.row);
@@ -320,7 +320,7 @@ $(document).ready(() => {
                         sameParent = false;
                         break;
                     }
-                    if (!rNode.ccid) {
+                    if (!rNode.formc) {
                         sameParent = false;
                         break;
                     }
@@ -337,7 +337,7 @@ $(document).ready(() => {
             const siblings = tree.getChildren(tree.getParent(first));
             let upPower = true;
             for (let order = last.order; order < siblings.length - 1; order ++) {
-                if (!siblings[order].ccid) {
+                if (!siblings[order].formc) {
                     upPower = false;
                     break;
                 }
@@ -345,15 +345,15 @@ $(document).ready(() => {
             const valid = !sheet.zh_setting.readOnly;
 
             setObjEnable($('a[name=base-opr][type=add]'), valid && first && first.level > 1);
-            setObjEnable($('a[name=base-opr][type=delete]'), valid && first && sameParent && first.level > 1 && !nodeUsed && first.ccid);
-            setObjEnable($('a[name=base-opr][type=up-move]'), valid && first && sameParent && first.level > 1 && preNode && first.ccid);
-            setObjEnable($('a[name=base-opr][type=down-move]'), valid && first && sameParent && first.level > 1 && !tree.isLastSibling(last) && first.ccid);
+            setObjEnable($('a[name=base-opr][type=delete]'), valid && first && sameParent && first.level > 1 && !nodeUsed && first.formc);
+            setObjEnable($('a[name=base-opr][type=up-move]'), valid && first && sameParent && first.level > 1 && preNode && first.formc);
+            setObjEnable($('a[name=base-opr][type=down-move]'), valid && first && sameParent && first.level > 1 && !tree.isLastSibling(last) && first.formc);
             const posRange = last ? pos.getLedgerPos(last.id) : [];
             setObjEnable($('a[name=base-opr][type=up-level]'), valid && first && sameParent && tree.getParent(first) && !nodeUsed
-                && first.level > 2 && ((!posRange || posRange.length === 0) || tree.isLastSibling(last)) && upPower && first.ccid);
+                && first.level > 2 && ((!posRange || posRange.length === 0) || tree.isLastSibling(last)) && upPower && first.formc);
             const preNodePosRange = preNode ? pos.getLedgerPos(preNode.id) : [];
             setObjEnable($('a[name=base-opr][type=down-level]'), valid && first && sameParent
-                && first.level > 1 && preNode && (preNode.children.length > 0 || (preNode.children.length === 0 && preNode.ccid && (!preNodePosRange || preNodePosRange.length === 0))) && !preNode.used && first.ccid);
+                && first.level > 1 && preNode && (preNode.children.length > 0 || (preNode.children.length === 0 && preNode.formc && (!preNodePosRange || preNodePosRange.length === 0))) && !preNode.used && first.formc);
             setObjEnable($('#cut'), valid);
             setObjEnable($('#paste'), valid);
         },
@@ -1159,7 +1159,7 @@ $(document).ready(() => {
                             sameParent = false;
                             break;
                         }
-                        if (!rNode.ccid) {
+                        if (!rNode.formc) {
                             return true;
                         }
                         nodeUsed = nodeUsed || rNode.used;
@@ -1172,7 +1172,7 @@ $(document).ready(() => {
                     }
                 }
                 const valid = !sheet.zh_setting.readOnly;
-                return !(valid && first && sameParent && !(first.level === 1 && first.node_type) && !nodeUsed && first.ccid);
+                return !(valid && first && sameParent && !(first.level === 1 && first.node_type) && !nodeUsed && first.formc);
             }
         };
         billsContextMenuOptions.items.sprBase = '----';
@@ -1396,8 +1396,8 @@ $(document).ready(() => {
             const preNode = sheet.zh_data[row - 1];
             const valid = !sheet.zh_setting.readOnly;
 
-            setObjEnable($('a[name=pos-opr][type=up-move]'), valid && first && preNode && first.ccid);
-            setObjEnable($('a[name=pos-opr][type=down-move]'), valid && first && (sheet.zh_data.indexOf(last) < sheet.zh_data.length - 1) && first.ccid);
+            setObjEnable($('a[name=pos-opr][type=up-move]'), valid && first && preNode && first.formc);
+            setObjEnable($('a[name=pos-opr][type=down-move]'), valid && first && (sheet.zh_data.indexOf(last) < sheet.zh_data.length - 1) && first.formc);
         },
         loadExprToInput: function () {
             const sel = posSheet.getSelections()[0];
@@ -1413,7 +1413,7 @@ $(document).ready(() => {
                         ? (data[exprInfo.expr] ? data[exprInfo.expr] : data[col.field])
                         : data[col.field];
                     $('#pos-expr').val(value).attr('field', col.field).attr('org', data[col.field])
-                        .attr('readOnly', readOnly || (!data.ccid && cell.locked())).attr('data-row', sel.row);
+                        .attr('readOnly', readOnly || (!data.formc && cell.locked())).attr('data-row', sel.row);
                 } else {
                     $('#pos-expr').val('').attr('readOnly', true);
                     $('#pos-expr').removeAttr('data-row');
@@ -1442,7 +1442,7 @@ $(document).ready(() => {
                 if (posSheet.zh_setting) {
                     posSheet.zh_setting.cols.forEach(function (col, i) {
                         for (let iRow = 0; iRow < posSheet.getRowCount(); iRow++) {
-                            posSheet.getCell(iRow, i).locked((posSheet.zh_data[iRow] && !posSheet.zh_data[iRow].ccid) || posSheet.zh_setting.readOnly || false);
+                            posSheet.getCell(iRow, i).locked((posSheet.zh_data[iRow] && !posSheet.zh_data[iRow].formc) || posSheet.zh_setting.readOnly || false);
                         }
                     });
                 }
@@ -1881,7 +1881,7 @@ $(document).ready(() => {
                     disabled: function (key, opt) {
                         if (posSheet.zh_data) {
                             const selection = posSheet.getSelections();
-                            return (posSheet.zh_data.length < selection[0].row + selection[0].rowCount) || (posSheet.zh_data[selection[0].row] && !posSheet.zh_data[selection[0].row].ccid);
+                            return (posSheet.zh_data.length < selection[0].row + selection[0].rowCount) || (posSheet.zh_data[selection[0].row] && !posSheet.zh_data[selection[0].row].formc);
                         } else {
                             return true;
                         }
@@ -2504,7 +2504,7 @@ $(document).ready(() => {
                             {
                                 key: 'revise', title: '新增部位', valid: true, parent: true,
                                 check: function (node) {
-                                    if (node.ccid || node.cid) {
+                                    if (node.formc || node.cid) {
                                         return true;
                                     } else {
                                         return false;

+ 6 - 0
app/public/js/global.js

@@ -1195,6 +1195,12 @@ const spreadColor = {
         differ: '#FFE699', // 同编号时,名称、单位、单价存在不同
         calc_differ: '#f8d7da', //
     },
+    pay: {
+        expr_err: '#FF9595',
+        pause: '#f2f2f2',
+        yf_without: '#d6d8db',
+        cur_add: '#FFFFE1',
+    }
 };
 
 $(document).ready(function () {

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

@@ -560,6 +560,15 @@ $(document).ready(function() {
                         }
                     }
                 }
+                if (col.field === 'node_type' && newValue && newValue !== '0') {
+                    const sameNodeType = sortData.find(x => { return x.node_type == newValue; });
+                    console.log(newValue, sameNodeType);
+                    if (sameNodeType) {
+                        toastr.error('已存在该费用类别,请勿重复选择');
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        return;
+                    }
+                }
                 // 获取更新数据
                 if (col.type === 'Number') {
                     const exprInfo = getExprInfo(col.field);
@@ -588,6 +597,7 @@ $(document).ready(function() {
                 } else {
                     data[col.field] = newValue;
                 }
+                if(col.field === 'node_type') console.log(data);
                 // 更新至服务器
                 postData(window.location.pathname + '/update', {postType: 'update', postData: data}, function (result) {
                     const refreshNode = ledgerTree.loadPostData(result);
@@ -2464,6 +2474,13 @@ $(document).ready(function() {
                         },
                         afterLocated: function () {
                             posOperationObj.loadCurPosData();
+                        },
+                        calcSum: function (result) {
+                            const sum = { name: '合计' };
+                            for (const r of result) {
+                                sum.quantity = ZhCalc.add(r.quantity, sum.quantity);
+                            }
+                            return sum;
                         }
                     });
                 }

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

@@ -235,6 +235,13 @@ $(document).ready(() => {
                         },
                         afterLocated: function () {
                             loadCurPosData();
+                        },
+                        calcSum: function (result) {
+                            const sum = { name: '合计' };
+                            for (const r of result) {
+                                sum.quantity = ZhCalc.add(r.quantity, sum.quantity);
+                            }
+                            return sum;
                         }
                     });
                 }

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

@@ -106,7 +106,7 @@ $(document).ready(() => {
     let gclGatherData;
     // 获取项目节数据
     function loadLeafXmjData(iGclRow) {
-        const gcl = gclGatherData[iGclRow];
+        const gcl = iGclRow ? gclGatherData[iGclRow] : SpreadJsObj.getSelectObject(gclSheet);
         SpreadJsObj.resetTopAndSelect(leafXmjSheet);
         if (gcl) {
             SpreadJsObj.loadSheetData(leafXmjSheet, SpreadJsObj.DataType.Data, gcl.leafXmjs);
@@ -192,15 +192,57 @@ $(document).ready(() => {
         // SpreadJsObj.reloadRowsBackColor(gclSheet, rows);
         SpreadJsObj.reLoadSheetData(gclSheet);
     });
+    const searchBills = $.listSearch({
+        selector: '#search',
+        searchSpread: gclSpread,
+        searchRangeStr: '清单编号/名称',
+        resultSpreadSetting: {
+            cols: [
+                {title: '清单编号', field: 'b_code', hAlign: 0, width: 80, formatter: '@'},
+                {title: '名称', field: 'name', width: 150, hAlign: 0, formatter: '@'},
+                {title: '单位', field: 'unit', width: 50, hAlign: 1, formatter: '@'},
+                {title: '单价', field: 'unit_price', hAlign: 2, width: 50},
+                {title: '台账数量', field: 'quantity', hAlign: 2, width: 60},
+                {title: '台账金额', field: 'total_price', hAlign: 2, width: 60},
+            ],
+            emptyRows: 0,
+            headRows: 1,
+            headRowHeight: [32],
+            headColWidth: [30],
+            defaultRowHeight: 21,
+            headerFont: '12px 微软雅黑',
+            font: '12px 微软雅黑',
+            selectedBackColor: '#fffacd',
+            readOnly: true,
+        },
+        check: function(data, keyword) {
+            return !keyword ||
+                (data.b_code && data.b_code.indexOf(keyword) > -1) ||
+                (data.name && data.name.indexOf(keyword) > -1);
+        },
+        afterLocated: function () {
+            loadLeafXmjData();
+        },
+        calcSum: function (result) {
+            const sum = { name: '合计', searchIndex: -1 };
+            for (const r of result) {
+                sum.quantity = ZhCalc.add(r.quantity, sum.quantity);
+                sum.total_price = ZhCalc.add(r.total_price, sum.total_price);
+            }
+            return sum;
+        }
+    });
     // 展开收起附件
-    $('a', '.right-nav').bind('click', function () {
+    $('a', '#side-menu').bind('click', function (e) {
+        e.preventDefault();
         const tab = $(this), tabPanel = $(tab.attr('content'));
         if (!tab.hasClass('active')) {
-            $('a', '.side-menu').removeClass('active');
-            $('.tab-content .tab-select-show').removeClass('active');
+            $('a', '#side-menu').removeClass('active');
+            $('.tab-content .tab-pane.active').removeClass('active');
             tab.addClass('active');
             tabPanel.addClass('active');
             showTools(tab.hasClass('active'));
+            if (tab.attr('content') === '#search') searchBills.spread.refresh();
         } else {
             tab.removeClass('active');
             tabPanel.removeClass('active');

+ 40 - 36
app/public/js/material_checklist.js

@@ -1085,10 +1085,10 @@ $(document).ready(() => {
     // 导入功能
     // 上传图片
     $('#upload-list').click(function () {
-        // if (materialChecklistData.length === 0) {
-        //     toastr.error('请选择调差清单再导入。');
-        //     return
-        // }
+        if (materialBillsData.length === 0) {
+            toastr.error('请添加工料再导入。');
+            return
+        }
         $(this).siblings('input').trigger('click');
     });
     $('#upload-list-file').change(function () {
@@ -1111,7 +1111,7 @@ $(document).ready(() => {
                     const workbook = XLSX.read(data, {type: 'binary'}); // 以二进制流方式读取得到整份excel表格对象
                     const jsonData = transExcel(XLSX.utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]], { defval: null }));
                     if (!(jsonData[0] && jsonData[0].b_code !== undefined && jsonData[0].b_code !== null &&
-                        jsonData[0].GLJcode !== undefined && jsonData[0].name !== undefined && jsonData[0].name !== undefined &&
+                        jsonData[0].gljcode !== undefined && jsonData[0].name !== undefined && jsonData[0].name !== undefined &&
                         jsonData[0].unit !== undefined && jsonData[0].unit_price !== undefined)) {
                         throw 'excel必须按指定格式内容上传';
                     }
@@ -1132,7 +1132,7 @@ $(document).ready(() => {
                     iconv.skipDecodeWarning = true
                     tree = JSON.parse(iconv.decode(data, ascii.encoding));// 需要转码,否则前端处理中文会出现乱码
                     if (!(tree[0] && tree[0].b_code !== undefined && tree[0].b_code !== null &&
-                        tree[0].GLJcode !== undefined && tree[0].name !== undefined && tree[0].name !== undefined &&
+                        tree[0].gljcode !== undefined && tree[0].name !== undefined && tree[0].name !== undefined &&
                         tree[0].unit !== undefined && tree[0].unit_price !== undefined)) {
                         throw 'json必须按指定格式内容上传';
                     }
@@ -1163,21 +1163,22 @@ $(document).ready(() => {
                         continue;
                     }
                     needPushTree.push(t);
-                    for (const c of t.children) {
-                        const mbOrder = _.findIndex(materialBillsData, { code: c.GLJcode, name: c.name, unit: c.unit });
-                        if (c.b_code !== null && mbOrder === -1 && _.findIndex(pushBillsData, { code: c.GLJcode, name: c.name, unit: c.unit }) === -1) {
-                            pushBillsData.push({
-                                code: c.GLJcode,
-                                name: c.name,
-                                unit: c.unit,
-                                spec: c.specs,
-                            })
-                        }
-                    }
+                    // for (const c of t.children) {
+                    //     const mbOrder = _.findIndex(materialBillsData, { code: c.gljcode, name: c.name, unit: c.unit });
+                    //     if (c.b_code !== null && mbOrder === -1 && _.findIndex(pushBillsData, { code: c.gljcode, name: c.name, unit: c.unit }) === -1) {
+                    //         pushBillsData.push({
+                    //             code: c.gljcode,
+                    //             name: c.name,
+                    //             unit: c.unit,
+                    //             spec: c.specs,
+                    //         })
+                    //     }
+                    // }
                 }
                 console.log(pushChecklist, pushBillsData);
-                // stopProgress($('#bill-progress'));
-                // await pushListData(needPushTree);
+                if (needPushTree.length === 0) {
+                    throw '不存在需要导入的工料清单含量';
+                }
                 // 先上传需要生成的清单及工料
                 if (pushChecklist.length > 0 || pushBillsData.length > 0) {
                     postData(window.location.pathname + '/save', { type:'exportCB', addChecklist: pushChecklist, addBillsList: pushBillsData }, async function (result) {
@@ -1286,7 +1287,7 @@ $(document).ready(() => {
                 }
                 const mbList = [];
                 for (const mb of t.children) {
-                    const mbInfo = _.find(materialBillsData, { code: mb.GLJcode, name: mb.name, unit: mb.unit });
+                    const mbInfo = _.find(materialBillsData, { code: mb.gljcode+'', name: mb.name+'', unit: mb.unit+'' });
                     if (mbInfo) {
                         const num = parseFloat(mb.quantity);
                         if (num < 0 || !/^\d+(\.\d{1,6})?$/.test(num)) {
@@ -1296,6 +1297,9 @@ $(document).ready(() => {
                         mbList.push({ id: mbInfo.id, quantity: mb.quantity });
                     }
                 }
+                if (mbList.length === 0) {
+                    continue;
+                }
 
                 const gclIndex = _.findIndex(gclGatherData, { b_code: t.b_code, name: t.name, unit: t.unit, unit_price: t.unit_price ? parseFloat(t.unit_price) : null });
                 const gcl = gclGatherData[gclIndex].leafXmjs;
@@ -1343,22 +1347,22 @@ $(document).ready(() => {
                 const select = _.find(materialChecklistData, { b_code: t.b_code, name: t.name, unit: t.unit, unit_price: t.unit_price ? parseFloat(t.unit_price) : null });
                 console.log(select, datas, mbList);
                 if (select) {
-                    await postData(window.location.pathname + '/save', {type: 'adds', checklist: { id: select.id, had_bills: 1 }, postData: {xmjs: datas, mbIds: mbList, export: true}}, function (result) {
-                        // materialListData = result;
-                        select.had_bills = 1;
-                        gclList = result;
-                        stopProgress($('#list-progress'));
-                        if (i+1 === tree.length) {
-                            toastr.success('导入成功');
-                            setTimeout(function () { $('#okedit').modal('hide') }, 2000);
-                            showSjsData();
-                        }
+                    const result = await postDataAsync(window.location.pathname + '/save', {type: 'adds', checklist: { id: select.id, had_bills: 1 }, postData: {xmjs: datas, mbIds: mbList, export: true}});
+                    // materialListData = result;
+                    select.had_bills = 1;
+                    gclList = result;
+                    stopProgress($('#list-progress'));
+                    if (i+1 === tree.length) {
+                        toastr.success('导入成功');
+                        setTimeout(function () { $('#okedit').modal('hide') }, 2000);
+                        showSjsData();
+                    }
 
-                    }, function () {
-                        stop = true;
-                        clearInterval(interval);
-                        setTimeout(function () { $('#okedit').modal('hide') }, 1000);
-                    });
+                    // }, function () {
+                    //     stop = true;
+                    //     clearInterval(interval);
+                    //     setTimeout(function () { $('#okedit').modal('hide') }, 1000);
+                    // });
                 } else {
                     stopProgress($('#list-progress'));
                 }
@@ -1369,7 +1373,7 @@ $(document).ready(() => {
     function transExcel(results) {
         const mapInfo = {
             '清单编号': 'b_code',
-            '工料编号': 'GLJcode',
+            '工料编号': 'gljcode',
             '名称': 'name',
             '单位': 'unit',
             '规格': 'specs',

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

@@ -124,7 +124,7 @@ $('a[data-target="#sp-list" ]').on('click', function () {
                                     <span class="pull-right ${auditConst.statusClass[auditor.status]}">
                                         ${auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''}
                                         ${auditor.status === auditConst.status.checkNo ? user.name : ''}
-                                        ${auditor.status === auditConst.status.checkNoPre ? auditors[index+1].name : ''}
+                                        ${auditor.status === auditConst.status.checkNoPre && auditor.sort - 1 > 0 ? _.find(auditors, { sort: auditor.sort - 1 }).name : ''}
                                     </span>
                                 </p>
                                 <p class="text-muted mb-0">${auditor.role}</p>

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

@@ -393,6 +393,7 @@ const createNewPathTree = function (type, setting) {
             for (const d of this.datas) {
                 d.filter = defaultValue;
             }
+            const parents = [];
             for (const s of this.select) {
                 const node = this.getItems(s);
                 if (!node) continue;
@@ -401,10 +402,12 @@ const createNewPathTree = function (type, setting) {
                 for (const p of posterity) {
                     p.filter = !defaultValue;
                 }
-                const parents = this.getAllParents(node);
-                for (const p of parents) {
-                    p.filter = !defaultValue;
-                }
+                parents.push(...this.getAllParents(node));
+            }
+            parents.sort((x, y) => { return y.level - x.level});
+            for (const parent of parents) {
+                parent.filter = !parent.children.find(x => { return !x.filter });
+
             }
             for (const node of this.nodes) {
                 const parent = this.getParent(node);
@@ -1225,6 +1228,7 @@ const createNewPathTree = function (type, setting) {
             });
         }
         checkRevisePrice(d) {
+            if (!this.price || this.price.length === 0) return false;
             const setting = this.setting;
             const pid = this.getAllParents(d).map(x => { return x[setting.id] + ''; });
             const checkRela = function(rela_lid) {

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

@@ -145,8 +145,8 @@ $(document).ready(function() {
         $('#sign-upload').change(function () {
             const file = this.files[0];
             const filesize = file.size;
-            if (filesize > 1 * 1024 * 1024) {
-                toastr.error('上传的文件大小不能超过1MB!');
+            if (filesize > 0.5 * 1024 * 1024) {
+                toastr.error('上传的文件大小不能超过512KB!');
                 return false;
             }
             const ext = file.name.toLowerCase().split('.').splice(-1)[0];
@@ -187,8 +187,8 @@ $(document).ready(function() {
                     return
                 }
                 const filesize = file.size;
-                if (filesize > 1 * 1024 * 1024) {
-                    toastr.error('上传的文件大小不能超过1MB!');
+                if (filesize > 0.5 * 1024 * 1024) {
+                    toastr.error('上传的文件大小不能超过512KB!');
                     return false;
                 }
                 formData.append('file[]', file);

+ 15 - 0
app/public/js/revise.js

@@ -516,6 +516,14 @@ $(document).ready(() => {
                         }
                     }
                 }
+                if (col.field === 'node_type' && newValue && newValue !== '0') {
+                    const sameNodeType = sortData.find(x => { return x.node_type == newValue; });
+                    if (sameNodeType) {
+                        toastr.error('已存在该费用类别,请勿重复选择');
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        return;
+                    }
+                }
                 // 获取更新数据
                 const exprInfo = getExprInfo(col.field);
                 if (info.editingText) {
@@ -2715,6 +2723,13 @@ $(document).ready(() => {
                         },
                         afterLocated: function () {
                             posSpreadObj.loadCurPosData();
+                        },
+                        calcSum: function (result) {
+                            const sum = { name: '合计' };
+                            for (const r of result) {
+                                sum.quantity = ZhCalc.add(r.quantity, sum.quantity);
+                            }
+                            return sum;
                         }
                     });
                 }

+ 40 - 10
app/public/js/revise_price.js

@@ -80,12 +80,12 @@ $(document).ready(() => {
         font: '12px 微软雅黑',
         readOnly,
         getColor: function (sheet, data, row, col, defaultColor) {
-            if (!data || (data.rela_lid && data.rela_cid)) return defaultColor;
+            if (!data || (data.rela_lid && data.rela_cid)) return '#f5deb3';
 
             const samePrice = sheet.zh_data.find(x => {
                 return x.b_code === data.b_code && x.name === data.name && x.unit === data.unit && x.org_price === data.org_price;
             });
-            return samePrice ? '#f5deb3' : defaultColor;
+            return samePrice ? defaultColor : '#f5deb3';
         }
     };
     const priceBwSpreadSetting = {
@@ -191,6 +191,7 @@ $(document).ready(() => {
             });
         }
         refreshTreeRela(price, samePrice) {
+            if (!samePrice) samePrice = this.getSamePrice(price);
             if (price.rela_lid) {
                 this.tree.loadFilter(price.rela_lid);
             } else {
@@ -203,11 +204,18 @@ $(document).ready(() => {
             }
         }
         refreshChangeRela(price, samePrice) {
+            if (!samePrice) samePrice = this.getSamePrice(price);
             if (price.rela_cid) {
                 const choose = price.rela_cid.split(',');
                 for (const c of this.change) {
                     c.rela = choose.indexOf(c.cid + '') >= 0;
-                    c.valid = c.rela;
+                    c.valid = !!c.rela;
+                }
+            } else if (readOnly && price.his_rela_cid) {
+                const his = price.his_rela_cid.split(',');
+                for (const c of this.change) {
+                    c.rela = his.indexOf(c.cid + '') >= 0;
+                    c.valid = !!c.rela;
                 }
             } else {
                 const invalid = [];
@@ -223,14 +231,17 @@ $(document).ready(() => {
                         const exist = c.bills.find(x => {
                             return x.code === price.b_code && x.name === price.name && x.unit === price.unit && x.unit_price === price.org_price;
                         });
-                        c.valid = exist;
+                        c.valid = !!exist;
                     }
                 }
             }
             this.relaChange.length = 0;
 
             for (const c of this.change) {
-                if (c.valid) this.relaChange.push(c);
+                if (c.valid){
+                    this.relaChange.push(c);
+                    c.visible = true;
+                }
             }
         }
         refreshRela(price) {
@@ -428,6 +439,7 @@ $(document).ready(() => {
                 revisePrice.loadUpdateData(result);
                 revisePrice.refreshTreeRela(price);
                 SpreadJsObj.refreshTreeRowVisible(priceBwSheet);
+                SpreadJsObj.reloadRowBackColor(priceSheet, revisePrice.data.indexOf(price));
             });
         },
         updateRelaCid: function (price, rela_cid) {
@@ -436,6 +448,7 @@ $(document).ready(() => {
                 revisePrice.loadUpdateData(result);
                 revisePrice.refreshChangeRela(price);
                 SpreadJsObj.reLoadSheetData(priceChangeSheet);
+                SpreadJsObj.reloadRowBackColor(priceSheet, revisePrice.data.indexOf(price));
             });
         },
         selectionChanged: function () {
@@ -510,7 +523,7 @@ $(document).ready(() => {
                 sprMove: '----',
                 chooseRelaBw: {
                     name: '选择应用部位',
-                    icon: 'fa-tags',
+                    icon: 'fa-link',
                     callback: function (key, opt) {
                         const price = SpreadJsObj.getSelectObject(priceSheet);
                         const samePrice = revisePrice.getSamePrice(price);
@@ -526,7 +539,7 @@ $(document).ready(() => {
                 },
                 chooseRelaChange: {
                     name: '选择应用变更令',
-                    icon: 'fa-tags',
+                    icon: 'fa-link',
                     callback: function (key, opt) {
                         const price = SpreadJsObj.getSelectObject(priceSheet);
                         const samePrice = revisePrice.getSamePrice(price);
@@ -540,6 +553,21 @@ $(document).ready(() => {
                         return !readOnly;
                     }
                 },
+                noRelaChange: {
+                    name: '不应用于变更令',
+                    icon: 'fa-unlink',
+                    callback: function (key, opt) {
+                        const price = SpreadJsObj.getSelectObject(priceSheet);
+                        priceOprObj.updateRelaCid(price, '-1');
+                    },
+                    disabled: function (key, opt) {
+                        const node = SpreadJsObj.getSelectObject(priceSheet);
+                        return !node;
+                    },
+                    visible: function (key, opt) {
+                        return !readOnly;
+                    }
+                },
             },
         });
     }
@@ -675,8 +703,8 @@ $(document).ready(() => {
         search(keyword) {
             this.searchResult = [];
             for (const node of this.tree.nodes) {
-                const code = node.code || '', name = node.name || '';
-                if (code.indexOf(keyword) >= 0 || name.indexOf(keyword) >= 0) this.searchResult.push(node);
+                const code = node.code || '', name = node.name || '', b_code = node.b_code || '';
+                if (code.indexOf(keyword) >= 0 || b_code.indexOf(keyword) >= 0 || name.indexOf(keyword) >= 0) this.searchResult.push(node);
             }
             $('#rela-bw-search-result').html(`结果:${this.searchResult.length}`);
             this.locate = 0;
@@ -938,15 +966,17 @@ $(document).ready(() => {
                 this.invalid.push(...cid);
             }
             for (const c of this.change) {
+                c.visible = true;
                 c.check = this.choose.indexOf(c.cid + '') >= 0;
                 c.invalid = this.invalid.indexOf(c.cid + '') >= 0;
                 if (!c.check && !c.invalid) {
                     const exist = c.bills.find(x => {
                         return x.code === price.b_code && x.name === price.name && x.unit === price.unit && x.unit_price === price.org_price;
                     });
-                    c.visible = !exist;
+                    c.visible = !!exist;
                 }
             }
+            if (this.sheet) SpreadJsObj.refreshTreeRowVisible(this.sheet);
             $('#choose-rela-change').modal('show');
         }
     }

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

@@ -289,6 +289,7 @@ $(document).ready(() => {
         sum () {
             const result = [];
             const sum = { name: '合计', positive_tp: 0, negative_tp: 0 };
+            const other = { name: '未分类', positive_tp: 0, negative_tp: 0 };
             for (const d of this.displayData) {
                 if (d.b_type) {
                     let type = result.find(x => { return d.b_type === x.name; });
@@ -301,6 +302,13 @@ $(document).ready(() => {
                     } else {
                         type.negative_tp = ZhCalc.add(type.negative_tp, d.tp);
                     }
+                } else {
+                    other.show = true;
+                    if (d.tp >= 0) {
+                        other.positive_tp = ZhCalc.add(other.positive_tp, d.tp);
+                    } else {
+                        other.negative_tp = ZhCalc.add(other.negative_tp, d.tp);
+                    }
                 }
                 if (d.tp >= 0) {
                     sum.positive_tp = ZhCalc.add(sum.positive_tp, d.tp);
@@ -308,6 +316,7 @@ $(document).ready(() => {
                     sum.negative_tp = ZhCalc.add(sum.negative_tp, d.tp);
                 }
             }
+            if (other.show) result.push(other);
             result.push(sum);
             result.forEach(x => { x.tp = ZhCalc.add(x.positive_tp, x.negative_tp); });
             return result;

+ 111 - 1
app/public/js/shares/cs_tools.js

@@ -543,6 +543,8 @@ const showSelectTab = function(select, spread, afterShow) {
                     searchResult.push(data);
                 }
             }
+            calculateCompletePercent(searchResult);
+            calculateSum();
             SpreadJsObj.loadSheetData(resultSpread.getActiveSheet(), 'data', searchResult);
         };
         const getCheckFun = function (key) {
@@ -576,8 +578,15 @@ const showSelectTab = function(select, spread, afterShow) {
                 }
             }
             calculateCompletePercent(searchResult);
+            calculateSum();
             SpreadJsObj.loadSheetData(resultSpread.getActiveSheet(), 'data', searchResult);
         };
+        const calculateSum = function () {
+            if (!searchResult || searchResult.length === 0 || !setting.calcSum) return;
+
+            const sum = setting.calcSum(searchResult);
+            if (sum) searchResult.unshift(sum);
+        };
         const calculateCompletePercent = function (searchResult) {
             if (!searchResult) return;
             for (const sr of searchResult) {
@@ -596,7 +605,7 @@ const showSelectTab = function(select, spread, afterShow) {
             if (!data) { return }
 
             const curBills = data[info.row];
-            if (!curBills) { return }
+            if (!curBills || curBills[setting.keyId] === undefined) { return }
 
             SpreadJsObj.locateTreeNode(searchSheet, curBills[setting.keyId], true);
             if (setting.afterLocated) {
@@ -605,6 +614,107 @@ const showSelectTab = function(select, spread, afterShow) {
         });
         return {spread: resultSpread};
     };
+    $.listSearch = function (setting) {
+        if (!setting.selector || !setting.searchSpread || !setting.resultSpreadSetting) return;
+        if (!setting.searchRangeStr) setting.searchRangeStr = '清单编号/名称';
+        const resultId = setting.id + '-search-result';
+        const obj = $(setting.selector);
+        let filter = [];
+        if (setting.searchOver || setting.searchEmpty) {
+            filter.push('<select class="input-group-text" id="search-filter">');
+            filter.push('<option value="">台账</option>');
+            if (setting.customSearch) {
+                for (const cs of setting.customSearch) {
+                    if (cs.valid) filter.push('<option value="' + cs.key + '">' + cs.title + '</option>');
+                }
+            }
+            filter.push('</select>');
+        }
+        obj.html(
+            '                        <div class="sjs-bar">\n' +
+            '                            <div class="input-group input-group-sm pb-1">\n' +
+            '                                <div class="input-group-prepend">\n' +
+            filter.join('') +
+            '                                </div>' +
+            '                                <input id="searchKeyword" type="text" class="form-control" autocomplete="off" placeholder="可查找 ' + setting.searchRangeStr + '" aria-label="Recipient\'s username" aria-describedby="button-addon2">\n' +
+            '                                <div class="input-group-append">\n' +
+            '                                    <button class="btn btn-outline-secondary" type="button"">搜索</button>\n' +
+            '                                </div>\n' +
+            '                            </div>\n' +
+            '                        </div>\n' +
+            '                        <div id="' + resultId + '" class="sjs-sh">\n' +
+            '                        </div>'
+        );
+        autoFlashHeight();
+        const resultSpread = SpreadJsObj.createNewSpread($('#' + resultId)[0]);
+        SpreadJsObj.initSheet(resultSpread.getActiveSheet(), setting.resultSpreadSetting);
+        const searchSheet = setting.searchSpread.getActiveSheet();
+        let searchResult = [];
+        const search = function () {
+            const filter = $('#search-filter').val();
+            if (filter) {
+                searchCustom(filter);
+            } else {
+                searchList();
+            }
+        };
+        const calulateSum = function () {
+            if (searchResult.length === 0 || !setting.calcSum) return;
+            searchResult.unshift(setting.calcSum(searchResult));
+        };
+        const searchList = function () {
+            const keyword = $('#searchKeyword', obj).val();
+            searchResult = [];
+            const sortData = SpreadJsObj.getSortData(searchSheet);
+            for (const [i, node] of sortData.entries()) {
+                if (setting.check(node, keyword)) {
+                    const data = JSON.parse(JSON.stringify(node));
+                    data.searchIndex = i;
+                    data.visible = true;
+                    searchResult.push(data);
+                }
+            }
+            calulateSum();
+            SpreadJsObj.loadSheetData(resultSpread.getActiveSheet(), 'data', searchResult);
+        };
+        const getCheckFun = function (key) {
+            const cs = setting.customSearch.find(function (x) {return x.key === key});
+            return cs ? cs.check : null;
+        };
+        const searchCustom = function (key) {
+            const keyword = $('#searchKeyword', obj).val();
+            const checkFun = getCheckFun(key);
+            searchResult = [];
+            const sortData = SpreadJsObj.getSortData(searchSheet);
+            for (const [i, node] of sortData.entries()) {
+                if (checkFun && checkFun(node)) {
+                    if (setting.check(node, keyword)) {
+                        const data = JSON.parse(JSON.stringify(node));
+                        data.searchIndex = i;
+                        data.visible = true;
+                        searchResult.push(data);
+                    }
+                }
+            }
+            calulateSum();
+            SpreadJsObj.loadSheetData(resultSpread.getActiveSheet(), 'data', searchResult);
+        };
+
+        $('input', obj).bind('keydown', function (e) {
+            if (e.keyCode == 13) search();
+        });
+        $('button', obj).bind('click', () => {search()});
+        resultSpread.getActiveSheet().bind(spreadNS.Events.CellDoubleClick, function (e, info) {
+            const cur = SpreadJsObj.getSelectObject(info.sheet);
+            if (!cur || cur.searchIndex < 0) return;
+
+            SpreadJsObj.locateRow(searchSheet, cur.searchIndex);
+            if (setting.afterLocated) {
+                setting.afterLocated();
+            }
+        });
+        return {spread: resultSpread};
+    };
 
     $.billsTag = function (setting) {
         if (!setting.selector || !setting.relaSpread) return;

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

@@ -781,6 +781,7 @@ $(document).ready(function () {
             }
             postData('/tender/' + cur_tenderid + '/shenpi/audit/save', data, function (result) {
                 self.loadPostData(result);
+                self.refreshOperate();
             });
         }
         refreshOperate() {

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

@@ -857,7 +857,7 @@ const SpreadJsObj = {
             this.endMassOperation(sheet);
         }
     },
-    reloadRowBackColor: function (sheet, row, count) {
+    reloadRowBackColor: function (sheet, row, count = 1) {
         const sortData = sheet.zh_dataType === 'tree' ? sheet.zh_tree.nodes : sheet.zh_data;
 
         this.beginMassOperation(sheet);
@@ -1083,12 +1083,21 @@ const SpreadJsObj = {
     locateData: function (sheet, data) {
         if (!sheet.zh_data) { return }
         const index = sheet.zh_data.indexOf(data);
+        if (index < 0) return;
+
         const sels = sheet.getSelections();
         sheet.setSelection(index, sels[0].col, 1, 1);
         SpreadJsObj.reloadRowsBackColor(sheet, [index, sels[0].row]);
         sheet.getParent().focus();
         sheet.showRow(index, spreadNS.VerticalPosition.center);
     },
+    locateRow: function (sheet, row) {
+        const sels = sheet.getSelections();
+        sheet.setSelection(row, sels[0].col, 1, 1);
+        SpreadJsObj.reloadRowsBackColor(sheet, [row, sels[0].row]);
+        sheet.getParent().focus();
+        sheet.showRow(row, spreadNS.VerticalPosition.center);
+    },
     saveTopAndSelect: function (sheet, cacheKey) {
         const sel = sheet.getSelections()[0];
         const top = sheet.getViewportTopRow(1);
@@ -1181,16 +1190,6 @@ const SpreadJsObj = {
                 sheet.setRowVisible(iRow, true);
             }
         }
-        // if (sheet.zh_tree) {
-        //     for (const iRow in sheet.zh_tree.nodes) {
-        //         const node = sheet.zh_tree.nodes[iRow];
-        //         if (node.visible !== undefined && node.visible !== null) {
-        //             sheet.setRowVisible(iRow, node.visible);
-        //         } else {
-        //             sheet.setRowVisible(iRow, true);
-        //         }
-        //     }
-        // }
         this.endMassOperation(sheet);
     },
     refreshColumnAlign: function (sheet) {

+ 17 - 1
app/public/js/stage.js

@@ -1646,6 +1646,10 @@ $(document).ready(() => {
                 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] || [];
+                const posMeasure = $('#pos-measure')[0].checked;
+                posData.forEach(x => {
+                    x.visible = !posMeasure || (!!x.contract_qty || !!x.qc_qty || !!x.qc_minus_qty);
+                });
                 SpreadJsObj.loadSheetData(spSpread.getActiveSheet(), 'data', posData, posReadOnly);
                 getNodeList(node.id);
             } else {
@@ -2033,6 +2037,7 @@ $(document).ready(() => {
             });
         }
     };
+    $('#pos-measure').click(stagePosSpreadObj.loadCurPosData);
     // 加载上下窗口resizer
     $.divResizer({
         select: '#main-resize',
@@ -4515,6 +4520,17 @@ $(document).ready(() => {
                         });
                         SpreadJsObj.refreshTreeRowVisible(sheet);
                         break;
+                    case "endMeasure":
+                        tree.expandByCustom(function (node) {
+                            for (const field of ['contract_tp', 'qc_tp', 'gather_tp', 'pre_contract_tp', 'pre_qc_tp', 'pre_gather_tp', 'end_contract_tp', 'end_qc_tp', 'end_gather_tp']) {
+                                if (node[field]) {
+                                    return true;
+                                }
+                            }
+                            return false;
+                        });
+                        SpreadJsObj.refreshTreeRowVisible(sheet);
+                        break;
                 }
                 closeWaitingView();
             }, 100);
@@ -4850,7 +4866,7 @@ $(document).ready(() => {
             if (stage.assist) {
                 const caption = stage.assist.user_id === stage.user_id
                     ? (stage.assist.confirm ? '撤销上报' : '确认上报')
-                    : (stage.assist.confirm ? '审批通过' : '重新审批');
+                    : (stage.assist.confirm ? '重新审批' : '审批通过');
                 if (!stage.readOnly || stage.assist.locked) $('#ass-confirm-btn').show().html(caption);
             } else {
                 $('#ass-confirm-btn').hide();

+ 225 - 2
app/public/js/stage_audit.js

@@ -59,7 +59,7 @@ $(document).ready(function () {
     })
 
     // 添加审批流程按钮逻辑
-    $('.book-list').on('click', 'dt', function () {
+    $('body').on('click', '.book-list dt', function () {
         const idx = $(this).find('.acc-btn').attr('data-groupid')
         const type = $(this).find('.acc-btn').attr('data-type')
         if (type === 'hide') {
@@ -126,7 +126,7 @@ $(document).ready(function () {
     //     $('#account_list').html(account_html);
     // });
     // 添加到审批流程中
-    $('dl').on('click', 'dd', function () {
+    $('#book-list').on('click', 'dd', function () {
         const id = parseInt($(this).data('id'));
         if (id !== 0) {
             postData(getUrlPre() + '/audit/add', { auditorId: id }, (datas) => {
@@ -242,6 +242,229 @@ $(document).ready(function () {
     });
 
 
+    // 管理员更改审批流程js部分
+    let timer2 = null;
+    let oldSearchVal2 = null;
+    $('body').on('input propertychange', '.gr-search', function(e) {
+        oldSearchVal2 = e.target.value;
+        timer2 && clearTimeout(timer2);
+        timer2 = setTimeout(() => {
+            const newVal = $(this).val();
+            const code = $(this).attr('data-code');
+            let html = '';
+            if (newVal && newVal === oldSearchVal2) {
+                accountList.filter(item => item && item.id !== stage_uid && (item.name.indexOf(newVal) !== -1 || (item.mobile && item.mobile.indexOf(newVal) !== -1))).forEach(item => {
+                    html += `<dd class="border-bottom p-2 mb-0 " data-id="${item.id}" >
+                        <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>`
+                });
+                $('#' + code + '_dropdownMenu .book-list').empty();
+                $('#' + code + '_dropdownMenu .book-list').append(html);
+            } else {
+                if (!$('#' + code + '_dropdownMenu .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 !== stage_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>';
+                    });
+                    $('#' + code + '_dropdownMenu .book-list').empty();
+                    $('#' + code + '_dropdownMenu .book-list').append(html);
+                }
+            }
+        }, 400);
+    });
+
+    $('body').on('click', '#admin-edit-shenpi dl dd', function () {
+        const id = parseInt($(this).attr('data-id'));
+        if (!id) return;
+
+        let this_aid = parseInt($(this).parents('.book-list').attr('data-aid'));
+        let this_operate = $(this).parents('.book-list').attr('data-operate');
+        const user = _.find(accountList, function (item) {
+            return item.id === id;
+        });
+        const auditorIndex = _.findIndex(auditorList, { aid: id });
+        if (auditorIndex !== -1) {
+            toastr.warning('该审核人已存在,请勿重复添加');
+            return;
+        }
+        const order = parseInt($(this).parents('tr').find('.shenpi-order').text());
+        const curAuditorIndex = _.findIndex(auditorList, { aid: this_aid });
+        const prop = {
+            operate: this_operate,
+            old_aid: this_aid,
+            new_aid: user.id,
+        };
+        postData(getUrlPre() + '/audit/save', prop, (datas) => {
+            if (this_operate === 'add') {
+                const addhtml = '<tr>\n' +
+                    `                                <td><span class="shenpi-order">${order+1}</span> ${user.name} <small class="text-muted">${user.role}</small></td>\n` +
+                    `                                <td style="text-align: center"><span class="">待审批</span></td>\n` +
+                    '                                <td style="text-align: center">\n' +
+                    '                                    <span class="dropdown mr-2">\n' +
+                    `                                    <a href="javascript: void(0)" class="add-audit" id="${user.id}_add_dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">增加</a>\n` +
+                    makeSelectAudit(user.id+'_add', user.id, 'add') +
+                    '                                        </div>\n' +
+                    '                                    </span>\n' +
+                    '                                    <span class="dropdown mr-2">\n' +
+                    `                                        <a href="javascript: void(0)" class="change-audit" id="${user.id}_change_dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">更换</a>\n` +
+                    makeSelectAudit(user.id+'_change', user.id, 'change') +
+                    '                                    </span>\n' +
+                    '                                    <span class="dropdown">\n' +
+                    '                                    <a href="javascript: void(0)" class="text-danger" title="移除" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">移除</a>\n' +
+                    '                                    <div class="dropdown-menu">\n' +
+                    '                                        <span class="dropdown-item">确认移除审批人?</span>\n' +
+                    '                                        <div class="dropdown-divider"></div>\n' +
+                    '                                        <div class="px-2 py-1 text-center">\n' +
+                    `                                            <button class="remove-audit btn btn-sm btn-danger" data-id="${user.id}">移除</button>\n` +
+                    '                                            <button class="btn btn-sm btn-secondary">取消</button>\n' +
+                    '                                        </div>\n' +
+                    '                                    </div>\n' +
+                    '                                    </span>\n' +
+                    '                                </td>\n' +
+                    '                            </tr>';
+                $(this).parents('tr').after(addhtml);
+                auditorList.splice(curAuditorIndex+1, 0, { aid: user.id, company: user.company, name: user.name, role: user.role });
+                updateOrder(user.id);
+            } else if (this_operate === 'change') {
+                const this_user = _.find(auditorList, { aid: this_aid });
+                this_user.aid = user.id;
+                this_user.company = user.company;
+                this_user.role = user.role;
+                this_user.name = user.name;
+                auditorList.splice(curAuditorIndex, 1, this_user);
+                $(this).parents('tr').children('td').eq(0).html(`<span class="shenpi-order">${order}</span> ${user.name} <small class="text-muted">${user.role}</small>`);
+                // 替换所有aid
+                $(this).parents('.book-list').attr('data-aid', user.id);
+                $(this).parents('.dropdown-menu').attr('id', user.id +'_change_dropdownMenu').attr('aria-labelledby', user.id +'_change_dropdownMenuButton');
+                $(this).parents('.dropdown-menu').children('mb-2').children('input').attr('data-code', user.id +'_change');
+                $(this).parents('.dropdown-menu').siblings('.change-audit').attr('id', user.id +'_change_dropdownMenuButton');
+                $(this).parents('td').children('span').eq(0).find('.add-audit').attr('id', user.id +'_add_dropdownMenuButton');
+                $(this).parents('td').children('span').eq(0).find('.dropdown-menu').attr('id', user.id +'_add_dropdownMenu').attr('aria-labelledby', user.id +'_add_dropdownMenuButton');
+                $(this).parents('td').children('span').eq(0).find('.dropdown-menu').children('mb-2').children('input').attr('data-code', user.id +'_add');
+                $(this).parents('td').children('span').eq(0).find('.dropdown-menu').children('.book-list').attr('data-aid', user.id);
+                $(this).parents('td').children('span').eq(2).find('.remove-audit').attr('data-id', user.id);
+            }
+            changeLiucheng(datas);
+        });
+    });
+
+    // 移除审批人
+    $('body').on('click', '.remove-audit', function () {
+        const id = parseInt($(this).attr('data-id'));
+        const prop = {
+            operate: 'del',
+            old_aid: id,
+        };
+        postData(getUrlPre() + '/audit/save', prop, (datas) => {
+            updateOrder(id, 0);
+            const curAuditorIndex = _.findIndex(auditorList, { aid: id });
+            auditorList.splice(curAuditorIndex, 1);
+            $(this).parents('tr').remove();
+            changeLiucheng(datas);
+        });
+    });
+
+    // 比uid大的序号进行调整
+    function updateOrder(aid, num = 1) {
+        console.log('hello');
+        const index = _.findIndex(auditorList, { aid });
+        console.log(index, auditorList.length);
+        for (let i = index;i < auditorList.length; i++) {
+            console.log(i,i+num, auditorList.length);
+            $('#admin-edit-shenpi tbody').children('tr').eq(i).find('.shenpi-order').text(i+num);
+        }
+    }
+
+    function changeLiucheng(datas) {
+        const auditorshtml = [];
+        let lastAuditorHtml = '';
+        for (const [index,data] of datas.entries()) {
+            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>');
+            if (data.status === auditConst.status.uncheck) {
+                lastAuditorHtml += '<li class="timeline-list-item pb-2 is_uncheck">\n' +
+                    '                                            <div class="timeline-item-date">\n' +
+                    '                                                \n' +
+                    '                                            </div>\n' +
+                    '                                            <div class="timeline-item-icon bg-secondary text-light">\n' +
+                    '                                            </div>\n' +
+                    '                                            <div class="timeline-item-content">\n' +
+                    '                                                <div class="card">\n' +
+                    '                                                    <div class="card-body p-3">\n' +
+                    '                                                        <div class="card-text">\n' +
+                    `                                                            <p class="mb-1"><span class="h5">${data.name}</span>\n` +
+                    '                                                                <span class="pull-right ">\n' +
+                    '                                                                </span>\n' +
+                    '                                                            </p>\n' +
+                    `                                                            <p class="text-muted mb-0">${data.role}</p>\n` +
+                    '                                                        </div>\n' +
+                    '                                                    </div>\n' +
+                    '                                                </div>\n' +
+                    '                                            </div>\n' +
+                    '                                        </li>';
+            }
+        }
+        console.log(lastAuditorHtml);
+        $('.last-auditor-list .is_uncheck').remove();
+        $('.last-auditor-list').append(lastAuditorHtml);
+        $('.auditors-list').html(auditorshtml.join(''));
+
+    }
+
+    // 审批流程-选择审批人html 生成
+    function makeSelectAudit(code, aid, status) {
+        let divhtml = '';
+        accountGroup.forEach((group, idx) => {
+            let didivhtml = '';
+            if(group) {
+                group.groupList.forEach(item => {
+                    didivhtml += item.id !== stage_uid ? '<dd class="border-bottom p-2 mb-0 " data-id="' + item.id + '" >\n' +
+                        '<p class="mb-0 d-flex"><span class="text-primary">' + item.name + '</span><span\n' +
+                        '                                                                                class="ml-auto">' + item.mobile + '</span></p>\n' +
+                        '                                                                    <span class="text-muted">' + item.role + '</span>\n' +
+                        '                                                                    </dd>\n' : '';
+                });
+                divhtml += '<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>\n' +
+                    '                                                                <div class="dd-content" data-toggleid="' + idx + '">\n' + didivhtml +
+                    '                                                                </div>\n';
+            }
+        });
+        let html = '<div class="dropdown-menu dropdown-menu-right" id="' + code + '_dropdownMenu" aria-labelledby="' + code + '_dropdownMenuButton" style="width:220px">\n' +
+            '                                                        <div class="mb-2 p-2"><input class="form-control form-control-sm gr-search"\n' +
+            '                                                                                     placeholder="姓名/手机 检索" autocomplete="off" data-code="' + code + '"></div>\n' +
+            '                                                        <dl class="list-unstyled book-list" data-aid="'+ aid +'" data-operate="'+ status +'">\n' + divhtml +
+            '                                                        </dl>\n' +
+            '                                                    </div>\n' +
+            '                                                </div>\n' +
+            '                                            </span>\n' +
+            '                                        </span>\n' +
+            '                                        </li>';
+        return html;
+    }
+
 });
 // 检查上报情况
 function checkAuditorFrom () {

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

@@ -72,7 +72,7 @@ const stageIm = (function () {
     function loadData (ledger, pos, stageDetail, stageChange, stageImportChange, stageDetailAtt) {
         up_field = 'unit_price';
         gsTree.loadDatas(ledger);
-        gsTree.loadFilter(filter);
+        if (filter) gsTree.loadFilter(filter);
         treeCalc.calculateAll(gsTree);
 
         gsPos.loadDatas(pos);

+ 82 - 19
app/public/js/stage_pay.js

@@ -59,6 +59,34 @@ $(document).ready(() => {
                 for (const b of this.bases) {
                     b.reg = new RegExp(b.code, 'igm');
                 }
+                this.orderReg = /f\d+/ig;
+            }
+
+            getLeafOrder(data, parentOrder) {
+                if (!data) return [];
+                const defaultResult = data.order ? [`f${data.order}`] : [];
+                if (!data.expr) return defaultResult;
+                const orderParam = data.expr.match(this.orderReg);
+                if (!orderParam || orderParam.length === 0) return defaultResult;
+
+                const result = [], payData = paySheet.zh_data || [];
+                for (const op of orderParam) {
+                    const order = op.substring(1, op.length);
+                    if (data.order === parseInt(order) || op === parentOrder) {
+                        result.push(op);
+                    } else {
+                        const subOrderParam = this.getLeafOrder(payData[parseInt(order) -1], data.order ? `f${data.order}` : parentOrder);
+                        result.push(...subOrderParam);
+                    }
+                }
+                return result;
+            }
+
+            checkCircularExpr(expr, selfOrder) {
+                const leafOrder = this.getLeafOrder({expr}, `f${selfOrder}`);
+
+                if (leafOrder.indexOf(`f${selfOrder}`) >= 0 || leafOrder.indexOf(`F${selfOrder}`) >= 0) return true;
+                return false;
             }
 
             calculateExpr(expr) {
@@ -85,6 +113,7 @@ $(document).ready(() => {
         return new PayCalc(b);
     })(calcBase);
     const paySpread = SpreadJsObj.createNewSpread($('#pay-spread')[0]);
+    const paySheet = paySpread.getActiveSheet();
     const wcPay = dealPay.find(function (x) {return x.ptype === 4});
 
     $.subMenu({
@@ -149,7 +178,7 @@ $(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: 'postil', hAlign: 0, width: 120},
+            {title: '本期批注', colSpan: '1', rowSpan: '1', field: 'postil', hAlign: 0, width: 120, formatter: '@', cellType: 'autoTip'},
             // {title: '状态', colSpan: '1', rowSpan: '1', field: '', hAlign: 0, width: 120, readOnly: true, getValue: 'getValue.state', foreColor: 'red', font: '8px 宋体'},
         ],
         emptyRows: 0,
@@ -172,7 +201,9 @@ $(document).ready(() => {
     };
     paySpreadSetting.getColor = function (sheet, data, row, col, defaultColor) {
         if (data) {
-            if (data.pause) {
+            if (data.expr && data.expr.indexOf('#ref!') >= 0) {
+                return spreadColor.pay.expr_err;
+            } else if (data.pause) {
                 return '#f2f2f2';
             } else if (!data.is_yf) {
                 return '#d6d8db';
@@ -230,7 +261,6 @@ $(document).ready(() => {
         },
         isLock: function (data) {
             const result = !!lockPayExpr && payBase.isStarted(data) && payBase.hasBase(data);
-            console.log(data.name, result);
             return result;
         }
     };
@@ -309,17 +339,31 @@ $(document).ready(() => {
     SpreadJsObj.loadSheetData(paySpread.getActiveSheet(), SpreadJsObj.DataType.Data, dealPay);
 
     const paySpreadObj = {
-        _checkExprValid(expr, invalidParam) {
+        _checkExprValid(expr, invalidParam, selfOrder) {
             if (!expr) return [true, null];
             const param = [];
+            const orderReg = /^f\d+/i;
             let num = '', base = '';
             for (let i = 0, iLen = expr.length; i < iLen; i++) {
+                const subExpr = expr.substring(i, expr.length);
                 if (/^[\d\.%]+/.test(expr[i])) {
                     if (base !== '') {
                         param.push({type: 'base', value: base});
                         base = '';
                     }
                     num = num + expr[i];
+                } else if (orderReg.test(subExpr)) {
+                    if (num !== '') {
+                        param.push({type: 'num', value: num});
+                        num = '';
+                    }
+                    if (base !== '') {
+                        param.push({type: 'base', value: base});
+                        base = '';
+                    }
+                    const order = orderReg.exec(subExpr);
+                    param.push({type: 'order', value: order[0]});
+                    i = i + order[0].length - 1;
                 } else if (/^[a-z]/.test(expr[i])) {
                     if (num !== '') {
                         param.push({type: 'num', value: num});
@@ -406,6 +450,12 @@ $(document).ready(() => {
                     if (i > 0 && (param[i - 1].type === 'num' || param[i - 1].type === 'right'))
                         return [false, '输入的表达式非法:' + p.value + '前应有运算符'];
                 }
+                if (p.type === 'order') {
+                    if (selfOrder === undefined) return [false, '输入的表达式错误:不支持行号引用'];
+
+                    if ([`f${selfOrder}`, `F${selfOrder}`].indexOf(p.value) >= 0) return [false, '输入的表达式非法:请勿引用自己'];
+                    if (['f1', 'f2', 'f3', 'F1', 'F2', 'F3'].indexOf(p.value) >= 0) return [false, '输入的表达式非法:请勿引用前三行'];
+                }
                 if (p.type === 'left') {
                     iLeftCount += 1;
                     if (i !== 0 && param[i-1].type !== 'calc')
@@ -421,6 +471,11 @@ $(document).ready(() => {
             }
             if (iLeftCount > iRightCount)
                 return [false, '输入的表达式非法:"("后无对应的")"'];
+
+            if (selfOrder !== undefined) {
+                const circular = payCalc.checkCircularExpr(expr, selfOrder);
+                if (circular) return [false, '输入的表达式非法:循环引用'];
+            }
             return [true, ''];
         },
         _checkSExpr: function (payNode, text, data) {
@@ -479,7 +534,7 @@ $(document).ready(() => {
                 return [true, ''];
             }
         },
-        _checkExpr: function (text, data) {
+        _checkExpr: function (text, data, order) {
             if (text) {
                 const num = _.toNumber(text);
                 if (num) {
@@ -487,7 +542,7 @@ $(document).ready(() => {
                     data.expr = null;
                 } else {
                     const expr = $.trim(text).replace('\t', '').replace('=', '').toLowerCase();
-                    const [valid, msg] = this._checkExprValid(expr, ['bqyf']);
+                    const [valid, msg] = this._checkExprValid(expr, ['bqyf'], order);
                     if (!valid) return [valid, msg];
                     data.expr = expr;
                     data.tp = null;
@@ -573,11 +628,11 @@ $(document).ready(() => {
             const sheet = paySpread.getActiveSheet();
             const select = SpreadJsObj.getSelectObject(sheet);
             if (payBase.isNonZero(select.tp)) {
-                toast('该支付(扣款)项存在数据,如需删除请先清除本期金额!');
+                toastr.warning('该支付(扣款)项存在数据,如需删除请先清除本期金额!');
                 return;
             } else if (payBase.isOld(select)) {
                 if (payBase.isStarted(select)) {
-                    toast('该合同支付项往期已进行计算,不允许删除');
+                    toastr.error('该合同支付项往期已进行计算,不允许删除');
                     return;
                 }
             }
@@ -596,10 +651,13 @@ $(document).ready(() => {
             const sheet = paySpread.getActiveSheet();
             const cur = SpreadJsObj.getSelectObject(sheet);
             const up = dealPay[dealPay.indexOf(cur) - 1];
-            postData(window.location.pathname + '/save', {type: 'changeOrder', id1: cur.pid, id2: up.pid}, function () {
-                const order = cur.order;
-                cur.order = up.order;
-                up.order = order;
+            postData(window.location.pathname + '/save', {type: 'changeOrder', id1: cur.pid, id2: up.pid}, function (result) {
+                for (const r of result) {
+                    const p = dealPay.find(x => { return x.pid === r.pid });
+                    if (!p) continue;
+                    p.order = r.order;
+                    p.expr = r.expr;
+                }
                 dealPay.sort(function (a, b) {
                     return a.order - b.order
                 });
@@ -608,6 +666,7 @@ $(document).ready(() => {
                 const index = dealPay.indexOf(cur);
                 sheet.setSelection(index, sel.length > 0 ? sel[0].col : 0, 1, 1);
                 paySpreadObj.refreshActn();
+                paySpreadObj.loadExprToInput();
             });
         },
         downMove: function () {
@@ -618,10 +677,13 @@ $(document).ready(() => {
                 type: 'changeOrder',
                 id1: cur.pid,
                 id2: down.pid
-            }, function () {
-                const order = cur.order;
-                cur.order = down.order;
-                down.order = order;
+            }, function (result) {
+                for (const r of result) {
+                    const p = dealPay.find(x => { return x.pid === r.pid });
+                    if (!p) continue;
+                    p.order = r.order;
+                    p.expr = r.expr;
+                }
                 dealPay.sort(function (a, b) {
                     return a.order - b.order
                 });
@@ -630,6 +692,7 @@ $(document).ready(() => {
                 const index = dealPay.indexOf(cur);
                 sheet.setSelection(index, sel.length > 0 ? sel[0].col : 0, 1, 1);
                 paySpreadObj.refreshActn();
+                paySpreadObj.loadExprToInput();
             });
         },
         selectionChanged: function (e, info) {
@@ -675,7 +738,7 @@ $(document).ready(() => {
                     case 'tp':
                         const [tpValid, tpMsg] = payBase.isSF(select)
                             ? paySpreadObj._checkSfExpr(validText, data.updateData)
-                            : paySpreadObj._checkExpr(validText, data.updateData);
+                            : paySpreadObj._checkExpr(validText, data.updateData, select.order);
                         if (!tpValid) {
                             toastr.warning(tpMsg);
                             SpreadJsObj.reLoadRowData(info.sheet, info.row);
@@ -848,7 +911,7 @@ $(document).ready(() => {
                             case 'tp':
                                 const [tpValid, tpMsg] = payBase.isSF(node)
                                     ? paySpreadObj._checkSfExpr(validText, updateData)
-                                    : paySpreadObj._checkExpr(validText, updateData);
+                                    : paySpreadObj._checkExpr(validText, updateData, select.order);
                                 if (!tpValid) {
                                     toastr.warning(tpMsg);
                                     SpreadJsObj.reLoadSheetData(paySpread.getActiveSheet());
@@ -923,7 +986,7 @@ $(document).ready(() => {
                 data.updateData = { pid: select.pid, tp: null, expr: newValue };
                 const [valid, msg] = payBase.isSF(select)
                     ? paySpreadObj._checkSfExpr(newValue, data.updateData)
-                    : paySpreadObj._checkExpr(newValue, data.updateData);
+                    : paySpreadObj._checkExpr(newValue, data.updateData, select.order);
                 if (!valid) {
                     toastr.warning(msg);
                     this.value = select.expr;

+ 42 - 36
app/public/js/tender_list_info.js

@@ -302,7 +302,7 @@ function recursiveGetTenderNodeHtml (node, arr, pid) {
     const html = [];
     html.push('<tr pid="' + pid + '">');
     // 名称
-    html.push('<td style="width: 14%" class="in-' + node.level + '">');
+    html.push('<td style="min-width: 300px;" class="in-' + node.level + '">');
     if (node.cid) {
         html.push('<span onselectstart="return false" style="{-moz-user-select:none}" class="fold-switch mr-1" title="收起" cid="'+ node.sort_id +'"><i class="fa fa-minus-square-o"></i></span> <i class="fa fa-folder-o"></i> ', node.name);
     } else {
@@ -310,71 +310,77 @@ function recursiveGetTenderNodeHtml (node, arr, pid) {
         html.push(arr.indexOf(node) === arr.length - 1 ? '└' : '├');
         html.push('</span>');
         //html.push('<a href="/tender/' + node.id + '">', node[c.field], '</a>');
-        html.push('<a href="javascript: void(0)" id="' + node.id + '">', node.name, '</a>');
+        html.push('<a href="javascript: void(0)" style="min-width: 300px;word-break:break-all;" id="' + node.id + '">', node.name, '</a>');
     }
     html.push('</td>');
     // 计量模式
-    html.push('<td style="width: 5%" class="text-center">');
+    html.push('<td style="width: 75px" class="text-center">');
     if (node.measure_type) {
         html.push(node.measure_type === measureType.tz.value ? '0号台账' : '工程量清单');
     }
     html.push('</td>');
     // 计量进度
-    html.push('<td style="width: 6%">');
+    html.push('<td style="width: 105px">');
     if (!node.cid && node.cur_flow) {
         html.push(node.cur_flow.title + ' (' + '<span class="' + node.cur_flow.status_class +'">' + node.cur_flow.status + '</span>' + ')');
     }
     html.push('</td>');
     // 当前流程
-    html.push('<td style="width: 6%">');
+    html.push('<td style="width: 230px">');
     if (!node.cid && node.cur_flow) {
-        html.push(node.cur_flow.name + ' ' + '<span class="' + node.cur_flow.status_class +'">' + node.cur_flow.status + '</span>');
+        html.push((node.lastStage && node.lastStage.status === auditConst.stage.status.uncheck) || (!node.lastStage && node.ledger_status === auditConst.ledger.status.uncheck ) ? '' :
+            '<a href="#sp-list" data-toggle="modal" data-target="#sp-list"  data-type="'+ (node.lastStage ? 'stage' : 'ledger') +'"' +
+            ' data-tender="'+ node.id +'" data-order="'+ (node.lastStage ? node.lastStage.order : '') +'">');
+        html.push(node.cur_flow.name+ (node.cur_flow.role ? '-'+node.cur_flow.role  : ''));
+        html.push((node.lastStage && node.lastStage.status === auditConst.stage.status.uncheck) || (!node.lastStage && node.ledger_status === auditConst.ledger.status.uncheck ) ? ' ':
+                '</a> ');
+        html.push('<span class="' + node.cur_flow.status_class +'">' + node.cur_flow.status + '</span>');
     }
     html.push('</td>');
     // 上一流程审批时间
-    html.push('<td style="width: 7%">');
-    if (!node.cid && node.pre_flow) {
-        html.push(node.pre_flow.name + ' ' + moment(node.pre_flow.time).format('YYYY-MM-DD'));
-    }
-    html.push('</td>');
+    // html.push('<td style="width: 7%">');
+    // if (!node.cid && node.pre_flow) {
+    //     html.push(node.pre_flow.name + ' ' + moment(node.pre_flow.time).format('YYYY-MM-DD'));
+    // }
+    // html.push('</td>');
     // 签约合同价
-    html.push('<td style="width: 6%" class="text-right">');
+    html.push('<td style="width: 100px" class="text-right">');
     html.push(node.contract_price || '');
     html.push('</td>');
     // 0号台账合同
-    html.push('<td style="width: 6%" class="text-right">');
+    html.push('<td style="width: 100px" class="text-right">');
     html.push(node.total_price || '');
     html.push('</td>');
     // 本期完成
-    html.push('<td style="width: 6%" class="text-right">');
+    html.push('<td style="width: 100px" class="text-right">');
     html.push(node.gather_tp || '');
     html.push('</td>');
     // 截止本期合同
-    html.push('<td style="width: 6%" class="text-right">');
+    html.push('<td style="width: 100px" class="text-right">');
     html.push(node.end_contract_tp || '');
     html.push('</td>');
     // 截止本期变更
-    html.push('<td style="width: 6%" class="text-right">');
+    html.push('<td style="width: 100px" class="text-right">');
     html.push(node.end_qc_tp || '');
     html.push('</td>');
     // 截止本期完成
-    html.push('<td style="width: 6%" class="text-right">');
+    html.push('<td style="width: 100px" class="text-right">');
     html.push(node.end_gather_tp || '');
     html.push('</td>');
     // 截止上期完成
-    html.push('<td style="width: 6%" class="text-right">');
+    html.push('<td style="width: 100px" class="text-right">');
     html.push(node.pre_gather_tp || '');
     html.push('</td>');
     // 预付款
-    html.push('<td style="width: 6%" class="text-right">');
+    html.push('<td style="width: 100px" class="text-right">');
     html.push(node.advance_tp || '');
     html.push('</td>');
     // 本期应付
-    html.push('<td style="width: 6%" class="text-right">');
+    html.push('<td style="width: 100px" class="text-right">');
     html.push(node.yf_tp || '');
     html.push('</td>');
     // 截止本期应付
-    html.push('<td style="width: 6%" class="text-right">');
+    html.push('<td style="width: 100px" class="text-right">');
     html.push(node.end_yf_tp || '');
     html.push('</td>');
     html.push('</tr>');
@@ -391,21 +397,21 @@ function getTenderTreeHtml () {
         const html = [];
         html.push('<table class="table table-hover table-bordered">');
         html.push('<thead style="position: fixed;left:56px;top: 34px;">', '<tr>');
-        html.push('<th class="text-center" style="width: 14%">', '标段名称', '</th>');
-        html.push('<th class="text-center" style="width: 5%">', '计量模式', '</th>');
-        html.push('<th class="text-center" style="width: 6%">', '计量进度', '</th>');
-        html.push('<th class="text-center" style="width: 6%">', '当前流程', '</th>');
-        html.push('<th class="text-center" style="width: 7%">', '上一流程审批时间', '</th>');
-        html.push('<th class="text-center" style="width: 6%">', '签约合同价', '</th>');
-        html.push('<th class="text-center" style="width: 6%">', '0号台账', '</th>');
-        html.push('<th class="text-center" style="width: 6%">', '本期完成', '</th>');
-        html.push('<th class="text-center" style="width: 6%">', '截止本期合同', '</th>');
-        html.push('<th class="text-center" style="width: 6%">', '截止本期变更', '</th>');
-        html.push('<th class="text-center" style="width: 6%">', '截止本期完成', '</th>');
-        html.push('<th class="text-center" style="width: 6%">', '截止上期完成', '</th>');
-        html.push('<th class="text-center" style="width: 6%">', '预付款', '<i class="fa fa-question-circle text-primary" data-placement="bottom" data-toggle="tooltip" data-original-title="预付款流程中截止本期金额"></i>', '</th>');
-        html.push('<th class="text-center" style="width: 6%">', '本期应付', '</th>');
-        html.push('<th class="text-center" style="width: 6%">', '截止本期应付', '</th>');
+        html.push('<th class="text-center" style="min-width: 300px;">', '标段名称', '</th>');
+        html.push('<th class="text-center" style="width: 75px">', '计量模式', '</th>');
+        html.push('<th class="text-center" style="width: 105px">', '计量进度', '</th>');
+        html.push('<th class="text-center" style="width: 230px">', '当前流程', '</th>');
+        // html.push('<th class="text-center" style="width: 7%">', '上一流程审批时间', '</th>');
+        html.push('<th class="text-center" style="width: 100px">', '签约合同价', '</th>');
+        html.push('<th class="text-center" style="width: 100px">', '0号台账', '</th>');
+        html.push('<th class="text-center" style="width: 100px">', '本期完成', '</th>');
+        html.push('<th class="text-center" style="width: 100px">', '截止本期合同', '</th>');
+        html.push('<th class="text-center" style="width: 100px">', '截止本期变更', '</th>');
+        html.push('<th class="text-center" style="width: 100px">', '截止本期完成', '</th>');
+        html.push('<th class="text-center" style="width: 100px">', '截止上期完成', '</th>');
+        html.push('<th class="text-center" style="width: 100px">', '预付款', '<i class="fa fa-question-circle text-primary" data-placement="bottom" data-toggle="tooltip" data-original-title="预付款流程中截止本期金额"></i>', '</th>');
+        html.push('<th class="text-center" style="width: 100px">', '本期应付', '</th>');
+        html.push('<th class="text-center" style="width: 100px">', '截止本期应付', '</th>');
         html.push('</tr>', '</thead>');
         parentId = 0;
         for (const t of tenderTree) {

+ 9 - 3
app/public/js/tender_list_progress.js

@@ -312,7 +312,7 @@ function recursiveGetTenderNodeHtml (node, arr, pid) {
     const html = [];
     html.push('<tr pid="' + pid + '">');
     // 名称
-    html.push('<td width="25%" class="in-' + node.level + '">');
+    html.push('<td width="20%" class="in-' + node.level + '">');
     if (node.cid) {
         html.push('<span onselectstart="return false" style="{-moz-user-select:none}" class="fold-switch mr-1" title="收起" cid="'+ node.sort_id +'"><i class="fa fa-minus-square-o"></i></span> <i class="fa fa-folder-o"></i> ', node.name);
     } else {
@@ -330,9 +330,15 @@ function recursiveGetTenderNodeHtml (node, arr, pid) {
     }
     html.push('</td>');
     // 当前流程
-    html.push('<td style="width: 8%">');
+    html.push('<td style="width: 13%">');
     if (!node.cid && node.cur_flow) {
-        html.push(node.cur_flow.name + ' ' + '<span class="' + node.cur_flow.status_class +'">' + node.cur_flow.status + '</span>');
+        html.push((node.lastStage && node.lastStage.status === auditConst.stage.status.uncheck) || (!node.lastStage && node.ledger_status === auditConst.ledger.status.uncheck ) ? '' :
+            '<a href="#sp-list" data-toggle="modal" data-target="#sp-list"  data-type="'+ (node.lastStage ? 'stage' : 'ledger') +'"' +
+            ' data-tender="'+ node.id +'" data-order="'+ (node.lastStage ? node.lastStage.order : '') +'">');
+        html.push(node.cur_flow.name+ (node.cur_flow.role ? '-'+node.cur_flow.role  : ''));
+        html.push((node.lastStage && node.lastStage.status === auditConst.stage.status.uncheck) || (!node.lastStage && node.ledger_status === auditConst.ledger.status.uncheck ) ? ' ':
+            '</a> ');
+        html.push('<span class="' + node.cur_flow.status_class +'">' + node.cur_flow.status + '</span>');
     }
     html.push('</td>');
     // 上一流程审批时间

+ 39 - 13
app/public/report/js/jpc_output.js

@@ -12,7 +12,7 @@ let JpcCanvasOutput = {
         ctx.clearRect(0,0, canvas.width, canvas.height);
         ctx.restore();
     },
-    drawToCanvas : function(pageObj, canvas, pageIdx) {
+    drawToCanvas : function(pageObj, canvas, pageIdx,hideSignature=false) {
         let me = this;
         let ctx = canvas.getContext("2d");
         let floatReg = /^(-?\d+)(\.\d+)?$/;
@@ -21,6 +21,8 @@ let JpcCanvasOutput = {
             strReplaceReg3 = new RegExp('\n', 'gm'),
             strReplaceReg4 = new RegExp('\r', 'gm')
         ;
+        // 新增签章移动的功能
+        const moveSignatureTool = new MoveSignatureTool(ctx,canvas,pageObj, pageIdx,this);
 
         function private_setupAreaH(area, type, fontAngle, dftFontHeight, outputPoint) {
             let lType = type;
@@ -415,10 +417,12 @@ let JpcCanvasOutput = {
             private_drawCellText(cell, fonts, controls);
             ctx.restore();
         }
-        function private_drawSignatureCell(cell, fonts, styles, controls, mergedBand) {
+        function private_drawSignatureCell(cell, fonts, styles, controls, mergedBand,moveSignatureTool) {
             ctx.save();
             ctx.translate(0.5,0.5);
             let style = styles[cell[JV.PROP_STYLE]];
+            // 印章的cell表框都要去掉
+            if(cell.path&&cell.signType) style =  'Default_None';
             if (style) {
                 let isNeedMergeBand = private_chkIfInMergedBand(mergedBand, cell);
                 private_drawLine(cell, ctx, style, JV.PROP_TOP, [JV.PROP_LEFT, JV.PROP_TOP],[JV.PROP_RIGHT, JV.PROP_TOP], mergedBand, styles, isNeedMergeBand);
@@ -426,10 +430,10 @@ let JpcCanvasOutput = {
                 private_drawLine(cell, ctx, style, JV.PROP_BOTTOM, [JV.PROP_RIGHT, JV.PROP_BOTTOM],[JV.PROP_LEFT, JV.PROP_BOTTOM], mergedBand, styles, isNeedMergeBand);
                 private_drawLine(cell, ctx, style, JV.PROP_LEFT, [JV.PROP_LEFT, JV.PROP_BOTTOM],[JV.PROP_LEFT, JV.PROP_TOP], mergedBand, styles, isNeedMergeBand);
             }
-            private_drawSignatureCellText(cell, controls);
+            private_drawSignatureCellText(cell, controls,moveSignatureTool);
             ctx.restore();
         }
-        function private_drawSignatureCellText(cell, controls) {
+        function private_drawSignatureCellText(cell, controls,moveSignatureTool) {
             let control = null;
             if (typeof cell[JV.PROP_CONTROL] === "string") {
                 control = controls[cell[JV.PROP_CONTROL]];
@@ -442,9 +446,9 @@ let JpcCanvasOutput = {
                 img.crossOrigin = 'anonymous';
                 img.onload = function() {
                     if (cell.signature_name.indexOf(JV.SIGNATURE_NAME_DUMMY) >= 0) {
-                        private_drawImage(cell, control, img, (!!cell.isStamp), [1, 1, 1, 1]);
+                        private_drawImage(cell, control, img,moveSignatureTool, (!!cell.isStamp), [1, 1, 1, 1]);
                     } else {
-                        private_drawImage(cell, control, img);
+                        private_drawImage(cell, control, img,moveSignatureTool);
                     }
                     // private_drawImage(cell, control, img);
                 };
@@ -459,9 +463,9 @@ let JpcCanvasOutput = {
                 img.crossOrigin = 'anonymous';
                 img.onload = function() {
                     if (cell.signature_name.indexOf(JV.SIGNATURE_NAME_DUMMY) >= 0) {
-                        private_drawImage(cell, control, img, (!!cell.isStamp), [1, 1, 1, 1]);
+                        private_drawImage(cell, control, img,moveSignatureTool, (!!cell.isStamp), [1, 1, 1, 1]);
                     } else {
-                        private_drawImage(cell, control, img);
+                        private_drawImage(cell, control, img,moveSignatureTool);
                     }
                     // private_drawImage(cell, control, img, (!!cell.isStamp));
                 };
@@ -528,7 +532,21 @@ let JpcCanvasOutput = {
             rst[3] = rst[3] + JpcCanvasOutput.offsetY;
             return rst;
         }
-        function private_drawImage(cell, control, imageData, orgSize = false, offsetArea) {
+        function private_drawImage(cell, control, imageData,moveSignatureTool, orgSize = false, offsetArea) {
+            // 新增签章数据到移动工具中
+            if(moveSignatureTool){
+                const {Left,Right,Top,Bottom}=cell.area; 
+                moveSignatureTool.setSignature(
+                    imageData, 
+                    Left* JpcCanvasOutput.scaleFactor, 
+                    Top* JpcCanvasOutput.scaleFactor, 
+                    (Right-Left)* JpcCanvasOutput.scaleFactor, 
+                    (Bottom-Top)* JpcCanvasOutput.scaleFactor,
+                    cell.signature_name,
+                    cell.signatureName,
+                    cell.signType
+                );
+            }  
             let area = private_getProperSignatureArea(cell, control);
             if (orgSize) {
                 area[0] = cell.area.Left + JpcCanvasOutput.offsetX; // Left
@@ -542,8 +560,12 @@ let JpcCanvasOutput = {
                 area[2] = area[2] - offsetArea[2]; // Right
                 area[3] = area[3] - offsetArea[3]; // Bottom
             }
-            ctx.drawImage(imageData, area[0] * JpcCanvasOutput.scaleFactor, area[1] * JpcCanvasOutput.scaleFactor,
-                (area[2] - area[0]) * JpcCanvasOutput.scaleFactor, (area[3] - area[1]) * JpcCanvasOutput.scaleFactor);
+            // debugger
+            if(!cell.isMoving){
+                ctx.drawImage(imageData, area[0] * JpcCanvasOutput.scaleFactor, area[1] * JpcCanvasOutput.scaleFactor,
+                    (area[2] - area[0]) * JpcCanvasOutput.scaleFactor, (area[3] - area[1]) * JpcCanvasOutput.scaleFactor);
+            }
+            
         }
         function getIniPageMergeBorder(mergedBand) {
             let rst = {};
@@ -576,14 +598,18 @@ let JpcCanvasOutput = {
             }
             //电子签名
             if (page.signature_cells && page.signature_cells.length > 0) {
-                for (let k = 0; k < page.signature_cells.length; k++) {
+              
+               for (let k = 0; k < page.signature_cells.length; k++) {
                     let cell = page.signature_cells[k];
                     if (PAGE_SHOW['isTextSignature'] === 0 || cell.signature_name.indexOf(JV.SIGNATURE_NAME_DUMMY) >= 0) {
                         // 如果是非文本签名或草图,才显示图片(文本签名已经在后台单独处理,在cells数组内增加一个合适的cell)
-                        private_drawSignatureCell(cell, fonts, styles, controls, newPageMergeBand);
+                      if(!hideSignature)  private_drawSignatureCell(cell, fonts, styles, controls, newPageMergeBand,moveSignatureTool);
                     }
                     // private_drawSignatureCell(cell, fonts, styles, controls, newPageMergeBand);
                 }
+              
+                moveSignatureTool.domAddEventListener(canvas);
+               
             }
             //水印
             if (PAGE_SHOW['closeWatermark'] === 0) {

+ 2 - 2
app/public/report/js/rpt_custom.js

@@ -306,7 +306,7 @@ const rptCustomObj = (function () {
         gatherSelectSpreadObj.initSelectTenders(gsSelect ? gsSelect.tenders : []);
 
         $('[name=gather-type]').hide();
-        if (gsObj.setting.type === 'month') $('#gather-by-month').show();
+        if (gsObj.setting.type === 'month' || gsObj.setting.type === 'months') $('#gather-by-month').show();
         if (gsObj.setting.type === 'zone') $('#gather-by-zone').show();
         if (gsObj.setting.type === 'stage') $('#gather-by-stage').show();
         if (gsObj.setting.type === 'stage-zone') $('#gather-by-stage-zone').show();
@@ -573,7 +573,7 @@ const rptCustomObj = (function () {
                 return;
             }
         }
-        if (gsObj.setting.type === 'month') {
+        if (gsObj.setting.type === 'month' || gsObj.setting.type === 'months') {
             data[sGatherSelect].month = $('#gather-month').val();
             if (data[sGatherSelect].month === '') {
                 hintObj.html('请选择 汇总年月').show();

+ 3 - 1
app/public/report/js/rpt_jspdf.js

@@ -149,6 +149,8 @@ let JpcJsPDFHelper = {
         function private_drawSignature(doc, ctx, cell, styles, controls, mergedBand, onlyShowBorder, signatureRelArr) {
             ctx.beginPath();
             let style = styles[cell[JV.PROP_STYLE]];
+             // 印章的cell表框都要去掉
+             if(cell.path&&cell.signType) style =  'Default_None';
             if (style) {
                 let isNeedMergeBand = private_chkIfInMergedBand(mergedBand, cell);
                 private_drawLine(cell, doc, ctx, style, JV.PROP_TOP, [JV.PROP_LEFT, JV.PROP_TOP],[JV.PROP_RIGHT, JV.PROP_TOP], mergedBand, styles, isNeedMergeBand);
@@ -249,7 +251,7 @@ let JpcJsPDFHelper = {
                 area[3] = area[3] - offsetArea[3]; // Bottom
             }
             doc.addImage(imageData, 'PNG', area[0] * PDF_SCALE, area[1] * PDF_SCALE,
-                (area[2] - area[0]) * PDF_SCALE, (area[3] - area[1]) * PDF_SCALE);
+                (area[2] - area[0]) * PDF_SCALE, (area[3] - area[1]) * PDF_SCALE, undefined , 'FAST');
         }
 
         function private_drawCell(doc, ctx, cell, fonts, styles, controls, mergedBand) {

+ 192 - 0
app/public/report/js/rpt_move_signature.js

@@ -0,0 +1,192 @@
+// 思路是点击签章的时候,原来的签章不显示,生成一个img用于拖动,实现拖动效果,再点击新生成的img就保存数据,干掉临时的img
+class MoveSignatureTool {
+    constructor(ctx, canvas, pageObj, pageIdx, jpcOutput) {
+        this.signatureList = [];
+        this.ctx = ctx;
+        // 被选中的签章
+        this.activeImg = undefined;
+        //鼠标位减图形位
+        this.subPos = null;
+        //图形是否被选择
+        this.selected = false;
+        // 上一个被选中的签章属性
+        this.lastPosition = { x: 0, y: 0, width: 0, height: 0 };
+        //canvas的dom对象
+        this.canvas = canvas;
+        this.pageObj = pageObj;
+        this.pageIdx = pageIdx;
+        this.jpcOutput = jpcOutput;
+        this.signatureCanvas = document.getElementById("rptSignatureCanvas");
+    }
+
+    setSignature(obj, x, y, width, height, signature_name, signatureName, signType) {
+        // 只有签章才能移动
+        if (signType) {
+            this.signatureList.push({
+                obj,
+                x,
+                y,
+                width,
+                height,
+                crossOrigin: 'Anonymous',
+                signature_name,
+                signatureName,
+                signType
+            })
+        }
+
+    }
+    //获取鼠标在canvas中的位置
+    getMousePos(event, me) {
+        //获取鼠标位置
+        const { clientX, clientY } = event;
+        //获取canvas 边界位置
+        const { top, left } = me.canvas.getBoundingClientRect();
+        //计算鼠标在canvas 中的位置
+        const x = clientX - left;
+        const y = clientY - top;
+        return { x, y };
+    }
+
+    //判断点是否在图形中
+    containPoint(data, mousePos) {
+        //解构图形位置和尺寸
+        const { x, y, width, height } = data;
+        //解构鼠标位置
+        const { x: mx, y: my } = mousePos;
+        //计算鼠标和图形上、下、左、右边界的关系
+        const l = mx > x;
+        const r = mx < x + width;
+        const t = my > y;
+        const b = my < y + height;
+
+        return l && r && t && b;
+    }
+
+    mousedownFn(event, me) {
+        //鼠标位置
+        const mousePos = me.getMousePos(event, me);
+
+        // 找到相应的对象
+        const target = me.signatureList.find((item) => me.containPoint(item, mousePos));
+        if ($('#templateSignature').length === 0 && target) {
+            //先把上一次的数据记录下来
+            const { x, y, width, height } = target;
+            Object.assign(me.lastPosition, { x, y, width, height });
+
+            me.selected = true;
+            me.activeImg = target;
+            //鼠标位减图形位
+            me.subPos = {
+                x: mousePos.x - me.activeImg.x,
+                y: mousePos.y - me.activeImg.y,
+            };
+
+            const orgTargetData = this.pageObj.items[0].signature_cells.find(item => item.signatureName === target.signatureName && item.signType === target.signType);
+            orgTargetData.isMoving = true;
+            this.render(me);
+
+            const { top, left } = $("#rptCanvas").position();
+            $("#rptCanvas").after(`<div id='templateSignature' data-ID='${orgTargetData.signature_name}' style="background-image:url('${orgTargetData.path}');background-size: contain;cursor: move;position: absolute;top: ${y + top + 10}px;left:${x + left + 10}px;width:${width}px;height:${height}px;"/>`);
+            $("#templateSignature").unbind('mousemove').on('mousemove', (e) => { this.templateSignatureMove(e, this) });
+            $("#templateSignature").unbind('click').on('click', (e) => { this.templateSignatureClick(e, this) });
+
+        } else {
+            me.selected = false;
+        }
+    }
+
+    saveSignature(me) {
+        //鼠标抬起,取消拖拽
+        me.selected = false;
+        me.render(me);
+        const params = {};
+        params.id = CURRENT_ROLE_REL_ID;
+        params.tender_id = TENDER_ID;
+        params.stage_id = getStageId();
+        params.rpt_id = zTreeOprObj.currentNode.refId;
+        params.rel_content = ROLE_REL_LIST;
+        const { x, y, width, height, signType, signatureName } = me.activeImg;
+        const target = ROLE_REL_LIST.find(item => item.signature_name === signatureName);
+        if (target) {
+            if (!target.areaData) target.areaData = {};
+            target.areaData[signType] = {
+                Top: y,
+                Bottom: y + height,
+                Left: x,
+                Right: x + width
+            }
+        }
+        CommonAjax.postXsrfEx("/tender/report_api/updateRoleRelationship", params, 10000, true, getCookie('csrfToken_j'),
+            async function (result) {
+                // console.log(result);
+            }
+        )
+    }
+
+    render(me, isHideSignature = false) {
+        let target;
+        // 因为只有一页,所以就默认取是一个数据
+        for (const signature of me.pageObj.items[0].signature_cells) {
+            //找到要移动的签章
+            if (signature.signature_name && me.activeImg && me.activeImg.signature_name.includes(signature.signature_name)) {
+                target = signature;
+            }
+        }
+        if (target) {
+            Object.assign(target.area, {
+                Top: me.activeImg.y,
+                Bottom: me.activeImg.y + me.activeImg.height,
+                Left: me.activeImg.x,
+                Right: me.activeImg.x + me.activeImg.width,
+            })
+            me.jpcOutput.cleanCanvas(me.canvas);
+            me.jpcOutput.drawPageBorder(me.pageObj, me.canvas, getScreenDPI())
+            me.jpcOutput.drawToCanvas(me.pageObj, me.canvas, me.pageIdx, isHideSignature);
+        }
+
+    }
+    domAddEventListener(canvas) {
+        //点击事件
+        $("#rptCanvas").unbind('mousedown').on('mousedown', (e) => { this.mousedownFn(e, this) });
+        // $("#rptCanvas").unbind('mousemove').on('mousemove', (e) => { this.mouseMove(e, this) });
+    }
+
+    // 临时签章移动事件(控制签章只能在报表中移动)
+    templateSignatureMove(event, me) {
+        // 表格大小
+        const size = me.jpcOutput.getReportSizeInPixel(me.pageObj, getScreenDPI());
+        const reportMarginTop= me.pageObj.打印页面_信息.页边距.Top * 96/2;
+        const reportMarginBottom= me.pageObj.打印页面_信息.页边距.Bottom * 96/2;
+        const { top, left } = $("#rptCanvas").position();
+        const signatureWidth = $("#templateSignature").width();
+        const signatureHeight = $("#templateSignature").height();
+        const maxTop = size[1] + top - signatureHeight + me.jpcOutput.offsetY - reportMarginBottom  ; //签章的最大y坐标
+        const maxLeft =  me.pageObj.MergeBand.Right + left - signatureWidth + me.jpcOutput.offsetX ; //签章的最大x坐标
+
+        const mousePos = me.getMousePos(event, me);
+        let newTop = mousePos.y - me.subPos.y + top;
+        let newLeft = mousePos.x - me.subPos.x + left;
+        if ((mousePos.y - me.subPos.y) < reportMarginTop+top) newTop = reportMarginTop + top + me.jpcOutput.offsetY;
+        if ((mousePos.x - me.subPos.x) < me.pageObj.MergeBand.Left) newLeft = me.pageObj.MergeBand.Left+left + me.jpcOutput.offsetX;
+        if ((mousePos.y - me.subPos.y) > maxTop ) newTop = maxTop + me.jpcOutput.offsetY*2;
+        if ((mousePos.x - me.subPos.x) > maxLeft - left) newLeft = maxLeft + me.jpcOutput.offsetX/2;
+
+        event.currentTarget.style.top = `${newTop}px`;
+        event.currentTarget.style.left = `${newLeft}px`;
+
+
+    }
+    // 临时签章点击事件
+    templateSignatureClick(event, me) {
+        const canvasOuterTop = $("#rptCanvas").position().top;
+        const canvasOuterLeft = $("#rptCanvas").position().left;
+        const { top, left } = $(event.currentTarget).position();
+        me.activeImg.y = top - canvasOuterTop - this.jpcOutput.offsetY;
+        me.activeImg.x = left - canvasOuterLeft - this.jpcOutput.offsetX;
+        const orgTargetData = this.pageObj.items[0].signature_cells.find(item => item.signature_name === $(event.currentTarget).data('id'));
+        orgTargetData.isMoving = false;
+        this.saveSignature(me);
+        $("#templateSignature").remove();
+    }
+}

+ 4 - 0
app/public/report/js/rpt_print.js

@@ -234,6 +234,10 @@ function buildSignatureCellSvg(cell, styles, controls, fonts, pageMergeBorder, r
         //引用了padding后,top坐标不用考虑offset了
         HtoVStr = ` transform="translate(${(actArea.Bottom - actArea.Top + 2)},0) rotate(90)"`;
     }
+    
+    // 印章的cell表框都要去掉
+    if(cell.path&&cell.signType) style =  '';
+
     if (style) {
         let leftBS = getActualBorderStyle(cell, styles, mergeBandStyle, (pageMergeBorder)?pageMergeBorder:rptMergeBorder[JV.PROP_AREA], JV.PROP_LEFT);
         // if (style[JV.PROP_LEFT] && parseFloat(style[JV.PROP_LEFT][JV.PROP_LINE_WEIGHT]) > 0) {

+ 39 - 0
app/public/report/js/rpt_show_level.js

@@ -0,0 +1,39 @@
+const rptShowLevel = (function (){
+    const ratio = [{value: '2', title: '项'}, {value: '3', title: '目'}, {value: '4', title: '节'}, {value: '5', title: '细目'}, {value: 'leafXmj', title: '最底层项目节'}, {value: 'last', title: '最底层'}];
+    const info = {
+        show_level: { title: '请选择显示层级', ratioValue: ['2', '3', '4', '5', 'last'] },
+    };
+    const data = {};
+    let curType = '';
+    const initList = function () {
+        const ratioHtml = [];
+        const ratioValue = info[curType].ratioValue;
+        for (const r of ratio) {
+            if (ratioValue.indexOf(r.value) >= 0) {
+                ratioHtml.push(`<div class="form-check form-check-inline"><input class="form-check-input" type="radio" value="${r.value}" id="ratio_${r.value}" name="show-level"><label class="form-check-label" for="radio_${r.value}" name="show-level">${r.title}</label></div>`);
+            }
+        }
+        $('#ssl-list').html(ratioHtml.join(''));
+        $(`#ratio_${data[curType]}`)[0].checked = true;
+    };
+    const show = async function (type) {
+        curType = type;
+        document.getElementById('ssl-title').innerText = info[type].title;
+        if (!data[type]) {
+            const result = await postDataAsync(`/tender/${window.location.pathname.split('/')[2]}/load`, { filter: curType });
+            data[curType] = result[curType];
+        }
+        initList();
+        $('#select-show-level').modal('show');
+    };
+    const update = async function () {
+        const updateData = {};
+        updateData[curType] = $('[name=show-level]:checked').val();
+        if (updateData[curType].length > 0) {
+            await postData(`/tender/${window.location.pathname.split('/')[2]}/saveRela`, updateData);
+            data[curType] = updateData[curType];
+        }
+        $('#select-show-level').modal('hide');
+    };
+    return { show, update }
+})();

+ 12 - 3
app/public/report/js/rpt_signature.js

@@ -327,7 +327,7 @@ let rptSignatureHelper = {
         const chkType = 'checkbox';
         let rdoNameStr = `dtp_${role_rel.signature_name}_${rptSignatureHelper.currentSelectedESignParentDivId}`;
         // 个人章列表
-        const stampPathList=userAcc.stamp_path?userAcc.stamp_path.split('!;!'):[];
+        const stampPathList=userAcc&&userAcc.stamp_path?userAcc.stamp_path.split('!;!'):[];
 
         if (hasIndividualStamp) {
             elementsStrArr.push('       <div class="form-check form-check-inline mx-1">');
@@ -759,7 +759,8 @@ let rptSignatureHelper = {
                             // { "FileSize": {"value": "2514"}, "Format": {"value": "png"}, "ImageHeight": {"value": "94"}, "ImageWidth": {"value": "94"} }
                             roleRelItem.stampFeature = stampPicFeatures[stmpIdx];
                             // 重新处理cell坐标
-                            _resetStampArea(pageData[JV.NODE_CONTROL_COLLECTION][signatureCell[JV.PROP_CONTROL]], signatureCell, roleRelItem);
+                            // 如果签章信息中存在位置信息,则不执行该方法
+                            if(!signatureCell.isSaveSignature) _resetStampArea(pageData[JV.NODE_CONTROL_COLLECTION][signatureCell[JV.PROP_CONTROL]], signatureCell, roleRelItem);
                         }
                         if (ifPushRoleRel) roleRel.push(roleRelItem);
                     }
@@ -1068,6 +1069,11 @@ function resetStampSignature(pageData, roleRelList) {
                                         if (dupPicPaths.indexOf(stampPath) < 0) {
                                             dupPicPaths.push(stampPath);
                                         }
+                                        // 签字信息中如果有签章位置信息,则直接用位置信息
+                                        let signatureArea;
+                                        if(role_rel.areaData&&role_rel.areaData[signType]){
+                                            signatureArea =  role_rel.areaData[signType]
+                                        }
                                         const newStampCell = {
                                             signature_name: JV.SIGNATURE_NAME_DUMMY,
                                             control: sCell.control,
@@ -1076,7 +1082,10 @@ function resetStampSignature(pageData, roleRelList) {
                                             isStamp: true,
                                             maxRect,
                                             orgArea: sCell.area,
-                                            area: {Left: sCell.area.Left, Right: sCell.area.Right, Top: sCell.area.Top, Bottom: sCell.area.Bottom},
+                                            area: signatureArea || {Left: sCell.area.Left, Right: sCell.area.Right, Top: sCell.area.Top, Bottom: sCell.area.Bottom},
+                                            signatureName: role_rel.signature_name, // 节点名称
+                                            signType, // 签章信息
+                                            isSaveSignature: !!signatureArea, // 是否有签章位置信息
                                         };
                                         newStampCells.push(newStampCell);
                                         break;

+ 2 - 0
app/router.js

@@ -340,6 +340,8 @@ module.exports = app => {
     app.post('/tender/:id/measure/stage/:order/audit/start', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.startAudit');
     app.post('/tender/:id/measure/stage/:order/audit/check', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.checkAudit');
     app.get('/tender/:id/measure/stage/:order/audit/check/again', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.checkAuditAgain');
+    app.get('/tender/:id/measure/stage/:order/audit/check/cancel', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.checkAuditCancel');
+    app.post('/tender/:id/measure/stage/:order/audit/save', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.saveAudit');
 
     // 部位台账
     app.get('/tender/:id/measure/stage/:order/bwtz', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.bwtz');

+ 4 - 4
app/service/change_audit_list.js

@@ -948,7 +948,7 @@ module.exports = app => {
                         l.id, l.code, l.b_code, l.name, l.unit, l.source, l.remark, l.ledger_id,
                         l.ledger_pid, l.level, l.order, l.full_path, l.is_leaf, l.quantity, l.total_price,
                         l.unit_price, l.drawing_code, l.memo, l.dgn_qty1, l.dgn_qty2, l.deal_qty, l.deal_tp,
-                        l.sgfh_qty, l.sgfh_tp, l.sjcl_qty, l.sjcl_tp, l.qtcl_qty, l.qtcl_tp, l.node_type, l.crid,
+                        l.sgfh_qty, l.sgfh_tp, l.sjcl_qty, l.sjcl_tp, l.qtcl_qty, l.qtcl_tp, l.node_type, l.crid, l.ccid,
                         l.tender_id, l.sgfh_expr, l.sjcl_expr, l.qtcl_expr, l.check_calc,
                         l.ex_memo1, l.ex_memo2, l.ex_memo3,
                     ];
@@ -961,7 +961,7 @@ module.exports = app => {
                     this.ctx.service.ledger.tableName +
                     '  (id, code, b_code, name, unit, source, remark, ledger_id, ledger_pid, level, `order`, full_path, is_leaf,' +
                     '     quantity, total_price, unit_price, drawing_code, memo, dgn_qty1, dgn_qty2, deal_qty, deal_tp,' +
-                    '     sgfh_qty, sgfh_tp, sjcl_qty, sjcl_tp, qtcl_qty, qtcl_tp, node_type, crid, tender_id,' +
+                    '     sgfh_qty, sgfh_tp, sjcl_qty, sjcl_tp, qtcl_qty, qtcl_tp, node_type, crid, ccid, tender_id,' +
                     '     sgfh_expr, sjcl_expr, qtcl_expr, check_calc,' +
                     '     ex_memo1, ex_memo2, ex_memo3) VALUES ' + insertLedgerArr.join(',') + ';';
                 await transaction.query(bSql, []);
@@ -971,7 +971,7 @@ module.exports = app => {
                 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_stage_order, p.add_times,
-                        p.add_user, p.sgfh_qty, p.sjcl_qty, p.qtcl_qty, p.crid, p.porder, p.position,
+                        p.add_user, p.sgfh_qty, p.sjcl_qty, p.qtcl_qty, p.crid, p.ccid, p.porder, p.position,
                         p.sgfh_expr, p.sjcl_expr, p.qtcl_expr, p.real_qty,
                         p.ex_memo1, p.ex_memo2, p.ex_memo3,
                     ];
@@ -984,7 +984,7 @@ module.exports = app => {
                     'Insert Into ' +
                     this.ctx.service.pos.tableName +
                     '  (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_qty, sjcl_qty, qtcl_qty, crid, ccid, porder, position, ' +
                     '     sgfh_expr, sjcl_expr, qtcl_expr, real_qty,' +
                     '     ex_memo1, ex_memo2, ex_memo3)  VALUES ' + insertPosArr.join(',') + ';';
                 await transaction.query(pSql, []);

+ 4 - 4
app/service/ledger_revise.js

@@ -114,12 +114,12 @@ module.exports = app => {
             const sql = 'Insert Into ' + this.ctx.service.reviseBills.tableName +
                 '  (id, code, b_code, name, unit, source, remark, ledger_id, ledger_pid, level, `order`, full_path, is_leaf,' +
                 '     quantity, total_price, unit_price, drawing_code, memo, dgn_qty1, dgn_qty2, deal_qty, deal_tp,' +
-                '     sgfh_qty, sgfh_tp, sjcl_qty, sjcl_tp, qtcl_qty, qtcl_tp, node_type, crid, tender_id,' +
+                '     sgfh_qty, sgfh_tp, sjcl_qty, sjcl_tp, qtcl_qty, qtcl_tp, node_type, crid, ccid, tender_id,' +
                 '     sgfh_expr, sjcl_expr, qtcl_expr, check_calc,' +
                 '     ex_memo1, ex_memo2, ex_memo3)' +
                 '  Select id, code, b_code, name, unit, source, remark, ledger_id, ledger_pid, level, `order`, full_path, is_leaf,' +
                 '      quantity, total_price, unit_price, drawing_code, memo, dgn_qty1, dgn_qty2, deal_qty, deal_tp,' +
-                '      sgfh_qty, sgfh_tp, sjcl_qty, sjcl_tp, qtcl_qty, qtcl_tp, node_type, crid, tender_id,' +
+                '      sgfh_qty, sgfh_tp, sjcl_qty, sjcl_tp, qtcl_qty, qtcl_tp, node_type, crid, ccid, tender_id,' +
                 '      sgfh_expr, sjcl_expr, qtcl_expr, 0,' +
                 '      ex_memo1, ex_memo2, ex_memo3' +
                 '  From ' + this.ctx.service.ledger.tableName +
@@ -131,11 +131,11 @@ module.exports = app => {
         async _initRevisePos(transaction, tid) {
             const sql = 'Insert Into ' + this.ctx.service.revisePos.tableName +
                 '  (id, tid, lid, name, drawing_code, quantity, add_stage, add_stage_order, add_times, add_user,' +
-                '     sgfh_qty, sjcl_qty, qtcl_qty, crid, in_time, porder, position,' +
+                '     sgfh_qty, sjcl_qty, qtcl_qty, crid, ccid, in_time, porder, position,' +
                 '     sgfh_expr, sjcl_expr, qtcl_expr, real_qty,' +
                 '     ex_memo1, ex_memo2, ex_memo3)' +
                 '  Select id, tid, lid, name, drawing_code, quantity, add_stage, add_stage_order, add_times, add_user,' +
-                '     sgfh_qty, sjcl_qty, qtcl_qty, crid, in_time, porder, position,' +
+                '     sgfh_qty, sjcl_qty, qtcl_qty, crid, ccid, in_time, porder, position,' +
                 '     sgfh_expr, sjcl_expr, qtcl_expr, real_qty,' +
                 '     ex_memo1, ex_memo2, ex_memo3' +
                 '  From ' + this.ctx.service.pos.tableName +

+ 1 - 1
app/service/material_bills.js

@@ -413,7 +413,7 @@ module.exports = app => {
                     await this.ctx.service.materialStage.updateMtp(transaction, ms_id);
                     result.stageBillsData = await transaction.select(this.ctx.service.materialStageBills.tableName, { where: { mid: this.ctx.material.id } });
                     result.stageData = await transaction.select(this.ctx.service.materialStage.tableName, { where: { mid: this.ctx.material.id } });
-                    result.billsData = await transaction.select(this.tableName, { where: { mid: this.ctx.material.id } });
+                    result.billsData = await transaction.select(this.tableName, { where: { mid: this.ctx.material.id }, orders: [['order', 'asc']] });
                 }
                 result.m_tp = await this.calcMaterialMTp(transaction);
                 await transaction.commit();

+ 1 - 1
app/service/material_stage.js

@@ -31,7 +31,7 @@ module.exports = app => {
                 m_tp: tp.total_price,
                 m_tax_tp: tp.tax_total_price,
             };
-            return await transaction.update(this.ctx.service.materialStage.tableName, updateData2);
+            return await transaction.update(this.tableName, updateData2);
         }
     }
     return MaterialStage;

+ 12 - 32
app/service/pay.js

@@ -60,7 +60,7 @@ module.exports = app => {
         }
 
         async _getMaxOrder(tenderId) {
-            const sql = 'SELECT Max(??) As value FROM ?? Where tid = ' + tenderId;
+            const sql = 'SELECT Max(??) As value FROM ?? Where valid = 1 and tid = ' + tenderId;
             const sqlParam = ['order', this.tableName];
             const queryResult = await this.db.queryOne(sql, sqlParam);
             return queryResult.value;
@@ -140,37 +140,6 @@ module.exports = app => {
             }
         }
 
-        /**
-         * 交换两个合同支付项的顺序
-         * @param {Number} id1 - 合同支付项1的id
-         * @param {Number} id2 - 合同支付项1的id
-         * @returns {Promise<void>}
-         */
-        async changeOrder(id1, id2) {
-            if (!this.ctx.tender || !this.ctx.stage) {
-                throw '数据错误';
-            }
-            const pay1 = await this.getDataByCondition({tid: this.ctx.tender.id, id: id1});
-            const pay2 = await this.getDataByCondition({tid: this.ctx.tender.id, id: id2});
-            if (!pay1 || !pay2) {
-                throw '数据错误';
-            }
-
-            const transaction = await this.db.beginTransaction();
-            try {
-                const order = pay1.order;
-                pay1.order = pay2.order;
-                pay2.order = order;
-                await transaction.update(this.tableName, {id: pay1.id, order: pay1.order});
-                await transaction.update(this.tableName, {id: pay2.id, order: pay2.order});
-                await transaction.commit();
-                return true;
-            } catch (err) {
-                await transaction.rollback();
-                throw err;
-            }
-        }
-
         async _save(data, transaction) {
             const pay = await this.getDataByCondition({tid: this.ctx.tender.id, id: data.id});
             if(!pay) {
@@ -228,6 +197,17 @@ module.exports = app => {
                 return updateData;
             }
         }
+
+        async doDeleteStage(stage, transaction) {
+            await transaction.delete(this.tableName, { csid: stage.id });
+            if (stage.order > 1) {
+                const preStage = await this.ctx.service.stage.getDataByCondition({ tid: stage.tid, order: stage.order - 1});
+                const max = await this.db.queryOne('SELECT MAX(stimes) as stimes, MAX(sorder) as sorder FROM ?? WHERE sid = ?', [this.ctx.service.stagePay.tableName, preStage.id]);
+                const resortSql = `UPDATE ${this.tableName} p LEFT JOIN ${this.ctx.service.stagePay.tableName} sp ON p.id = sp.pid`+
+                    '  SET p.`order` = sp.porder WHERE p.tid = ? and p.valid = 1 and sp.sid = ? and sp.stimes = ? and sp.sorder = ?';
+                await transaction.query(resortSql, [stage.tid, preStage.id, max.stimes, max.sorder]);
+            }
+        }
     }
 
     return Pay;

+ 11 - 0
app/service/report.js

@@ -14,6 +14,7 @@ const MaterialSource = require('../lib/rm/material');
 const rptCustomData = require('../lib/rptCustomData');
 const bindData = {
     materialGather: ['mem_material_gather_bills', 'mem_material_gather_xmj', 'mem_material_gather_gl'],
+    gatherChange: ['mem_gather_change', 'mem_gather_change_bills'],
 };
 
 
@@ -121,6 +122,10 @@ module.exports = app => {
                             runnableRst.push(service.reportMemory.getStageAuditors(params.tender_id, params.stage_id));
                             runnableKey.push(filter);
                             break;
+                        case 'mem_stage_audit_ass':
+                            runnableRst.push(service.reportMemory.getStageAuditAss(params.tender_id, params.stage_id));
+                            runnableKey.push(filter);
+                            break;
                         case 'mem_stage_bills':
                             runnableRst.push(service.reportMemory.getStageBillsData(params.tender_id, params.stage_id, memFieldKeys[filter]));
                             runnableKey.push(filter);
@@ -395,6 +400,12 @@ module.exports = app => {
                             rst[d] = mgResult[d];
                         }
                         break;
+                    case 'gatherChange':
+                        const gcResult = await service.rptGatherMemory.getGatherChange(memFieldKeys['mem_gather_change'],
+                            customDefine.gather_select, customSelect ? customSelect.gather_select : null);
+                        for (const d in gcResult) {
+                            rst[d] = gcResult[d];
+                        }
                     default:
                         break;
                 }

+ 6 - 0
app/service/report_memory.js

@@ -1418,6 +1418,12 @@ module.exports = app => {
             return result;
         }
 
+        async getStageAuditAss(tid, sid) {
+            await this.ctx.service.tender.checkTender(tid);
+            await this.ctx.service.stage.checkStage(sid);
+            return await this.ctx.service.stageAuditAss.getReportData(this.ctx.stage.id, this.ctx.stage.times);
+        }
+
         async getChangeInfo(tid, sid) {
             await this.ctx.service.tender.checkTender(tid);
             await this.ctx.service.stage.checkStage(sid);

+ 4 - 4
app/service/revise_audit.js

@@ -305,12 +305,12 @@ module.exports = app => {
                 this.ctx.service.ledger.tableName +
                 '  (id, code, b_code, name, unit, source, remark, ledger_id, ledger_pid, level, `order`, full_path, is_leaf,' +
                 '     quantity, total_price, unit_price, drawing_code, memo, dgn_qty1, dgn_qty2, deal_qty, deal_tp,' +
-                '     sgfh_qty, sgfh_tp, sjcl_qty, sjcl_tp, qtcl_qty, qtcl_tp, node_type, crid, tender_id,' +
+                '     sgfh_qty, sgfh_tp, sjcl_qty, sjcl_tp, qtcl_qty, qtcl_tp, node_type, crid, ccid, tender_id,' +
                 '     sgfh_expr, sjcl_expr, qtcl_expr, check_calc,' +
                 '     ex_memo1, ex_memo2, ex_memo3)' +
                 '  Select id, code, b_code, name, unit, source, remark, ledger_id, ledger_pid, level, `order`, full_path, is_leaf,' +
                 '      quantity, total_price, unit_price, drawing_code, memo, dgn_qty1, dgn_qty2, deal_qty, deal_tp,' +
-                '      sgfh_qty, sgfh_tp, sjcl_qty, sjcl_tp, qtcl_qty, qtcl_tp, node_type, crid, tender_id, ' +
+                '      sgfh_qty, sgfh_tp, sjcl_qty, sjcl_tp, qtcl_qty, qtcl_tp, node_type, crid, ccid, tender_id, ' +
                 '      sgfh_expr, sjcl_expr, qtcl_expr, check_calc,' +
                 '      ex_memo1, ex_memo2, ex_memo3' +
                 '  From ' +
@@ -322,11 +322,11 @@ module.exports = app => {
                 'Insert Into ' +
                 this.ctx.service.pos.tableName +
                 '  (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_qty, sjcl_qty, qtcl_qty, crid, ccid, porder, position, ' +
                 '     sgfh_expr, sjcl_expr, qtcl_expr, real_qty,' +
                 '     ex_memo1, ex_memo2, ex_memo3)' +
                 '  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_qty, sjcl_qty, qtcl_qty, crid, ccid, porder, position,' +
                 '     sgfh_expr, sjcl_expr, qtcl_expr, real_qty,' +
                 '     ex_memo1, ex_memo2, ex_memo3' +
                 '  From ' +

+ 109 - 0
app/service/rpt_gather_memory.js

@@ -13,6 +13,7 @@ const PayCalculator = require('../lib/pay_calc');
 
 const auditConst = require('../const/audit');
 const payConst = require('../const/deal_pay');
+const changeConst = require('../const/change');
 
 const moment = require('moment');
 
@@ -339,6 +340,15 @@ module.exports = app => {
             }
             return [validStages, preStage, endStage];
         }
+        async _getMonthStages(tender, month) {
+            const stages = await this._getValidStages(tender.id, 'asc'), validStages = [];
+            for (const stage of stages) {
+                if (stage.s_time === month) validStages.push(stage);
+            }
+            const preStage = stages.length > 0 ? stages.find(x => { return x.order === stages[0].order - 1; }) : null;
+            const endStage = stages.length > 0 ? stages.find(x => { return x.order === stages[stages.length - 1].order + 1; }) : null;
+            return [validStages, preStage, endStage];
+        }
 
         /**
          * 台账数据
@@ -478,6 +488,10 @@ module.exports = app => {
             const [stages, preStage, endStage] = await this._getCheckedZoneStages(tender, zone);
             await this._gatherStagesData(completeData, tender, stages, preStage);
         }
+        async _gatherMonthsData(tender, completeData, month) {
+            const [stages, preStage, endStage] = await this._getMonthStages(tender, month);
+            await this._gatherStagesData(completeData, tender, stages, preStage);
+        }
 
         async _gatherStageData(completeData, tender, stage, hasPre) {
             const helper = this.ctx.helper;
@@ -664,6 +678,9 @@ module.exports = app => {
                         case 'month':
                             await this._gatherMonthData(tender, completeData, gsCustom.month, gsSetting.hasPre);
                             break;
+                        case 'months':
+                            await this._gatherMonthsData(tender, completeData, gsCustom.months);
+                            break;
                         case 'zone':
                             await this._gatherZoneData(tender, completeData, gsCustom.zone);
                             break;
@@ -1153,6 +1170,98 @@ module.exports = app => {
             gatherUtils.completeGatherData(this.resultDealBills, completeDatas);
             return this.resultDealBills;
         }
+
+        _getChangeConstName(define, value) {
+            for (const prop in define) {
+                if (define[prop].value === value) {
+                    return define[prop].name;
+                }
+            }
+            return '';
+        }
+
+        async _gatherChange(tender) {
+            const self = this;
+            try {
+                const info = await this.ctx.service.tenderInfo.getTenderInfo(tender.id);
+                const decimal = info.decimal, ctx = this.ctx;
+                const where = { tid: tender.id, valid: 1 };
+                if (this.ctx.session.sessionProject.page_show.isOnlyChecked) where.status = auditConst.flow.status.checked;
+                const change = await this.ctx.service.change.getAllDataByCondition({ where, orders: [['in_time', 'desc']] });
+                for (const c of change) {
+                    c.tender_name = tender.name;
+                    const types = ctx.helper._.map(c.type.split(','), function (t) {
+                        return self._getChangeConstName(changeConst.type, ctx.helper._.toInteger(t));
+                    });
+                    c.type = types.join(';');
+                    c.class = this._getChangeConstName(changeConst.class, c.class);
+                    c.quality = this._getChangeConstName(changeConst.quality, c.quality);
+                    c.charge = this._getChangeConstName(changeConst.charge, c.charge);
+                    c.attachments = await ctx.service.changeAtt.getChangeAttachment(c.cid);
+                    const names = ctx.helper._.map(c.attachments, function (x) {
+                        return x.filename + x.fileext;
+                    });
+                    c.attNames = names.join('\n');
+                }
+                const changeBills = await this.ctx.service.changeAuditList.getChangeAuditBills(tender.id, this.ctx.session.sessionProject.page_show.isOnlyChecked);
+                for (const d of changeBills) {
+                    d.tender_name = tender.name;
+                    d.o_qty = d.oamount;
+                    d.o_tp = this.ctx.helper.mul(d.o_qty, d.unit_price, decimal.tp);
+                    d.c_qty = d.camount;
+                    d.c_tp = this.ctx.helper.mul(d.c_qty, d.unit_price, decimal.tp);
+                    d.s_qty = d.samount ? parseFloat(d.samount) : 0;
+                    d.s_tp = this.ctx.helper.mul(d.s_qty, d.unit_price, decimal.tp);
+                    d.sp_qty = d.spamount;
+                    d.sp_tp = this.ctx.helper.mul(d.sp_qty, d.unit_price, decimal.tp);
+
+                    const auditAmount = d.audit_amount ? d.audit_amount.split(',') : [];
+                    const relaChange = ctx.helper._.find(change, {cid: d.cid});
+                    for (const [i, aa] of auditAmount.entries()) {
+                        const amountField = 'qty_' + (i+1), tpField = 'tp_' + (i+1);
+                        d[amountField] = aa ? parseFloat(aa) : 0;
+                        d[tpField] = ctx.helper.mul(d[amountField], d.unit_price, decimal.tp);
+                        if (relaChange) {
+                            relaChange[tpField] = ctx.helper.add(relaChange[tpField], d[tpField]);
+                        }
+                    }
+                }
+
+                change.sort(function (a, b) {
+                    return a.code.localeCompare(b.code);
+                });
+                changeBills.sort(function (a, b) {
+                    const aCIndex = change.findIndex(function (c) {
+                        return c.cid === a.cid;
+                    });
+                    const bCIndex = change.findIndex(function (c) {
+                        return c.cid === b.cid;
+                    });
+                    return aCIndex === bCIndex
+                        ? ctx.helper.compareCode(a.code, b.code)
+                        : aCIndex - bCIndex;
+                });
+                return [change, changeBills];
+            } catch(err) {
+                this.ctx.helper.log(err);
+                throw err;
+                return [[], []];
+            }
+        }
+
+        async getGatherChange(memFieldKeys, gsDefine, gsCustom) {
+            if (!gsDefine || !gsDefine.enable) return [];
+            if (!gsCustom || !gsCustom.tenders || gsCustom.tenders.length === 0) return [];
+
+            const result = { mem_gather_change: [], mem_gather_change_bills: [] };
+            for (const t of gsCustom.tenders) {
+                const tender = await this.ctx.service.tender.getCheckTender(t.tid);
+                const [change, changeBills] = await this._gatherChange(tender);
+                result.mem_gather_change.push(...change);
+                result.mem_gather_change_bills.push(...changeBills);
+            }
+            return result
+        }
     }
 
     return RptGatherMemory;

+ 61 - 0
app/service/shenpi_audit.js

@@ -197,6 +197,67 @@ module.exports = app => {
                 throw err;
             }
         }
+
+        // 更新审批流程
+        async updateAuditList(transaction, tenderId, sp_status, sp_type, ids) {
+            if (sp_status === shenpiConst.sp_status.gdspl) {
+                const auditList = await this.getAuditList(tenderId, sp_type, sp_status);
+                const oldIds = this._.map(auditList, 'audit_id');
+                if (this._.isEqual(ids, oldIds)) {
+                    return;
+                }
+                // 更新固定审批流
+                await transaction.delete(this.tableName, { tid: tenderId, sp_type, sp_status });
+                const insertDatas = [];
+                for (const id of ids) {
+                    insertDatas.push({
+                        tid: tenderId,
+                        sp_type,
+                        sp_status,
+                        audit_id: id,
+                    });
+                }
+                await transaction.insert(this.tableName, insertDatas);
+                if (sp_type === shenpiConst.sp_type.stage) {
+                    // 判断哪些audit_id不存在了,哪些audit_为新增
+                    const exist = this._.difference(ids, oldIds);// 判断新增的
+                    const unExist = this._.difference(oldIds, ids);// 判断已删除的
+                    console.log(ids, oldIds, exist, unExist);
+                    if (exist.length > 0) {
+                        const options = {
+                            where: {
+                                tid: this.ctx.tender.id,
+                                user_id: exist,
+                            },
+                        };
+                        const updateData = {
+                            status: 1,
+                        };
+                        await transaction.update(this.ctx.service.ledgerCooperation.tableName, updateData, options);
+                    }
+                    if (unExist.length > 0) {
+                        const options2 = {
+                            where: {
+                                tid: this.ctx.tender.id,
+                                user_id: unExist,
+                            },
+                        };
+                        const updateData2 = {
+                            status: 0,
+                        };
+                        await transaction.update(this.ctx.service.ledgerCooperation.tableName, updateData2, options2);
+                    }
+                }
+            } else if (sp_status === shenpiConst.sp_status.gdzs) {
+                const audit = await this.getAudit(tenderId, sp_type, sp_status);
+                if (audit && audit.audit_id !== ids[ids.length - 1]) {
+                    // 更换终审
+                    await transaction.update(this.tableName, { audit_id: ids[ids.length - 1] }, { where: { tid: tenderId, sp_type, sp_status } });
+                } else if (!audit) {
+                    await transaction.insert(this.tableName, { tid: tenderId, sp_type, sp_status, audit_id: ids[ids.length - 1] });
+                }
+            }
+        }
     }
 
     return ShenpiAudit;

+ 41 - 16
app/service/stage.js

@@ -69,22 +69,48 @@ module.exports = app => {
             return result;
         }
 
-        async doCheckStage(stage, force = false) {
+        async loadStageUser(stage) {
             const status = auditConst.status;
+            const accountId = this.ctx.session.sessionUser.accountId;
+
             stage.auditors = await this.ctx.service.stageAudit.getAuditors(stage.id, stage.times);
             stage.curAuditor = await this.ctx.service.stageAudit.getCurAuditor(stage.id, stage.times);
 
-            const accountId = this.ctx.session.sessionUser.accountId,
-                auditorIds = this._.map(stage.auditors, 'aid'),
-                shareIds = [];
+            stage.assists = await this.service.stageAuditAss.getData(stage);
+            stage.userAssists = stage.assists.filter(x => { return x.user_id === stage.user_id; }); // 原报协同人
+            stage.auditAssists = stage.assists.filter(x => { return x.user_id !== stage.user_id; }); // 审批协同人
+            stage.relaAssists = stage.assists.filter(x => { return x.user_id === accountId });
+            stage.auditorIds = this._.map(stage.auditors, 'aid');
+            stage.userAssistIds = this._.map(stage.userAssists, 'ass_user_id');
+            stage.auditAssistIds = this._.map(stage.auditAssists, 'ass_user_id');
+            stage.users = stage.status === status.uncheck
+                ? [stage.user_id, ...stage.userAssistIds]
+                : [stage.user_id, ...stage.userAssistIds, ...stage.auditorIds, ...stage.auditAssistIds];
+        }
+
+        async doCheckStage(stage, force = false) {
+            const status = auditConst.status;
+            await this.loadStageUser(stage);
+
+            const accountId = this.ctx.session.sessionUser.accountId, shareIds = [];
+            if (stage.status === status.uncheck || stage.status === status.checkNo) {
+                stage.readOnly = accountId !== stage.user_id && stage.userAssistIds.indexOf(accountId) < 0;
+                if (!stage.readOnly) stage.assist = stage.userAssists.find(x => { return x.ass_user_id === accountId; });
+            } else if (stage.status === status.checked) {
+                stage.readOnly = true;
+            } else {
+                const ass = stage.auditAssists.find(x => { return x.user_id === stage.curAuditor.aid && x.ass_user_id === accountId; });
+                stage.readOnly = stage.curAuditor.aid !== accountId && !ass;
+                if (!stage.readOnly) stage.assist = ass;
+            }
+            if (stage.readOnly) {
+                stage.assist = accountId === stage.user_id || stage.auditorIds.indexOf(accountId) >= 0
+                    ? null
+                    : stage.assists.find(x => { return x.ass_user_id === accountId});
+            }
             const isTenderTourist = await this.service.tenderTourist.getDataByCondition({ tid: stage.tid, user_id: accountId });
             const permission = this.ctx.session.sessionUser.permission;
-            if (accountId === stage.user_id) { // 原报
-                if (stage.curAuditor) {
-                    stage.readOnly = stage.curAuditor.aid !== accountId;
-                } else {
-                    stage.readOnly = stage.status !== status.uncheck && stage.status !== status.checkNo;
-                }
+            if (accountId === stage.user_id || stage.userAssistIds.indexOf(accountId) >= 0) { // 原报
                 stage.curTimes = stage.times;
                 if (stage.status === status.uncheck || stage.status === status.checkNo) {
                     stage.curOrder = 0;
@@ -93,6 +119,7 @@ module.exports = app => {
                 } else {
                     stage.curOrder = stage.curAuditor.aid === accountId ? stage.curAuditor.order : stage.curAuditor.order - 1;
                 }
+                stage.filePermission = true;
             } else if (!!isTenderTourist || force) { // 游客
                 stage.readOnly = true;
                 stage.curTimes = stage.times;
@@ -103,10 +130,8 @@ module.exports = app => {
                 } else {
                     stage.curOrder = stage.curAuditor.order;
                 }
-            } else if (auditorIds.indexOf(accountId) !== -1) { // 审批人
-                if (stage.status === status.uncheck) {
-                    throw '您无权查看该数据';
-                }
+            } else if (stage.auditorIds.indexOf(accountId) >= 0 || stage.auditAssistIds.indexOf(accountId) >= 0) { // 审批人
+                if (stage.status === status.uncheck) throw '您无权查看该数据';
                 stage.curTimes = stage.status === status.checkNo ? stage.times - 1 : stage.times;
                 if (stage.status === status.checked) {
                     stage.curOrder = this._.max(this._.map(stage.auditors, 'order'));
@@ -118,7 +143,7 @@ module.exports = app => {
                 } else {
                     stage.curOrder = accountId === stage.curAuditor.aid ? stage.curAuditor.order : stage.curAuditor.order - 1;
                 }
-                stage.readOnly = (stage.status !== status.checking && stage.status !== status.checkNoPre) || accountId !== stage.curAuditor.aid;
+                stage.filePermission = true;
             } else if (shareIds.indexOf(accountId) !== -1 || (permission !== null && permission.tender !== undefined && permission.tender.indexOf('2') !== -1)) { // 分享人
                 if (stage.status === status.uncheck) {
                     throw '您无权查看该数据';
@@ -601,7 +626,7 @@ module.exports = app => {
                     }
                 }
                 await transaction.delete(this.ctx.service.stagePay.tableName, { sid: id });
-                await transaction.delete(this.ctx.service.pay.tableName, { csid: id });
+                await this.ctx.service.pay.doDeleteStage(stageInfo, transaction);
                 // 删除计量附件文件
                 const attList = await this.ctx.service.stageAtt.getAllDataByCondition({ where: { tid: stageInfo.tid, sid: stageInfo.order } });
                 if (attList.length !== 0) {

+ 402 - 12
app/service/stage_audit.js

@@ -85,11 +85,11 @@ module.exports = app => {
          */
         async getAuditors(stageId, times = 1, order_sort = 'asc') {
             const sql =
-                'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, pa.sign_path, la.`times`, la.`order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time`, g.`sort` ' +
-                'FROM ?? AS la, ?? AS pa, (SELECT sa.`aid`,(@i:=@i+1) as `sort` FROM (SELECT * FROM ?? ORDER BY `order` ASC) sa, (select @i:=0) as it WHERE sa.`sid` = ? AND sa.`times` = ? GROUP BY sa.`aid`) as g ' +
+                'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, pa.`mobile`, pa.`telephone`, pa.sign_path, la.`times`, la.`order`, la.`status`, la.`opinion`, la.`begin_time`, la.`end_time`, la.`is_old`, g.`sort` ' +
+                'FROM ?? AS la, ?? AS pa, (SELECT sa.`aid`,(@i:=@i+1) as `sort` FROM (SELECT * FROM ?? WHERE `sid` = ? AND `times` = ? GROUP BY `aid` ORDER BY `order` ASC) sa, (select @i:=0) as it WHERE sa.`sid` = ? AND sa.`times` = ? GROUP BY sa.`aid`) as g ' +
                 'WHERE la.`sid` = ? and la.`times` = ? and la.`aid` = pa.`id` and g.`aid` = la.`aid` order by la.`order` ' +
                 order_sort;
-            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, this.tableName, stageId, times, stageId, times];
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, this.tableName, stageId, times, stageId, times, stageId, times];
             const result = await this.db.query(sql, sqlParam);
             const sql2 = 'SELECT COUNT(a.`aid`) as num FROM (SELECT `aid` FROM ?? WHERE `sid` = ? AND `times` = ? GROUP BY `aid`) as a';
             const sqlParam2 = [this.tableName, stageId, times];
@@ -489,6 +489,7 @@ module.exports = app => {
                         cache_time_r: this.ctx.stage.cache_time_l,
                         his_id,
                     });
+                    await this.ctx.service.stagePay.cacheOrder(this.ctx.stage, transaction);
 
                     // 添加短信通知-审批通过提醒功能
                     const stageInfo = await this.ctx.service.stage.getDataById(stageId);
@@ -775,7 +776,7 @@ module.exports = app => {
                 const stageInfo = await this.ctx.service.stage.getDataById(audit.sid);
                 const shenpiUrl = await this.ctx.helper.urlToShort(this.ctx.protocol + '://' + this.ctx.host + '/wap/tender/' + this.ctx.tender.id + '/stage/' + stageInfo.order);
                 const users = this._.map(this.ctx.stage.auditAssists.filter(x => {return x.user_id === preAuditor.aid}), 'ass_user_id');
-                user.push(preAuditor.aid);
+                users.push(preAuditor.aid);
                 await this.ctx.helper.sendAliSms(users, smsTypeConst.const.JL, smsTypeConst.judge.approval.toString(), SmsAliConst.template.stage_check, {
                     qi: stageInfo.order,
                     code: shenpiUrl,
@@ -939,7 +940,7 @@ module.exports = app => {
                 // }
                 const stageInfo = await this.ctx.service.stage.getDataById(audit.sid);
                 const shenpiUrl = await this.ctx.helper.urlToShort(this.ctx.protocol + '://' + this.ctx.host + '/wap/tender/' + this.ctx.tender.id + '/stage/' + stageInfo.order);
-                const users = this._.map(this.ctx.stage.auditAssist.filter(x => { return x.user_id == audit.aid; }), 'ass_user_id');
+                const users = this._.map(this.ctx.stage.auditAssists.filter(x => { return x.user_id == audit.aid; }), 'ass_user_id');
                 users.push(audit.aid);
                 await this.ctx.helper.sendAliSms(users, smsTypeConst.const.JL, smsTypeConst.judge.approval.toString(), SmsAliConst.template.stage_check, {
                     qi: stageInfo.order,
@@ -977,6 +978,319 @@ module.exports = app => {
         }
 
         /**
+         * 审批撤回
+         * @param {Number} stageId - 标段id
+         * @param {Number} times - 第几次审批
+         * @return {Promise<void>}
+         */
+        async checkCancel(stageId, times = 1) {
+            // 分3种情况,根据ctx.cancancel值判断:
+            // 1.原报发起撤回,当前流程删除,并回到待上报
+            // 2.审批人撤回审批通过,增加流程,并回到它审批中
+            // 3.审批人撤回审批退回上一人,并删除退回人,增加流程,并回到它审批中,并更新计量期状态为审批中
+            // 4.审批人撤回退回原报操作,删除新增的审批流,增加流程,回滚到它审批中
+            const transaction = await this.db.beginTransaction();
+            const time = new Date();
+            try {
+                if (this.ctx.stage.cancancel === 1) {
+                    // 原报撤回,判断是否为多次,多次则为退回状态
+                    // 整理当前流程审核人状态更新
+                    const curAudit = await this.getDataByCondition({ sid: stageId, times, status: auditConst.status.checking });
+                    // // 审批人变成待审批状态
+                    await transaction.update(this.tableName, {
+                        id: curAudit.id,
+                        status: auditConst.status.uncheck,
+                        begin_time: null,
+                        opinion: null,
+                    });
+                    const tpData = await this.ctx.service.stageBills.getSumTotalPrice(this.ctx.stage);
+                    // 计算并合同支付最终数据
+                    const [yfPay, sfPay] = await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
+                    this.ctx.stage.tp_history.push({
+                        times,
+                        order: curAudit.order,
+                        contract_tp: tpData.contract_tp,
+                        qc_tp: tpData.qc_tp,
+                        positive_qc_tp: tpData.positive_qc_tp,
+                        negative_qc_tp: tpData.negative_qc_tp,
+                        yf_tp: yfPay.tp,
+                        sf_tp: sfPay.tp,
+                    });
+                    await transaction.update(this.ctx.service.stage.tableName, {
+                        id: stageId,
+                        contract_tp: tpData.contract_tp,
+                        qc_tp: tpData.qc_tp,
+                        positive_qc_tp: tpData.positive_qc_tp,
+                        negative_qc_tp: tpData.negative_qc_tp,
+                        times,
+                        yf_tp: yfPay.tp,
+                        sf_tp: sfPay.tp,
+                        tp_history: JSON.stringify(this.ctx.stage.tp_history),
+                        cache_time_r: this.ctx.stage.cache_time_l,
+                        status: times === 1 ? auditConst.status.uncheck : auditConst.status.checkNo,
+                    });
+                    // 计算该审批人最终数据
+                    await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
+                    // 复制一份最新数据给下一人
+                    await this.ctx.service.stagePay.deleteAuditStagePays(this.ctx.stage, this.ctx.stage.times, 1, transaction);
+                    await this.ctx.service.stageJgcl.updateHistory(this.ctx.stage, transaction);
+                    await this.ctx.service.stageBonus.updateHistory(this.ctx.stage, transaction);
+                    await this.ctx.service.stageOther.updateHistory(this.ctx.stage, transaction);
+                    await this.ctx.service.stageSafeProd.updateHistory(this.ctx.stage, transaction);
+                    await this.ctx.service.stageTempLand.updateHistory(this.ctx.stage, transaction);
+                } else if (this.ctx.stage.cancancel === 2) {
+                    const tpData = await this.ctx.service.stageBills.getSumTotalPrice(this.ctx.stage);
+                    // 整理当前流程审核人状态更新
+                    const curAudit = await this.getDataByCondition({ sid: stageId, times, status: auditConst.status.checking });
+                    const preAudit = this.ctx.stage.preAudit;
+                    if (!curAudit || curAudit.order <= 1) {
+                        throw '撤回用户数据错误';
+                    }
+                    // 顺移气候审核人流程顺序
+                    this.initSqlBuilder();
+                    this.sqlBuilder.setAndWhere('sid', { value: this.ctx.stage.id, operate: '=' });
+                    this.sqlBuilder.setAndWhere('order', { value: curAudit.order, operate: '>' });
+                    this.sqlBuilder.setUpdateData('order', { value: 2, selfOperate: '+' });
+                    const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'update');
+                    const data = await transaction.query(sql, sqlParam);
+                    // 当前审批人2次添加至流程中
+                    const newAuditors = [];
+                    newAuditors.push({
+                        tid: curAudit.tid,
+                        sid: curAudit.sid,
+                        aid: preAudit.aid,
+                        times: curAudit.times,
+                        order: curAudit.order,
+                        status: auditConst.status.checkCancel,
+                        begin_time: time,
+                        end_time: time,
+                        opinion: '',
+                    });
+                    newAuditors.push({
+                        tid: curAudit.tid,
+                        sid: curAudit.sid,
+                        aid: preAudit.aid,
+                        times: curAudit.times,
+                        order: curAudit.order + 1,
+                        status: auditConst.status.checking,
+                        begin_time: time,
+                    });
+                    await transaction.insert(this.tableName, newAuditors);
+                    // 当前审批人变成待审批
+                    await transaction.update(this.tableName, { id: curAudit.id, order: curAudit.order + 2, begin_time: null, status: auditConst.status.uncheck });
+                    // 计算并合同支付最终数据
+                    const [yfPay, sfPay] = await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
+                    this.ctx.stage.tp_history.push({
+                        times,
+                        order: curAudit.order,
+                        contract_tp: tpData.contract_tp,
+                        qc_tp: tpData.qc_tp,
+                        positive_qc_tp: tpData.positive_qc_tp,
+                        negative_qc_tp: tpData.negative_qc_tp,
+                        yf_tp: yfPay.tp,
+                        sf_tp: sfPay.tp,
+                    });
+                    // 同步 期信息
+                    await transaction.update(this.ctx.service.stage.tableName, {
+                        id: stageId,
+                        contract_tp: tpData.contract_tp,
+                        qc_tp: tpData.qc_tp,
+                        positive_qc_tp: tpData.positive_qc_tp,
+                        negative_qc_tp: tpData.negative_qc_tp,
+                        times,
+                        yf_tp: yfPay.tp,
+                        sf_tp: sfPay.tp,
+                        tp_history: JSON.stringify(this.ctx.stage.tp_history),
+                        cache_time_r: this.ctx.stage.cache_time_l,
+                    });
+
+                    // 计算该审批人最终数据
+                    await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
+                    // 复制一份最新数据给下一人
+                    await this.ctx.service.stagePay.copyAuditStagePays(this.ctx.stage, this.ctx.stage.times, curAudit.order + 1, transaction);
+                    await this.ctx.service.stageJgcl.updateHistory(this.ctx.stage, transaction);
+                    await this.ctx.service.stageBonus.updateHistory(this.ctx.stage, transaction);
+                    await this.ctx.service.stageOther.updateHistory(this.ctx.stage, transaction);
+                    await this.ctx.service.stageSafeProd.updateHistory(this.ctx.stage, transaction);
+                    await this.ctx.service.stageTempLand.updateHistory(this.ctx.stage, transaction);
+
+                    // 锁定本人数据,保留锁定数据相关确认状态
+                    // await this.ctx.service.stageAuditAss.lockConfirm4CheckNoPre(this.ctx.stage, curAudit.aid, preAudit.aid, transaction);
+                } else if (this.ctx.stage.cancancel === 3) {
+                    const tpData = await this.ctx.service.stageBills.getSumTotalPrice(this.ctx.stage);
+                    // 整理当前流程审核人状态更新
+                    const curAudit = await this.getDataByCondition({ sid: stageId, times, status: auditConst.status.checking });
+                    const preAudit = this.ctx.stage.preAudit;
+                    if (!curAudit || curAudit.order <= 1) {
+                        throw '撤回用户数据错误';
+                    }
+                    // // 顺移气候审核人流程顺序
+                    // this.initSqlBuilder();
+                    // this.sqlBuilder.setAndWhere('sid', { value: this.ctx.stage.id, operate: '=' });
+                    // this.sqlBuilder.setAndWhere('order', { value: curAudit.order, operate: '>' });
+                    // this.sqlBuilder.setUpdateData('order', { value: 2, selfOperate: '+' });
+                    // const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'update');
+                    // const data = await transaction.query(sql, sqlParam);
+                    // 添加撤回人到审批流程中
+                    const newAuditors = [];
+                    newAuditors.push({
+                        tid: curAudit.tid,
+                        sid: curAudit.sid,
+                        aid: preAudit.aid,
+                        times: curAudit.times,
+                        order: curAudit.order,
+                        status: auditConst.status.checkCancel,
+                        begin_time: time,
+                        end_time: time,
+                        opinion: '',
+                    });
+                    await transaction.insert(this.tableName, newAuditors);
+                    // 删除当前审批人
+                    await transaction.delete(this.tableName, { id: curAudit.id });
+                    // 更新上一个人为审批中
+                    await transaction.update(this.tableName, { begin_time: time, status: auditConst.status.checking }, {
+                        where: {
+                            sid: curAudit.sid,
+                            times: curAudit.times,
+                            order: curAudit.order + 1,
+                        }
+                    });
+                    // 修改stage状态为审批中
+                    await transaction.update(this.ctx.service.stage.tableName, { id: this.ctx.stage.id, status: auditConst.status.checking });
+                    // 计算并合同支付最终数据
+                    const [yfPay, sfPay] = await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
+                    this.ctx.stage.tp_history.push({
+                        times,
+                        order: curAudit.order,
+                        contract_tp: tpData.contract_tp,
+                        qc_tp: tpData.qc_tp,
+                        positive_qc_tp: tpData.positive_qc_tp,
+                        negative_qc_tp: tpData.negative_qc_tp,
+                        yf_tp: yfPay.tp,
+                        sf_tp: sfPay.tp,
+                    });
+                    // 同步 期信息
+                    await transaction.update(this.ctx.service.stage.tableName, {
+                        id: stageId,
+                        contract_tp: tpData.contract_tp,
+                        qc_tp: tpData.qc_tp,
+                        positive_qc_tp: tpData.positive_qc_tp,
+                        negative_qc_tp: tpData.negative_qc_tp,
+                        times,
+                        yf_tp: yfPay.tp,
+                        sf_tp: sfPay.tp,
+                        tp_history: JSON.stringify(this.ctx.stage.tp_history),
+                        cache_time_r: this.ctx.stage.cache_time_l,
+                    });
+
+                    // 计算该审批人最终数据
+                    await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
+                    // 复制一份最新数据给下一人
+                    await this.ctx.service.stagePay.copyAuditStagePays(this.ctx.stage, this.ctx.stage.times, curAudit.order + 1, transaction);
+                    await this.ctx.service.stageJgcl.updateHistory(this.ctx.stage, transaction);
+                    await this.ctx.service.stageBonus.updateHistory(this.ctx.stage, transaction);
+                    await this.ctx.service.stageOther.updateHistory(this.ctx.stage, transaction);
+                    await this.ctx.service.stageSafeProd.updateHistory(this.ctx.stage, transaction);
+                    await this.ctx.service.stageTempLand.updateHistory(this.ctx.stage, transaction);
+
+                    // 锁定本人数据,保留锁定数据相关确认状态
+                    // await this.ctx.service.stageAuditAss.lockConfirm4CheckNoPre(this.ctx.stage, curAudit.aid, preAudit.aid, transaction);
+                } else if (this.ctx.stage.cancancel === 4) {
+                    // 原报撤回,判断是否为多次,多次则为退回状态
+                    // 整理上一个流程审核人状态更新
+                    const curAudit = await this.getDataByCondition({ sid: stageId, times: times - 1, status: auditConst.status.checkNo });
+                    // 顺移气候审核人流程顺序
+                    this.initSqlBuilder();
+                    this.sqlBuilder.setAndWhere('sid', { value: this.ctx.stage.id, operate: '=' });
+                    this.sqlBuilder.setAndWhere('order', { value: curAudit.order, operate: '>' });
+                    this.sqlBuilder.setUpdateData('order', { value: 2, selfOperate: '+' });
+                    const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'update');
+                    const data = await transaction.query(sql, sqlParam);
+                    // 当前审批人2次添加至流程中
+                    const newAuditors = [];
+                    newAuditors.push({
+                        tid: curAudit.tid,
+                        sid: curAudit.sid,
+                        aid: curAudit.aid,
+                        times: curAudit.times,
+                        order: curAudit.order + 1,
+                        status: auditConst.status.checkCancel,
+                        begin_time: time,
+                        end_time: time,
+                        opinion: '',
+                    });
+                    newAuditors.push({
+                        tid: curAudit.tid,
+                        sid: curAudit.sid,
+                        aid: curAudit.aid,
+                        times: curAudit.times,
+                        order: curAudit.order + 2,
+                        status: auditConst.status.checking,
+                        begin_time: time,
+                    });
+                    await transaction.insert(this.tableName, newAuditors);
+                    // 删除当前次审批流
+                    await transaction.delete(this.tableName, { sid: stageId, times });
+                    const tpData = await this.ctx.service.stageBills.getSumTotalPrice(this.ctx.stage);
+                    // 计算并合同支付最终数据
+                    const [yfPay, sfPay] = await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
+                    this.ctx.stage.tp_history.push({
+                        times,
+                        order: curAudit.order,
+                        contract_tp: tpData.contract_tp,
+                        qc_tp: tpData.qc_tp,
+                        positive_qc_tp: tpData.positive_qc_tp,
+                        negative_qc_tp: tpData.negative_qc_tp,
+                        yf_tp: yfPay.tp,
+                        sf_tp: sfPay.tp,
+                    });
+                    await transaction.update(this.ctx.service.stage.tableName, {
+                        id: stageId,
+                        contract_tp: tpData.contract_tp,
+                        qc_tp: tpData.qc_tp,
+                        positive_qc_tp: tpData.positive_qc_tp,
+                        negative_qc_tp: tpData.negative_qc_tp,
+                        times: times - 1,
+                        yf_tp: yfPay.tp,
+                        sf_tp: sfPay.tp,
+                        tp_history: JSON.stringify(this.ctx.stage.tp_history),
+                        cache_time_r: this.ctx.stage.cache_time_l,
+                        status: auditConst.status.checking,
+                    });
+                    // 计算该审批人最终数据
+                    await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
+                    // 复制一份最新数据给下一人
+                    await this.ctx.service.stagePay.deleteAuditStagePays(this.ctx.stage, this.ctx.stage.times, 0, transaction);
+                    await this.ctx.service.stagePay.copyAuditStagePays(this.ctx.stage, this.ctx.stage.times - 1, curAudit.order + 1, transaction);
+                    await this.ctx.service.stagePay.copyAuditStagePays(this.ctx.stage, this.ctx.stage.times - 1, curAudit.order + 2, transaction);
+                    await this.ctx.service.stageJgcl.updateHistory(this.ctx.stage, transaction);
+                    await this.ctx.service.stageBonus.updateHistory(this.ctx.stage, transaction);
+                    await this.ctx.service.stageOther.updateHistory(this.ctx.stage, transaction);
+                    await this.ctx.service.stageSafeProd.updateHistory(this.ctx.stage, transaction);
+                    await this.ctx.service.stageTempLand.updateHistory(this.ctx.stage, transaction);
+                }
+                // 上报/审批 - 检查三方特殊推送
+                await this.ctx.service.specMsg.addStageMsg(transaction, this.ctx.session.sessionProject.id, this.ctx.stage, pushOperate.stage.flow);
+                await transaction.commit();
+                // 通知发送 - 第三方更新
+                if (this.ctx.session.sessionProject.custom && syncApiConst.notice_type.indexOf(this.ctx.session.sessionProject.customType) !== -1) {
+                    const base_data = {
+                        tid: this.ctx.tender.id,
+                        sid: stageId,
+                        op: 'update',
+                    };
+                    this.ctx.helper.syncNoticeSend(this.ctx.session.sessionProject.customType, JSON.stringify(base_data));
+                    base_data.op = 'update';
+                    base_data.sid = -1;
+                    this.ctx.helper.syncNoticeSend(this.ctx.session.sessionProject.customType, JSON.stringify(base_data));
+                }
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        /**
          * 获取审核人需要审核的期列表
          *
          * @param auditorId
@@ -1080,13 +1394,19 @@ module.exports = app => {
          * @param auditorId
          * @return {Promise<*>}
          */
-        async getAuditGroupByList(stageId, times) {
+        async getAuditGroupByList(stageId, times, transaction = false) {
+            // const sql =
+            //     'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`sid`, la.`aid`, la.`order`, la.`status`' +
+            //     '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id` ' +
+            //     '  WHERE la.`sid` = ? and la.`times` = ? and la.`is_old` = 0 GROUP BY la.`aid` ORDER BY la.`order`';
+            // const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, stageId, times];
             const sql =
-                'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`sid`, la.`aid`, la.`order` ' +
-                '  FROM ?? AS la Left Join ?? AS pa On la.`aid` = pa.`id` ' +
-                '  WHERE la.`sid` = ? and la.`times` = ? GROUP BY la.`aid` ORDER BY la.`order`';
-            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, stageId, times];
-            return await this.db.query(sql, sqlParam);
+                'SELECT la.`aid`, pa.`name`, pa.`company`, pa.`role`, la.`times`, la.`sid`, la.`order`, la.`status`' +
+                ' FROM (SELECT `aid`, max(`order`) `order` FROM ?? WHERE `sid` = ? and `times` = ? and `is_old` = ? GROUP BY aid) sa' +
+                ' LEFT JOIN ?? la ON sa.`aid` = la.`aid` AND sa.`order` = la.`order`' +
+                ' Left JOIN ?? AS pa On la.`aid` = pa.`id` WHERE la.`sid` = ? and la.`times` = ? and la.`is_old` = ? order BY la.`order`';
+            const sqlParam = [this.tableName, stageId, times, 0, this.tableName, this.ctx.service.projectAccount.tableName, stageId, times, 0];
+            return transaction !== false ? await transaction.query(sql, sqlParam) : await this.db.query(sql, sqlParam);
         }
 
         /**
@@ -1232,7 +1552,7 @@ module.exports = app => {
             await transaction.delete(this.ctx.service.stageChange.tableName, { sid, stimes: times });
             await transaction.delete(this.ctx.service.stagePay.tableName, { sid, stimes: times });
             await transaction.delete(this.ctx.service.pay.tableName, { csid: sid, cstimes: times });
-            await transaction.delete(this.ctx.serivce.stageAuditAss.tableName, { sid, times });
+            await transaction.delete(this.ctx.service.stageAuditAss.tableName, { sid, times });
             // 其他台账
             await this.ctx.service.stageJgcl.deleteStageTimesData(sid, times, transaction);
             await this.ctx.service.stageOther.deleteStageTimesData(sid, times, transaction);
@@ -1403,6 +1723,76 @@ module.exports = app => {
             const result = await this.db.queryOne(sql, sqlParam);
             return result ? result.num : 0;
         }
+
+        /**
+         * 删除本次审批流程
+         * @param {Number} stageId - 标段id
+         * @param {Number} times - 第几次审批
+         * @param {Object} data - 更改参数
+         * @return {Promise<void>}
+         */
+        async saveAudit(stageId, times, data) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                const auditors = await this.getAuditGroupByList(stageId, times);
+                const now_audit = this._.find(auditors, { aid: data.old_aid });
+                if (data.operate === 'add') {
+                    if (now_audit.status !== auditConst.status.uncheck && now_audit.status !== auditConst.status.checking) {
+                        throw '当前人下无法操作新增';
+                    }
+                    const newAudit = {
+                        tid: this.ctx.tender.id,
+                        sid: stageId,
+                        aid: data.new_aid,
+                        order: now_audit.order+1,
+                        times: times,
+                        status: 1
+                    };
+                    // order+1
+                    await this._syncOrderByDelete(transaction, stageId, now_audit.order+1, times, '+');
+                    await transaction.insert(this.tableName, newAudit);
+                    // 更新审批流程页数据,如果存在
+                } else if (data.operate === 'del') {
+                    if (now_audit.status !== auditConst.status.uncheck) {
+                        throw '当前人无法操作删除';
+                    }
+                    await transaction.delete(this.tableName, { sid: stageId, times, aid: now_audit.aid, order: now_audit.order });
+                    await this._syncOrderByDelete(transaction, stageId, now_audit.order, times);
+                    // 旧的更新为is_old为1
+                    await transaction.update(this.tableName, { is_old: 1 }, {
+                        where: {
+                            sid: stageId,
+                            times,
+                            aid: data.old_aid,
+                        }
+                    });
+                } else if (data.operate === 'change') {
+                    const nowAudit = await this.getDataByCondition({ sid: stageId, times, aid: now_audit.aid, order: now_audit.order });
+                    if (now_audit.status !== auditConst.status.uncheck || !nowAudit) {
+                        throw '当前人无法操作替换';
+                    }
+                    nowAudit.aid = data.new_aid;
+                    await transaction.update(this.tableName, nowAudit);
+                    // 旧的更新为is_old为1
+                    await transaction.update(this.tableName, { is_old: 1 }, {
+                        where: {
+                            sid: stageId,
+                            times,
+                            aid: data.old_aid,
+                        }
+                    });
+                }
+                if (this.ctx.tender.info.shenpi.stage === shenpiConst.sp_status.gdspl || this.ctx.tender.info.shenpi.stage === shenpiConst.sp_status.gdzs) {
+                    const newAuditors = await this.getAuditGroupByList(stageId, times, transaction);
+                    await this.ctx.service.shenpiAudit.updateAuditList(transaction, this.ctx.tender.id, this.ctx.tender.info.shenpi.stage, shenpiConst.sp_type.stage, this._.map(newAuditors, 'aid'));
+                }
+                // 更新到审批流程方法
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
     }
 
     return StageAudit;

+ 13 - 6
app/service/stage_audit_ass.js

@@ -97,19 +97,19 @@ module.exports = app => {
                 x.locked_ledger_id = x.ass_ledger_id.split(',');
             });
             // 审批人数据锁定
-            const locking = await this.getAllDataByCondition({ where: { sid: stage.id, times: stage.curTimes, user_id: uid } });
+            const locking = await this.getAllDataByCondition({ where: { sid: stage.id, times: stage.curTimes, user_id: uid, confirm: 1 } });
             if (locking.length > 0) {
                 const updateLock = locking.map(x => { return { id: x.id, locked: 1 }; });
                 await transaction.updateRows(this.tableName, updateLock);
             }
-            const preConfirm = await this.getAllDataByCondition({ where: { sid: stage.id, times: stage.curTimes, user_id: pre_uid } });
+            const preConfirm = await this.getAllDataByCondition({ where: { sid: stage.id, times: stage.curTimes, user_id: pre_uid, confirm: 1 } });
             // 前审批人数据,与被锁定数据相关数据确认状态保留
             locked.push(...locking);
             if (preConfirm.length > 0) {
                 const delConfirm = [];
                 for (const pc of preConfirm) {
                     const bills = await this.ctx.service.ledger.getDataByCondition({ tender_id: stage.tid, ledger_id: pc.ledger_id });
-                    const billsOwner = bills.full_path.split('-');
+                    const billsOwner = bills ? bills.full_path.split('-') : [];
                     const exist = locked.find(x => {
                         for (const id of x.locked_ledger_id) {
                             if (billsOwner.indexOf(id) >= 0) return true;
@@ -125,7 +125,7 @@ module.exports = app => {
         async lockConfirm4CheckNo(stage, uid, auditors, transaction) {
             if (!transaction) throw '缺少参数';
             // 审批人数据锁定
-            const lockConfirm = await this.getAllDataByCondition({ where: { sid: stage.id, times: stage.curTimes, user_id: uid } });
+            const lockConfirm = await this.getAllDataByCondition({ where: { sid: stage.id, times: stage.curTimes, user_id: uid, confirm: 1 } });
             if (lockConfirm.length === 0) return;
 
             const insertData = [];
@@ -141,10 +141,10 @@ module.exports = app => {
                 if (a.aid === uid) break;
                 keepUid.push(a.aid);
             }
-            const keepConfirm = await this.getAllDataByCondition({ where: { sid: stage.id, times: stage.curTimes, uid: keepUid } });
+            const keepConfirm = await this.getAllDataByCondition({ where: { sid: stage.id, times: stage.curTimes, user_id: keepUid, confirm: 1 } });
             for (const kc of keepConfirm) {
                 const bills = await this.ctx.service.ledger.getDataByCondition({ tender_id: stage.tid, ledger_id: kc.ledger_id });
-                const billsOwner = bills.full_path.split('-');
+                const billsOwner = bills ? bills.full_path.split('-') : [];
                 const exist = lockConfirm.find(x => {
                     const lid = x.ass_ledger_id.split(',');
                     for (const id of lid) {
@@ -169,6 +169,13 @@ module.exports = app => {
         async getLockedId(stage) {
             return await this.getAllDataByCondition({ where: { sid: stage.id, times: stage.times, locked: 1 } });
         }
+
+        async getReportData(sid, stimes) {
+            const sql = 'SELECT saa.tid, saa.sid, saa.times, saa.user_id, saa.ass_ledger_id, saa.ass_user_id, saa.name, saa.company, saa.role, pa.sign_path, pa.stamp_path' +
+                `   FROM ${this.tableName} saa LEFT JOIN ${this.ctx.service.projectAccount.tableName} pa ON saa.ass_user_id = pa.id` +
+                `   WHERE saa.sid = ? AND saa.stimes = ?`;
+            return this.db.query(sql, [sid, stimes]);
+        }
     }
 
     return StageAuditAss;

+ 1 - 1
app/service/stage_bills.js

@@ -491,7 +491,7 @@ module.exports = app => {
                 const sumLoad = new SumLoad(this.ctx);
                 const loadTree = await sumLoad.stageGatherGcl(select, maxId, tenders, null, cover);
                 const result = loadTree.getUpdateData();
-                if (result.errors.length > 100) throw '您导入的数据存在大量数据错误,请您仔细检查';
+                // if (result.errors.length > 100) throw '您导入的数据存在大量数据错误,请您仔细检查';
 
                 const stageBills = await this.getLastestStageData2(this.ctx.tender.id, this.ctx.stage.id);
                 const updateStageBills = [], insertStageBills = [];

+ 174 - 3
app/service/stage_pay.js

@@ -80,6 +80,7 @@ module.exports = app => {
          */
         async addInitialStageData(stage, transaction) {
             const basesReg = new RegExp(payConst.calcBase.map(x => {return '(' + x.code + ')'}).join('|'));
+            const orderReg = /f\d+/ig;
             if (!stage) {
                 throw '初始化期合同支付数据失败';
             }
@@ -107,7 +108,7 @@ module.exports = app => {
                         stimes: stage.times, sorder: 0,
                         name: pp.name,
                         expr: (p.ptype === payConst.payType.normal || p.ptype === payConst.payType.sf)
-                            ? (basesReg.test(pp.expr) ? pp.expr : null)
+                            ? (basesReg.test(pp.expr) || orderReg.test(pp.expr) ? pp.expr : null)
                             : (p.type === payConst.wc ? pp.expr : null),
                         pause: pp.pause,
                         pre_tp: pp.end_tp,
@@ -137,6 +138,11 @@ module.exports = app => {
             return await this.getAuditorStagePay(pid, stage.id, stage.curTimes, stage.curOrder);
         }
 
+        async resortStagePays(stage, stagePays) {
+            if (stage.status !== auditConst.stage.status.checked) return;
+            stagePays.sort((x, y) => { return x.porder - y.porder; });
+        }
+
         /**
          * 获取某期合同支付数据
          *
@@ -144,7 +150,9 @@ module.exports = app => {
          * @returns {Promise<*>}
          */
         async getStagePays(stage) {
-            return await this.getAuditorStageData(stage.id, stage.curTimes, stage.curOrder);
+            const result = await this.getAuditorStageData(stage.id, stage.curTimes, stage.curOrder);
+            this.resortStagePays(stage, result);
+            return result;
         }
 
         /**
@@ -165,7 +173,8 @@ module.exports = app => {
                 '  WHERE SP.`sid` = ? AND P.`valid` = true' +
                 '  ORDER BY P.`order`';
             const sqlParam = [sid, sid];
-            return await this.db.query(sql, sqlParam);
+            const result = await this.db.query(sql, sqlParam);
+            return result;
         }
 
         /**
@@ -298,6 +307,21 @@ module.exports = app => {
             return await transaction.query(sql, sqlParam);
         }
 
+        /**
+         * 删除操作人数据
+         * @param stage - 期数据
+         * @param times - 操作人 该期第几次
+         * @param order - 操作人顺序
+         * @param transaction - 事务
+         * @returns {Promise<*>}
+         */
+        async deleteAuditStagePays(stage, times, order, transaction) {
+            if (!stage || !transaction || !times || order === undefined) {
+                throw '数据错误';
+            }
+            return await transaction.delete(this.tableName, { sid: stage.id, stimes: times, sorder: order });
+        }
+
         async getLastestPayId(id) {
             const info = await this.getDataById(id);
             const sql = 'SELECT SP.* FROM ?? As SP WHERE SP.`sid` = ? AND SP.`pid` = ? ORDER BY SP.`stimes` DESC, SP.`sorder` DESC';
@@ -326,6 +350,153 @@ module.exports = app => {
                 stage.id, copyTimes, copyOrder];
             return await transaction.query(sql, sqlParam);
         }
+
+        _splitExprByOrder(expr) {
+            const orderParam = [...expr.matchAll(/f\d+/ig)];
+            if (orderParam.length === 0) return [false, expr];
+
+            const exprPart = [];
+            for (const [i, op] of orderParam.entries()) {
+                if (i === 0) {
+                    exprPart.push(expr.substring(0, op.index));
+                } else {
+                    const preOp = orderParam[i-1];
+                    exprPart.push(expr.substring(preOp.index + preOp[0].length, op.index));
+                }
+                exprPart.push(op[0]);
+                if (i === orderParam.length - 1) exprPart.push(expr.substring(op.index + op[0].length, expr.length));
+            }
+            return [true, exprPart];
+        }
+
+        _newExprAfterChangeOrder(expr, orderPart1, orderPart2) {
+            if (!expr) return [false, expr];
+            const [hasOrder, exprPart] = this._splitExprByOrder(expr);
+            if (!hasOrder) return [false, expr];
+
+            const newExprPart = [];
+
+            let change = false;
+            for (const ep of exprPart) {
+                if (ep === orderPart1) {
+                    newExprPart.push(orderPart2);
+                    change = true;
+                } else if (ep === orderPart2) {
+                    newExprPart.push(orderPart1);
+                    change = true;
+                } else {
+                    newExprPart.push(ep);
+                }
+            }
+            return [change, newExprPart.join('')];
+        }
+        /**
+         * 交换两个合同支付项的顺序
+         * @param {Number} id1 - 合同支付项1的id
+         * @param {Number} id2 - 合同支付项1的id
+         * @returns {Promise<void>}
+         */
+        async changeOrder(id1, id2) {
+            if (!this.ctx.tender || !this.ctx.stage) throw '数据错误';
+            const pay1 = await this.ctx.service.pay.getDataByCondition({tid: this.ctx.tender.id, id: id1});
+            const pay2 = await this.ctx.service.pay.getDataByCondition({tid: this.ctx.tender.id, id: id2});
+            if (!pay1 || !pay2) throw '数据错误';
+
+            const stagePays = await this.getStagePays(this.ctx.stage);
+
+            const conn = await this.db.beginTransaction();
+            try {
+                const orderPart1 = `f${pay1.order}`;
+                const orderPart2 = `f${pay2.order}`;
+                const updateData = [{id: pay1.id, order: pay2.order}, {id: pay2.id, order: pay1.order}];
+                const updateStageData = [];
+                for (const p of stagePays) {
+                    const [change, newExpr] = this._newExprAfterChangeOrder(p.expr, orderPart1, orderPart2);
+                    if (change) {
+                        updateStageData.push({ condition: { where: { sid: this.ctx.stage.id, pid: p.pid } }, update: { expr: newExpr } });
+                    }
+                }
+                await conn.updateRows(this.ctx.service.pay.tableName, updateData);
+                for (const usd of updateStageData) {
+                    await conn.update(this.tableName, usd.update, usd.condition);
+                }
+                await conn.commit();
+                return await this.getStagePays(this.ctx.stage);
+            } catch (err) {
+                await conn.rollback();
+                throw err;
+            }
+        }
+
+        _newExprAfterDelete(expr, orderParts) {
+            if (!expr) return [false, expr];
+            const [hasOrder, exprPart] = this._splitExprByOrder(expr);
+            if (!hasOrder) return [false, expr];
+
+            const newExprPart = [];
+
+            let change = false;
+            for (const ep of exprPart) {
+                const orderPart = orderParts.find(x => {return x.orgOrder === ep; });
+                if (orderPart) {
+                    change = true;
+                    newExprPart.push(orderPart.newOrder);
+                } else {
+                    newExprPart.push(ep);
+                }
+            }
+            return [change, newExprPart.join('')];
+        }
+
+        async del(id) {
+            if (!this.ctx.tender || !this.ctx.stage) throw '数据错误';
+            // 检查是否可以删除
+            const pay = await this.ctx.service.pay.getDataByCondition({id: id});
+            if (!pay) {
+                throw '合同支付项不存在';
+            } else if (pay.ptype !== payConst.payType.normal) {
+                throw '该合同支付项不可删除';
+            }
+
+            const stagePays = await this.getStagePays(this.ctx.stage);
+            const orderParts = [], updateStageData = [], updateData = [];
+            for (const sp of stagePays) {
+                if (sp.order === pay.order) {
+                    orderParts.push({ orgOrder: `f${sp.order}`, newOrder: '#ref!'});
+                } else if (sp.order > pay.order) {
+                    orderParts.push({ orgOrder: `f${sp.order}`, newOrder: `f${sp.order-1}`});
+                    updateData.push({id: sp.pid, order: sp.order -1 });
+                }
+            }
+            for (const sp of stagePays) {
+                const [change, newExpr] = this._newExprAfterDelete(sp.expr, orderParts);
+                if (change) {
+                    updateStageData.push({ condition: { where: { sid: this.ctx.stage.id, pid: sp.pid } }, update: { expr: newExpr } });
+                }
+            }
+
+            // 删除合同支付
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 假删除
+                const result = await transaction.update(this.ctx.service.pay.tableName, { id: id, valid: false });
+                if (result.affectedRows !== 1) throw '删除合同支付项失败';
+                if (updateData.length > 0) await transaction.updateRows(this.ctx.service.pay.tableName, updateData);
+                for (const usd of updateStageData) {
+                    await transaction.update(this.tableName, usd.update, usd.condition);
+                }
+                await transaction.commit();
+                return true;
+            } catch(err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        async cacheOrder(stage, transaction) {
+            const sql = `UPDATE ${this.tableName} sp LEFT JOIN ${this.ctx.service.pay.tableName} p ON sp.pid = p.id SET sp.porder = p.\`order\` Where sp.sid = ?`;
+            await transaction.query(sql, [stage.id]);
+        }
     }
 
     return StagePay;

+ 1 - 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', 'his_id',
+    'total_price', 'deal_tp', 'copy_id', 's2b_gxby_check', 's2b_gxby_limit', 's2b_dagl_check', 's2b_dagl_limit', 'has_rela', 'his_id', 'rpt_show_level',
 ];
 
 module.exports = app => {

+ 5 - 0
app/view/ledger/gather.ejs

@@ -51,6 +51,8 @@
                             </table>
                         </div>
                     </div>
+                    <div id="search" class="tab-pane">
+                    </div>
                 </div>
             </div>
         </div>
@@ -61,6 +63,9 @@
                 <li class="nav-item">
                     <a class="nav-link active" content="#chapter" href="javascript: void(0);">章节合计</a>
                 </li>
+                <li class="nav-item">
+                    <a class="nav-link" content="#search" href="javascript: void(0);">查找定位</a>
+                </li>
             </ul>
         </div>
     </div>

+ 2 - 0
app/view/material/checklist.ejs

@@ -27,6 +27,8 @@
                 </div>
             </div>
             <div class="ml-auto">
+                <a href="https://jiliang-qa.oss-cn-shenzhen.aliyuncs.com/loginimg/%E6%B8%85%E5%8D%95%E6%9D%90%E6%96%99%E5%8D%95%E4%BD%8D%E6%B6%88%E8%80%97%E9%87%8FExcel%E7%A4%BA%E4%BE%8B.xlsx" class="mx-2"
+                   data-toggle="tooltip" data-placement="top" title="" data-original-title="下载导入示例"><i class="fa fa-cloud-download"></i></a>
                 <button class="btn btn-primary btn-sm" id="upload-list">导入清单工料单位消耗</button>
                 <input type="file" class="form-control-file" id="upload-list-file" style="display: none;" accept=".xls,.xlsx,.json">
             </div>

+ 6 - 2
app/view/report/index.ejs

@@ -31,6 +31,7 @@
                     <div class="dropdown">
                         <button class="btn btn-sm btn-light dropdown-toggle text-primary" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">报表数据预设</button>
                         <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
+                            <a class="dropdown-item" href="javascript: void(0)" onclick="rptShowLevel.show('show_level');">显示层级</a>
                             <a class="dropdown-item" href="javascript: void(0)" onclick="rptOtherStage.showOtherStage('advance');">预付款</a>
                             <a class="dropdown-item" href="javascript: void(0)" onclick="rptChangeRela.showChangeRela('change_project');">变更立项</a>
                             <a class="dropdown-item" href="javascript: void(0)" onclick="rptChangeRela.showChangeRela('change_apply');">变更申请</a>
@@ -259,10 +260,13 @@
                         </div>
                     </div>
                     <div class="sjs-height-4">
-                        <div class="print-view form-view">
+                        <div class="print-view form-view" style="position: relative;">
                             <div class="pageContainer">
                                 <canvas id="rptCanvas" height="820" width="920"></canvas>
                             </div>
+                            <!-- <div class="pageContainer signatureRptBar"  id='signatureRptBar'  style="display: none;">
+                                <canvas class="signatureCanvas" id="signatureRptCanvas" height="820" width="920"></canvas>
+                            </div> -->
                         </div>
                     </div>
                 </div>
@@ -344,7 +348,7 @@
 <script type="text/javascript" src="/public/report/js/rpt_jspdf.js"></script>
 <script type="text/javascript" src="/public/report/js/rpt_jsexcel.js"></script>
 <script type="text/javascript" src="/public/report/js/rpt_indexDb.js"></script>
-
+<script type="text/javascript" src="/public/report/js/rpt_move_signature.js"></script>
 <!--
 <script type="text/javascript" src="/public/report/js/rpt_custom.js"></script>
 -->

+ 20 - 0
app/view/report/rpt_all_popup.ejs

@@ -604,6 +604,26 @@
         </div>
     </div>
 </div>
+<div class="modal" id="select-show-level" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title" id="ssl-title">请选择显示层级</h5>
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">&times;</span>
+                </button>
+            </div>
+            <div class="modal-body">
+                <div class="form-group" id="ssl-list">
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                <button class="btn btn-sm btn-primary" id="sos-ok" onclick="rptShowLevel.update();">确定</button>
+            </div>
+        </div>
+    </div>
+</div>
 <!--管理推荐报表-->
 <div class="modal fade" id="man-c" data-backdrop="static">
     <div class="modal-dialog" role="document">

+ 1 - 2
app/view/revise/price_modal.ejs

@@ -24,8 +24,7 @@
                     </div>
                     <div class="d-inline-block">
                         <div class="input-group input-group-sm">
-
-                            <input type="text" class="form-control form-control-sm" placeholder="按项目节编号/名称查找" id="rela-bw-search-keyword">
+                            <input type="text" class="form-control form-control-sm" placeholder="按项目节编号/清单编号/名称查找" id="rela-bw-search-keyword" style="width: 300px">
                             <div class="input-group-append">
                                 <span class="input-group-text" id="rela-bw-search-result">结果:0</span>
                             </div>

+ 1 - 1
app/view/setting/fun.ejs

@@ -67,7 +67,7 @@
                                     <div>
                                         <div class="form-check form-check-inline">
                                             <input class="form-check-input" type="checkbox" id="lockPayExpr" name="lockPayExpr" <% if (funRela.lockPayExpr) { %>checked<% } %> onchange="updateSetting();">
-                                            <label class="form-check-label" for="lockPayExpr">锁定往期带技术计算的合同支付项</label>
+                                            <label class="form-check-label" for="lockPayExpr">锁定往期带基数计算的合同支付项</label>
                                         </div>
                                     </div>
                                 </div>

+ 3 - 0
app/view/stage/audit_btn.ejs

@@ -39,6 +39,9 @@
     <% if (ctx.stage.auditors !== undefined && ctx.stage.auditors.length !== 0 && ctx.stage.auditors[ctx.stage.auditors.length-1].aid === ctx.session.sessionUser.accountId && ctx.stage.status === auditConst.status.checked && ctx.stage.order === ctx.stage.highOrder) { %>
         <a href="javascript: void(0);" data-toggle="modal" data-target="#sp-down-back" class="btn btn-warning btn-sm btn-block">重新审批</a>
     <% } %>
+    <% if (ctx.stage && ctx.stage.cancancel) { %>
+        <a href="javascript: void(0);" data-toggle="modal" data-target="#sp-down-cancel" class="btn btn-danger btn-sm btn-block">撤回</a>
+    <% } %>
     <% if (ctx.stage.user_id === ctx.session.sessionUser.accountId && ctx.stage.order === ctx.stage.highOrder && (ctx.stage.status === auditConst.status.checkNo || ctx.stage.status === auditConst.status.uncheck)) { %>
         <a href="#del-qi" data-toggle="modal" data-target="#del-qi" class="btn btn-outline-danger btn-sm btn-block mt-5">删除本期</a>
     <% } %>

+ 221 - 31
app/view/stage/audit_modal.ejs

@@ -17,7 +17,7 @@
                         <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">
+                            <dl class="list-unstyled book-list" id="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>
@@ -81,12 +81,12 @@
                 <div class="modal-body">
                     <div class="row">
                         <div class="col-4">
-                            <% if(ctx.stage.status === auditConst.status.checkNo) { %>
-                            <a class="sp-list-item" href="#sub-sp" data-toggle="modal" data-target="#sub-sp"
+                            <% if(ctx.stage.status !== auditConst.status.checked && ctx.session.sessionUser.is_admin) { %>
+                            <a class="sp-list-item" href="#sub-sp2" data-toggle="modal" data-target="#sub-sp2"
                                 id="hideSp">修改审批流程</a>
                             <% } %>
                             <div class="card mt-3">
-                                <ul class="list-group list-group-flush" id="auditors-list">
+                                <ul class="list-group list-group-flush auditors-list" id="auditors-list">
                                     <% ctx.stage.auditors2.forEach((item, idx) => { %>
                                     <% if (idx === 0) { %>
                                     <li class="list-group-item" data-auditorId="<%- item.aid %>">
@@ -121,7 +121,7 @@
                                 <% } %>
                                 <div class="<%- idx < ctx.stage.auditHistory.length - 1 ? 'fold-card' : '' %>">
                                     <div class="text-center text-muted"><%- idx+1 %>#</div>
-                                    <ul class="timeline-list list-unstyled mt-2">
+                                    <ul class="timeline-list list-unstyled mt-2 <% if (idx === ctx.stage.auditHistory.length - 1 && ctx.stage.auditHistory.length !== 1) { %>last-auditor-list<% } %>">
                                         <% auditors.forEach((auditor, index) => { %>
                                         <% if (index === 0) { %>
                                         <li class="timeline-list-item pb-2">
@@ -146,7 +146,7 @@
                                                 </div>
                                             </div>
                                         </li>
-                                        <li class="timeline-list-item pb-2">
+                                        <li class="timeline-list-item pb-2 <% if (auditor.status === auditConst.status.uncheck && idx === ctx.stage.auditHistory.length - 1 && ctx.stage.auditHistory.length !== 1) { %>is_uncheck<% } %>">
                                             <div class="timeline-item-date">
                                                 <%- ctx.helper.formatDate(auditor.end_time) %>
                                             </div>
@@ -157,7 +157,7 @@
                                             <div class="timeline-item-icon bg-success text-light">
                                                 <i class="fa fa-check"></i>
                                             </div>
-                                            <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {%>
+                                            <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre || auditor.status === auditConst.status.checkCancel) {%>
                                             <div class="timeline-item-icon bg-warning text-light">
                                                 <i class="fa fa-level-up"></i>
                                             </div>
@@ -190,7 +190,7 @@
                                             </div>
                                         </li>
                                         <% } else {%>
-                                        <li class="timeline-list-item pb-2">
+                                        <li class="timeline-list-item pb-2 <% if (auditor.status === auditConst.status.uncheck && idx === ctx.stage.auditHistory.length - 1 && ctx.stage.auditHistory.length !== 1) { %>is_uncheck<% } %>">
                                             <div class="timeline-item-date">
                                                 <%- ctx.helper.formatDate(auditor.end_time) %>
                                             </div>
@@ -201,7 +201,7 @@
                                             <div class="timeline-item-icon bg-success text-light">
                                                 <i class="fa fa-check"></i>
                                             </div>
-                                            <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {%>
+                                            <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre || auditor.status === auditConst.status.checkCancel) {%>
                                             <div class="timeline-item-icon bg-warning text-light">
                                                 <i class="fa fa-level-up"></i>
                                             </div>
@@ -221,7 +221,7 @@
                                                                 <span class="pull-right <%- auditConst.statusClass[auditor.status] %>">
                                                                     <%- auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''%>
                                                                     <%- auditor.status === auditConst.status.checkNo ? ctx.stage.user.name : '' %>
-                                                                    <%- auditor.status === auditConst.status.checkNoPre ? auditors[index+1].name : '' %>
+                                                                    <%- auditor.status === auditConst.status.checkNoPre && auditor.sort - 1 > 0 ? ctx.helper._.find(auditors, { sort: auditor.sort - 1 }).name : '' %>
                                                                 </span>
                                                             </p>
                                                             <p class="text-muted mb-0"><%- auditor.role %></p>
@@ -267,9 +267,13 @@
                 </div>
                 <div class="modal-body">
                     <div class="row">
+                        <% if(ctx.stage.status !== auditConst.status.checked && ctx.session.sessionUser.is_admin) { %>
+                            <a class="sp-list-item" href="#sub-sp2" data-toggle="modal" data-target="#sub-sp2"
+                               id="hideSp">修改审批流程</a>
+                        <% } %>
                         <div class="col-4">
                             <div class="card mt-3">
-                                <ul class="list-group list-group-flush">
+                                <ul class="list-group list-group-flush auditors-list">
                                     <% ctx.stage.auditors2.forEach((item, idx) => { %>
                                     <% if (idx === 0) { %>
                                     <li class="list-group-item" data-auditorId="<%- item.aid %>">
@@ -304,7 +308,7 @@
                                 <% } %>
                             <div class="<%- idx < ctx.stage.auditHistory.length - 1 ? 'fold-card' : '' %>">
                                 <div class="text-center text-muted"><%- idx+1 %>#</div>
-                                <ul class="timeline-list list-unstyled mt-2">
+                                <ul class="timeline-list list-unstyled mt-2 <% if (idx === ctx.stage.auditHistory.length - 1 && ctx.stage.auditHistory.length !== 1) { %>last-auditor-list<% } %>">
                                     <% auditors.forEach((auditor, index) => { %>
                                     <% if (index === 0) { %>
                                     <li class="timeline-list-item pb-2">
@@ -329,7 +333,7 @@
                                             </div>
                                         </div>
                                     </li>
-                                    <li class="timeline-list-item pb-2">
+                                    <li class="timeline-list-item pb-2 <% if (auditor.status === auditConst.status.uncheck && idx === ctx.stage.auditHistory.length - 1 && ctx.stage.auditHistory.length !== 1) { %>is_uncheck<% } %>">
                                         <div class="timeline-item-date">
                                             <%- ctx.helper.formatDate(auditor.end_time) %>
                                         </div>
@@ -340,7 +344,7 @@
                                         <div class="timeline-item-icon bg-success text-light">
                                             <i class="fa fa-check"></i>
                                         </div>
-                                        <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {%>
+                                        <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre || auditor.status === auditConst.status.checkCancel) {%>
                                         <div class="timeline-item-icon bg-warning text-light">
                                             <i class="fa fa-level-up"></i>
                                         </div>
@@ -378,7 +382,7 @@
                                         </div>
                                     </li>
                                     <% } else {%>
-                                    <li class="timeline-list-item pb-2">
+                                    <li class="timeline-list-item pb-2 <% if (auditor.status === auditConst.status.uncheck && idx === ctx.stage.auditHistory.length - 1 && ctx.stage.auditHistory.length !== 1) { %>is_uncheck<% } %>">
                                         <div class="timeline-item-date">
                                             <%- ctx.helper.formatDate(auditor.end_time) %>
                                         </div>
@@ -389,7 +393,7 @@
                                         <div class="timeline-item-icon bg-success text-light">
                                             <i class="fa fa-check"></i>
                                         </div>
-                                        <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {%>
+                                        <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre || auditor.status === auditConst.status.checkCancel) {%>
                                         <div class="timeline-item-icon bg-warning text-light">
                                             <i class="fa fa-level-up"></i>
                                         </div>
@@ -409,7 +413,7 @@
                                                             <span class="pull-right <%- auditConst.statusClass[auditor.status] %>">
                                                                 <%- auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''%>
                                                                 <%- auditor.status === auditConst.status.checkNo ? ctx.stage.user.name : '' %>
-                                                                <%- auditor.status === auditConst.status.checkNoPre ? auditors[index+1].name : '' %>
+                                                                <%- auditor.status === auditConst.status.checkNoPre && auditor.sort - 1 > 0 ? ctx.helper._.find(auditors, { sort: auditor.sort - 1 }).name : '' %>
                                                             </span>
                                                         </p>
                                                         <p class="text-muted mb-0"><%- auditor.role %></p>
@@ -457,9 +461,13 @@
                 </div>
                 <div class="modal-body">
                     <div class="row">
+                        <% if(ctx.stage.status !== auditConst.status.checked && ctx.session.sessionUser.is_admin) { %>
+                            <a class="sp-list-item" href="#sub-sp2" data-toggle="modal" data-target="#sub-sp2"
+                               id="hideSp">修改审批流程</a>
+                        <% } %>
                         <div class="col-4">
                             <div class="card mt-3">
-                                <ul class="list-group list-group-flush">
+                                <ul class="list-group list-group-flush auditors-list">
                                     <% ctx.stage.auditors2.forEach((item, idx) => { %>
                                     <% if (idx === 0) { %>
                                     <li class="list-group-item" data-auditorId="<%- item.aid %>">
@@ -486,9 +494,14 @@
                         </div>
                         <div class="col-8 modal-height-500" style="overflow: auto">
                             <% ctx.stage.auditHistory.forEach((auditors, idx) => { %>
+                            <!-- 展开/收起历史流程 -->
+                            <% if(idx === ctx.stage.auditHistory.length - 1 && ctx.stage.auditHistory.length !== 1) { %>
+                                <div class="text-right"><a href="javascript: void(0);" id="fold-btn" data-target="show"
+                                                           data-idx="<%- idx + 1 %>">展开历史审批流程</a></div>
+                            <% } %>
                             <div class="<%- idx < ctx.stage.auditHistory.length - 1 ? 'fold-card' : '' %>">
                                 <div class="text-center text-muted"><%- idx+1 %>#</div>
-                                <ul class="timeline-list list-unstyled mt-2">
+                                <ul class="timeline-list list-unstyled mt-2 <% if (idx === ctx.stage.auditHistory.length - 1 && ctx.stage.auditHistory.length !== 1) { %>last-auditor-list<% } %>">
                                     <% auditors.forEach((auditor, index) => { %>
                                     <% if (index === 0) { %>
                                     <li class="timeline-list-item pb-2">
@@ -513,7 +526,7 @@
                                             </div>
                                         </div>
                                     </li>
-                                    <li class="timeline-list-item pb-2">
+                                    <li class="timeline-list-item pb-2 <% if (auditor.status === auditConst.status.uncheck && idx === ctx.stage.auditHistory.length - 1 && ctx.stage.auditHistory.length !== 1) { %>is_uncheck<% } %>">
                                         <div class="timeline-item-date">
                                             <%- ctx.helper.formatDate(auditor.end_time) %>
                                         </div>
@@ -524,7 +537,7 @@
                                         <div class="timeline-item-icon bg-success text-light">
                                             <i class="fa fa-check"></i>
                                         </div>
-                                        <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {%>
+                                        <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre || auditor.status === auditConst.status.checkCancel) {%>
                                         <div class="timeline-item-icon bg-warning text-light">
                                             <i class="fa fa-level-up"></i>
                                         </div>
@@ -589,7 +602,7 @@
                                         </div>
                                     </li>
                                     <% } else {%>
-                                    <li class="timeline-list-item pb-2">
+                                    <li class="timeline-list-item pb-2 <% if (auditor.status === auditConst.status.uncheck && idx === ctx.stage.auditHistory.length - 1 && ctx.stage.auditHistory.length !== 1) { %>is_uncheck<% } %>">
                                         <div class="timeline-item-date">
                                             <%- ctx.helper.formatDate(auditor.end_time) %>
                                         </div>
@@ -600,7 +613,7 @@
                                         <div class="timeline-item-icon bg-success text-light">
                                             <i class="fa fa-check"></i>
                                         </div>
-                                        <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre) {%>
+                                        <% } else if(auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre || auditor.status === auditConst.status.checkCancel) {%>
                                         <div class="timeline-item-icon bg-warning text-light">
                                             <i class="fa fa-level-up"></i>
                                         </div>
@@ -620,7 +633,7 @@
                                                             <span class="pull-right <%- auditConst.statusClass[auditor.status] %>">
                                                                 <%- auditor.status !== auditConst.status.uncheck ? auditConst.statusString[auditor.status] : ''%>
                                                                 <%- auditor.status === auditConst.status.checkNo ? ctx.stage.user.name : '' %>
-                                                                <%- auditor.status === auditConst.status.checkNoPre ? auditors[index+1].name : '' %>
+                                                                <%- auditor.status === auditConst.status.checkNoPre && auditor.sort - 1 > 0 ? ctx.helper._.find(auditors, { sort: auditor.sort - 1 }).name : '' %>
                                                             </span>
                                                         </p>
                                                         <p class="text-muted mb-0"><%- auditor.role %></p>
@@ -672,11 +685,6 @@
                                     <% }) %>
                                 </ul>
                             </div>
-                            <!-- 展开/收起历史流程 -->
-                            <% if(idx === ctx.stage.auditHistory.length - 1 && ctx.stage.auditHistory.length !== 1) { %>
-                            <div class="text-right"><a href="javascript: void(0);" id="fold-btn" data-target="show"
-                                    data-idx="<%- idx + 1 %>">展开历史审批流程</a></div>
-                            <% } %>
                             <% }) %>
                         </div>
                     </div>
@@ -801,8 +809,162 @@
 </div>
 <% } %>
 <% } %>
-
-
+<% if (ctx.stage && ctx.stage.cancancel) { %>
+<div class="modal fade" id="sp-down-cancel" 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>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">取消</button>
+                <button type="submit" class="btn btn-danger btn-sm" id="cancel-shenpi-btn">确定撤回</button>
+            </div>
+        </div>
+    </div>
+</div>
+<% } %>
+<% if (ctx.stage && ctx.stage.status !== auditConst.status.checked && ctx.session.sessionUser.is_admin) { %>
+<!--上报审批-->
+<div class="modal fade" id="sub-sp2" data-backdrop="static">
+    <div class="modal-dialog" style="max-width: 650px" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">修改审批流程</h5>
+            </div>
+            <div class="modal-body">
+                <div class="card mt-1">
+                    <div class="modal-height-500" style="overflow: visible">
+                        <style>
+                            #admin-edit-shenpi thead th {
+                                border-bottom: 0;
+                            }
+                            #admin-edit-shenpi td, #admin-edit-shenpi th {
+                                padding: 0.75rem;
+                            }
+                            #admin-edit-shenpi th {
+                                background: none;
+                                color: #212529;
+                                border-top: 0;
+                            }
+                        </style>
+                        <table class="table table-hover" id="admin-edit-shenpi">
+                            <thead>
+                            <tr class="card-header">
+                                <th>审批流程</th>
+                                <th width="80" style="text-align: center">审批状态</th>
+                                <th width="200" style="text-align: center">操作</th>
+                            </tr>
+                            </thead>
+                            <% for (let i = 1, iLen = ctx.stage.auditors2.length; i < iLen; i++) { %>
+                            <tr>
+                                <td><span class="shenpi-order"><%- i %></span> <%- ctx.stage.auditors2[i].name %> <small class="text-muted"><%- ctx.stage.auditors2[i].role %></small></td>
+                                <td style="text-align: center"><span class="<%- auditConst.auditStringClass[ctx.stage.auditors2[i].status] %>"><%- ctx.stage.auditors2[i].status !== auditConst.status.uncheck ? auditConst.auditString[ctx.stage.auditors2[i].status] : '待审批'  %></span></td>
+                                <td style="text-align: center">
+                                    <% if (ctx.stage.auditors2[i].status === auditConst.status.checking) { %>
+                                    <span class="dropdown mr-2">
+                                    <a href="javascript: void(0)" class="add-audit" id="<%- ctx.stage.auditors2[i].aid %>_add_dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">增加</a>
+                                    <div class="dropdown-menu dropdown-menu-right" id="<%- ctx.stage.auditors2[i].aid %>_add_dropdownMenu" aria-labelledby="<%- ctx.stage.auditors2[i].aid %>_add_dropdownMenuButton" style="width:220px">
+                                        <div class="mb-2 p-2"><input class="form-control form-control-sm gr-search"
+                                                                     placeholder="姓名/手机 检索" autocomplete="off" data-code="<%- ctx.stage.auditors2[i].aid %>_add"></div>
+                                        <dl class="list-unstyled book-list" data-aid="<%- ctx.stage.auditors2[i].aid %>" data-operate="add">
+                                            <% 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.stage.user_id) { %>
+                                                            <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>
+                                    </span>
+                                    <% } %>
+                                    <% if (ctx.stage.auditors2[i].status === auditConst.status.uncheck) { %>
+                                    <span class="dropdown mr-2">
+                                    <a href="javascript: void(0)" class="add-audit" id="<%- ctx.stage.auditors2[i].aid %>_add_dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">增加</a>
+                                        <div class="dropdown-menu dropdown-menu-right" id="<%- ctx.stage.auditors2[i].aid %>_add_dropdownMenu" aria-labelledby="<%- ctx.stage.auditors2[i].aid %>_add_dropdownMenuButton" style="width:220px">
+                                            <div class="mb-2 p-2"><input class="form-control form-control-sm gr-search"
+                                                                         placeholder="姓名/手机 检索" autocomplete="off" data-code="<%- ctx.stage.auditors2[i].aid %>_add"></div>
+                                            <dl class="list-unstyled book-list" data-aid="<%- ctx.stage.auditors2[i].aid %>" data-operate="add">
+                                                <% 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.stage.user_id) { %>
+                                                                <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>
+                                    </span>
+                                    <span class="dropdown mr-2">
+                                        <a href="javascript: void(0)" class="change-audit" id="<%- ctx.stage.auditors2[i].aid %>_change_dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">更换</a>
+                                        <div class="dropdown-menu dropdown-menu-right" id="<%- ctx.stage.auditors2[i].aid %>_change_dropdownMenu" aria-labelledby="<%- ctx.stage.auditors2[i].aid %>_change_dropdownMenuButton" style="width:220px">
+                                            <div class="mb-2 p-2"><input class="form-control form-control-sm gr-search"
+                                                                         placeholder="姓名/手机 检索" autocomplete="off" data-code="<%- ctx.stage.auditors2[i].aid %>_change"></div>
+                                            <dl class="list-unstyled book-list" data-aid="<%- ctx.stage.auditors2[i].aid %>" data-operate="change">
+                                                <% 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.stage.user_id) { %>
+                                                                <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>
+                                    </span>
+                                    <span class="dropdown">
+                                    <a href="javascript: void(0)" class="text-danger" title="移除" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">移除</a>
+                                    <div class="dropdown-menu">
+                                        <span class="dropdown-item" href="javascript:void(0);">确认移除审批人?</span>
+                                        <div class="dropdown-divider"></div>
+                                        <div class="px-2 py-1 text-center">
+                                            <button class="remove-audit btn btn-sm btn-danger" data-id="<%- ctx.stage.auditors2[i].aid %>">移除</button>
+                                            <button class="btn btn-sm btn-secondary">取消</button>
+                                        </div>
+                                    </div>
+                                    </span>
+                                    <% } %>
+                                </td>
+                            </tr>
+                            <% } %>
+                        </table>
+                    </div>
+                </div>
+            </div>
+            <form class="modal-footer">
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+            </form>
+        </div>
+    </div>
+</div>
+<% } %>
 <% include ../shares/check_data_modal.ejs %>
 <script type="text/javascript">
     const csrf = '<%= ctx.csrf %>';
@@ -815,6 +977,14 @@
     const shenpi_status = <%- ctx.tender.info.shenpi.stage %>;
     const shenpiConst =  JSON.parse('<%- JSON.stringify(shenpiConst) %>');
 </script>
+<% } else if (ctx.stage && ctx.stage.status !== auditConst.status.checked && ctx.session.sessionUser.is_admin) { %>
+    <script type="text/javascript">
+        const accountGroup = JSON.parse(unescape('<%- escape(JSON.stringify(accountGroup)) %>'));
+        const accountList = JSON.parse(unescape('<%- escape(JSON.stringify(accountList)) %>'));
+        const stage_uid = parseInt('<%= ctx.stage.user_id %>');
+        const auditorList = JSON.parse(unescape('<%- escape(JSON.stringify(ctx.stage.auditors2)) %>'));
+        const cur_tenderid = parseInt('<%- ctx.tender.id %>');
+    </script>
 <% } %>
 <script>
     const preUrl = '<%- preUrl %>';
@@ -928,4 +1098,24 @@
                 }
             });
     });
+
+    <% if (ctx.stage && ctx.stage.cancancel) { %>
+    $("#cancel-shenpi-btn").click(function () {
+        const data = {
+        };
+        $.ajax({
+            url: '<%- preUrl %>/audit/check/cancel',
+            type: 'get',
+            data: data,
+            dataTye: 'json',
+            success: function (response) {
+                if (response.err === 0) {
+                    window.location.href = response.url;
+                } else {
+                    toast(response.msg, 'error');
+                }
+            }
+        });
+    });
+    <% } %>
 </script>

+ 17 - 1
app/view/stage/index.ejs

@@ -18,6 +18,7 @@
                             <a class="dropdown-item" name="showLevel" tag="last" href="javascript: void(0);">最底层</a>
                             <a class="dropdown-item" name="showLevel" tag="leafXmj" href="javascript: void(0);">只显示项目节</a>
                             <a class="dropdown-item" name="showLevel" tag="curMeasure" href="javascript: void(0);">只显示本期计量</a>
+                            <a class="dropdown-item" name="showLevel" tag="endMeasure" href="javascript: void(0);">只显示截止本期计量</a>
                         </div>
                     </div>
                 </div>
@@ -68,6 +69,21 @@
                             <li class="nav-item">
                                 <a class="nav-link active" href="#">计量单元</a>
                             </li>
+
+                            <li class="nav-item">
+                                <div class="ml-2">
+                                    <div class="input-group input-group-sm">
+                                        <div class="input-group-prepend">
+                                            <div class="input-group-text">
+                                                <div class="form-group form-check mb-0">
+                                                    <input type="checkbox" class="form-check-input group-checkbox" id="pos-measure">
+                                                    <label class="form-check-label" for="pos-measure">本期已计量</label><!--勾选状态增加颜色text-danger-->
+                                                </div>
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
+                            </li>
                             <!--工程量清单模式操作-->
                             <li class="nav-item">
                                 <div class="ml-2">
@@ -511,7 +527,7 @@
         regExp = new RegExp(FindText, 'g');
         return this.replace(regExp, RepText);
     }
-    const readOnly = <%- stage.readOnly || stage.revising || (!!stage.assist && !stage.assist.confirm) %>;
+    const readOnly = <%- stage.readOnly || stage.revising || (!stage.assist || !!stage.assist.confirm) %>;
     const ledgerSpreadSetting = JSON.parse('<%- JSON.stringify(ledgerSpread) %>');
     ledgerSpreadSetting.localCache = {
         key: 'stage-bills',

+ 221 - 1
app/view/tender/modal.ejs

@@ -80,4 +80,224 @@
             </div>
         </div>
     </div>
-</div>
+</div>
+<div class="modal fade" id="sp-list" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">审批进度详情</h5>
+            </div>
+            <div class="modal-body">
+                <div class="modal-height-500" style="overflow: auto" id="audit-list">
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+            </div>
+        </div>
+    </div>
+</div>
+<script>
+    $(document).ready(function () {
+        // 获取审批流程
+        $('a[data-target="#sp-list" ]').on('click', function () {
+            const type = $(this).attr('data-type');
+            const data = {
+                order: $(this).attr('data-order'),
+            };
+            const tenderId = $(this).attr('data-tender');
+            let url = '';
+            let auditConst2 = '';
+            if (type === 'stage') {
+                url = '/tender/' + tenderId + '/measure/stage/auditors';
+                auditConst2 = JSON.parse('<%- JSON.stringify(auditConst.stage) %>');
+            } else if (type === 'ledger') {
+                url = '/tender/' + tenderId + '/measure/ledger/auditors';
+                auditConst2 = JSON.parse('<%- JSON.stringify(auditConst.ledger) %>');
+            } else if (type === 'material') {
+                url = '/tender/' + tenderId + '/measure/material/auditors';
+                auditConst2 = JSON.parse('<%- JSON.stringify(auditConst.material) %>');
+            }
+            const dayMode = true;
+            postData(url, data, function (result) {
+                const { auditHistory, auditors, user } = result
+                let historyHTML = ''
+                const leftAuditors = auditors;
+                const darkHTML = !dayMode ? 'bg-dark border-secondary text-white' : '';
+                auditHistory.forEach((auditors, idx) => {
+                    if(idx === auditHistory.length - 1 && auditHistory.length !== 1) {
+                        historyHTML += `<div class="text-right"><a href="javascript: void(0);" id="fold-btn" data-target="show"
+                    >展开历史审批流程</a></div>`
+                    }
+                    historyHTML += `<div class="${idx < 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) {
+                            historyHTML += `<li class="timeline-list-item pb-2">
+                            <div class="timeline-item-date">
+                                ${formatDate(auditor.begin_time, !dayMode)}
+                            </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 ${darkHTML}">
+                                    <div class="card-body p-3">
+                                        <div class="card-text">
+                                            <p class="mb-1"><span
+                                                    class="h5">${user.name}</span><span
+                                                    class="pull-right text-success">${idx !== 0 ? '重新' : ''}上报审批</span>
+                                            </p>
+                                            <p class="text-muted mb-0">${user.role}</p>
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                        </li>
+                        <li class="timeline-list-item pb-2">
+                            <div class="timeline-item-date">
+                                ${formatDate(auditor.end_time, !dayMode)}
+                            </div>`
+
+                            if(index < auditors.length - 1) {
+                                historyHTML += `<div class="timeline-item-tail"></div>`
+                            }
+                            if(auditor.status === auditConst2.status.checked) {
+                                historyHTML += `<div class="timeline-item-icon bg-success text-light">
+                                    <i class="fa fa-check"></i>
+                                </div>`
+
+                            } else if(auditor.status === auditConst2.status.checkNo || auditor.status === auditConst2.status.checkNoPre) {
+                                historyHTML += `<div class="timeline-item-icon bg-warning text-light">
+                                    <i class="fa fa-level-up"></i>
+                                </div>`
+                            } else if(auditor.status === auditConst2.status.checking) {
+                                historyHTML += `<div class="timeline-item-icon bg-warning text-light">
+                                    <i class="fa fa-ellipsis-h"></i>
+                                </div>`
+                            } else {
+                                historyHTML += `<div class="timeline-item-icon bg-secondary text-light"></div>`
+
+                            }
+                            historyHTML += `<div class="timeline-item-content">
+                                <div class="card ${darkHTML}">
+                                    <div class="card-body p-3">
+                                        <div class="card-text">
+                                            <p class="mb-1"><span class="h5">${auditor.name}</span><span
+                                                    class="pull-right ${auditConst2.statusClass[auditor.status]}">${auditConst2.statusString[auditor.status]}</span>
+                                            </p>
+                                            <p class="text-muted mb-0">${auditor.role}</p>
+                                        </div>
+                                    </div>`
+                            if (auditor.opinion) {
+                                historyHTML += `<div class="card-body p-3 border-top">
+                                    <p style="margin: 0;">${auditor.opinion}</p>
+                                </div>`
+                            }
+                            historyHTML += `</div></div></li>`
+                        } else {
+                            historyHTML += `<li class="timeline-list-item pb-2">
+                        <div class="timeline-item-date">
+                            ${formatDate(auditor.end_time, !dayMode)}
+                        </div>`
+
+                            if(index < auditors.length - 1) {
+                                historyHTML += `<div class="timeline-item-tail"></div>`
+                            }
+                            if(auditor.status === auditConst2.status.checked) {
+                                historyHTML += `<div class="timeline-item-icon bg-success text-light">
+                                <i class="fa fa-check"></i>
+                            </div>`
+                            } else if(auditor.status === auditConst2.status.checkNo || auditor.status === auditConst2.status.checkNoPre) {
+                                historyHTML += `<div class="timeline-item-icon bg-warning text-light">
+                                <i class="fa fa-level-up"></i>
+                            </div>`
+
+                            } else if(auditor.status === auditConst2.status.checking) {
+                                historyHTML += `<div class="timeline-item-icon bg-warning text-light">
+                                <i class="fa fa-ellipsis-h"></i>
+                            </div>`
+                            } else {
+                                historyHTML += `<div class="timeline-item-icon bg-secondary text-light"></div>`
+                            }
+                            historyHTML += `<div class="timeline-item-content">
+                        <div class="card ${darkHTML}">
+                            <div class="card-body p-3">
+                                <div class="card-text">
+                                    <p class="mb-1"><span class="h5">${auditor.name}</span>
+                                        <span
+                                            class="pull-right
+                                                            ${auditConst2.statusClass[auditor.status]}">${auditor.status !== auditConst2.status.uncheck ? auditConst2.statusString[auditor.status] : ''}
+                                            ${auditor.status === auditConst2.status.checkNo ? user.name : ''}
+                                            ${auditor.status === auditConst2.status.checkNoPre && auditor.sort - 1 > 0 ? _.find(auditors, { sort: auditor.sort - 1 }).name : ''}
+                                        </span>
+                                    </p>
+                                    <p class="text-muted mb-0">${auditor.role}</p>
+                                </div>
+                            </div>`
+
+                            if (auditor.opinion) {
+                                historyHTML += `<div class="card-body p-3 border-top">
+                            <p style="margin: 0;">${auditor.opinion} </p>
+                        </div>`
+                            }
+                            historyHTML += `</div></div></li>`
+                        }
+                    })
+                    historyHTML += '</ul></div>'
+
+                })
+                $('#audit-list').empty()
+                $('#audit-list').append(historyHTML)
+            });
+        });
+
+        // 展开/收起历史审核记录
+        $('#audit-list').on('click', 'a', 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('#end-target').text($(this).data('idx') + '#')
+                    auditCard.find('#fold-btn').text('收起历史审核记录')
+                })
+            } else {
+                $(this).data('target', 'show')
+                auditCard.find('.fold-card').slideUp('swing', () => {
+                    auditCard.find('#end-target').text('1#')
+                    auditCard.find('#fold-btn').text('展开历史审核记录')
+                })
+            }
+        });
+
+        function formatDate(date, dayMode) {
+            if (!date) return '';
+            date = new Date(date)
+            const year = date.getFullYear();
+            let mon = date.getMonth() + 1;
+            let day = date.getDate();
+            let hour = date.getHours();
+            let minute = date.getMinutes();
+            let scond = date.getSeconds();
+            if (mon < 10) {
+                mon = '0' + mon.toString();
+            }
+            if (day < 10) {
+                day = '0' + day.toString();
+            }
+            if (hour < 10) {
+                hour = '0' + hour.toString();
+            }
+            if (minute < 10) {
+                minute = '0' + minute.toString();
+            }
+            if (scond < 10) {
+                scond = '0' + scond.toString();
+            }
+            return `${year}<span class="${dayMode ? 'text-light' : ''}">${mon}-${day}</span><span class="${dayMode ? 'text-light' : ''}">${hour}:${minute}:${scond}</span>`;
+        };
+    })
+</script>

ファイルの差分が大きいため隠しています
+ 0 - 2839
builder_report_index_define.js


+ 2 - 0
config/web.js

@@ -218,6 +218,7 @@ const JsFiles = {
                     '/public/js/div_resizer.js',
                     '/public/js/spreadjs_rela/spreadjs_zh.js',
                     '/public/js/shares/sjs_setting.js',
+                    '/public/js/shares/cs_tools.js',
                     '/public/js/zh_calc.js',
                     '/public/js/path_tree.js',
                     '/public/js/gcl_gather.js',
@@ -802,6 +803,7 @@ const JsFiles = {
                     '/public/js/stage_audit.js',
                     '/public/report/js/rpt_change_rela.js',
                     '/public/report/js/rpt_other_stage.js',
+                    '/public/report/js/rpt_show_level.js',
                 ],
                 mergeFile: 'report_main',
             },

+ 1 - 1
db_script/baseUtils.js

@@ -4,7 +4,7 @@
 const fs = require('fs');
 const path = require('path');
 var util = require('util');
-var logPath = path.join(__dirname, `${process.argv[1]}.log`);
+var logPath = path.join(`${process.argv[1]}.log`);
 var logFile = fs.createWriteStream(logPath, { flags: 'a' });
 console.log = function() {
     logFile.write(util.format.apply(null, arguments) + '\n');

+ 42 - 0
db_script/pay_order.js

@@ -0,0 +1,42 @@
+const BaseUtil = require('./baseUtils');
+const querySql = BaseUtil.querySql;
+
+const checkPayOrder = async function (tid) {
+    const pays = await querySql('SELECT * FROM zh_pay WHERE tid = ? and valid = 1 order by `order`', [tid]);
+    for (const [i, pay] of pays.entries()) {
+        if (pay.order !== i + 1) await querySql('Update zh_pay Set `order` = ? Where id = ?', [i+1, pay.id]);
+    }
+};
+
+const doComplete = async function() {
+    try {
+        const tender = await querySql('Select * From zh_tender');
+        for (const t of tender) {
+            console.log(`Update Tender ${t.id}`);
+            await checkPayOrder(t.id);
+        }
+    } catch (err) {
+        console.log(err);
+    }
+    BaseUtil.closePool();
+};
+const doCompleteTest = async function(tid) {
+    try {
+        const tender = await querySql('Select * From zh_tender where id = ?', [tid]);
+        for (const t of tender) {
+            console.log(`Update Tender ${t.id}`);
+            await checkPayOrder(t.id);
+        }
+    } catch (err) {
+        console.log(err);
+    }
+    BaseUtil.closePool();
+};
+
+const tenderId = process.argv[3];
+if (tenderId) {
+    console.log(tenderId);
+    doCompleteTest(tenderId);
+} else {
+    doComplete()
+}

+ 13 - 0
publish.md

@@ -10,6 +10,19 @@
 ##特殊操作
 如果没有特殊说明,则在第默认操作的第3步前,执行相关脚本,如果有特殊要求,需特别说明
 
+### V3.9
+2022-1024 ~ ...(uat) ~ ... prod
+3.1 pay:
+    - 重算所有合同支付项order
+### uat
+```bash
+$ node pay_order uat
+```
+### prod
+```bash
+$ node pay_order default
+```
+
 ### V3.6
 2022-08-03 ~ ...(uat) ~ ...(prod)
 3.1 change:

+ 147 - 2
sql/update.sql

@@ -2,9 +2,10 @@ ALTER TABLE `zh_project_account` CHANGE `stamp_path` `stamp_path` VARCHAR(5000)
 
 ALTER TABLE `zh_revise_price`
 ADD COLUMN `rela_lid`  varchar(1000) NOT NULL DEFAULT '' COMMENT '关联台账id(zh_ledger.ledger_id)' AFTER `use_stage_order`;
-
 ALTER TABLE `zh_revise_price`
 ADD COLUMN `rela_cid`  varchar(5000) NOT NULL DEFAULT '' COMMENT '关联变更令id(zh_change.cid)' AFTER `rela_lid`;
+ALTER TABLE `zh_revise_price`
+ADD COLUMN `his_rela_cid`  varchar(5000) NOT NULL DEFAULT '' COMMENT '审批通过后,关联变更令id(zh_change.cid)' AFTER `rela_cid`;
 
 CREATE TABLE `zh_audit_ass` (
   `id` bigint(20) NOT NULL AUTO_INCREMENT,
@@ -40,4 +41,148 @@ CREATE TABLE `zh_stage_audit_ass` (
   PRIMARY KEY (`id`),
   KEY `idx_sid_times` (`sid`,`times`) USING BTREE,
   KEY `idx_sid_times_user` (`sid`,`times`,`user_id`) USING BTREE
-) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
+
+ALTER TABLE `zh_budget_final`
+ADD COLUMN `deal_dgn_qty1`  decimal(24,8) NOT NULL DEFAULT 0 COMMENT '决算-合同-数量1' AFTER `dgn_qty`,
+ADD COLUMN `deal_dgn_qty2`  decimal(24,8) NOT NULL DEFAULT 0 COMMENT '决算-合同-数量2' AFTER `deal_dgn_qty1`,
+ADD COLUMN `c_dgn_qty1`  decimal(24,8) NOT NULL DEFAULT 0 COMMENT '决算-变更-数量1' AFTER `deal_dgn_qty2`,
+ADD COLUMN `c_dgn_qty2`  decimal(24,8) NOT NULL DEFAULT 0 COMMENT '决算-变更-数量2' AFTER `c_dgn_qty1`,
+ADD COLUMN `final_contract_tp`  decimal(24,8) NOT NULL DEFAULT 0 COMMENT '决算-合同金额' AFTER `final_dgn_qty2`,
+ADD COLUMN `final_qc_tp`  decimal(24,8) NOT NULL DEFAULT 0 COMMENT '决算-变更金额' AFTER `final_cotnract_tp`;
+
+ALTER TABLE `zh_tender`
+ADD COLUMN `rpt_show_level`  varchar(50) NOT NULL DEFAULT 'last' AFTER `c_plan_list_rule`;
+
+ALTER TABLE `zh_ledger_0`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_ledger_1`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_ledger_2`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_ledger_3`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_ledger_4`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_ledger_5`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_ledger_6`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_ledger_7`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_ledger_8`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_ledger_9`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+
+ALTER TABLE `zh_pos_0`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_pos_1`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_pos_2`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_pos_3`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_pos_4`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_pos_5`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_pos_6`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_pos_7`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_pos_8`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_pos_9`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_pos_10`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_pos_11`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_pos_12`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_pos_13`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_pos_14`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_pos_15`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_pos_16`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_pos_17`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_pos_18`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_pos_19`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+
+ALTER TABLE `zh_revise_bills_0`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_revise_bills_1`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_revise_bills_2`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_revise_bills_3`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_revise_bills_4`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_revise_bills_5`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_revise_bills_6`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_revise_bills_7`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_revise_bills_8`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_revise_bills_9`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+
+ALTER TABLE `zh_revise_pos_0`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_revise_pos_1`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_revise_pos_2`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_revise_pos_3`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_revise_pos_4`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_revise_pos_5`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_revise_pos_6`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_revise_pos_7`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_revise_pos_8`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_revise_pos_9`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_revise_pos_10`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_revise_pos_11`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_revise_pos_12`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_revise_pos_13`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_revise_pos_14`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_revise_pos_15`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_revise_pos_16`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_revise_pos_17`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_revise_pos_18`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+ALTER TABLE `zh_revise_pos_19`
+ADD COLUMN `ccid`  varchar(36) NULL DEFAULT '' COMMENT 'create_change_id(uuid)' AFTER `crid`;
+
+ALTER TABLE `zh_stage_pay`
+ADD COLUMN `porder` tinyint(4) NOT NULL DEFAULT '0' COMMENT '排序' AFTER `sorder`;
+UPDATE zh_stage_pay sp LEFT JOIN zh_pay p ON sp.pid = p.id SET sp.porder = p.`order`;
+
+ALTER TABLE `zh_change_ledger` ADD `formc` TINYINT(1) NOT NULL DEFAULT '1' COMMENT '是否为变更新增(用于区分台账)' AFTER `ccid`;
+ALTER TABLE `zh_change_pos` ADD `formc` TINYINT(1) NOT NULL DEFAULT '1' COMMENT '是否为变更新增(用于区分台账)' AFTER `ccid`;
+
+ALTER TABLE `zh_stage_audit` ADD `is_old` TINYINT(1) NOT NULL DEFAULT '0' COMMENT '是否为旧流程(用于管理员修改流程时旧数据保留但不影响新流程)' AFTER `lock_lid`;