浏览代码

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

Tony Kang 8 月之前
父节点
当前提交
be241c354c
共有 95 个文件被更改,包括 4387 次插入497 次删除
  1. 11 11
      app/base/base_tree_service.js
  2. 20 2
      app/const/tender_info.js
  3. 1 1
      app/controller/datacollect_controller.js
  4. 68 6
      app/controller/financial_controller.js
  5. 16 0
      app/controller/ledger_controller.js
  6. 1 1
      app/controller/setting_controller.js
  7. 5 2
      app/controller/stage_controller.js
  8. 188 3
      app/controller/sub_proj_controller.js
  9. 1 1
      app/controller/tender_controller.js
  10. 6 6
      app/controller/wap_controller.js
  11. 1 1
      app/extend/helper.js
  12. 54 22
      app/lib/ledger.js
  13. 1 1
      app/lib/material_calc.js
  14. 4 4
      app/lib/pay_calc.js
  15. 6 0
      app/lib/sum_load.js
  16. 74 0
      app/lib/ybp.js
  17. 300 0
      app/lib/ybp_tree.js
  18. 2 2
      app/middleware/financial_pay_audit_check.js
  19. 7 6
      app/middleware/project_manager_check.js
  20. 6 0
      app/public/css/bootstrap/bootstrap-select.min.css
  21. 1 1
      app/public/css/main.css
  22. 9 0
      app/public/js/bootstrap/bootstrap-select.min.js
  23. 1 0
      app/public/js/bootstrap/bootstrap-select.min.js.map
  24. 1 1
      app/public/js/change_information_set.js
  25. 1 1
      app/public/js/change_revise.js
  26. 1 1
      app/public/js/colResizable/colResizable-1.6.js
  27. 1 1
      app/public/js/colResizable/colResizable-1.6.min.js
  28. 3 3
      app/public/js/financial_pay.js
  29. 31 16
      app/public/js/financial_pay_detail.js
  30. 348 0
      app/public/js/financial_summary.js
  31. 17 1
      app/public/js/financial_transfer_tender.js
  32. 71 28
      app/public/js/global.js
  33. 17 1
      app/public/js/ledger.js
  34. 1 1
      app/public/js/ledger_check.js
  35. 1 1
      app/public/js/material_checklist.js
  36. 1 1
      app/public/js/material_list.js
  37. 37 7
      app/public/js/measure_compare.js
  38. 2 0
      app/public/js/path_tree.js
  39. 1 1
      app/public/js/revise.js
  40. 1 1
      app/public/js/se_other.js
  41. 2 2
      app/public/js/shares/select_rela_tender.js
  42. 38 13
      app/public/js/shares/tools_att.js
  43. 1 0
      app/public/js/shares/tree_expr_calc.js
  44. 617 0
      app/public/js/sp_progress.js
  45. 485 0
      app/public/js/sp_push.js
  46. 37 11
      app/public/js/spreadjs_rela/spreadjs_zh.js
  47. 1 1
      app/public/js/sr_detail.js
  48. 5 7
      app/public/js/stage.js
  49. 24 26
      app/public/js/stage_gather.js
  50. 1 1
      app/public/js/stage_pay.js
  51. 11 0
      app/public/js/tender_list_base.js
  52. 17 16
      app/public/js/tender_list_progress.js
  53. 3 1
      app/public/js/tender_showhide.js
  54. 21 8
      app/router.js
  55. 12 0
      app/service/account_cert.js
  56. 8 3
      app/service/change.js
  57. 8 2
      app/service/financial_pay.js
  58. 3 2
      app/service/financial_pay_audit.js
  59. 20 2
      app/service/financial_pay_contract.js
  60. 4 0
      app/service/financial_transfer_tender.js
  61. 101 0
      app/service/ledger.js
  62. 12 0
      app/service/material_list.js
  63. 5 2
      app/service/stage_audit.js
  64. 94 0
      app/service/sub_proj_file.js
  65. 186 0
      app/service/sub_proj_progress.js
  66. 132 0
      app/service/sub_proj_push.js
  67. 1 1
      app/service/sub_project.js
  68. 1 0
      app/service/tender_info.js
  69. 15 7
      app/view/dashboard/index.ejs
  70. 2 2
      app/view/financial/pay.ejs
  71. 4 4
      app/view/financial/pay_detail_modal.ejs
  72. 2 2
      app/view/financial/pay_modal.ejs
  73. 146 0
      app/view/financial/summary.ejs
  74. 1 1
      app/view/financial/transfer.ejs
  75. 2 0
      app/view/financial/transfer_tender.ejs
  76. 4 4
      app/view/financial/transfer_tender_modal.ejs
  77. 3 0
      app/view/ledger/explode.ejs
  78. 20 0
      app/view/ledger/explode_modal.ejs
  79. 2 2
      app/view/measure/compare_modal.ejs
  80. 1 1
      app/view/shares/select_rela_tender_modal.ejs
  81. 1 38
      app/view/stage/gather.ejs
  82. 1 0
      app/view/stage/index.ejs
  83. 22 0
      app/view/stage/modal.ejs
  84. 183 9
      app/view/sub_proj/info.ejs
  85. 107 0
      app/view/sub_proj/progress.ejs
  86. 2 0
      app/view/sub_proj/progress_modal.ejs
  87. 85 0
      app/view/sub_proj/push.ejs
  88. 1 0
      app/view/sub_proj/push_modal.ejs
  89. 1 0
      app/view/tender/detail.ejs
  90. 88 0
      app/view/tender/detail_modal.ejs
  91. 1 1
      config/config.default.js
  92. 55 0
      config/web.js
  93. 1 0
      package.json
  94. 96 193
      sql/update.sql
  95. 373 0
      sql/update20241030.sql

+ 11 - 11
app/base/base_tree_service.js

@@ -136,7 +136,7 @@ class TreeService extends Service {
     async getLastChildData(mid, pid) {
         this.initSqlBuilder();
         this.sqlBuilder.setAndWhere(this.setting.mid, {
-            value: mid,
+            value: this.db.escape(mid),
             operate: '=',
         });
         this.sqlBuilder.setAndWhere(this.setting.pid, {
@@ -168,7 +168,7 @@ class TreeService extends Service {
     async getChildBetween(mid, pid, order1, order2) {
         this.initSqlBuilder();
         this.sqlBuilder.setAndWhere(this.setting.mid, {
-            value: mid,
+            value: this.db.escape(mid),
             operate: '=',
         });
         this.sqlBuilder.setAndWhere(this.setting.pid, {
@@ -201,7 +201,7 @@ class TreeService extends Service {
     async getNextsData(mid, pid, order) {
         this.initSqlBuilder();
         this.sqlBuilder.setAndWhere(this.setting.mid, {
-            value: mid,
+            value: this.db.escape(mid),
             operate: '=',
         });
         this.sqlBuilder.setAndWhere(this.setting.pid, {
@@ -229,7 +229,7 @@ class TreeService extends Service {
     async getDataByFullPath(mid, fullPath) {
         this.initSqlBuilder();
         this.sqlBuilder.setAndWhere(this.setting.mid, {
-            value: mid,
+            value: this.db.escape(mid),
             operate: '=',
         });
         this.sqlBuilder.setAndWhere(this.setting.fullPath, {
@@ -252,7 +252,7 @@ class TreeService extends Service {
 
         this.initSqlBuilder();
         this.sqlBuilder.setAndWhere(this.setting.mid, {
-            value: mid,
+            value: this.db.escape(mid),
             operate: '=',
         });
         this.sqlBuilder.setAndWhere(this.setting.fullPath, {
@@ -278,7 +278,7 @@ class TreeService extends Service {
 
         this.initSqlBuilder();
         this.sqlBuilder.setAndWhere(this.setting.mid, {
-            value: mid,
+            value: this.db.escape(mid),
             operate: '=',
         });
         this.sqlBuilder.setAndWhere(this.setting.pid, {
@@ -305,7 +305,7 @@ class TreeService extends Service {
 
         this.initSqlBuilder();
         this.sqlBuilder.setAndWhere(this.setting.mid, {
-            value: mid,
+            value: this.db.escape(mid),
             operate: '=',
         });
         this.sqlBuilder.setAndWhere(this.setting.fullPath, {
@@ -378,7 +378,7 @@ class TreeService extends Service {
     async _updateChildrenOrder(mid, pid, order, incre = 1) {
         this.initSqlBuilder();
         this.sqlBuilder.setAndWhere(this.setting.mid, {
-            value: mid,
+            value: this.db.escape(mid),
             operate: '=',
         });
         this.sqlBuilder.setAndWhere(this.setting.order, {
@@ -530,7 +530,7 @@ class TreeService extends Service {
     async _deletePosterity(mid, node) {
         this.initSqlBuilder();
         this.sqlBuilder.setAndWhere(this.setting.mid, {
-            value: mid,
+            value: this.db.escape(mid),
             operate: '=',
         });
         this.sqlBuilder.setAndWhere(this.setting.fullPath, {
@@ -751,7 +751,7 @@ class TreeService extends Service {
     async _syncUplevelChildren(select) {
         this.initSqlBuilder();
         this.sqlBuilder.setAndWhere(this.setting.mid, {
-            value: select[this.setting.mid],
+            value: this.db.escape(select[this.setting.mid]),
             operate: '=',
         });
         this.sqlBuilder.setAndWhere(this.setting.fullPath, {
@@ -910,7 +910,7 @@ class TreeService extends Service {
     async _syncDownlevelChildren(select, newFullPath) {
         this.initSqlBuilder();
         this.sqlBuilder.setAndWhere(this.setting.mid, {
-            value: select[this.setting.mid],
+            value: this.db.escape(select[this.setting.mid]),
             operate: '=',
         });
         this.sqlBuilder.setAndWhere(this.setting.fullPath, {

+ 20 - 2
app/const/tender_info.js

@@ -8,8 +8,9 @@
  * @version
  */
 
-const parseInfo = ['deal_info', 'construction_unit', 'tech_param', 'decimal', 'precision', 'deal_param', 'display', 'pay_account', 'shenpi', 'bid_info', 'ledger_check', 'fun_rela'];
+const parseInfo = ['deal_info', 'construction_unit', 'tech_param', 'decimal', 'precision', 'deal_param', 'display', 'pay_account', 'shenpi', 'bid_info', 'ledger_check', 'fun_rela', 'over_range_check'];
 const arrayInfo = ['chapter'];
+
 const defaultInfo = {
     // 合同信息
     deal_info: {
@@ -209,14 +210,21 @@ const defaultInfo = {
             show: false,
         },
     },
+    over_range_check: {
+        field: 'tz', // 'deal', 'both',
+        percent: 100,
+        billsWithPos: 'pos', // 'bills', 'both',
+    },
 };
 const gclDefaultInfo = (function () {
     const result = JSON.parse(JSON.stringify(defaultInfo));
     result.display.ledger.deal = true;
+    result.over_range_check = { field: 'deal', percent: 100, billsWithPos: 'bills'};
     return result;
 })(defaultInfo);
 const tzDefaultInfo = (function () {
     const result = JSON.parse(JSON.stringify(defaultInfo));
+    result.over_range_check = { field: 'tz', percent: 100, billsWithPos: 'both'};
     return result;
 })(defaultInfo);
 const paymentDefaultInfo = (function () {
@@ -225,10 +233,19 @@ const paymentDefaultInfo = (function () {
     result.construction_unit = JSON.parse(JSON.stringify(defaultInfo.construction_unit));
     result.tech_param = JSON.parse(JSON.stringify(defaultInfo.tech_param));
     result.pay_account = JSON.parse(JSON.stringify(defaultInfo.pay_account));
-
     return result;
 })(defaultInfo);
 
+const transOverRangeCheck = function(helper, check) {
+    const result = {};
+    result.coe = helper.div(check.percent, 100);
+    result.checkTz = check.field === 'tz' || check.field === 'both';
+    result.checkDeal = check.field === 'deal' || check.field === 'both';
+    result.hasPosCheckBills = check.billsWithPos === 'bills' || check.billsWithPos === 'both';
+    result.hasPosCheckPos = check.billsWithPos === 'pos' || check.billsWithPos === 'both';
+    return result;
+};
+
 module.exports = {
     parseInfo,
     arrayInfo,
@@ -236,4 +253,5 @@ module.exports = {
     gclDefaultInfo,
     tzDefaultInfo,
     paymentDefaultInfo,
+    transOverRangeCheck,
 };

+ 1 - 1
app/controller/datacollect_controller.js

@@ -77,7 +77,7 @@ module.exports = app => {
                 if (ctx.params.index) {
                     ctx.session.sessionProject.dataCollect = parseInt(ctx.params.index);
                 }
-                const is_dz2 = ['P0505', 'P0506', 'P1201', 'P1202', 'GY18Y', 'GYJJ1'].indexOf(ctx.session.sessionProject.code) !== -1
+                const is_dz2 = ['P0505', 'P0506', 'P1201', 'P1202', 'GY18Y', 'GYJJ1', 'P1103'].indexOf(ctx.session.sessionProject.code) !== -1
                     && projectData.data_collect_pages.includes('6') && ctx.session.sessionProject.dataCollect === 6;
                 const renderData = {
                     projectData,

+ 68 - 6
app/controller/financial_controller.js

@@ -598,9 +598,21 @@ module.exports = app => {
             }
             const fptAudits = await ctx.service.financialPayTenderAudit.getAllDataByCondition({ where: { spid: ctx.subProject.id, uid: ctx.session.sessionUser.accountId } });
             const fptAuditTids = ctx.helper._.map(fptAudits, 'tid');
-            const tenders = await ctx.service.tender.getAllDataByCondition({ where: { spid: ctx.subProject.id, id: fptAuditTids }, columns: ['id', 'name'] });
-            const filter = JSON.parse(JSON.stringify(auditConst.financial.filter));
+            const fptReportTids = ctx.helper._.map(ctx.helper._.filter(fptAudits, { is_report: 1 }), 'tid');
             const filterTids = tid === null ? (ctx.session.sessionUser.is_admin ? null : fptAuditTids) : [tid];
+            const tenderCondition = { spid: ctx.subProject.id };
+            let hadTender = false;
+            if (ctx.session.sessionUser.is_admin) {
+                hadTender = true;
+            } else if (fptAuditTids.length !== 0) {
+                hadTender = true;
+                tenderCondition.id = fptAuditTids;
+            } else {
+                hadTender = false;
+                tenderCondition.id = -1;
+            }
+            const tenders = hadTender ? await ctx.service.tender.getAllDataByCondition({ where: tenderCondition, columns: ['id', 'name'] }) : [];
+            const filter = JSON.parse(JSON.stringify(auditConst.financial.filter));
             filter.count = [];
             filter.count[filter.status.pending] = await ctx.service.financialPay.getCountByStatus(ctx.subProject.id, filter.status.pending, filterTids, used);// await ctx.service.change.pendingDatas(tender.id, ctx.session.sessionUser.accountId);
             filter.count[filter.status.uncheck] = await ctx.service.financialPay.getCountByStatus(ctx.subProject.id, filter.status.uncheck, filterTids, used);// await ctx.service.change.checkingDatas(tender.id, ctx.session.sessionUser.accountId);
@@ -614,7 +626,6 @@ module.exports = app => {
             });
             const payTenders = await ctx.service.financialPayTender.getAllDataByCondition({ where: { spid: ctx.subProject.id } });
             for (const t of tenders) {
-                const fptReportTids = ctx.helper._.map(ctx.helper._.filter(fptAudits, { is_report: 1 }), 'tid');
                 const info = await ctx.service.tenderInfo.getDataByCondition({ tid: t.id });
                 t.pay_account = info && info.pay_account ? JSON.parse(info.pay_account).project : '';
                 const pt = ctx.helper._.find(payTenders, { tid: t.id });
@@ -643,7 +654,7 @@ module.exports = app => {
                         await ctx.service.financialPay.defaultUpdate({ final_auditor_str }, { where: { id: pay.id } });
                     }
                 }
-                if (pay.status === auditConst.financial.status.uncheck || pay.status === auditConst.financial.status.checkNo) {
+                if (pay.status !== auditConst.financial.status.checked) {
                     pay.entities = await ctx.service.financialPayContract.getEntities(pay.id);
                 }
             }
@@ -664,6 +675,7 @@ module.exports = app => {
                 used,
                 payList,
                 fptAuditTids,
+                fptReportTids,
                 moment,
                 auditType: auditConst.auditType,
                 preUrl: '/financial',
@@ -877,10 +889,10 @@ module.exports = app => {
                         responseData.data = await ctx.service.financialPay.savePay(ctx.financialPay.id, data);
                         break;
                     case 'pay_contract_list':
-                        responseData.data = await ctx.service.financialPayContract.getContractList(ctx.financialPay.id);
+                        responseData.data = await ctx.service.financialPayContract.getContractList(ctx.financialPay.id, ctx.financialPay.status !== auditConst.financial.status.checked);
                         break;
                     case 'contract_list':
-                        responseData.data.contracts = await ctx.service.contract.getAllDataByCondition({ where: { tid: ctx.financialPay.tid, contract_type: 1 } });
+                        responseData.data.contracts = await ctx.service.contract.getAllDataByCondition({ where: { tid: ctx.financialPay.tid, contract_type: 1 }, orders: [['level', 'asc'], ['order', 'asc']] });
                         responseData.data.contractTrees = await ctx.service.contractTree.getAllDataByCondition({ where: { tid: ctx.financialPay.tid, contract_type: 1 } });
                         break;
                     case 'contract_white_add':
@@ -1037,6 +1049,56 @@ module.exports = app => {
                 }
             }
         }
+
+        async summary(ctx) {
+            try {
+                const financialPermission = await ctx.service.financialAudit.getPermission(ctx.subProject.id, ctx.session.sessionUser.accountId);
+                if (!financialPermission.transfer_show && !financialPermission.pay_show) {
+                    throw '没有查看权限';
+                }
+                const renderData = {
+                    financialPermission,
+                    usedList: financialConst.used,
+                    preUrl: '/financial',
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.financial.summary),
+                };
+                await this.layout('financial/summary.ejs', renderData);
+            } catch (err) {
+                this.log(err);
+                ctx.redirect('/financial');
+            }
+        }
+
+        async summaryLoad(ctx) {
+            try {
+                const responseData = {
+                    err: 0, msg: '', data: {},
+                };
+                const tid = parseInt(ctx.query.tid) || null;
+                const fptAudits = await ctx.service.financialPayTenderAudit.getAllDataByCondition({ where: { spid: ctx.subProject.id, uid: ctx.session.sessionUser.accountId } });
+                const fptAuditTids = ctx.helper._.map(fptAudits, 'tid');
+                const filterTids = tid === null ? (ctx.session.sessionUser.is_admin ? null : fptAuditTids) : [tid];
+                const tenderCondition = { spid: ctx.subProject.id };
+                let hadTender = false;
+                if (ctx.session.sessionUser.is_admin) {
+                    hadTender = true;
+                } else if (fptAuditTids.length !== 0) {
+                    hadTender = true;
+                    tenderCondition.id = fptAuditTids;
+                } else {
+                    hadTender = false;
+                    tenderCondition.id = -1;
+                }
+                responseData.data.tenders = hadTender ? await ctx.service.tender.getAllDataByCondition({ where: tenderCondition, columns: ['id', 'name'] }) : [];
+                responseData.data.transferList = tid === null ? await ctx.service.financialTransfer.getAllDataByCondition({ where: { spid: ctx.subProject.id } }) : [];
+                responseData.data.transferTenderList = tid === null ? await ctx.service.financialTransferTender.getAllDataByCondition({ where: { spid: ctx.subProject.id } }) : [];
+                responseData.data.payList = await ctx.service.financialPay.getListByStatus(ctx.subProject.id, 0, filterTids);
+                ctx.body = responseData;
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: {} };
+            }
+        }
     }
 
     return FinancialController;

+ 16 - 0
app/controller/ledger_controller.js

@@ -24,6 +24,7 @@ const stdConst = require('../const/standard');
 const sendToWormhole = require('stream-wormhole');
 const spreadSetting = require('../lib/spread_setting');
 const PermissionCheck = require('../const/account_permission').PermissionCheck;
+const streamToArray = require('stream-to-array');
 
 module.exports = app => {
 
@@ -545,6 +546,21 @@ module.exports = app => {
                 ctx.body = { err: 1, msg: err.toString(), data: null };
             }
         }
+
+        async uploadYbp(ctx) {
+            const stream = await ctx.getFileStream();
+            try {
+                // 读取字节流
+                const parts = await streamToArray(stream);
+                // 转化为buffer
+                const buffer = Buffer.concat(parts);
+                const bills = await ctx.service.ledger.importYbp(ctx.tender, buffer.toString());
+                ctx.body = { err: 0, msg: '', data: { bills, pos: [] }};
+            } catch (err) {
+                await sendToWormhole(stream);
+                ctx.log(err);
+            }
+        }
         // async uploadExcel(ctx) {
         //     let stream;
         //     try {

+ 1 - 1
app/controller/setting_controller.js

@@ -1158,7 +1158,7 @@ module.exports = app => {
                 const categoryData = await ctx.service.category.getAllCategory(ctx.session.sessionProject.id);
                 const tenders = await ctx.service.tender.getList('', null, 1);
                 const dcTenders = await ctx.service.datacollectTender.getList(projectId);
-                const is_dz2 = ['P0505', 'P0506', 'P1201', 'P1202', 'GY18Y', 'GYJJ1'].indexOf(ctx.session.sessionProject.code) !== -1 ? 6 : false;
+                const is_dz2 = ['P0505', 'P0506', 'P1201', 'P1202', 'GY18Y', 'GYJJ1', 'P1103'].indexOf(ctx.session.sessionProject.code) !== -1 ? 6 : false;
                 const renderData = {
                     projectData,
                     dataCollectAudits,

+ 5 - 2
app/controller/stage_controller.js

@@ -271,7 +271,7 @@ module.exports = app => {
             const settleStatus = ctx.stage.readySettle ? await ctx.service.settleBills.getAllDataByCondition({ where: { settle_id: ctx.stage.readySettle.id }}) : [];
             // 查询截止上期数据
             const preStageData = ctx.stage.preCheckedStage ? await ctx.service.stageBillsFinal.getFinalData(ctx.tender.data, ctx.stage.preCheckedStage.order) : [];
-            const exprData = !ctx.stage.readOnly ? await ctx.service.expr.getAllDataByCondition({ where: { tid: ctx.stage.tid, calc_module: 'stage' } }) : [];
+            const exprData = await ctx.service.expr.getAllDataByCondition({ where: { tid: ctx.stage.tid, calc_module: 'stage' } });
             this.ctx.helper.assignRelaData(ledgerData, [
                 { data: dgnData, fields: ['deal_dgn_qty1', 'deal_dgn_qty2', 'c_dgn_qty1', 'c_dgn_qty2'], prefix: '', relaId: 'id' },
                 { data: memoData, fields: this.ledgerMemoColumn, prefix: '', relaId: 'id' },
@@ -419,6 +419,7 @@ module.exports = app => {
                             responseData.data.spec = spec;
                             break;
                         case 'expr':
+                            const exprData = await ctx.service.expr.getAllDataByCondition({ where: { tid: ctx.stage.tid, calc_module: 'stage' } });
                             responseData.data.expr = exprData;
                             break;
                     }
@@ -464,7 +465,9 @@ module.exports = app => {
                 await this.ctx.service.s2bProj.refreshSessionS2b();
                 checkData.check3fLimit(ctx.tender.data);
 
-                projRela.banOver && ctx.tender.info.ledger_check.over && checkData.checkOverRange(['contract_qty', 'qc_qty']);
+                if (projRela.banOver && ctx.tender.info.ledger_check.over) {
+                    checkData.checkOverRange(ctx.tender.info.checkOverInfo);
+                }
                 checkData.checkBillsTp([
                     { qty: 'contract_qty', tp: 'contract_tp' }, { qty: 'qc_qty', tp: 'qc_tp' },
                 ], this.ctx.tender.info.decimal, x => { return x.is_tp; });

+ 188 - 3
app/controller/sub_proj_controller.js

@@ -9,6 +9,9 @@
  */
 const auditConst = require('../const/audit');
 const accountGroup = require('../const/account_group').group;
+const sendToWormhole = require('stream-wormhole');
+const path = require('path');
+
 module.exports = app => {
     class SubProjController extends app.BaseController {
 
@@ -169,12 +172,12 @@ module.exports = app => {
         async saveRela(ctx) {
             try {
                 const data = JSON.parse(ctx.request.body.data);
-                if (!data.id || !data.rela_tender) throw '参数有误';
+                if (!data.id || data.rela_tender === undefined) throw '参数有误';
                 const permission = ctx.session.sessionUser.is_admin
                     ? ctx.service.subProjPermission.adminPermission
                     : await ctx.service.subProjPermission.getSubProjectUserPermission(data.id, ctx.session.sessionUser.accountId);
                 if (!permission || permission.manage_permission.indexOf(ctx.service.subProjPermission.PermissionConst.manage.rela.value) < 0) throw '您无权进行该操作';
-                const result = await ctx.service.subProject.setRelaTender({ id: data.id, rela_tender: data.rela_tender });
+                const result = await ctx.service.subProject.setRelaTender({ id: data.id, rela_tender: data.rela_tender || '' });
                 ctx.body = { err: 0, msg: '', data: { update: [result] } };
             } catch(err) {
                 ctx.log(err);
@@ -208,6 +211,10 @@ module.exports = app => {
         async info(ctx) {
             try {
                 const info = await this.ctx.service.subProjInfo.getInfo(ctx.subProject.id);
+                info.lx_tp = await this.ctx.service.budgetGu.getSumTp(ctx.subProject.budget_id);
+                info.cb_tp = await this.ctx.service.budgetGai.getSumTp(ctx.subProject.budget_id);
+                info.sgt_tp = await this.ctx.service.budgetYu.getSumTp(ctx.subProject.budget_id);
+                info.zbys_tp = await this.ctx.service.budgetZb.getSumTp(ctx.subProject.budget_id);
                 const renderData = {
                     info,
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.subProject.info),
@@ -238,7 +245,185 @@ module.exports = app => {
                 ctx.body = { err: 0, msg: '', data: result };
             } catch(err) {
                 ctx.log(err);
-                ctx.ajaxErrorBody(err, '保存数据失败');
+            }
+        }
+
+        async progress(ctx) {
+            try {
+                const renderData = {
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.subProject.progress),
+                };
+                await this.layout('sub_proj/progress.ejs', renderData, 'sub_proj/progress_modal.ejs');
+            } catch (err) {
+                ctx.log(err);
+                ctx.postError(err, '查看阶段进度')
+            }
+        }
+
+        async load(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const filter = data.filter.split(';');
+                const result = {};
+                for (const f of filter) {
+                    switch(f) {
+                        case 'progress':
+                            result[f] = await ctx.service.subProjProgress.getData(ctx.subProject);
+                            break;
+                        case 'progress_file':
+                            result[f] = await ctx.service.subProjFile.getData(ctx.subProject.id, 'progress');
+                            break;
+                        case 'push':
+                            result[f] = await ctx.service.subProjPush.getData(ctx.subProject.id);
+                            break;
+                        case 'push_file':
+                            result[f] = await ctx.service.subProjFile.getData(ctx.subProject.id, 'push');
+                            break;
+                        default:
+                            continue;
+                    }
+                }
+                ctx.body = { err: 0, msg: '', data: result };
+            } catch(err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '获取阶段进度数据有误');
+            }
+        }
+
+        async _progressBase(subProj, type, data) {
+            if (isNaN(data.id) || data.id <= 0) throw '数据错误';
+            if (type !== 'add') {
+                if (isNaN(data.count) || data.count <= 0) data.count = 1;
+            }
+            switch (type) {
+                case 'add':
+                    return await this.ctx.service.subProjProgress.addProgressNode(subProj.id, data.id, data.count);
+                case 'delete':
+                    return await this.ctx.service.subProjProgress.delete(subProj.id, data.id, data.count);
+                case 'up-move':
+                    return await this.ctx.service.subProjProgress.upMoveNode(subProj.id, data.id, data.count);
+                case 'down-move':
+                    return await this.ctx.service.subProjProgress.downMoveNode(subProj.id, data.id, data.count);
+                case 'up-level':
+                    return await this.ctx.service.subProjProgress.upLevelNode(subProj.id, data.id, data.count);
+                case 'down-level':
+                    return await this.ctx.service.subProjProgress.downLevelNode(subProj.id, data.id, data.count);
+            }
+        }
+
+        async progressUpdate(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.postType || !data.postData) throw '数据错误';
+                const responseData = { err: 0, msg: '', data: {} };
+
+                switch (data.postType) {
+                    case 'add':
+                    case 'delete':
+                    case 'up-move':
+                    case 'down-move':
+                    case 'up-level':
+                    case 'down-level':
+                        responseData.data = await this._progressBase(ctx.subProject, data.postType, data.postData);
+                        break;
+                    case 'update':
+                        responseData.data = await this.ctx.service.subProjProgress.updateInfos(ctx.subProject.id, data.postData);
+                        break;
+                        break;
+                    default:
+                        throw '未知操作';
+                }
+                ctx.body = responseData;
+            } catch (err) {
+                this.log(err);
+                ctx.body = this.ajaxErrorBody(err, '数据错误');
+            }
+        }
+
+        async push(ctx) {
+            try {
+                const renderData = {
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.subProject.push),
+                };
+                await this.layout('sub_proj/push.ejs', renderData, 'sub_proj/push_modal.ejs');
+            } catch (err) {
+                ctx.log(err);
+            }
+        }
+
+        async pushUpdate(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const result = await ctx.service.subProjPush.updateDatas(data);
+                ctx.body = { err: 0, msg: '', data: result };
+            } catch (error) {
+                ctx.helper.log(error);
+                ctx.body = this.ajaxErrorBody(error, '提交数据失败,请重试');
+            }
+        }
+
+        /**
+         * 上传附件
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async uploadFile(ctx) {
+            let stream;
+            try {
+                const parts = ctx.multipart({autoFields: true});
+
+                let index = 0;
+                const create_time = Date.parse(new Date()) / 1000;
+                let stream = await parts();
+                const user = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId);
+                const rela_id = parts.field.rela_id;
+
+                const uploadfiles = [];
+                while (stream !== undefined) {
+                    if (!stream.filename) throw '未发现上传文件!';
+
+                    const fileInfo = path.parse(stream.filename);
+                    const filepath = `sp/progress/${ctx.subProject.id}/${ctx.moment().format('YYYYMMDD')}/${create_time + '_' + index + fileInfo.ext}`;
+
+                    // 保存文件
+                    await ctx.app.fujianOss.put(ctx.app.config.fujianOssFolder + filepath, stream);
+                    await sendToWormhole(stream);
+
+                    // 插入到stage_pay对应的附件列表中
+                    uploadfiles.push({
+                        rela_id,
+                        filename: fileInfo.name,
+                        fileext: fileInfo.ext,
+                        filesize: Array.isArray(parts.field.size) ? parts.field.size[index] : parts.field.size,
+                        filepath,
+                    });
+                    ++index;
+                    if (Array.isArray(parts.field.size) && index < parts.field.size.length) {
+                        stream = await parts();
+                    } else {
+                        stream = undefined;
+                    }
+                }
+
+                const result = await ctx.service.subProjFile.addFiles(ctx.subProject.id, ctx.request.url.split('/')[3], uploadfiles, user);
+                ctx.body = {err: 0, msg: '', data: result};
+            } catch (error) {
+                ctx.log(error);
+                // 失败需要消耗掉stream 以防卡死
+                if (stream) await sendToWormhole(stream);
+                ctx.body = this.ajaxErrorBody(error, '上传附件失败,请重试');
+            }
+        }
+
+        async deleteFile(ctx) {
+            try{
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data) throw '缺少参数';
+                const result = await ctx.service.subProjFile.delFiles(data);
+                ctx.body = { err: 0, msg: '', data: result };
+            } catch(error) {
+                this.log(error);
+                ctx.ajaxErrorBody(error, '删除附件失败');
             }
         }
     }

+ 1 - 1
app/controller/tender_controller.js

@@ -682,7 +682,7 @@ module.exports = app => {
         async saveTenderInfo2(ctx) {
             try {
                 const data = JSON.parse(ctx.request.body.data);
-                if (!data || (!data.ledger_check && !data.fun_rela && !data.s_type)) throw '提交数据错误';
+                if (!data || (!data.ledger_check && !data.fun_rela && !data.s_type && !data.over_range_check)) throw '提交数据错误';
                 if (!ctx.session.sessionUser.is_admin) throw '您无权修改该内容';
 
                 const updateData = {};

+ 6 - 6
app/controller/wap_controller.js

@@ -329,12 +329,12 @@ module.exports = app => {
         async stage(ctx) {
             try {
                 const tender = ctx.tender.data;
-                const stages = await ctx.service.stage.getValidStages(ctx.tender.id);
-                const lastStage = stages.length > 0 ? stages[0] : null;
-                if (lastStage) {
-                    await this.ctx.service.stage.checkStageGatherData(lastStage);
-                }
-                const stage = lastStage;
+                // const stages = await ctx.service.stage.getValidStages(ctx.tender.id);
+                // const lastStage = stages.length > 0 ? stages[0] : null;
+                // if (lastStage) {
+                //     await this.ctx.service.stage.checkStageGatherData(lastStage);
+                // }
+                const stage = ctx.stage;
                 await ctx.service.stage.loadStageAuditViewData(stage);
                 const renderData = {
                     moment,

+ 1 - 1
app/extend/helper.js

@@ -1409,7 +1409,7 @@ module.exports = {
     },
 
     calcExpr(expr) {
-        const validExpr = expr.replace('=', '').replace('%', '/100');
+        const validExpr = expr.replace('=', '').replace(new RegExp('%', 'gm'), '/100');
         return parseFloat(math.eval(validExpr));
     },
 

+ 54 - 22
app/lib/ledger.js

@@ -975,41 +975,73 @@ class checkData {
             }
         }
     }
-    _checkPosOverRange(p) {
+    _checkPosOverRangeTz(p, coe) {
         const end_contract_qty = this.ctx.helper.add(p.pre_contract_qty, p.contract_qty);
-        if (!p.quantity) return !!end_contract_qty;
-        return p.quantity > 0
-            ? end_contract_qty > p.final_1_qty
-            : (p.final_1_qty > 0 ? true : end_contract_qty < p.final_1_qty || end_contract_qty > 0);
+        const base_qty = p.quantity;
+        const compare_qty = this.ctx.helper.mul(p.final_1_qty, coe);
+        if (!base_qty) return !!end_contract_qty;
+        return base_qty > 0
+            ? end_contract_qty > compare_qty
+            : (compare_qty > 0 ? true : end_contract_qty < compare_qty || end_contract_qty > 0);
     }
-    _checkBillsOverRange(bills, posRange, isTz) {
-        if (isTz && posRange.length > 0) {
-            for (const p of posRange) {
-                if (this._checkPosOverRange(p)) return true;
-            }
+    _checkPosOverRange(p, checkInfo) {
+        const checkTz = checkInfo.checkTz ? this._checkPosOverRangeTz(p, checkInfo.coe) : false;
+        const checkDeal = false;
+        return checkTz || checkDeal;
+    }
+    _checkBillsOverRangeTz(bills, coe) {
+        const end_contract_qty = this.ctx.helper.add(bills.contract_qty, bills.pre_contract_qty);
+        const end_contract_tp = this.ctx.helper.add(bills.contract_tp, bills.pre_contract_tp);
+        if (bills.is_tp) {
+            const base_tp = bills.total_price;
+            const compare_tp = this.ctx.helper.mul(base_tp, coe);
+            if (!base_tp) return !!end_contract_tp;
+            return base_tp >= 0 ? end_contract_tp > compare_tp : end_contract_tp < compare_tp || end_contract_tp > 0;
+        } else {
+            const base_qty = bills.quantity;
+            const compare_qty = this.ctx.helper.mul(bills.final_1_qty, coe);
+            if (!base_qty) return !!end_contract_qty;
+            return base_qty > 0
+                ? end_contract_qty > compare_qty
+                : (compare_qty > 0 ? true : end_contract_qty < compare_qty || end_contract_qty > 0);
         }
+    }
+    _checkBillsOverRangeDeal(bills, coe) {
         const end_contract_qty = this.ctx.helper.add(bills.contract_qty, bills.pre_contract_qty);
         const end_contract_tp = this.ctx.helper.add(bills.contract_tp, bills.pre_contract_tp);
         if (bills.is_tp) {
-            const compare_tp = isTz ? bills.total_price : bills.deal_tp;
-            if (!compare_tp) return !!end_contract_tp;
-            return compare_tp >= 0 ? end_contract_tp > compare_tp : end_contract_tp < compare_tp || end_contract_tp > 0;
+            const base_tp = bills.deal_tp;
+            const compare_tp = this.ctx.helper.mul(base_tp, coe);
+            if (!base_tp) return !!end_contract_tp;
+            return base_tp >= 0 ? end_contract_tp > compare_tp : end_contract_tp < compare_tp || end_contract_tp > 0;
         } else {
-            const compare_qty1 = isTz ? bills.quantity : bills.deal_qty;
-            const compare_qty2 = isTz ? bills.final_1_qty : bills.deal_final_1_qty;
-            if (!compare_qty1) return !!end_contract_qty;
-            return compare_qty1 > 0
-                ? end_contract_qty > compare_qty2
-                : (compare_qty2 > 0 ? true : end_contract_qty < compare_qty2 || end_contract_qty > 0);
+            const base_qty = bills.deal_qty;
+            const compare_qty = this.ctx.helper.mul(bills.deal_final_1_qty, coe);
+            if (!base_qty) return !!end_contract_qty;
+            return base_qty > 0
+                ? end_contract_qty > compare_qty
+                : (compare_qty > 0 ? true : end_contract_qty < compare_qty || end_contract_qty > 0);
+        }
+    }
+    _checkBillsOverRange(bills, posRange, checkInfo) {
+        if (checkInfo.hasPosCheckPos && posRange.length > 0) {
+            for (const p of posRange) {
+                if (this._checkPosOverRange(p, checkInfo)) return true;
+            }
+        }
+        if (checkInfo.hasPosCheckBills || posRange.length === 0) {
+            const checkTz = checkInfo.checkTz ? this._checkBillsOverRangeTz(bills, checkInfo.coe) : false;
+            const checkDeal = checkInfo.checkDeal ? this._checkBillsOverRangeDeal(bills, checkInfo.coe) : false;
+            return checkTz || checkDeal;
         }
+        return false;
     }
-    checkOverRange() {
-        const isTz = this.ctx.tender.data.measure_type === this.measureType.tz.value;
+    checkOverRange(checkInfo) {
         for (const b of this.checkBills.nodes) {
             if (b.children && b.children.length > 0) continue;
             const pr = this.checkPos.getLedgerPos(b.id) || [];
 
-            if (this._checkBillsOverRange(b, pr, isTz)) {
+            if (this._checkBillsOverRange(b, pr, checkInfo)) {
                 this.checkResult.error.push({
                     ledger_id: b.ledger_id,
                     b_code: b.b_code,

+ 1 - 1
app/lib/material_calc.js

@@ -48,7 +48,7 @@ class MaterialCalculate {
             const percent = formula.match(this.percentReg);
             if (percent) {
                 for (const p of percent) {
-                    const v = math.eval(p.replace('%', '/100'));
+                    const v = math.eval(p.replace(new RegExp('%', 'gm'), '/100'));
                     formula = formula.replace(p, v);
                 }
             }

+ 4 - 4
app/lib/pay_calc.js

@@ -80,7 +80,7 @@ class PayCalculate {
         const percent = formula.match(this.percentReg);
         if (percent) {
             for (const p of percent) {
-                const v = math.eval(p.replace('%', '/100'));
+                const v = math.eval(p.replace(new RegExp('%', 'gm'), '/100'));
                 formula = formula.replace(p, v);
             }
         }
@@ -105,7 +105,7 @@ class PayCalculate {
         const percent = formula.match(this.percentReg);
         if (percent) {
             for (const p of percent) {
-                const v = math.eval(p.replace('%', '/100'));
+                const v = math.eval(p.replace(new RegExp('%', 'gm'), '/100'));
                 formula = formula.replace(p, v);
             }
         }
@@ -137,12 +137,12 @@ class PayCalculate {
             // }
             // 上一期已计量的合同支付项,不予计算起扣金额、扣款限额
             if (!p.pre_used) {
-                if (!p.sprice && p.sexpr && p.sexpr !== '') {
+                if (p.sexpr && p.sexpr !== '') {
                     p.sprice = this.ctx.helper.round(this._calculateExpr(p.sexpr), this.decimal);
                 } else if (p.sprice && !p.sexpr) {
                     p.sprice = this.ctx.helper.round(p.sprice, this.decimal);
                 }
-                if (!p.rprice && p.rexpr && p.rexpr !== '') {
+                if (p.rexpr && p.rexpr !== '') {
                     p.rprice = this.ctx.helper.round(this._calculateExpr(p.rexpr), this.decimal);
                 } else if (p.rprice && !p.rexpr) {
                     p.rprice = this.ctx.helper.round(p.rprice, this.decimal);

+ 6 - 0
app/lib/sum_load.js

@@ -479,6 +479,9 @@ class sumLoad {
                 stageId: 'id',
             });
             billsTree.loadDatas(billsData);
+            billsTree.nodes.forEach(x => {
+                x.match = tender.match_code ? x.code === tender.match_code : true;
+            });
             for (const top of billsTree.children) {
                 if ([1].indexOf(top.node_type) < 0) continue;
                 this.recusiveLoadGatherGcl(top, null);
@@ -510,6 +513,9 @@ class sumLoad {
                 stageId: 'id',
             });
             billsTree.loadDatas(billsData);
+            billsTree.nodes.forEach(x => {
+                x.match = tender.match_code ? x.code === tender.match_code : true;
+            });
             for (const top of billsTree.children) {
                 if ([1].indexOf(top.node_type) < 0) continue;
                 this.recusiveLoadGatherGcl(top, null);

+ 74 - 0
app/lib/ybp.js

@@ -0,0 +1,74 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const CryptoJS = require('crypto-js');
+const LzString = require('lz-string');
+const { decode } = require('js-base64');
+
+const _interopDefaultLegacy = function(e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; };
+const CryptoJS__default = /* #__PURE__*/_interopDefaultLegacy(CryptoJS);
+const key = CryptoJS__default.default.enc.Utf8.parse('7563850888ABCDEF'); // 十六位十六进制数作为密钥
+const iv = CryptoJS__default.default.enc.Utf8.parse('ABCDEF7563850888'); // 十六位十六进制数作为密钥偏移量
+
+const fs = require('fs');
+const ybpSpr = '|----|', rationSpr = '|====|';
+
+const Decrypt = function(str) {
+    const encryptedHexStr = CryptoJS__default.default.enc.Hex.parse(str);
+    const src = CryptoJS__default.default.enc.Base64.stringify(encryptedHexStr);
+    const decrypt = CryptoJS__default.default.AES.decrypt(src, key, {
+        iv,
+        mode: CryptoJS__default.default.mode.CBC,
+        padding: CryptoJS__default.default.pad.Pkcs7,
+    });
+    const decryptedStr = decrypt.toString(CryptoJS__default.default.enc.Utf8);
+    return decryptedStr.toString();
+};
+
+class ybp {
+    parseSubject(str) {
+        if (str.indexOf(rationSpr) >= 0) {
+            const arr = str.split(rationSpr);
+            const subject = JSON.parse(LzString.decompressFromBase64(arr[0]));
+            for (let i = 1; i < arr.length; i++) {
+                const ration = JSON.parse(LzString.decompressFromBase64(arr[i]));
+                subject.rations.push(...ration);
+            }
+            return subject;
+        } else {
+            return JSON.parse(LzString.decompressFromBase64(str));
+        }
+    }
+    decryptBuffer(buffer) {
+        const arr = buffer.split(ybpSpr);
+        const result = { header: '', subjects: [] };
+        arr.forEach((s, i) => {
+            if (i === 0) {
+                result.header = JSON.parse(Decrypt(s));
+            } else {
+                switch (result.header.version) {
+                    case '3.0':
+                        result.subjects.push(this.parseSubject(s));
+                        break;
+                    default:
+                        result.subjects.push(JSON.parse(Decrypt(s)));
+                        break;
+                }
+            }
+        });
+        return result;
+    }
+    async decrypt(file) {
+        const str = await fs.readFileSync(file, 'utf-8');
+        return this.decryptBuffer(str);
+    }
+}
+
+module.exports = ybp;

+ 300 - 0
app/lib/ybp_tree.js

@@ -0,0 +1,300 @@
+'use strict';
+const YbpNodeKind = {
+    dxfy: 1, // 大项费用
+    fb: 2, // 分部
+    fx: 3, // 分项
+    bill: 4, // 清单
+    bx: 5, // 补项
+    cs: 6, // 分类
+    dt: 7, // 费用明细
+    xmj: 8,
+};
+const defaultMerge = [ YbpNodeKind.dxfy, YbpNodeKind.fb, YbpNodeKind.cs, YbpNodeKind.xmj ];
+const YbpImportType = { flow: 0, merge: 1 };
+
+class YbpTree {
+    /**
+     *
+     * @param setting - {Object}配置
+     * setting中必须设置id,pid,order,rootId(id, 父id, 同层排序, 根节点id)
+     * e.g.{id: 'ID', pid: 'parentID', order: 'seq'}
+     * 目前仅用于载入建筑ybp文件中的清单部分,生成树结构以便汇总导入
+     */
+    constructor(setting) {
+        this.setting = JSON.parse(JSON.stringify(setting));
+        this.clearDatas();
+    }
+    clearDatas() {
+        // 数据集合
+        this.datas = [];
+        // id索引
+        this.items = {};
+        // 排序索引
+        this.nodes = [];
+        // 首层节点
+        this.children = [];
+    }
+
+    sortChildren(children, recursive) {
+        const setting = this.setting;
+        children.sort((x, y) => { return x[setting.order] > y[setting.order]; });
+
+        if (!recursive) return;
+        for (const c of children) {
+            this.sortChildren(c.children);
+        }
+    }
+    sort() {
+        this.sortChildren(this.children, true);
+        const self = this;
+        const _loadNode = function(node) {
+            self.nodes.push(node);
+            for (const c of node.children) {
+                _loadNode(c);
+            }
+        };
+        for (const child of this.children) {
+            _loadNode(child);
+        }
+    }
+    loadDatas(datas) {
+        this.clearDatas();
+        const setting = this.setting;
+        const self = this;
+        const _loadData = function(d) {
+            if (d[setting.pid] === setting.rootId) {
+                self.children.push(d);
+            } else {
+                let parent = self.items[d[setting.pid]];
+                if (!parent) {
+                    parent = datas.find(x => { return x[setting.id] === d[setting.pid]; });
+                    if (!parent) {
+                        // console.log(d[setting.pid]);
+                        return null;
+                    }
+                    parent = _loadData(parent);
+                }
+                if (!parent) return null;
+                parent.children.push(d);
+            }
+            d.children = [];
+            self.datas.push(d);
+            self.items[d[setting.id]] = d;
+            return d;
+        };
+        for (const d of datas) {
+            if (this.items[d[setting.id]]) continue;
+            _loadData(d);
+        }
+        this.sort();
+    }
+}
+
+class YbpImportTree {
+    /**
+     *
+     * @param {Object} setting - 树结构配置
+     * @param {YbpImportType} type - 导入类型
+     * @param {Object} helper - this.ctx.helper
+     *
+     * setting中必须设置id, pid, level, order, full_path, rootId(id, 父id, 层次, 同层排序, 完整路径, 根节点id)
+     *
+     */
+    constructor(setting, type, helper, decimal) {
+        this.setting = setting;
+        this.importType = type;
+        this.helper = helper;
+        this.decimal = decimal;
+        this.clearDatas();
+    }
+    set newId(num) {
+        this._newId = num;
+    }
+    get newId() {
+        this._newId++;
+        return this._newId;
+    }
+    loadTemplate(template) {
+        if (!template || template.length === 0) return;
+
+        this.clearDatas();
+        const setting = this.setting;
+        const self = this;
+        const _loadData = function(d) {
+            d.children = [];
+            self.datas.push(d);
+            self.items[d[setting.id]] = d;
+            if (d[setting.pid] === setting.rootId) {
+                self.children.push(d);
+            } else {
+                const parent = self.items[d[setting.pid]];
+                if (!parent) {
+                    const parent = self.datas.find(x => { return x[setting.id] === d[setting.pid]; });
+                    if (!parent) throw '找不到父项';
+                    _loadData(parent);
+                }
+                parent.children.push(d);
+            }
+        };
+        for (const t of template) {
+            if (this.items[t[setting.id]]) continue;
+            _loadData(t);
+        }
+    }
+    clearDatas() {
+        // 数据集合
+        this.datas = [];
+        // id索引
+        this.items = {};
+        // 排序索引
+        this.nodes = [];
+        // 首层节点
+        this.children = [];
+        // 新增节点id
+        this.newId = 100;
+    }
+    _findNode(data, parent) {
+        const children = parent ? parent.children : this.children;
+        return this.helper._.find(children, data);
+        // return children.find(x => {
+        // return children.find(x => {
+        //   return x.kind === data.kind &&
+        //       x.code === data.code && x.b_code === data.b_code && x.name === data.name && x.unit === data.unit &&
+        //       x.unit_price === data.unit_price;
+        // });
+    }
+    _importNode(node, parent, loadRelaFun) {
+        const setting = this.setting;
+        const compareData = { kind: node.kind };
+        const hasUp = (!node.children || node.children.length === 0) && defaultMerge.indexOf(compareData.kind) < 0;
+        compareData.code = defaultMerge.indexOf(compareData.kind) < 0 ? '' : node.code || '';
+        compareData.b_code = defaultMerge.indexOf(compareData.kind) < 0 ? node.code || '' : '';
+        compareData.name = node.name || '';
+        compareData.unit = node.unit || '';
+        compareData.unit_price = hasUp
+            ? (node.fees && node.fees.marketCommon ? node.fees.marketCommon.unitPrice : 0)
+            : 0;
+
+        let cur = (this.importType === YbpImportType.merge || defaultMerge.indexOf(compareData.kind) >= 0)
+            ? this._findNode(compareData, parent)
+            : null;
+        if (!cur) {
+            cur = compareData;
+            cur.children = [];
+            cur.source = [];
+            cur[setting.id] = this.newId;
+            cur[setting.pid] = parent ? parent[setting.id] : setting.rootId;
+            cur[setting.level] = parent ? parent[setting.level] + 1 : 1;
+            cur[setting.full_path] = parent ? `${parent[setting.full_path]}-${cur[setting.id]}` : `${cur[setting.id]}`;
+            if (parent) {
+                parent.children.push(cur);
+            } else {
+                this.children.push(cur);
+            }
+            this.datas.push(cur);
+            cur.quantity = node.quantity || 0;
+            cur.total_fee = node.fees ? node.fees.marketCommon.totalFee : 0;
+            cur.labour_fee = node.fees && node.fees.marketLabour ? node.fees.marketLabour.totalFee : 0;
+        } else {
+            cur.quantity = this.helper.add(cur.quantity, node.quantity);
+            cur.total_fee = this.helper.add(cur.total_fee, node.fees ? node.fees.marketCommon.totalFee : 0);
+            cur.labour_fee = this.helper.add(cur.labour_fee, node.fees && node.fees.marketLabour ? node.fees.marketLabour.totalFee : 0);
+        }
+        if (loadRelaFun) loadRelaFun(cur, node);
+        cur.source.push(this.unitName);
+        for (const c of node.children) {
+            this._importNode(c, cur, loadRelaFun);
+        }
+    }
+    importTree(tree, unitName, loadRelaFun) {
+        this.unitName = unitName;
+        for (const n of tree.children) {
+            this._importNode(n, null, loadRelaFun);
+        }
+    }
+
+    sortChildren(children, recursive) {
+        children.sort((x, y) => {
+            return x.kind === YbpNodeKind.bill
+                ? (x.b_code ? (y.b_code ? x.b_code.localeCompare(y.b_code) : 1) : -1)
+                : (x.code ? (y.code ? x.code.localeCompare(y.code) : -1) : 1);
+        });
+        children.forEach((c, i) => { c.order = i + 1; });
+
+        if (!recursive) return;
+        for (const c of children) {
+            this.sortChildren(c.children, recursive);
+        }
+    }
+    generateSortNodes() {
+        const self = this;
+        const _loadNode = function(node) {
+            self.nodes.push(node);
+            for (const c of node.children) {
+                _loadNode(c);
+            }
+        };
+        for (const child of this.children) {
+            _loadNode(child);
+        }
+    }
+    sort() {
+        this.sortChildren(this.children, true);
+        this.generateSortNodes();
+    }
+    _mapTreeNode() {
+        let map = {},
+            maxLevel = 0;
+        for (const node of this.nodes) {
+            let levelArr = map[node.level];
+            if (!levelArr) {
+                levelArr = [];
+                map[node.level] = levelArr;
+            }
+            if (node.level > maxLevel) {
+                maxLevel = node.level;
+            }
+            levelArr.push(node);
+        }
+        return [ maxLevel, map ];
+    }
+    _calculateNode(node, fun) {
+        const self = this;
+        if (node.children && node.children.length > 0) {
+            const gather = node.children.reduce(function(rst, x) {
+                const result = {};
+                for (const cf of self.setting.calcFields) {
+                    result[cf] = self.helper.add(rst[cf], x[cf]);
+                }
+                return result;
+            });
+            // 汇总子项
+            for (const cf of this.setting.calcFields) {
+                if (gather[cf]) {
+                    node[cf] = gather[cf];
+                } else {
+                    node[cf] = null;
+                }
+            }
+        }
+        // 自身运算
+        if (fun) fun(node, this.helper, this.decimal);
+    }
+    calculateAll(fun) {
+        const [ maxLevel, levelMap ] = this._mapTreeNode();
+        for (let i = maxLevel; i >= 0; i--) {
+            const levelNodes = levelMap[i];
+            if (levelNodes && levelNodes.length > 0) {
+                for (const node of levelNodes) {
+                    this._calculateNode(node, fun || this.setting.calc);
+                }
+            }
+        }
+    }
+}
+
+module.exports = {
+    YbpImportType,
+    YbpTree,
+    YbpImportTree,
+};

+ 2 - 2
app/middleware/financial_pay_audit_check.js

@@ -8,7 +8,7 @@
  * @version
  */
 
-const status = require('../const/audit').changeApply.status;
+const status = require('../const/audit').financial.status;
 const shenpiConst = require('../const/shenpi');
 const _ = require('lodash');
 
@@ -36,7 +36,7 @@ module.exports = options => {
                 this.financialPay = financialPay;
             }
             const financialPay = this.financialPay;
-            if ((financialPay.status === status.uncheck || financialPay.status === status.checkNo)) {
+            if (financialPay.status === status.uncheck || financialPay.status === status.checkNo) {
                 // 进一步比较审批流是否与审批流程设置的相同,不同则替换为固定审批流或固定的终审
                 const auditList = yield this.service.financialPayAudit.getAllDataByCondition({ where: { fpid: financialPay.id, times: financialPay.times }, orders: [['order', 'asc']] });
                 const condition = { tid: financialPay.tid, sp_type: shenpiConst.sp_other_type.financial, sp_status: shenpiConst.sp_status.gdspl };

+ 7 - 6
app/middleware/project_manager_check.js

@@ -21,12 +21,13 @@ module.exports = app => {
                 throw '当前用户没有权限';
             }
         } catch (error) {
-            this.session.message = {
-                type: 'error',
-                icon: 'exclamation-circle',
-                message: error.toString(),
-            };
-            return this.redirect(this.request.headers.referer);
+            this.log(error);
+            if (this.helper.isAjax(this.request)) {
+                this.ajaxErrorBody(error, '管理员权限异常');
+            } else {
+                this.postError(error, '管理员权限异常');
+                return this.redirect(this.request.headers.referer);
+            }
         }
         yield next;
     };

文件差异内容过多而无法显示
+ 6 - 0
app/public/css/bootstrap/bootstrap-select.min.css


+ 1 - 1
app/public/css/main.css

@@ -703,7 +703,7 @@ input.nospin[type="number"]{-moz-appearance:textfield;}
   border-radius: 0;
   background: #192948
 }
-.bg-nav > li > a:hover,.bg-nav > li.active > a:hover{
+.bg-nav > li.active > a,.bg-nav > li > a:hover,.bg-nav > li.active > a:hover{
   background: #192948;
   color:#f2f2f2;
   text-decoration: none;

文件差异内容过多而无法显示
+ 9 - 0
app/public/js/bootstrap/bootstrap-select.min.js


文件差异内容过多而无法显示
+ 1 - 0
app/public/js/bootstrap/bootstrap-select.min.js.map


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

@@ -936,7 +936,7 @@ $(document).ready(() => {
                     const [valid, msg] = this._checkExprValid(expr);
                     if (!valid) return [valid, msg];
                     data.expr = expr;
-                    data.quantity = ZhCalc.calcExpr.calcExprStrRpn(expr.replace('%', '/100'));
+                    data.quantity = ZhCalc.calcExpr.calcExprStrRpn(expr.replace(new RegExp('%', 'gm'), '/100'));
                     // const ce = new CalcEvalMin();
                     // data.quantity = ce.eval(expr);
                     // console.log(data.quantity);

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

@@ -25,7 +25,7 @@ function getExprInfo (field, converse = false) {
     return converse ?  _.find(exprField, {expr: field}) : _.find(exprField, {qty: field});
 }
 function transExpr(expr) {
-    return $.trim(expr).replace('\t', '').replace('=', '').replace('%', '/100');
+    return $.trim(expr).replace('\t', '').replace('=', '').replace(new RegExp('%', 'gm'), '/100');
 }
 const copyBlockTag = 'zh.calc.copyBlock';
 const dskCompilation = 'zh.calc.dskCompilation';

文件差异内容过多而无法显示
+ 1 - 1
app/public/js/colResizable/colResizable-1.6.js


文件差异内容过多而无法显示
+ 1 - 1
app/public/js/colResizable/colResizable-1.6.min.js


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

@@ -573,7 +573,7 @@ $(function () {
             if (is_admin) {
                 t = tenders[0];
             } else {
-                const filterTender = tenders.filter(t => _.includes(fptAuditTids, t.id));
+                const filterTender = tenders.filter(t => _.includes(fptReportTids, t.id));
                 if (filterTender.length > 0) {
                     t = filterTender[0];
                 }
@@ -650,7 +650,7 @@ $(function () {
             $('#payaccount input[name="bank_account"]').val(tender.pt.bank_account);
             $('#payaccount input[name="contact"]').val(tender.pt.contact);
             $('#payaccount input[name="phone"]').val(tender.pt.phone);
-            if (is_admin || (fptAuditTids && _.includes(fptAuditTids, tid))) {
+            if (is_admin || (fptReportTids && _.includes(fptReportTids, tid))) {
                 $('#payaccount table input').attr('readonly', false);
                 $('#get-form-tender').show();
                 $('#set-pay-btn').show();
@@ -687,7 +687,7 @@ $(function () {
             toastr.error('标段不存在');
             return;
         }
-        if (is_admin || (fptAuditTids && _.includes(fptAuditTids, tender.id))) {
+        if (is_admin || (fptReportTids && _.includes(fptReportTids, tender.id))) {
             const data = {
                 id: parseInt($('#payaccount input[name="id"]').val()),
                 tid: tender.id,

+ 31 - 16
app/public/js/financial_pay_detail.js

@@ -116,16 +116,16 @@ $(function () {
             {title: '合同编号', colSpan: '1', rowSpan: '2', field: 'c_code', hAlign: 0, width: 110, formatter: '@', readOnly: true, backColor2: 'backColor.fromContract'},
             {title: '合同名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 130, formatter: '@', readOnly: true, backColor2: 'backColor.fromContract'},
             {title: '合同金额', colSpan: '1', rowSpan: '2', field: 'total_price', hAlign: 2, width: 80, type: 'Number', readOnly: 'readOnly.isEdit'},
-            {title: '收款单位', colSpan: '1', rowSpan: '2', field: 'entity', hAlign: 1, width: 130, formatter: '@', readOnly: 'readOnly.isEdit2'},
-            {title: '收款单位账号', colSpan: '1', rowSpan: '2', field: 'bank_account', hAlign: 1, formatter: '@', width: 130, readOnly: 'readOnly.isEdit2'},
-            {title: '收款单位开户行', colSpan: '1', rowSpan: '2', field: 'bank', hAlign: 1, formatter: '@', width: 130, readOnly: 'readOnly.isEdit2'},
+            {title: '收款单位', colSpan: '1', rowSpan: '2', field: 'entity', hAlign: 1, width: 130, formatter: '@', readOnly: 'readOnly.isEdit', cellType: 'ellipsisAutoTip', scrollHeightClass: '.sjs-height-0'},
+            {title: '收款单位账号', colSpan: '1', rowSpan: '2', field: 'bank_account', hAlign: 1, formatter: '@', width: 130, readOnly: 'readOnly.isEdit', cellType: 'ellipsisAutoTip', scrollHeightClass: '.sjs-height-0'},
+            {title: '收款单位开户行', colSpan: '1', rowSpan: '2', field: 'bank', hAlign: 1, formatter: '@', width: 130, readOnly: 'readOnly.isEdit', cellType: 'ellipsisAutoTip', scrollHeightClass: '.sjs-height-0'},
             {title: '小额支出', colSpan: '1', rowSpan: '2', field: 'small_expenses', cellType: 'checkbox', hAlign: 1, width: 60, readOnly: 'readOnly.isEdit2'},
             {title: '支付金额', colSpan: '1', rowSpan: '2', field: 'pay_price', hAlign: 2, width: 80, type: 'Number', readOnly: 'readOnly.isEdit2'},
             {title: '累计支付', colSpan: '1', rowSpan: '2', field: 'accumulate_pay_price', getValue: 'getValue.accumulate_pay_price', hAlign: 2, width: 80, type: 'Number', readOnly: true },
             {title: '结算金额', colSpan: '1', rowSpan: '2', field: 'settle_price', hAlign: 2, width: 80, type: 'Number', readOnly: 'readOnly.isEdit2'},
             {title: '累计结算', colSpan: '1', rowSpan: '2', field: 'accumulate_settle_price', getValue: 'getValue.accumulate_settle_price', hAlign: 2, width: 80, type: 'Number', readOnly: true },
             {title: '未结算', colSpan: '1', rowSpan: '2', field: 'not_pay_price', getValue: 'getValue.not_pay_price', hAlign: 2, width: 80, type: 'Number', readOnly: true },
-            {title: '支付方式', colSpan: '1', rowSpan: '2', field: 'pay_type', hAlign: 2, width: 80, cellType: 'unit', comboItems: payTypeList },
+            {title: '支付方式', colSpan: '1', rowSpan: '2', field: 'pay_type', hAlign: 1, width: 80, readOnly: 'readOnly.isEdit2', cellType: 'unit', comboItems: payTypeList },
             {title: '发票', colSpan: '1', rowSpan: '2', field: 'bill', hAlign: 1, cellType: 'checkbox', width: 60, readOnly: 'readOnly.isEdit2' },
             {title: '附件', colSpan: '1', rowSpan: '2', field: 'attachment', hAlign: 0, width: 60, readOnly: true, cellType: 'imageBtn',
                 normalImg: '#rela-file-icon', hoverImg: '#rela-file-hover', getValue: 'getValue.attachment' },
@@ -452,9 +452,12 @@ $(function () {
             postData(preUrl + '/save', { type: 'contract_list' }, function (result) {
                 contracts = result.contracts;
                 contractTrees = result.contractTrees;
-                sqTree.loadDatas(contractTrees);
+                sqTree.loadDatas(contractTrees.concat(contracts));
                 const level2Tree = _.filter(sqTree.nodes, function (item) {
-                    return item.level === 2;
+                    return item.level === 2 && !item.c_code;
+                });
+                contracts = _.filter(sqTree.nodes, function (item) {
+                    return item.c_code && item.c_code !== '';
                 });
                 makeContractListHtml(contracts);
                 let html2 = '<option value="0">全部</option>';
@@ -465,13 +468,14 @@ $(function () {
             });
         });
 
-        function makeContractListHtml(list) {
+        function makeContractListHtml(list, hide = false) {
             let html = '';
-            for (const c of list) {
-                const tree = _.find(contractTrees, function (item) {
-                    return item.contract_id === parseInt(c.full_path.split('-')[1] || 0);
-                });
-                html += `<tr class="text-center">
+            if (!hide) {
+                for (const c of list) {
+                    const tree = _.find(contractTrees, function (item) {
+                        return item.contract_id === parseInt(c.full_path.split('-')[1] || 0);
+                    });
+                    html += `<tr class="text-center tr-show" data-id="${c.id}">
                                     <td><input type="checkbox" data-contract-id="${c.id}" ${_.findIndex(contractList, { cid: c.id }) !== -1 ? 'checked disabled' : ''}></td>
                                     <td>${tree ? tree.name : ''}</td>
                                     <td class="text-left">${c.c_code}</td>
@@ -480,8 +484,14 @@ $(function () {
                                     <td>${c.entity}</td>
                                     <td>${c.bank_account}</td>
                                 </tr>`;
+                }
+                $('#contract-list').html(html);
+            } else {
+                $('#contract-list tr').removeClass('tr-show').hide();
+                for (const c of list) {
+                    $(`#contract-list tr[data-id="${c.id}"]`).addClass('tr-show').show();
+                }
             }
-            $('#contract-list').html(html);
         }
 
         $('body').on('change', '#contract-tree', function () {
@@ -506,11 +516,11 @@ $(function () {
             const list = _.filter(contracts, function (item) {
                 return (selectTree !== '0' ? _.includes(item.full_path, '-' + selectTree + '-') : true) && (keyword !== '' ? (item.c_code.indexOf(keyword) > -1 || item.name.indexOf(keyword) > -1) : true);
             });
-            makeContractListHtml(list);
+            makeContractListHtml(list, true);
         }
 
         $('#select-all-contract').click(function () {
-            $('#contract-list input:not(:disabled)').prop('checked', $(this).prop('checked'));
+            $('#contract-list tr[class*="tr-show"] input:not(:disabled)').prop('checked', $(this).prop('checked'));
         });
 
         $('#add-contract-btn').click(function () {
@@ -679,7 +689,12 @@ function checkStartFrom() {
     }
     if (notBills.length > 0) {
         for (const c of notBills) {
-            toastr.error(`合同${c.c_code}未上传发票`);
+            if (c.cid) {
+                toastr.error(`合同 ${c.c_code} 未上传发票`);
+            } else {
+                toastr.error(`收款单位 ${c.entity} 未上传发票`);
+            }
+
         }
         return false;
     }

+ 348 - 0
app/public/js/financial_summary.js

@@ -0,0 +1,348 @@
+$(document).ready(function() {
+    autoFlashHeight();
+
+    postData('/financial/' + spid + '/summary/load', {}, function (result) {
+        console.log(result);
+        tenders = result.tenders;
+        transferList = result.transferList;
+        transferTenderList = result.transferTenderList;
+        payList = result.payList;
+        summaryObj.setTenderSelect(result.tenders);
+        summaryObj.changeHtmlAndCharts(result.tenders, result.transferList, result.transferTenderList, result.payList);
+    });
+
+    $('#tender-select').on('changed.bs.select', function (e, clickedIndex, isSelected, previousValue) {
+        const vals = $(this).val();
+        if (vals.length === 0) {
+            $('#tender-select').selectpicker('val', '0');
+        } else if (clickedIndex !== 0 && vals.length > 1 && _.includes(vals, '0')) {
+            $('#tender-select').selectpicker('val', _.without(vals, '0'));
+        } else if (clickedIndex === 0 && isSelected) {
+            $('#tender-select').selectpicker('val', '0');
+        }
+    });
+
+    $('#search-btn').click(function () {
+        const tenderSelects = $('#tender-select').val().map(function (item, index) {
+            return parseInt(item);
+        });
+        // 判断是单还是多个标段
+        const is_single = tenderSelects.length === 1 && tenderSelects[0] !== 0;
+        const is_all = tenderSelects.length === 1 && tenderSelects[0] === 0;
+        const startMonth = $('#start-month').val();
+        const endMonth = $('#end-month').val();
+        const condition = {};
+        let newTransferList = _.cloneDeep(transferList);
+        let newTransferTenderList = _.cloneDeep(transferTenderList);
+        let newPayList = _.cloneDeep(payList);
+        if (startMonth !== '' && endMonth !== '') {
+            // 判断输入合法和endMonth大于startMonth
+            if (!/^\d{4}-\d{2}$/.test(startMonth) || !/^\d{4}-\d{2}$/.test(endMonth)) {
+                toastr.warning('请输入正确的日期格式');
+                return;
+            }
+            if (startMonth > endMonth) {
+                toastr.warning('结束日期不能小于开始日期');
+                return;
+            }
+            newTransferList = _.filter(newTransferList, function (item) {
+                return item.t_time >= startMonth && item.t_time <= endMonth;
+            });
+            newTransferTenderList = _.filter(newTransferTenderList, function (item) {
+                return _.includes(_.map(newTransferList, 'id'), item.trid);
+            });
+            newPayList = _.filter(newPayList, function (item) {
+                const thisMonth = moment(item.create_time).format('YYYY-MM');
+                return thisMonth >= startMonth && thisMonth <= endMonth;
+            });
+        } else if (startMonth !== '' || endMonth !== '') {
+            toastr.warning('请选择开始和结束日期查询');
+            return;
+        }
+        newTransferTenderList = !is_all ? _.filter(newTransferTenderList, function (item) { return _.includes(tenderSelects, item.tid); }) : newTransferTenderList;
+        newPayList = !is_all ? _.filter(newPayList, function (item) { return _.includes(tenderSelects, item.tid); }) : newPayList;
+        const newTenders = !is_all ? _.filter(tenders, function (item) { return _.includes(tenderSelects, item.id); }) : tenders;
+        summaryObj.changeHtmlAndCharts(newTenders, newTransferList, newTransferTenderList, newPayList, tenderSelects);
+    });
+
+    const summaryObj = {
+        setTenderSelect: function (tenders) {
+            let tenderSelectHtml = '<option value="0" selected>全部</option>';
+            for (const tender of tenders) {
+                tenderSelectHtml += `<option value="${tender.id}">${tender.name}</option>`;
+            }
+            $('#tender-select').html(tenderSelectHtml);
+            $('#tender-select').selectpicker();
+        },
+        changeHtmlAndCharts: function (tenders, transferList, transferTenderList, payList, tenderSelects = [0]) {
+            const transfer_price = tenderSelects !== [0] ? _.map(transferTenderList, 'hb_tp') : _.map(transferList, 'total_price');
+            const transfer_sum_price = ZhCalc.sum(transfer_price);
+            const pay_price = _.map(payList, 'total_price');
+            const pay_sum_price = ZhCalc.sum(pay_price);
+            const pend_sum_price = ZhCalc.sub(transfer_sum_price, pay_sum_price);
+            const small_expenses_tp = _.map(payList, 'small_expenses_tp');
+            const small_expenses_sum_price = ZhCalc.sum(small_expenses_tp);
+            // 金额概况
+            $('#transfer_price').text(transfer_sum_price.format2Str('#,##0.######'));
+            $('#pay_price').text(pay_sum_price.format2Str('#,##0.######'));
+            $('#pend_price').text(pend_sum_price.format2Str('#,##0.######'));
+            // 支付进度
+            const pay_progress = (ZhCalc.div(pay_sum_price, ZhCalc.add(pay_sum_price, pend_sum_price)) * 100).toFixed(0);
+            const pend_progress = 100 - pay_progress;
+            $('#pay_progress').html(`
+               <div class="progress-bar bg-success" style="width: ${pay_progress}%;" data-placement="bottom" data-toggle="tooltip" data-original-title="累计支付:¥${pay_sum_price.format2Str('#,##0.######')}">${pay_progress}%</div>
+               <div class="progress-bar bg-gray" style="width: ${pend_progress}%;" data-placement="bottom" data-toggle="tooltip" data-original-title="待支付:¥${pend_sum_price.format2Str('#,##0.######')}">${pend_progress}%</div>`);
+            $('[data-toggle="tooltip"]').tooltip();
+            // 资金支出明细
+            let pay_used_html = '';
+            payUsedOption.series[0].data = [];
+            for (const used of usedList) {
+                const usedPays = _.filter(payList, { used: used });
+                if (usedPays.length > 0) {
+                    const used_price = _.map(usedPays, 'total_price');
+                    const used_sum_price = ZhCalc.sum(used_price)
+                    pay_used_html += `<tr class="text-center"><td>${used}</td><td>${used_sum_price.format2Str('#,##0.######')}</td> <td>${(pay_sum_price ? ZhCalc.div(used_sum_price, pay_sum_price) * 100 : 0).toFixed(2)}%</td></tr>`;
+                    payUsedOption.series[0].data.push({ value: used_sum_price, name: used });
+                }
+            }
+            pay_used_html += `<tr class="text-center"><td><strong>合计</strong></td><td>${pay_sum_price.format2Str('#,##0.######')}</td> <td></td></tr>`;
+            pay_used_html += `<tr class="text-center"><td>其中:小额支出</td><td>${small_expenses_sum_price.format2Str('#,##0.######')}</td><td>${(pay_sum_price ? ZhCalc.div(small_expenses_sum_price, pay_sum_price) * 100 : 0).toFixed(2)}%</td></tr>`;
+            $('#pay_used_table').html(pay_used_html);
+
+            // 标段支出概况图表
+            tenderOption.xAxis[0].data = _.map(tenders, 'name');
+            tenderOption.dataZoom[0].start = 0;
+            tenderOption.dataZoom[0].end = computedPosition(tenders.length);
+            tenderOption.series[0].data = [];
+            tenderOption.series[1].data = [];
+            for (const t of tenders) {
+                const transferTenders = _.filter(transferTenderList, { tid: t.id });
+                const transfer_tender_price = ZhCalc.sum(_.map(transferTenders, 'hb_tp'));
+                const payTenders = _.filter(payList, { tid: t.id });
+                const pay_tender_price = ZhCalc.sum(_.map(payTenders, 'total_price'));
+                tenderOption.series[0].data.push(transfer_tender_price);
+                tenderOption.series[1].data.push(pay_tender_price);
+            }
+            tenderChart.setOption(tenderOption);
+
+            // 支出明细占比图表
+            payUsedChart.setOption(payUsedOption);
+
+            // 小额支出占比
+            paySeOption.series[0].data = [
+                { value: small_expenses_sum_price, name: '小额支出' },
+                { value: ZhCalc.sub(pay_sum_price, small_expenses_sum_price), name: '其他' },
+            ];
+            paySeChart.setOption(paySeOption);
+        },
+    }
+
+    let resizeTimer = null;
+    $(window).bind('resize', function () {
+        if (resizeTimer) clearTimeout(resizeTimer);
+        resizeTimer = setTimeout(function () {
+            echartsReset();
+        }, 500);
+    })
+
+    function echartsReset() {
+        tenderChart.resize();
+        payUsedChart.resize();
+        paySeChart.resize();
+    }
+
+    // 计算显示滚动条长度
+    function computedPosition(xArrayLength) {
+        if (xArrayLength >= 8) {
+            return Math.floor(8 / xArrayLength * 100) > 100 ? 100 : Math.floor(8 / xArrayLength * 100);
+            // return length <= 10 ? 35 : 100 - Math.floor(35 / length * 100);
+        } else {
+            return 100;
+        }
+    }
+
+    //标段占比
+    const tenderChart = echarts.init(document.getElementById('jlchart3'));
+    const tenderOption = {
+        tooltip: {
+            trigger: 'axis',
+            axisPointer: {
+                type: 'shadow'
+            }
+        },
+        dataZoom: [
+            {
+                // brushSelect:false,
+                // zoomLock: false,
+                type: 'slider',
+                // show: true,
+                // realtime: true,
+                showdetail: false,
+                showDataShadow: false,
+                // // dataZoomIndex: 10,
+                start: 0,
+                end: 10,
+                height: 10,
+                bottom: '5%',
+            },
+        ],
+        legend: {},
+        grid: {
+            left: '3%',
+            right: '4%',
+            bottom: '10%',
+            containLabel: true
+        },
+        xAxis: [
+            {
+                type: 'category',
+                data: [],
+                axisLabel: {
+                    show: true,
+                    interval: 0,
+                    formatter: function(value) {
+                        var res = value;
+                        if(res.length > 8) {
+                            res = res.substring(0, 7) + "..";
+                        }
+                        return res;
+                    }
+                }
+            }
+        ],
+        yAxis: [
+            {
+                type: 'value'
+            }
+        ],
+        series: [
+            {
+                name: '累计划拨',
+                type: 'bar',
+                emphasis: {
+                    focus: 'series'
+                },
+                data: []
+            },
+            {
+                name: '累计支付',
+                type: 'bar',
+                stack: 'Ad',
+                emphasis: {
+                    focus: 'series'
+                },
+                data: []
+            }
+
+        ]
+    };
+    // tenderChart.setOption(tenderOption);
+
+    //费用明细占比//
+    const payUsedChart = echarts.init(document.getElementById('jlchart1'));
+    const payUsedOption = {
+        color: ['#9489fa', '#f06464', '#f7af59', '#f0da49', '#71c16f', '#2aaaef', '#5690dd', '#bd88f5', '#009db2', '#0780cf'],
+        //backgroundColor: '#343a40 ',
+        tooltip: {
+            trigger: 'item'
+        },
+        legend: {
+            orient: 'vertical',
+            left:'60%',
+            right:'20%',
+            top: 'middle'
+        },
+        series: [
+            {
+                name: '金额',
+                type: 'pie',
+                top:'20',
+                bottom:'20',
+                right:'40%',
+                radius: ['80%'],
+                avoidLabelOverlap: false,
+                label: {
+                    show: false,
+                    position: 'right'
+                },
+                emphasis: {
+                    label: {
+                        show: true,
+                        fontSize: '40',
+                        fontWeight: 'bold'
+                    }
+                },
+                labelLine: {
+                    show: false
+                },
+                data: [],
+            }
+        ]
+    };
+
+    // 为echarts对象加载数据
+    // myChart1.setOption(option1);
+
+    //小额支出概况//
+    var paySeChart = echarts.init(document.getElementById('jlchart2'));
+    var paySeOption = {
+        color: [ '#71c16f', '#2aaaef'],
+        //backgroundColor: '#343a40 ',
+        tooltip: {
+            trigger: 'item'
+        },
+        legend: {
+            orient: 'vertical',
+            left:'60%',
+            right: '20%',
+            top: 'middle'
+        },
+        series: [
+            {
+                name: '金额',
+                type: 'pie',
+                top:'20',
+                bottom:'20',
+                right:'40%',
+                radius: ['80%'],
+                avoidLabelOverlap: false,
+                label: {
+                    show: false,
+                    position: 'right'
+                },
+                emphasis: {
+                    label: {
+                        show: true,
+                        fontSize: '40',
+                        fontWeight: 'bold'
+                    }
+                },
+                labelLine: {
+                    show: false
+                },
+                data: []
+            }
+        ]
+    };
+
+    // 为echarts对象加载数据
+    // myChart2.setOption(option2);
+
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        key: 'menu.1.0.0',
+        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            autoFlashHeight();
+            echartsReset();
+        }
+    });
+});

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

@@ -43,9 +43,25 @@ $(function () {
 
     $('#tender-list input[type="text"]').change(function () {
         const ftid = $(this).data('ftid');
+        if (!ftid) {
+            toastr.error('获取资金划拨标段信息失败');
+            return;
+        }
+        const ftInfo = _.find(tenderList, { id: parseInt(ftid) });
+        if (!ftInfo) {
+            toastr.error('获取资金划拨信息失败');
+            return;
+        }
         const value = $(this).val() || 0;
+        // 判断只能输入数字,支持负数和小数
+        if (!/^-?\d+(\.\d+)?$/.test(value)) {
+            toastr.error('请输入正确的金额');
+            $(this).val(ftInfo.hb_tp);
+            return;
+        }
         postData(window.location.pathname + '/update', {postType: 'update-hb_tp', postData: { node: ftid, hb_tp: parseFloat(value) }}, function (result) {
-            window.location.reload();
+            // window.location.reload();
+            ftInfo.hb_tp = parseFloat(value);
         });
     });
 

+ 71 - 28
app/public/js/global.js

@@ -40,7 +40,18 @@ function autoFlashHeight(){
     }
     /*工程变更添加清单高度*/
     $(".sjs-biangeng-height").height($(window).height()/3.3);
+    // 大于高度时隐藏nav文字
+    if (!navOriginalHeight) {
+        navOriginalHeight = getObjHeight($('.nav-top')) + getObjHeight($('.nav-bottom')) + getObjHeight($('.logo'));
+    }
+    if (navOriginalHeight > getObjHeight($('.main-nav'))) {
+        $('.bg-nav li a span').hide();
+    } else {
+        $('.bg-nav li a span').show();
+    }
+
 };
+let navOriginalHeight = false;
 $(window).resize(autoFlashHeight);
 /*全局自适应高度结束*/
 $(function(){
@@ -1022,38 +1033,70 @@ $.fn.extend({
 });
 
 const checkUtils = {
-    posOver(data) {
-        if (!data) return false;
-        if (!data.quantity) return !!data.end_contract_qty;
-        return data.quantity > 0
-            ? data.end_contract_qty > data.final_1_qty
-            : (data.final_1_qty > 0 ? true : data.end_contract_qty < data.final_1_qty || data.end_contract_qty > 0);
+    _checkPosOverRangeTz(p, coe) {
+        const end_contract_qty = ZhCalc.add(p.pre_contract_qty, p.contract_qty);
+        const base_qty = p.quantity;
+        const compare_qty = ZhCalc.mul(p.final_1_qty, coe);
+        if (!base_qty) return !!end_contract_qty;
+        return base_qty > 0
+            ? end_contract_qty > compare_qty
+            : (compare_qty > 0 ? true : end_contract_qty < compare_qty || end_contract_qty > 0);
     },
-    billsOver(data, isTz, relaPos) {
-        if (!data) return false;
-        if (isTz) {
-            const posRange = relaPos.ledgerPos[itemsPre + data.id] || [];
-            if (posRange.length > 0) {
-                for (const p of posRange) {
-                    if (checkUtils.posOver(p)) return true;
-                }
-            }
+    _checkBillsOverRangeTz(bills, coe) {
+        const end_contract_qty = ZhCalc.add(bills.contract_qty, bills.pre_contract_qty);
+        const end_contract_tp = ZhCalc.add(bills.contract_tp, bills.pre_contract_tp);
+        if (bills.is_tp) {
+            const base_tp = bills.total_price;
+            const compare_tp = ZhCalc.mul(base_tp, coe);
+            if (!base_tp) return !!end_contract_tp;
+            return base_tp >= 0 ? end_contract_tp > compare_tp : end_contract_tp < compare_tp || end_contract_tp > 0;
+        } else {
+            const base_qty = bills.quantity;
+            const compare_qty = ZhCalc.mul(bills.final_1_qty, coe);
+            if (!base_qty) return !!end_contract_qty;
+            return base_qty > 0
+                ? end_contract_qty > compare_qty
+                : (compare_qty > 0 ? true : end_contract_qty < compare_qty || end_contract_qty > 0);
         }
-
-        if (data.is_tp) {
-            const compare_tp = isTz ? data.total_price : data.deal_tp;
-            if (!compare_tp) return !!data.end_contract_tp;
-            return compare_tp > 0
-                ? data.end_contract_tp > compare_tp
-                : data.end_contract_tp < compare_tp || data.end_contract_tp > 0;
+    },
+    _checkBillsOverRangeDeal(bills, coe) {
+        const end_contract_qty = ZhCalc.add(bills.contract_qty, bills.pre_contract_qty);
+        const end_contract_tp = ZhCalc.add(bills.contract_tp, bills.pre_contract_tp);
+        if (bills.is_tp) {
+            const base_tp = bills.deal_tp;
+            const compare_tp = ZhCalc.mul(base_tp, coe);
+            if (!base_tp) return !!end_contract_tp;
+            return base_tp >= 0 ? end_contract_tp > compare_tp : end_contract_tp < compare_tp || end_contract_tp > 0;
         } else {
-            const compare_qty1 = isTz ? data.quantity : data.deal_qty;
-            const compare_qty2 = isTz ? data.final_1_qty : data.deal_final_1_qty;
-            if (!compare_qty1) return !!data.end_contract_qty;
-            return compare_qty1 > 0
-                ? data.end_contract_qty > compare_qty2
-                : (compare_qty2 > 0 ? true : data.end_contract_qty < compare_qty2 || data.end_contract_qty > 0);
+            const base_qty = bills.deal_qty;
+            const compare_qty = ZhCalc.mul(bills.deal_final_1_qty, coe);
+            if (!base_qty) return !!end_contract_qty;
+            return base_qty > 0
+                ? end_contract_qty > compare_qty
+                : (compare_qty > 0 ? true : end_contract_qty < compare_qty || end_contract_qty > 0);
+        }
+    },
+    posOver(pos, checkInfo) {
+        if (!pos) return false;
+        const checkTz = checkInfo.checkTz ? this._checkPosOverRangeTz(pos, checkInfo.coe) : false;
+        const checkDeal = false;
+        return checkTz || checkDeal;
+    },
+    billsOver(bills, relaPos, checkInfo) {
+        if (!bills) return false;
+
+        const posRange = relaPos.ledgerPos[itemsPre + bills.id] || [];
+        if (checkInfo.hasPosCheckPos && posRange.length > 0) {
+            for (const p of posRange) {
+                if (this.posOver(p, checkInfo)) return true;
+            }
+        }
+        if (checkInfo.hasPosCheckBills || posRange.length === 0) {
+            const checkTz = checkInfo.checkTz ? this._checkBillsOverRangeTz(bills, checkInfo.coe) : false;
+            const checkDeal = checkInfo.checkDeal ? this._checkBillsOverRangeDeal(bills, checkInfo.coe) : false;
+            return checkTz || checkDeal;
         }
+        return false;
     },
     compareCode(str1, str2, symbol = '-') {
         if (!str1) {

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

@@ -33,7 +33,7 @@ function getExprInfo (field, converse = false) {
     return _.find(exprField, {qty: field});
 }
 function transExpr(expr) {
-    return expr.replace('=', '').replace('%', '/100');
+    return expr.replace('=', '').replace(new RegExp('%', 'gm'), '/100');
 }
 const checkOption = {
     sibling: { enable: 1 },
@@ -4962,6 +4962,22 @@ $(document).ready(function() {
           $('#showAttachment').attr('file-id', '');
       });
   });
+    $('#upload-ybp-file').click(function () {
+        const file = $('#ybp-file')[0];
+        const formData = new FormData();
+        formData.append('file', file.files[0]);
+        postDataWithFile('ledger/ybp', formData, function (result) {
+            ledgerTree.loadDatas(result.bills);
+            treeCalc.calculateAll(ledgerTree);
+            SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), 'tree', ledgerTree);
+            pos.loadDatas(result.pos);
+            posOperationObj.loadCurPosData();
+            checkShowLast(result.bills.length);
+            $('#upload-ybp').modal('hide');
+        }, function () {
+            $('#upload-ybp').modal('hide');
+        });
+    });
 });
 // 生成当前节点列表
 function getNodeList(node) {

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

@@ -152,7 +152,7 @@ const ledgerCheckUtil = {
         for (const node of ledgerTree.nodes) {
             if (node.children && node.children.length > 0) continue;
 
-            if (checkUtils.billsOver(node, option.isTz, ledgerPos)) error.push(node);
+            if (checkUtils.billsOver(node, ledgerPos, option.checkInfo)) error.push(node);
         }
         return error;
     },

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

@@ -903,7 +903,7 @@ $(document).ready(() => {
                         const [valid, msg] = this._checkExprValid(expr);
                         if (!valid) return [valid, msg];
                         data.expr = expr;
-                        data.quantity = ZhCalc.calcExpr.calcExprStrRpn(expr.replace('%', '/100'));
+                        data.quantity = ZhCalc.calcExpr.calcExprStrRpn(expr.replace(new RegExp('%', 'gm'), '/100'));
                     }
                 } else {
                     data.quantity = 0;

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

@@ -1801,7 +1801,7 @@ $(document).ready(() => {
                         const [valid, msg] = this._checkExprValid(expr);
                         if (!valid) return [valid, msg];
                         data.expr = expr;
-                        data.quantity = ZhCalc.calcExpr.calcExprStrRpn(expr.replace('%', '/100'));
+                        data.quantity = ZhCalc.calcExpr.calcExprStrRpn(expr.replace(new RegExp('%', 'gm'), '/100'));
                         // const ce = new CalcEvalMin();
                         // data.quantity = ce.eval(expr);
                         // console.log(data.quantity);

+ 37 - 7
app/public/js/measure_compare.js

@@ -22,6 +22,10 @@ const billsSpreadSetting = {
         {title: '%s|数量', colSpan: '2|1', rowSpan: '1|1', field: '{%s}_qty{%d}', hAlign: 2, width: 60, type: 'Number', },
         {title: '|金额', colSpan: '|1', rowSpan: '|1', field: '{%s}_tp{%d}', hAlign: 2, width: 60, type: 'Number', },
     ],
+    specExtraCols: [
+        {title: '合计|数量', colSpan: '2|1', rowSpan: '1|1', field: 'sum_{%s}_qty', hAlign: 2, width: 60, type: 'Number', },
+        {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'sum_{%s}_tp', hAlign: 2, width: 60, type: 'Number', },
+    ],
     emptyRows: 3,
     headRows: 2,
     headRowHeight: [25, 25],
@@ -34,10 +38,13 @@ const billsSpreadSetting = {
 const posSpreadSetting = {
     baseCols: [
         {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 230, formatter: '@'},
-        {title: '台账数量', colSpan: '1', rowSpan: '1', field: 'quantity', hAlign: 2, width: 60},
+        {title: '台账数量', colSpan: '1', rowSpan: '1', field: 'quantity', hAlign: 2, width: 60, type: 'Number'},
     ],
     extraCols: [
-        {title: '%s数量', colSpan: '1', rowSpan: '1', field: '{%s}_qty{%d}', hAlign: 2, width: 60},
+        {title: '%s数量', colSpan: '1', rowSpan: '1', field: '{%s}_qty{%d}', hAlign: 2, width: 60, type: 'Number'},
+    ],
+    specExtraCols: [
+        {title: '合计数量', colSpan: '1', rowSpan: '1', field: 'sum_{%s}_qty', hAlign: 2, width: 60, type: 'Number', },
     ],
     emptyRows: 3,
     headRows: 1,
@@ -63,6 +70,10 @@ const exportBillsSpreadSetting = {
         {title: '%s|数量', colSpan: '2|1', rowSpan: '1|1', field: '{%s}_qty{%d}', hAlign: 2, width: 60, type: 'Number', },
         {title: '|金额', colSpan: '|1', rowSpan: '|1', field: '{%s}_tp{%d}', hAlign: 2, width: 60, type: 'Number', },
     ],
+    specExtraCols: [
+        {title: '合计|数量', colSpan: '2|1', rowSpan: '1|1', field: 'sum_{%s}_qty', hAlign: 2, width: 60, type: 'Number', },
+        {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'sum_{%s}_tp', hAlign: 2, width: 60, type: 'Number', },
+    ],
     endCols: [],
     emptyRows: 3,
     headRows: 2,
@@ -89,6 +100,10 @@ const gclSpreadSetting = {
         {title: '%s|数量', colSpan: '2|1', rowSpan: '1|1', field: '{%s}_qty{%d}', hAlign: 2, width: 60, type: 'Number', },
         {title: '|金额', colSpan: '|1', rowSpan: '|1', field: '{%s}_tp{%d}', hAlign: 2, width: 60, type: 'Number', },
     ],
+    specExtraCols: [
+        {title: '合计|数量', colSpan: '2|1', rowSpan: '1|1', field: 'sum_{%s}_qty', hAlign: 2, width: 60, type: 'Number', },
+        {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'sum_{%s}_tp', hAlign: 2, width: 60, type: 'Number', },
+    ],
     emptyRows: 0,
     headRows: 2,
     headRowHeight: [25, 25],
@@ -112,6 +127,9 @@ const leafXmjSpreadSetting = {
     extraCols: [
         {title: '%s数量', colSpan: '1', rowSpan: '2', field: '{%s}_qty{%d}', hAlign: 2, width: 60},
     ],
+    specExtraCols: [
+        {title: '合计数量', colSpan: '1', rowSpan: '2', field: 'sum_{%s}_qty', hAlign: 2, width: 60, type: 'Number', },
+    ],
     emptyRows: 0,
     headRows: 2,
     headRowHeight: [25, 25],
@@ -142,6 +160,10 @@ const exportGclSpreadSetting = {
         {title: '%s|数量', colSpan: '2|1', rowSpan: '1|1', field: '{%s}_qty{%d}', hAlign: 2, width: 60, type: 'Number', },
         {title: '|金额', colSpan: '|1', rowSpan: '|1', field: '{%s}_tp{%d}', hAlign: 2, width: 60, type: 'Number', },
     ],
+    specExtraCols: [
+        {title: '合计|数量', colSpan: '2|1', rowSpan: '1|1', field: 'sum_{%s}_qty', hAlign: 2, width: 60, type: 'Number', },
+        {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'sum_{%s}_tp', hAlign: 2, width: 60, type: 'Number', },
+    ],
     endCols: [
         {title: '图册号', colSpan: '1', rowSpan: '2', field: 'drawing_code', hAlign: 0, width: 80, formatter: '@'},
     ],
@@ -175,6 +197,14 @@ function initSpreadSettingWithRoles(compareRoles) {
         for (const index in fieldSufs) {
             addExtraCols(fieldSufs[index], Roles[index]);
         }
+        if (setting.specExtraCols) {
+            const sourceType = $('[name=compare-data]:checked').val();
+            for (const col of setting.specExtraCols) {
+                const eCol = JSON.parse(JSON.stringify(col));
+                eCol.field = _.replace(eCol.field, '{%s}', sourceType);
+                setting.cols.push(eCol);
+            }
+        }
         if (setting.endCols) {
             for (const col of setting.endCols) {
                 setting.cols.push(col);
@@ -283,7 +313,7 @@ $(document).ready(() => {
         keys: ['id', 'tender_id', 'ledger_id'],
         masterId: 'id',
         minorId: 'lid',
-        calcFields: ['total_price'],
+        calcFields: ['total_price', 'sum_contract_tp', 'sum_qc_tp', 'sum_gather_tp'],
         markFoldKey: 'bills-fold',
         markFoldSubKey: window.location.pathname.split('/')[2],
     });
@@ -481,14 +511,14 @@ $(document).ready(() => {
     $('[name=compare-data]').click(function () {
         initSpreadSettingWithRoles(compareStages);
         SpreadJsObj.reLoadSheetHeader(billsSheet);
-        SpreadJsObj.reloadColData(billsSheet, billsSpreadSetting.baseCols.length, compareStages.length * billsSpreadSetting.extraCols.length);
+        SpreadJsObj.reloadColData(billsSheet, billsSpreadSetting.baseCols.length, compareStages.length * billsSpreadSetting.extraCols.length + billsSpreadSetting.specExtraCols.length);
         SpreadJsObj.reLoadSheetHeader(posSheet);
-        SpreadJsObj.reloadColData(posSheet, posSpreadSetting.baseCols.length, compareStages.length * posSpreadSetting.extraCols.length);
+        SpreadJsObj.reloadColData(posSheet, posSpreadSetting.baseCols.length, compareStages.length * posSpreadSetting.extraCols.length + billsSpreadSetting.specExtraCols.length);
 
         SpreadJsObj.reLoadSheetHeader(gclSheet);
-        SpreadJsObj.reloadColData(gclSheet, gclSpreadSetting.baseCols.length, compareStages.length * gclSpreadSetting.extraCols.length);
+        SpreadJsObj.reloadColData(gclSheet, gclSpreadSetting.baseCols.length, compareStages.length * gclSpreadSetting.extraCols.length + billsSpreadSetting.specExtraCols.length);
         SpreadJsObj.reLoadSheetHeader(leafXmjSheet);
-        SpreadJsObj.reloadColData(leafXmjSheet, leafXmjSpreadSetting.baseCols.length, compareStages.length * leafXmjSpreadSetting.extraCols.length);
+        SpreadJsObj.reloadColData(leafXmjSheet, leafXmjSpreadSetting.baseCols.length, compareStages.length * leafXmjSpreadSetting.extraCols.length + billsSpreadSetting.specExtraCols.length);
     });
     $('[name=compareType]').click(function () {
         $('[name=compareType]').removeClass('active');

+ 2 - 0
app/public/js/path_tree.js

@@ -259,6 +259,7 @@ class MasterPosData extends PosData {
                 for (const prop of fields) {
                     if (data[prop] !== undefined) {
                         node[prop + fieldSuf] = data[prop];
+                        node['sum_' + prop] = ZhCalc.add(node['sum_' + prop], data[prop]);
                     }
                 }
                 loadedData.push(node);
@@ -1635,6 +1636,7 @@ const createNewPathTree = function (type, setting) {
                     for (const prop of fields) {
                         if (data[prop] !== undefined) {
                             node[prop + fieldSuf] = data[prop];
+                            node['sum_' + prop] = ZhCalc.add(node['sum_' + prop], data[prop]);
                         }
                     }
                     loadedData.push(node);

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

@@ -25,7 +25,7 @@ function getExprInfo (field, converse = false) {
     return converse ?  _.find(exprField, {expr: field}) : _.find(exprField, {qty: field});
 }
 function transExpr(expr) {
-    return $.trim(expr).replace('\t', '').replace('=', '').replace('%', '/100');
+    return $.trim(expr).replace('\t', '').replace('=', '').replace(new RegExp('%', 'gm'), '/100');
 }
 const copyBlockTag = 'zh.calc.copyBlock';
 const checkOption = {

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

@@ -492,7 +492,7 @@ $(document).ready(() => {
                     }
                 }
             },
-        })
+        });
     }
 
     $('#exportExcel').click(function () {

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

@@ -8,7 +8,7 @@ class srObject {
         const srSpreadSetting = {
             cols: [
                 {title: '选择', field: 'selected', hAlign: 1, width: 40, formatter: '@', cellType: 'checkbox'},
-                {title: '名称', field: 'name', hAlign: 0, width: 180, formatter: '@', cellType: 'tree'},
+                {title: '名称', field: 'name', hAlign: 0, width: 440, formatter: '@', cellType: 'tree', wordWrap: true},
                 {title: '期数', field: 'lastStageOrder', hAlign: 1, width: 60, formatter: '@'},
                 {title: '审批状态', field: 'lastStageStatus', hAlign: 1, width: 60, formatter: '@'},
             ],
@@ -28,7 +28,7 @@ class srObject {
 
         const srResultSpreadSetting = {
             cols: [
-                {title: '名称', field: 'name', hAlign: 0, width: 180, formatter: '@', },
+                {title: '名称', field: 'name', hAlign: 0, width: 400, formatter: '@', wordWrap: true },
             ],
             emptyRows: 0,
             headRows: 1,

+ 38 - 13
app/public/js/shares/tools_att.js

@@ -14,6 +14,7 @@
         if (!setting.selector) return;
         if (!setting.masterKey) setting.masterKey = setting.key;
         const obj = $(setting.selector);
+        const fileInfo = setting.fileInfo || { user_name: 'username', user_id: 'uid', create_time: 'in_time' };
         const pageLength = 20;
         let curNode = null, curPage = 0;
         obj.html(
@@ -53,17 +54,20 @@
 
         let allAtts = [], nodeIndexes = {};
 
+        const getNodeHint = function(node) {
+            return setting.getCurHint ? setting.getCurHint(node) : `${node.code || node.b_code || ''}/${node.name || ''}`;
+        };
         const getAttHtml = function(att, tipNode = false) {
             const html = [];
             html.push('<tr>');
             html.push(`<td width="25"><input type="checkbox" class="check-file" file-id=${att.id}></td>`);
             let nodeInfo = '';
-            if (tipNode && att.node) nodeInfo = `${att.node.code || att.node.b_code || ''}/${att.node.name || ''}`;
-            const tipHtml = nodeInfo ? `${nodeInfo}\n${att.in_time}` : att.in_time;// nodeInfo ? `${nodeInfo}<br/>${att.in_time}` : att.in_time;
+            if (tipNode && att.node) nodeInfo = getNodeHint(att.node);
+            const tipHtml = nodeInfo ? `${nodeInfo}\n${att[fileInfo.create_time]}` : att[fileInfo.create_time];
             const tipType = 'title='; //'data-toggle="tooltip" data-html="true" data-placement="left" data-original-title=';
             html.push(`<td><div class="d-flex"><a href="javascript:void(0)" ${tipType}"${tipHtml}" class="pl-0 col-11" file-id=${att.id}>${att.filename}${att.fileext}</a></div></td>`);
-            html.push(`<td>${att.username}</td>`);
-            const canDel = setting.readOnly ? false : att.uid === userID && (!setting.checked || att.extra_upload);
+            html.push(`<td>${att[fileInfo.user_name]}</td>`);
+            const canDel = setting.readOnly ? false : att[fileInfo.user_id] === userID && (!setting.checked || att.extra_upload);
             html.push('<td width="80">',
                 `<a class="ml-1" href="javascript:void(0)" ${tipType}"定位" name="att-locate" file-id="${att.id}"><i class="fa fa-crosshairs"></i></a>`,
                 att.viewpath ? `<a class="ml-1" href="${att.viewpath}" ${tipType}"预览"  target="_blank"><i class="fa fa-eye"></i></a>` : '',
@@ -105,12 +109,18 @@
         const getCurAttHtml = function (node) {
             curNode = node;
             if (curNode) {
-                $('#att-cur-hint').text(`${curNode.code || curNode.b_code || ''}/${curNode.name || ''}`);
+                $('#att-cur-hint').text(getNodeHint(curNode));
             } else {
                 $('#att-cur-hint').text('');
             }
             refreshCurAttHtml();
         };
+        const findFile = setting.fileIdType === 'string'
+            ? function (list, id) { return list.find(item => item.id === id); }
+            : function (list, id) { return list.find(item => item.id === parseInt(id)); };
+        const findFileIndex = setting.fileIdType === 'string'
+            ? function (list, id) { return list.findIndex(item => item.id === id); }
+            : function (list, id) { return list.findIndex(item => item.id === parseInt(id)); };
 
         // 选中行
         $('body').on('click', '#all-att-list tr', function() {
@@ -160,9 +170,7 @@
             }
             $(node).each(function() {
                 const fid = $(this).attr('file-id');
-                const att = allAtts.find(function (item) {
-                    return item.id === parseInt(fid);
-                });
+                const att = findFile(allAtts, fid);
                 att && files.push(att);
             });
 
@@ -189,7 +197,7 @@
             }
             const files = $('#upload-file')[0].files;
             const formData = new FormData();
-            formData.append('lid', curNode[setting.key]);
+            formData.append(setting.masterKey, curNode[setting.key]);
             for (const file of files) {
                 if (file === undefined) {
                     toastr.error('未选择上传文件!');
@@ -227,7 +235,7 @@
         });
         $('body').on('click', 'a[name=att-locate]', function () {
             const fid = this.getAttribute('file-id');
-            const att = allAtts.find(item => item.id === parseInt(fid));
+            const att = findFile(allAtts, fid);
             setting.locate && setting.locate(att);
         });
         $('body').on('click', 'a[name=att-delete]', function () {
@@ -235,11 +243,11 @@
             const data = {id: fid};
             postData(setting.deleteUrl, data, function (result) {
                 // 删除
-                const att_index = allAtts.findIndex(item => { return item.id === parseInt(fid); });
+                const att_index = findFileIndex(allAtts, fid);
                 const att = allAtts[att_index];
                 allAtts.splice(att_index, 1);
                 const xi = nodeIndexes[att.node[setting.key]];
-                xi.splice(xi.findIndex(x => { return x.id === parseInt(fid); }), 1);
+                xi.splice(findFileIndex(xi, fid), 1);
                 // 重新生成List
                 if (allAtts.length === 1) {
                     getAllAttHtml();
@@ -282,7 +290,24 @@
             }
             getAllAttHtml();
         };
+        const deleteFileByNodeId = function(nodeIds) {
+            for (const id of nodeIds) {
+                const nodeIndex = nodeIndexes[id];
+                if (!nodeIndex || nodeIndex.length === 0) continue;
+                for (const att of nodeIndex) {
+                    const att_index = findFileIndex(allAtts, att.id);
+                    allAtts.splice(att_index, 1);
+                }
+                delete nodeIndexes[id];
+            }
+            if (allAtts.length === 1) {
+                getAllAttHtml();
+            } else {
+                refreshAllAttHtml();
+            }
+            refreshCurAttHtml();
+        };
 
-        return { loadDatas, getCurAttHtml }
+        return { loadDatas, getCurAttHtml, deleteFileByNodeId }
     };
 })(jQuery);

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

@@ -194,6 +194,7 @@ const TreeExprCalc = (function(){
                 formula = formula.replace(ip, getIdParamValue(ip));
             }
         }
+        if (formula.indexOf('%') >= 0) formula = formula.replace(new RegExp('%', 'gm'), '/100');
         return [formula, math.evaluate(formula)];
     };
     const addCache = function(expr) {

+ 617 - 0
app/public/js/sp_progress.js

@@ -0,0 +1,617 @@
+const showSideTools = function (show) {
+    const left = $('#left-view'), right = $('#right-view'), parent = left.parent();
+    if (show) {
+        right.show();
+        autoFlashHeight();
+        /**
+         * right.show()后, parent被撑开成2倍left.height, 导致parent.width减少了10px
+         * 第一次left.width调整后,parent的缩回left.height, 此时parent.width又增加了10px
+         * 故需要通过最终的parent.width再计算一次left.width
+         *
+         * Q: 为什么不通过先计算left.width的宽度,以避免计算两次left.width?
+         * A: 右侧工具栏不一定显示,当右侧工具栏显示过一次后,就必须使用parent和right来计算left.width
+         *
+         */
+            //left.css('width', parent.width() - right.outerWidth());
+            //left.css('width', parent.width() - right.outerWidth());
+        const percent = 100 - right.outerWidth() /parent.width() * 100;
+        left.css('width', percent + '%');
+    } else {
+        left.width(parent.width());
+        right.hide();
+    }
+};
+const progressCombo = [{ text: '', value: '' }, { text: '进行中', value: '进行中' }, { text: '已完成', value: '已完成' }];
+
+$(document).ready(() => {
+    autoFlashHeight();
+    let datepicker;
+
+    class ProgressObj {
+        constructor() {
+            this.spread = SpreadJsObj.createNewSpread($('#progress-spread')[0]);
+            this.sheet = this.spread.getActiveSheet();
+            this.treeSetting = {
+                id: 'tree_id',
+                pid: 'tree_pid',
+                order: 'tree_order',
+                level: 'tree_level',
+                isLeaf: 'tree_is_leaf',
+                fullPath: 'tree_full_path',
+                rootId: -1,
+                calcFields: [],
+                keys: ['id', 'spid', 'tree_id'],
+            };
+            this.tree = createNewPathTree('ledger', this.treeSetting);
+            this.spreadSetting = {
+                cols: [
+                    {title: '序号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 165, formatter: '@', cellType: 'tree'},
+                    {title: '阶段/项目名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 210, formatter: '@', cellType: 'autoTip'},
+                    {title: '成果编制|进度', colSpan: '3|1', rowSpan: '1|1', field: 'edit_progress', hAlign: 1, width: 80, formatter: '@', cellType: 'customizeCombo', comboItems: progressCombo },
+                    {
+                        title: '|日期', colSpan: '|1', rowSpan: '|1', field: 'edit_date', hAlign: 2, width: 80, formatter: '@', readOnly: true,
+                        formatter: 'yyyy-MM-dd', cellType: 'activeImageBtn', normalImg: '#ellipsis-icon', indent: 5,
+                        showImage: function (data) {
+                            return data !== undefined && data !== null;
+                        }
+                    },
+                    {title: '|部门', colSpan: '|1', rowSpan: '|1', field: 'edit_department', hAlign: 2, width: 120, formatter: '@', },
+                    {title: '报审情况|进度', colSpan: '3|1', rowSpan: '1|1', field: 'submit_progress', hAlign: 1, width: 80, formatter: '@', cellType: 'customizeCombo', comboItems: progressCombo },
+                    {
+                        title: '|日期', colSpan: '|1', rowSpan: '|1', field: 'submit_date', hAlign: 2, width: 80, formatter: '@', readOnly: true,
+                        formatter: 'yyyy-MM-dd', cellType: 'activeImageBtn', normalImg: '#ellipsis-icon', indent: 5,
+                        showImage: function (data) {
+                            return data !== undefined && data !== null;
+                        }
+                    },
+                    {title: '|部门', colSpan: '|1', rowSpan: '|1', field: 'submit_department', hAlign: 2, width: 120, formatter: '@', },
+                    {title: '批复情况|进度', colSpan: '4|1', rowSpan: '1|1', field: 'reply_progress', hAlign: 1, width: 80, formatter: '@', cellType: 'customizeCombo', comboItems: progressCombo },
+                    {
+                        title: '|日期', colSpan: '|1', rowSpan: '|1', field: 'reply_date', hAlign: 2, width: 80, formatter: '@', readOnly: true,
+                        formatter: 'yyyy-MM-dd', cellType: 'activeImageBtn', normalImg: '#ellipsis-icon', indent: 5,
+                        showImage: function (data) {
+                            return data !== undefined && data !== null;
+                        }
+                    },
+                    {title: '|部门', colSpan: '|1', rowSpan: '|1', field: 'reply_department', hAlign: 2, width: 120, formatter: '@', },
+                    {title: '|文号', colSpan: '|1', rowSpan: '|1', field: 'reply_code', hAlign: 2, width: 120, formatter: '@', },
+                    {title: '备注', colSpan: '1', rowSpan: '2', field: 'memo', hAlign: 0, width: 120, formatter: '@', cellType: 'ellipsisAutoTip'},
+                ],
+                emptyRows: 0,
+                headRows: 2,
+                headRowHeight: [25, 25],
+                defaultRowHeight: 21,
+                headerFont: '12px 微软雅黑',
+                font: '12px 微软雅黑',
+                localCache: {
+                    key: 'sub-proj-progress',
+                    colWidth: true,
+                },
+                readOnly: false,
+                imageClick: function (data, hitinfo) {
+                    if (!data) return;
+
+                    const setting = hitinfo.sheet.zh_setting;
+                    if (!setting) return;
+                    const col = setting.cols[hitinfo.col];
+                    if (!col || col.field.indexOf('_date') < 0) return;
+
+                    const pos = SpreadJsObj.getObjPos(hitinfo.sheet.getParent().qo);
+                    if (!datepicker) {
+                        datepicker = $('.datepicker-here').datepicker({
+                            language: 'zh',
+                            dateFormat: 'yyyy-MM-dd',
+                            autoClose: true,
+                            onSelect: function (formattedDate, date, inst) {
+                                if (!inst.visible) return;
+                                const sels = hitinfo.sheet.getSelections();
+                                if (!sels || !sels[0]) return;
+                                const node = SpreadJsObj.getSelectObject(hitinfo.sheet);
+                                const uData = {id: node.id};
+                                const relaCol = hitinfo.sheet.zh_setting.cols[sels[0].col];
+                                uData[relaCol.field] = formattedDate;
+
+                                postData('progress/update', {postType: 'update', postData: uData}, function (result) {
+                                    const refreshNode = progressObj.tree.loadPostData(result);
+                                    progressObj.refreshTree(refreshNode);
+                                }, function () {
+                                    SpreadJsObj.reLoadRowData(hitinfo.sheet, sels[0].row, 1);
+                                });
+                            }
+                        }).data('datepicker');
+                    }
+                    const value = hitinfo.sheet.getValue(hitinfo.row, hitinfo.col);
+                    if (value) {
+                        datepicker.selectDate(value);
+                    } else {
+                        datepicker.clear();
+                    }
+                    datepicker.show();
+                    $('#datepickers-container').css('top', hitinfo.cellRect.y + pos.y).css('left', hitinfo.cellRect.x + pos.x);
+                    // $('#datepickers-container').css('top', pos.y).css('left', pos.x);
+                }
+            };
+            this.ckSpread = window.location.pathname + '-progressSelect';
+
+            this.initSpread();
+            this.initOtherEvent();
+        }
+        initSpread() {
+            SpreadJsObj.initSheet(this.sheet, this.spreadSetting);
+            this.spread.bind(spreadNS.Events.SelectionChanged, this.selectionChanged);
+            this.spread.bind(spreadNS.Events.topRowChanged, this.topRowChanged);
+            this.spread.bind(spreadNS.Events.ClipboardChanging, function (e, info) {
+                const copyText = SpreadJsObj.getFilterCopyText(info.sheet);
+                SpreadJsObj.Clipboard.setCopyData(copyText);
+            });
+            this.spread.bind(spreadNS.Events.EditEnded, this.editEnded);
+            this.spread.bind(spreadNS.Events.EditStarting, this.editStarting);
+            this.spread.bind(spreadNS.Events.ClipboardPasting, this.clipboardPasting);
+            SpreadJsObj.addDeleteBind(this.spread, this.deletePress);
+        }
+        initOtherEvent() {
+            const self = this;
+            // 增删上下移升降级
+            $('a[name="base-opr"]').click(function () {
+                self.baseOpr(this.getAttribute('type'));
+            });
+        }
+        refreshOperationValid() {
+            const setObjEnable = function (obj, enable) {
+                if (enable) {
+                    obj.removeClass('disabled');
+                } else {
+                    obj.addClass('disabled');
+                }
+            };
+            const invalidAll = function () {
+                setObjEnable($('a[name=base-opr][type=add]'), false);
+                setObjEnable($('a[name=base-opr][type=delete]'), false);
+                setObjEnable($('a[name=base-opr][type=up-move]'), false);
+                setObjEnable($('a[name=base-opr][type=down-move]'), false);
+                setObjEnable($('a[name=base-opr][type=up-level]'), false);
+                setObjEnable($('a[name=base-opr][type=down-level]'), false);
+            };
+            const sel = this.sheet.getSelections()[0];
+            const row = sel ? sel.row : -1;
+            const tree = this.sheet.zh_tree;
+            if (!tree) {
+                invalidAll();
+                return;
+            }
+            const first = tree.nodes[row];
+            if (!first) {
+                invalidAll();
+                return;
+            }
+            let last = first, sameParent = true;
+            if (sel.rowCount > 1 && first) {
+                for (let r = 1; r < sel.rowCount; r++) {
+                    const rNode = tree.nodes[sel.row + r];
+                    if (!rNode) {
+                        sameParent = false;
+                        break;
+                    }
+                    if (rNode.tree_level > first.tree_level) continue;
+                    if ((rNode.tree_level < first.tree_level) || (rNode.tree_level === first.tree_level && rNode.tree_pid !== first.tree_pid)) {
+                        sameParent = false;
+                        break;
+                    }
+                    last = rNode;
+                }
+            }
+            const preNode = tree.getPreSiblingNode(first);
+            const valid = !this.sheet.zh_setting.readOnly;
+
+            setObjEnable($('a[name=base-opr][type=add]'), valid && first);
+            setObjEnable($('a[name=base-opr][type=delete]'), valid && first && sameParent && first.tree_level > 1);
+            setObjEnable($('a[name=base-opr][type=up-move]'), valid && first && sameParent && first.tree_level > 1 && preNode);
+            setObjEnable($('a[name=base-opr][type=down-move]'), valid && first && sameParent && first.tree_level > 1 && !tree.isLastSibling(last));
+            setObjEnable($('a[name=base-opr][type=up-level]'), valid && first && sameParent && tree.getParent(first) && first.tree_level > 2);
+            setObjEnable($('a[name=base-opr][type=down-level]'), valid && first && sameParent && first.tree_level > 1 && preNode );
+        }
+        loadRelaData() {
+            this.refreshOperationValid();
+            SpreadJsObj.saveTopAndSelect(this.sheet, this.ckSpread);
+            progressFile.getCurAttHtml(SpreadJsObj.getSelectObject(this.sheet));
+        }
+        refreshTree(data) {
+            const sheet = this.sheet;
+            SpreadJsObj.massOperationSheet(sheet, function () {
+                const tree = sheet.zh_tree;
+                // 处理删除
+                if (data.delete) {
+                    data.delete.sort(function (a, b) {
+                        return b.deleteIndex - a.deleteIndex;
+                    });
+                    for (const d of data.delete) {
+                        sheet.deleteRows(d.deleteIndex, 1);
+                    }
+                }
+                // 处理新增
+                if (data.create) {
+                    const newNodes = data.create;
+                    if (newNodes) {
+                        newNodes.sort(function (a, b) {
+                            return a.index - b.index;
+                        });
+
+                        for (const node of newNodes) {
+                            sheet.addRows(node.index, 1);
+                            SpreadJsObj.reLoadRowData(sheet, tree.nodes.indexOf(node), 1);
+                        }
+                    }
+                }
+                // 处理更新
+                if (data.update) {
+                    const rows = [];
+                    for (const u of data.update) {
+                        rows.push(tree.nodes.indexOf(u));
+                    }
+                    SpreadJsObj.reLoadRowsData(sheet, rows);
+                }
+                // 处理展开
+                if (data.expand) {
+                    const expanded = [];
+                    for (const e of data.expand) {
+                        if (expanded.indexOf(e) === -1) {
+                            const posterity = tree.getPosterity(e);
+                            for (const p of posterity) {
+                                sheet.setRowVisible(tree.nodes.indexOf(p), p.visible);
+                                expanded.push(p);
+                            }
+                        }
+                    }
+                }
+            });
+        }
+        loadData(datas) {
+            this.tree.loadDatas(datas);
+            SpreadJsObj.loadSheetData(this.sheet, SpreadJsObj.DataType.Tree, this.tree);
+            SpreadJsObj.loadTopAndSelect(this.sheet, this.ckSpread);
+            this.refreshOperationValid();
+        }
+        getDefaultSelectInfo() {
+            if (!this.tree) return;
+            const sel = this.sheet.getSelections()[0];
+            const node = this.sheet.zh_tree.nodes[sel.row];
+            if (!node) return;
+            let count = 1;
+            if (sel.rowCount > 1) {
+                for (let r = 1; r < sel.rowCount; r++) {
+                    const rNode = this.sheet.zh_tree.nodes[sel.row + r];
+                    if (rNode.tree_level > node.tree_level) continue;
+                    if ((rNode.tree_level < node.tree_level) || (rNode.tree_level === node.tree_level && rNode.tree_pid !== node.tree_pid)) {
+                        toastr.warning('请选择同一节点下的节点,进行该操作');
+                        return;
+                    }
+                    count += 1;
+                }
+            }
+            return [this.tree, node, count];
+        }
+        baseOpr(type, addCount = 1) {
+            const self = this;
+            const sheet = self.sheet;
+            const sel = sheet.getSelections()[0];
+            const [tree, node, count] = this.getDefaultSelectInfo();
+            if (!tree || !node || !count) return;
+
+            const updateData = {
+                postType: type,
+                postData: {
+                    id: node.tree_id,
+                    count: type === 'add' ? addCount : count,
+                }
+            };
+            if (type === 'delete') {
+                deleteAfterHint(function () {
+                    postData('progress/update', updateData, function (result) {
+                        progressFile.deleteFileByNodeId(result.delete.map(x => { return x.id; }));
+                        const refreshData = tree.loadPostData(result);
+                        self.refreshTree(refreshData);
+                        if (sel) {
+                            sheet.setSelection(sel.row, sel.col, 1, sel.colCount);
+                        }
+                        self.refreshOperationValid();
+                        self.loadRelaData();
+                    });
+                });
+            } else {
+                postData('progress/update', updateData, function (result) {
+                    const refreshData = tree.loadPostData(result);
+                    self.refreshTree(refreshData);
+                    if (['up-move', 'down-move'].indexOf(type) > -1) {
+                        if (sel) {
+                            sheet.setSelection(tree.nodes.indexOf(node), sel.col, sel.rowCount, sel.colCount);
+                        }
+                    } else if (type === 'add') {
+                        const sel = sheet.getSelections()[0];
+                        if (sel) {
+                            sheet.setSelection(tree.nodes.indexOf(refreshData.create[0]), sel.col, sel.rowCount, sel.colCount);
+                        }
+                    }
+                    self.refreshOperationValid();
+                });
+            }
+        }
+        // 事件
+        selectionChanged(e, info) {
+            if (info.newSelections) {
+                if (!info.oldSelections || info.newSelections[0].row !== info.oldSelections[0].row) {
+                    progressObj.loadRelaData();
+                }
+            }
+        }
+        topRowChanged(e, info) {
+            SpreadJsObj.saveTopAndSelect(info.sheet, progressObj.ckBillsSpread);
+        }
+        editEnded(e, info) {
+            if (!info.sheet.zh_setting) return;
+
+            const tree = info.sheet.zh_tree;
+            const node = SpreadJsObj.getSelectObject(info.sheet);
+            const data = { id: node.id, spid: node.spid, tree_id: node.tree_id };
+            // 未改变值则不提交
+            const col = info.sheet.zh_setting.cols[info.col];
+            const orgValue = node[col.field];
+            const newValue = trimInvalidChar(info.editingText);
+            if (orgValue == info.editingText || ((!orgValue || orgValue === '') && (newValue === ''))) return;
+
+            if (info.editingText) {
+                const text = newValue;
+                if (col.type === 'Number') {
+                    const num = _.toNumber(text);
+                    if (_.isFinite(num)) {
+                        data[col.field] = num;
+                    } else {
+                        try {
+                            data[col.field] = math.evaluate(transExpr(text));
+                        } catch(err) {
+                            toastr.error('输入的表达式非法');
+                            SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                            return;
+                        }
+                    }
+                } else {
+                    data[col.field] = text;
+                }
+            } else {
+                data[col.field] = col.type === 'Number' ? 0 : '';
+            }
+            // 更新至服务器
+            postData('progress/update', {postType: 'update', postData: data}, function (result) {
+                const refreshNode = progressObj.tree.loadPostData(result);
+                progressObj.refreshTree(refreshNode);
+            }, function () {
+                SpreadJsObj.reLoadRowData(info.sheet, info.row, 1);
+            });
+        }
+        editStarting(e, info) {
+            if (!info.sheet.zh_setting || !info.sheet.zh_tree) return;
+
+            const col = info.sheet.zh_setting.cols[info.col];
+            const node = info.sheet.zh_tree.nodes[info.row];
+            if (!node) {
+                info.cancel = true;
+                return;
+            }
+
+            switch (col.field) {
+                case 'edit_progress':
+                case 'edit_date':
+                case 'edit_department':
+                case 'submit_progress':
+                case 'submit_date':
+                case 'submit_department':
+                case 'reply_progress':
+                case 'reply_date':
+                case 'reply_department':
+                case 'reply_code':
+                    info.cancel = node.tree_level <= 1;
+                    break;
+            }
+        }
+        deletePress (sheet) {
+            if (!sheet.zh_setting) return;
+            const sel = sheet.getSelections()[0], datas = [];
+            for (let iRow = sel.row; iRow < sel.row + sel.rowCount; iRow++) {
+                let bDel = false;
+                const node = sheet.zh_tree.nodes[iRow];
+                const data = sheet.zh_tree.getNodeKeyData(node);
+                for (let iCol = sel.col; iCol < sel.col + sel.colCount; iCol++) {
+                    const col = sheet.zh_setting.cols[iCol];
+                    const style = sheet.getStyle(iRow, iCol);
+                    if (style.locked) continue;
+
+                    data[col.field] = col.type === 'Number' ? 0 : '';
+                    bDel = true;
+                }
+                if (bDel) datas.push(data);
+            }
+            if (datas.length > 0) {
+                postData('progress/update', {postType: 'update', postData: datas}, function (result) {
+                    const refreshNode = sheet.zh_tree.loadPostData(result);
+                    progressObj.refreshTree(refreshNode);
+                }, function () {
+                    SpreadJsObj.reLoadRowData(info.sheet, sel.row, sel.rowCount);
+                });
+            }
+        }
+        clipboardPasting(e, info) {
+            info.cancel = true;
+            const tree = info.sheet.zh_tree, setting = info.sheet.zh_setting;
+            if (!setting || !tree) return;
+
+            const pasteData = info.pasteData.html
+                ? SpreadJsObj.analysisPasteHtml(info.pasteData.html)
+                : (info.pasteData.text === ''
+                    ? SpreadJsObj.Clipboard.getAnalysisPasteText()
+                    : SpreadJsObj.analysisPasteText(info.pasteData.text));
+            const hint = {
+                invalidExpr: {type: 'warning', msg: '粘贴的表达式非法'},
+                parent: {type: 'warning', msg: '含有子项的清单,不可粘贴成果编制、报审情况、批复情况'},
+            };
+            const datas = [], filterNodes = [];
+
+            let level, filterRow = 0;
+            for (let iRow = 0; iRow < info.cellRange.rowCount; iRow ++) {
+                const curRow = info.cellRange.row + iRow;
+                const node = tree.nodes[curRow];
+                if (!node) continue;
+
+                if (!level) level = node.level;
+                if (node.level < level) break;
+
+                let bPaste = false;
+                const data = info.sheet.zh_tree.getNodeKeyData(node);
+                for (let iCol = 0; iCol < info.cellRange.colCount; iCol++) {
+                    const curCol = info.cellRange.col + iCol;
+                    const colSetting = info.sheet.zh_setting.cols[curCol];
+                    const value = trimInvalidChar(pasteData[iRow-filterRow][iCol]);
+                    if (node.children && node.children.length > 0 && invalidFields.parent.indexOf(colSetting.field) >= 0) {
+                        toastMessageUniq(hint.parent);
+                        continue;
+                    }
+
+                    if (colSetting.type === 'Number') {
+                        const num = _.toNumber(value);
+                        if (num) {
+                            data[colSetting.field] = num;
+                        } else {
+                            try {
+                                data[colSetting.field] = math.evaluate(transExpr(value));
+                                bPaste = true;
+                            } catch (err) {
+                                toastMessageUniq(hint.invalidExpr);
+                                continue;
+                            }
+                        }
+                    } else {
+                        data[colSetting.field] = value;
+                    }
+                    bPaste = true;
+                }
+                if (bPaste) {
+                    datas.push(data);
+                } else {
+                    filterNodes.push(node);
+                }
+            }
+            if (datas.length > 0) {
+                postData('progress/update', {postType: 'update', postData: datas}, function (result) {
+                    const refreshNode = tree.loadPostData(result);
+                    if (refreshNode.update) refreshNode.update = refreshNode.update.concat(filterNodes);
+                    progressObj.refreshTree(refreshNode);
+                }, function () {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
+                });
+            } else {
+                SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
+            }
+        }
+    }
+    const progressObj = new ProgressObj();
+
+    const progressFile = $.ledger_att({
+        selector: '#fujian',
+        key: 'id',
+        masterKey: 'rela_id',
+        uploadUrl: 'progress/file/upload',
+        deleteUrl: 'progress/file/delete',
+        checked: false,
+        zipName: `阶段进度-附件.zip`,
+        readOnly: false,
+        fileIdType: 'string',
+        fileInfo: {
+            user_name: 'user_name',
+            user_id: 'user_id',
+            create_time: 'create_time',
+        },
+        locate: function (att) {
+            if (!att) return;
+            SpreadJsObj.locateTreeNode(progressObj.sheet, att.node.tree_id, true);
+            progressFile.getCurAttHtml(att.node);
+        }
+    });
+    // 展开收起标准清单
+    $('a', '#side-menu').bind('click', function (e) {
+        e.preventDefault();
+        const tab = $(this), tabPanel = $(tab.attr('content'));
+        // 展开工具栏、切换标签
+        if (!tab.hasClass('active')) {
+            // const close = $('.active', '#side-menu').length === 0;
+            $('a', '#side-menu').removeClass('active');
+            $('.tab-content .tab-select-show.tab-pane.active').removeClass('active');
+            tab.addClass('active');
+            tabPanel.addClass('active');
+            // $('.tab-content .tab-pane').removeClass('active');
+            showSideTools(tab.hasClass('active'));
+        } else { // 收起工具栏
+            tab.removeClass('active');
+            tabPanel.removeClass('active');
+            showSideTools(tab.hasClass('active'));
+        }
+        progressObj.spread.refresh();
+    });
+
+    postData('load', { filter: 'progress;progress_file'}, function(result) {
+        progressObj.loadData(result.progress);
+        for (const f of result.progress_file) {
+            f.node = progressObj.tree.datas.find(x => { return x.id === f.rela_id; });
+        }
+        progressFile.loadDatas(result.progress_file);
+        progressFile.getCurAttHtml(SpreadJsObj.getSelectObject(progressObj.sheet));
+    });
+
+    // 工具栏spr
+    $.divResizer({
+        select: '#right-spr',
+        callback: function () {
+            progressObj.spread.refresh();
+        }
+    });
+    // 导航Menu
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        key: 'menu.1.0.0',
+        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            autoFlashHeight();
+            progressObj.spread.refresh();
+        }
+    });
+    // 显示层次
+    (function (select, sheet) {
+        $(select).click(function () {
+            if (!sheet.zh_tree) return;
+            const tag = $(this).attr('tag');
+            const tree = sheet.zh_tree;
+            setTimeout(() => {
+                showWaitingView();
+                switch (tag) {
+                    case "1":
+                    case "2":
+                    case "3":
+                    case "4":
+                        tree.expandByLevel(parseInt(tag));
+                        SpreadJsObj.refreshTreeRowVisible(sheet);
+                        break;
+                    case "last":
+                        tree.expandByCustom(() => { return true; });
+                        SpreadJsObj.refreshTreeRowVisible(sheet);
+                        break;
+                }
+                closeWaitingView();
+            }, 100);
+        });
+    })('a[name=showLevel]', progressObj.sheet);
+});

+ 485 - 0
app/public/js/sp_push.js

@@ -0,0 +1,485 @@
+const showSideTools = function (show) {
+    const left = $('#left-view'), right = $('#right-view'), parent = left.parent();
+    if (show) {
+        right.show();
+        autoFlashHeight();
+        /**
+         * right.show()后, parent被撑开成2倍left.height, 导致parent.width减少了10px
+         * 第一次left.width调整后,parent的缩回left.height, 此时parent.width又增加了10px
+         * 故需要通过最终的parent.width再计算一次left.width
+         *
+         * Q: 为什么不通过先计算left.width的宽度,以避免计算两次left.width?
+         * A: 右侧工具栏不一定显示,当右侧工具栏显示过一次后,就必须使用parent和right来计算left.width
+         *
+         */
+            //left.css('width', parent.width() - right.outerWidth());
+            //left.css('width', parent.width() - right.outerWidth());
+        const percent = 100 - right.outerWidth() /parent.width() * 100;
+        left.css('width', percent + '%');
+    } else {
+        left.width(parent.width());
+        right.hide();
+    }
+};
+
+$(document).ready(() => {
+    autoFlashHeight();
+    let datepicker;
+
+    class PushObj {
+        constructor() {
+            this.spread = SpreadJsObj.createNewSpread($('#push-spread')[0]);
+            this.sheet = this.spread.getActiveSheet();
+            this.data = [];
+            this.spreadSetting = {
+                cols: [
+                    { title: '序号', colSpan: '1', rowSpan: '1', field: 'push_order', hAlign: 1, width: 80, readOnly: true },
+                    {
+                        title: '日期', colSpan: '1', rowSpan: '1', field: 'push_date', hAlign: 2, width: 100, formatter: '@',
+                        formatter: 'yyyy-MM-dd', cellType: 'activeImageBtn', normalImg: '#ellipsis-icon', indent: 5
+                    },
+                    {title: '记录内容', colSpan: '1', rowSpan: '1', field: 'push_content', hAlign: 0, width: 450, formatter: '@', wordWrap: true},
+                    {title: '备注', colSpan: '1', rowSpan: '2', field: 'memo', hAlign: 0, width: 200, formatter: '@', cellType: 'ellipsisAutoTip'},
+                ],
+                emptyRows: 3,
+                headRows: 1,
+                headRowHeight: [32],
+                defaultRowHeight: 21,
+                headerFont: '12px 微软雅黑',
+                font: '12px 微软雅黑',
+                localCache: {
+                    key: 'sub-proj-push',
+                    colWidth: true,
+                },
+                forceLoadEmpty: true,
+                imageClick: function (data, hitinfo) {
+                    const setting = hitinfo.sheet.zh_setting;
+                    if (!setting) return;
+                    const col = setting.cols[hitinfo.col];
+                    if (!col || col.field.indexOf('_date') < 0) return;
+
+                    const pos = SpreadJsObj.getObjPos(hitinfo.sheet.getParent().qo);
+                    if (!datepicker) {
+                        datepicker = $('.datepicker-here').datepicker({
+                            language: 'zh',
+                            dateFormat: 'yyyy-MM-dd',
+                            autoClose: true,
+                            onSelect: function (formattedDate, date, inst) {
+                                if (!inst.visible) return;
+                                const sels = hitinfo.sheet.getSelections();
+                                if (!sels || !sels[0]) return;
+                                const node = SpreadJsObj.getSelectObject(hitinfo.sheet);
+                                const relaCol = hitinfo.sheet.zh_setting.cols[sels[0].col];
+
+                                const updateData = {};
+                                if (node) {
+                                    updateData.update = { id: node.id };
+                                    updateData.update[relaCol.field] = formattedDate;
+                                } else {
+                                    updateData.add = { push_order: hitinfo.sheet.zh_data.length + 1 };
+                                    updateData.add[relaCol.field] = formattedDate;
+                                }
+
+                                postData('push/update', updateData, function (result) {
+                                    pushObj.refreshData(result);
+                                    SpreadJsObj.reLoadSheetData(pushObj.sheet);
+                                }, function () {
+                                    SpreadJsObj.reLoadRowData(hitinfo.sheet, sels[0].row, 1);
+                                });
+                            }
+                        }).data('datepicker');
+                    }
+                    const value = hitinfo.sheet.getValue(hitinfo.row, hitinfo.col);
+                    if (value) {
+                        datepicker.selectDate(value);
+                    } else {
+                        datepicker.clear();
+                    }
+                    datepicker.show();
+                    $('#datepickers-container').css('top', hitinfo.cellRect.y + pos.y).css('left', hitinfo.cellRect.x + pos.x);
+                }
+            };
+            this.ckSpread = window.location.pathname + '-pushSelect';
+
+            this.initSpread();
+            this.initOtherEvent();
+        }
+        initSpread() {
+            SpreadJsObj.initSheet(this.sheet, this.spreadSetting);
+            this.spread.bind(spreadNS.Events.SelectionChanged, this.selectionChanged);
+            this.spread.bind(spreadNS.Events.topRowChanged, this.topRowChanged);
+            this.spread.bind(spreadNS.Events.ClipboardChanging, function (e, info) {
+                const copyText = SpreadJsObj.getFilterCopyText(info.sheet);
+                SpreadJsObj.Clipboard.setCopyData(copyText);
+            });
+            this.spread.bind(spreadNS.Events.EditEnded, this.editEnded);
+            this.spread.bind(spreadNS.Events.ClipboardPasting, this.clipboardPasting);
+            SpreadJsObj.addDeleteBind(this.spread, this.deletePress);
+        }
+        initOtherEvent() {
+            const self = this;
+            // 增删上下移升降级
+            $('a[name="base-opr"]').click(function () {
+                self.baseOpr(this.getAttribute('type'));
+            });
+            $.contextMenu({
+                selector: '#push-spread',
+                build: function ($trigger, e) {
+                    const target = SpreadJsObj.safeRightClickSelection($trigger, e, self.spread);
+                    return target.hitTestType === spreadNS.SheetArea.viewport || target.hitTestType === spreadNS.SheetArea.rowHeader;
+                },
+                items: {
+                    add: {
+                        name: '插入',
+                        icon: 'fa-plus',
+                        callback: function (key, opt) {
+                            pushObj.insert();
+                        },
+                    },
+                    del: {
+                        name: '删除',
+                        icon: 'fa-remove',
+                        callback: function (key, opt) {
+                            pushObj.delete();
+                        },
+                        disabled: function (key, opt) {
+                            const node = SpreadJsObj.getSelectObject(pushObj.sheet);
+                            return !node;
+                        },
+                    },
+                    sprDel: '------------',
+                    upMove: {
+                        name: '上移',
+                        icon: 'fa-arrow-up',
+                        callback: function (key, opt) {
+                            pushObj.upMove();
+                        },
+                        disabled: function (key, opt) {
+                            const sels = pushObj.sheet.getSelections();
+                            if (!sels || !sels[0] || sels[0].row === 0) return true;
+                        },
+                    },
+                    downMove: {
+                        name: '下移',
+                        icon: 'fa-arrow-down',
+                        callback: function (key, opt) {
+                            pushObj.downMove();
+                        },
+                        disabled: function (key, opt) {
+                            const sels = pushObj.sheet.getSelections();
+                            if (!sels || !sels[0] || sels[0].row >= pushObj.data.length - 1) return true;
+                        },
+                    }
+                },
+            })
+        }
+        loadRelaData() {
+            SpreadJsObj.saveTopAndSelect(this.sheet, this.ckSpread);
+            pushFile.getCurAttHtml(SpreadJsObj.getSelectObject(this.sheet));
+        }
+        refreshData(data) {
+            if (data.add) {
+                for (const a of data.add) {
+                    this.data.push(a);
+                }
+            }
+            if (data.update) {
+                for (const u of data.update) {
+                    const d = this.data.find(function (x) {
+                        return u.id === x.id;
+                    });
+                    if (d) {
+                        _.assign(d, u);
+                    } else {
+                        this.data.push(d);
+                    }
+                }
+            }
+            if (data.del) {
+                _.remove(this.data, function (d) {
+                    return data.del.indexOf(d.id) >= 0;
+                });
+            }
+            this.resortData();
+        }
+        resortData() {
+            this.data.sort(function (a, b) {
+                return a.push_order - b.push_order;
+            });
+        }
+        loadData(datas) {
+            this.data.length = 0;
+            this.data.push(...datas);
+            this.resortData();
+            SpreadJsObj.loadSheetData(this.sheet, SpreadJsObj.DataType.Data, this.data);
+            SpreadJsObj.loadTopAndSelect(this.sheet, this.ckSpread);
+        }
+        // 事件
+        selectionChanged(e, info) {
+            if (info.newSelections) {
+                if (!info.oldSelections || info.newSelections[0].row !== info.oldSelections[0].row) {
+                    pushObj.loadRelaData();
+                }
+            }
+        }
+        topRowChanged(e, info) {
+            SpreadJsObj.saveTopAndSelect(info.sheet, pushObj.ckBillsSpread);
+        }
+        editEnded(e, info) {
+            if (!info.sheet.zh_setting || !info.sheet.zh_data) return;
+
+            const node = info.sheet.zh_data[info.row];
+            const col = info.sheet.zh_setting.cols[info.col];
+            const data = {};
+
+            if (node) {
+                data.update = {};
+                data.update.id = node.id;
+
+                const oldValue = node ? node[col.field] : null;
+                const newValue = trimInvalidChar(info.editingText);
+                if (oldValue == info.editingText || ((!oldValue || oldValue === '') && (newValue === ''))) {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    return;
+                }
+                data.update[col.field] = newValue;
+            } else {
+                data.add = {};
+                data.add.push_order = info.sheet.zh_data.length + 1;
+                data.add[col.field] = trimInvalidChar(info.editingText);
+            }
+            // 更新至服务器
+            postData('push/update', data, function (result) {
+                pushObj.refreshData(result);
+                SpreadJsObj.reLoadSheetData(pushObj.sheet);
+                if (data.add) pushObj.loadRelaData();
+            }, function () {
+                SpreadJsObj.reLoadRowData(info.sheet, info.row, 1);
+            });
+        }
+        deletePress (sheet) {
+            if (!sheet.zh_setting) return;
+            const sel = sheet.getSelections()[0], datas = [];
+            for (let iRow = sel.row; iRow < sel.row + sel.rowCount; iRow++) {
+                let bDel = false;
+                const node = sheet.zh_tree.nodes[iRow];
+                const data = sheet.zh_tree.getNodeKeyData(node);
+                for (let iCol = sel.col; iCol < sel.col + sel.colCount; iCol++) {
+                    const col = sheet.zh_setting.cols[iCol];
+                    const style = sheet.getStyle(iRow, iCol);
+                    if (style.locked) continue;
+
+                    data[col.field] = col.type === 'Number' ? 0 : '';
+                    bDel = true;
+                }
+                if (bDel) datas.push(data);
+            }
+            if (datas.length > 0) {
+                postData('push/update', {update: datas}, function (result) {
+                    pushObj.refreshData(result);
+                    SpreadJsObj.reLoadSheetData(pushObj.sheet);
+                }, function () {
+                    SpreadJsObj.reLoadRowData(info.sheet, sel.row, sel.rowCount);
+                });
+            }
+        }
+        clipboardPasting(e, info) {
+            info.cancel = true;
+            const setting = info.sheet.zh_setting, sortData = info.sheet.zh_data;
+            if (!setting || !sortData) return;
+
+            const pasteData = info.pasteData.html
+                ? SpreadJsObj.analysisPasteHtml(info.pasteData.html)
+                : (info.pasteData.text === ''
+                    ? SpreadJsObj.Clipboard.getAnalysisPasteText()
+                    : SpreadJsObj.analysisPasteText(info.pasteData.text));
+            const uDatas = [], iDatas = [];
+            for (let iRow = 0; iRow < info.cellRange.rowCount; iRow++) {
+                const curRow = info.cellRange.row + iRow;
+                const node = sortData[curRow];
+
+                let bPaste = false;
+                const data = {};
+                for (let iCol = 0; iCol < info.cellRange.colCount; iCol++) {
+                    const curCol = info.cellRange.col + iCol;
+                    const colSetting = setting.cols[curCol];
+                    const value = trimInvalidChar(pasteData[iRow][iCol]);
+
+                    if (colSetting.type === 'Number') {
+                        const num = _.toNumber(value);
+                        if (num) {
+                            data[colSetting.field] = num;
+                            bPaste = true;
+                        }
+                    } else {
+                        data[colSetting.field] = value || '';
+                        bPaste = true;
+                    }
+                }
+                if (bPaste) {
+                    if (node) {
+                        data.id = node.id;
+                        uDatas.push(data);
+                    } else {
+                        data.push_order = curRow + 1;
+                        iDatas.push(data);
+                    }
+                }
+            }
+            const updateData = {};
+            if (uDatas.length > 0) updateData.update = uDatas;
+            if (iDatas.length > 0) updateData.add = iDatas;
+            if (uDatas.length > 0 || iDatas.length > 0) {
+                postData('push/update', updateData, function (result) {
+                    pushObj.refreshData(result);
+                    SpreadJsObj.reLoadSheetData(info.sheet);
+                    if (updateData.add && !updateData.update) pushObj.loadRelaData();
+                });
+            } else {
+                SpreadJsObj.reLoadSheetData(info.sheet);
+            }
+        }
+        // 操作
+        insert () {
+            const sheet = this.sheet;
+            const node = SpreadJsObj.getSelectObject(sheet);
+            const push_order = node ? node.push_order : sheet.getRowCount() - 2;
+            const add = { push_order };
+            // 更新至服务器
+            postData('push/update', { add }, function (result) {
+                pushObj.refreshData(result);
+                SpreadJsObj.reLoadSheetData(pushObj.sheet);
+            }, function () {
+                SpreadJsObj.reLoadRowData(info.sheet, info.row, 1);
+            });
+        }
+        delete () {
+            const sortData = this.sheet.zh_data;
+            const datas = [];
+            const sels = this.sheet.getSelections();
+            if (!sels || !sels[0]) return;
+
+            for (let iRow = sels[0].row, iLen = sels[0].row + sels[0].rowCount; iRow < iLen; iRow++) {
+                const node = sortData[iRow];
+                datas.push(node.id);
+            }
+            if (datas.length > 0) {
+                postData('push/update', {del: datas}, function (result) {
+                    pushObj.refreshData(result);
+                    SpreadJsObj.reLoadSheetData(pushObj.sheet);
+                    pushFile.deleteFileByNodeId(result.del);
+                    pushObj.loadRelaData();
+                }, function () {
+                    SpreadJsObj.reLoadSheetData(pushObj.sheet);
+                });
+            }
+        }
+        upMove () {
+            const sels = this.sheet.getSelections(), sortData = this.sheet.zh_data;
+            const node = sortData[sels[0].row];
+            const preNode = sortData[sels[0].row - 1];
+            const data = [
+                {id: node.id, push_order: preNode.push_order},
+                {id: preNode.id, push_order: node.push_order}
+            ];
+            postData('push/update', {update: data}, function (result) {
+                pushObj.refreshData(result);
+                SpreadJsObj.reLoadRowsData(pushObj.sheet, [sels[0].row, sels[0].row - 1]);
+                pushObj.sheet.setSelection(sels[0].row - 1, sels[0].col, sels[0].rowCount, sels[0].colCount);
+            });
+        }
+        downMove () {
+            const sels = this.sheet.getSelections(), sortData = this.sheet.zh_data;
+            const node = sortData[sels[0].row];
+            const nextNode = sortData[sels[0].row + 1];
+            const data = [
+                {id: node.id, push_order: nextNode.push_order},
+                {id: nextNode.id, push_order: node.push_order}
+            ];
+            postData('push/update', {update: data}, function (result) {
+                pushObj.refreshData(result);
+                SpreadJsObj.reLoadRowsData(pushObj.sheet, [sels[0].row, sels[0].row + 1]);
+                pushObj.sheet.setSelection(sels[0].row + 1, sels[0].col, sels[0].rowCount, sels[0].colCount);
+            });
+        }
+    }
+    const pushObj = new PushObj();
+
+    const pushFile = $.ledger_att({
+        selector: '#fujian',
+        key: 'id',
+        masterKey: 'rela_id',
+        uploadUrl: 'push/file/upload',
+        deleteUrl: 'push/file/delete',
+        checked: false,
+        zipName: `推进记录-附件.zip`,
+        readOnly: false,
+        fileIdType: 'string',
+        fileInfo: {
+            user_name: 'user_name',
+            user_id: 'user_id',
+            create_time: 'create_time',
+        },
+        getCurHint: function(node) { return ''; },
+        locate: function (att) {
+            if (!att) return;
+            SpreadJsObj.locateDataBy(pushObj.sheet, function(x) { return x.id === att.node.id; });
+            pushFile.getCurAttHtml(att.node);
+        }
+    });
+    // 展开收起标准清单
+    $('a', '#side-menu').bind('click', function (e) {
+        e.preventDefault();
+        const tab = $(this), tabPanel = $(tab.attr('content'));
+        // 展开工具栏、切换标签
+        if (!tab.hasClass('active')) {
+            // const close = $('.active', '#side-menu').length === 0;
+            $('a', '#side-menu').removeClass('active');
+            $('.tab-content .tab-select-show.tab-pane.active').removeClass('active');
+            tab.addClass('active');
+            tabPanel.addClass('active');
+            // $('.tab-content .tab-pane').removeClass('active');
+            showSideTools(tab.hasClass('active'));
+        } else { // 收起工具栏
+            tab.removeClass('active');
+            tabPanel.removeClass('active');
+            showSideTools(tab.hasClass('active'));
+        }
+        pushObj.spread.refresh();
+    });
+
+    postData('load', { filter: 'push;push_file'}, function(result) {
+        pushObj.loadData(result.push);
+        for (const f of result.push_file) {
+            f.node = pushObj.data.find(x => { return x.id === f.rela_id; });
+        }
+        pushFile.loadDatas(result.push_file);
+        pushFile.getCurAttHtml(SpreadJsObj.getSelectObject(pushObj.sheet));
+    });
+
+    // 工具栏spr
+    $.divResizer({
+        select: '#right-spr',
+        callback: function () {
+            pushObj.spread.refresh();
+        }
+    });
+    // 导航Menu
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        key: 'menu.1.0.0',
+        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            autoFlashHeight();
+            pushObj.spread.refresh();
+        }
+    });
+});

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

@@ -535,6 +535,26 @@ const SpreadJsObj = {
             cell.setBorder(sheet.borderLine, {all: true});
         });
     },
+    _autoFitRowWithTree: function(sheet, data, row, treeCol) {
+        const treeCell = sheet.getCell(row, treeCol);
+        const setting = sheet.zh_tree.setting;
+        const xOffset = 2 + 5;
+        const indent = 20;
+        const levelIndent = -5;
+        const move = (data[setting.level]) * indent + (data[setting.level]) * levelIndent + xOffset;
+        const validWidth = sheet.zh_setting.cols[treeCol].width - move;
+        const getTextHeight = function(sheet, str, font, width) {
+            const xs = sheet.getParent().xs;
+            const ctx = xs.childNodes[0].getContext("2d");
+            ctx.font = font;
+            const textMertrices = ctx.measureText(str);
+            const fontHeight = textMertrices.fontBoundingBoxAscent + textMertrices.fontBoundingBoxDescent;
+            const textRow = Math.ceil(textMertrices.width / width);
+            return textRow * fontHeight + 2 + (textRow - 1) * 1;
+        };
+        const treeCellHeight = getTextHeight(sheet, treeCell.text(), treeCell.font(), validWidth);
+        sheet.setRowHeight(row, Math.max(treeCellHeight, sheet.getRowHeight(row)));
+    },
     _loadRowData: function (sheet, data, row) {
         // 单元格重新写入数据
         if (!data) { return }
@@ -542,8 +562,9 @@ const SpreadJsObj = {
             data.waitingLoading = true;
             return;
         }
-        let autoFit = false;
+        let autoFit = false, treeWordWrapCol = -1;
         sheet.zh_setting.cols.forEach(function (col, j) {
+            if (col.cellType === 'tree' && col.wordWrap) treeWordWrapCol = j;
             const cell = sheet.getCell(row, j);
             if (col.getValue && Object.prototype.toString.apply(col.getValue) === "[object Function]") {
                 cell.value(col.getValue(data, col));
@@ -595,7 +616,10 @@ const SpreadJsObj = {
             }
             data.waitingLoading = false;
         });
-        if (autoFit) sheet.autoFitRow(row);
+        if (autoFit) {
+            sheet.autoFitRow(row);
+            if (treeWordWrapCol >= 0) this._autoFitRowWithTree(sheet, data, row, treeWordWrapCol);
+        }
     },
     _addActivePaintEvents: function (sheet, cellType) {
         if (!sheet.ActiveType) {
@@ -795,22 +819,23 @@ const SpreadJsObj = {
             // 设置总行数
             const totalRow = sortData.length + sheet.zh_setting.emptyRows;
             sheet.setRowCount(totalRow, spreadNS.SheetArea.viewport);
-            // 控制空白行
-            const emptyRows = sheet.getRange(sortData.length, -1, sheet.zh_setting.emptyRows, -1);
-            emptyRows.locked(sheet.zh_dataType === 'tree' || sheet.zh_setting.readOnly);
             if (sortData) {
                 // 单元格写入数据
                 sortData.forEach(function (data, i) {
                     self._loadRowData(sheet, data, i);
                     sheet.setRowVisible(i, data.visible);
                 });
-                // for (let iRow = sortData.length - 1; iRow < totalRow; iRow++) {
-                //     self._loadRowStyle(sheet, iRow);
-                // }
+            }
+            // 控制空白行
+            const emptyRows = sheet.getRange(sortData.length, -1, sheet.zh_setting.emptyRows, -1);
+            emptyRows.locked(sheet.zh_dataType === 'tree' || sheet.zh_setting.readOnly);
+            if (sheet.zh_setting.forceLoadEmpty) {
+                for (let iRow = emptyRows.row; iRow < emptyRows.row + emptyRows.rowCount; iRow++) {
+                    self._loadRowData(sheet, {}, iRow);
+                }
             }
             // 设置列单元格格式
             sheet.zh_setting.cols.forEach(function (col, j) {
-                //if (!col.cellType) { return; }
                 self._defineColCellType(sheet, j, col);
             });
             this.endMassOperation(sheet);
@@ -2541,11 +2566,12 @@ const SpreadJsObj = {
                 const self = this;
                 if (editorContext) {
                     const $editor = $(editorContext);
-                    //spreadNS.CellTypes.Text.prototype.activateEditor.apply(this, arguments);
+                    spreadNS.CellTypes.Text.prototype.activateEditor.apply(this, arguments);
                     $editor.css("position", "absolute");
                     datepicker = $editor.datepicker({
                         language: 'zh',
-                        dateFormat: 'yyyy-MM-DD'
+                        dateFormat: 'yyyy-MM-DD',
+                        autoClose: true,
                     }).data('datepicker');
                     datepicker.show();
                 }

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

@@ -13,7 +13,7 @@ function checkTzMeasureType () {
 }
 
 function transExpr(expr) {
-    return $.trim(expr).replace('\t', '').replace('=', '').replace('%', '/100');
+    return $.trim(expr).replace('\t', '').replace('=', '').replace(new RegExp('%', 'gm'), '/100');
 }
 function getExprInfo (field) {
     const exprField = [

+ 5 - 7
app/public/js/stage.js

@@ -13,7 +13,7 @@ function checkTzMeasureType () {
 }
 
 function transExpr(expr) {
-    return $.trim(expr).replace('\t', '').replace('=', '').replace('%', '/100');
+    return $.trim(expr).replace('\t', '').replace('=', '').replace(new RegExp('%', 'gm'), '/100');
 }
 function getExprInfo (field) {
     const exprField = [
@@ -249,7 +249,7 @@ $(document).ready(() => {
                 return node.is_tp;
             }
         },
-        over: { enable: 1, isTz: checkTzMeasureType(), },
+        over: { enable: 1, isTz: checkTzMeasureType(), checkInfo: tenderInfo.checkOverInfo },
         limit3f: { enable: 1, checkType: [], status: thirdParty, },
         minus_cb: { enable: hintMinusCb },
         // change_over: { enable: 1 },
@@ -749,7 +749,7 @@ $(document).ready(() => {
             if (data.settle_status === settleStatus.finish) {
                 return spreadColor.stage.settle;
             }
-            return hintOver && checkUtils.billsOver(data, checkTzMeasureType(), stagePos) ? spreadColor.stage.over : defaultColor;
+            return hintOver && checkUtils.billsOver(data, stagePos, tenderInfo.checkOverInfo) ? spreadColor.stage.over : defaultColor;
         } else {
             return defaultColor;
         }
@@ -841,9 +841,7 @@ $(document).ready(() => {
                 return spreadColor.stage.settle;
             }
         }
-        if (checkTzMeasureType()) {
-            return hintOver && checkUtils.posOver(data)  ? spreadColor.stage.over : defaultColor;
-        }
+        return hintOver && checkUtils.posOver(data, tenderInfo.checkOverInfo)  ? spreadColor.stage.over : defaultColor;
     };
     sjsSettingObj.setGridSelectStyle(posSpreadSetting);
     if (thousandth) sjsSettingObj.setTpThousandthFormat(posSpreadSetting);
@@ -4544,7 +4542,7 @@ $(document).ready(() => {
                         }, {
                             key: 'over', title: '超计', valid: true,
                             check: function (node) {
-                                return checkUtils.billsOver(node, checkTzMeasureType(), stagePos);
+                                return checkUtils.billsOver(node, stagePos, tenderInfo.checkOverInfo);
                             }
                         }, {
                             key: 'empty', title: '漏计', valid: false,

+ 24 - 26
app/public/js/stage_gather.js

@@ -45,13 +45,13 @@ function generateChapterHtml(data) {
 
 $(document).ready(function () {
     const preUrl = window.location.pathname.split('/').slice(0, 6).join('/');
-    let per = _.toNumber(getLocalCache('StageGatherOverPercent'));
-    if (per && !_.isNaN(per)) {
-        $('#over-percent').val(per >= 50 ? (per <= 100 ? per : 100) : 50);
-    }
-
-    let overType = $('#' + getLocalCache('StageGatherOverType'))[0];
-    if (overType) overType.checked = true;
+    // let per = _.toNumber(getLocalCache('StageGatherOverPercent'));
+    // if (per && !_.isNaN(per)) {
+    //     $('#over-percent').val(per >= 50 ? (per <= 100 ? per : 100) : 50);
+    // }
+    //
+    // let overType = $('#' + getLocalCache('StageGatherOverType'))[0];
+    // if (overType) overType.checked = true;
     autoFlashHeight();
     // 初始化工程量清单
     const gclSpread = SpreadJsObj.createNewSpread($('#gcl-spread')[0]);
@@ -165,14 +165,12 @@ $(document).ready(function () {
                     : data.end_contract_tp < ZhCalc.mul(data[tpField], per) || data.end_contract_tp > 0;
             }
         };
-        const bQty = $('#customRadio1')[0].checked, bDealQty = $('#customRadio2')[0].checked;
-        const nPercent = Math.min(Math.max(ZhCalc.div(parseFloat($('#over-percent').val()), 100), 0.5), 1);
+        const nPercent = checkOverInfo.coe;
         for (const node of data) {
             if (node) {
-                if (node.b_code === '103-4')console.log(node);
-                const bOverRangeTz = billsGatherOver(node, 'final_1_qty', 'final_1_tp', nPercent);
-                const bOverRangeDeal = billsGatherOver(node, 'deal_final_1_qty', 'deal_final_1_tp', nPercent);
-                node.overRange = bQty ? bOverRangeTz : (bDealQty ? bOverRangeDeal : bOverRangeTz || bOverRangeDeal);
+                const bOverRangeTz = checkOverInfo.checkTz ? billsGatherOver(node, 'final_1_qty', 'final_1_tp', nPercent) : false;
+                const bOverRangeDeal = checkOverInfo.checkDeal ? billsGatherOver(node, 'deal_final_1_qty', 'deal_final_1_tp', nPercent) : false;
+                node.overRange = bOverRangeTz || bOverRangeDeal;
             }
         }
     }
@@ -181,19 +179,19 @@ $(document).ready(function () {
         const iOldRow = info.oldSelections[0].row, iNewRow = info.newSelections[0].row;
         if (iNewRow !== iOldRow) loadRelaData(iNewRow);
     });
-    $('.custom-radio').click(() => {
-        setLocalCache('StageGatherOverType', $('input[name=customRadio]:checked').attr('id'));
-        checkOverRange(gclGatherData);
-        SpreadJsObj.reLoadSheetData(gclSpread.getActiveSheet());
-        // 收起菜单
-        $('.dropdown-menu').click();
-    });
-    $('#over-percent').change(function () {
-        this.value = this.value >= 50 ? (this.value <= 100 ? ZhCalc.round(this.value, 2) : 100) : 50;
-        setLocalCache('StageGatherOverPercent', this.value);
-        checkOverRange(gclGatherData);
-        SpreadJsObj.reLoadSheetData(gclSpread.getActiveSheet());
-    });
+    // $('.custom-radio').click(() => {
+    //     setLocalCache('StageGatherOverType', $('input[name=customRadio]:checked').attr('id'));
+    //     checkOverRange(gclGatherData);
+    //     SpreadJsObj.reLoadSheetData(gclSpread.getActiveSheet());
+    //     // 收起菜单
+    //     $('.dropdown-menu').click();
+    // });
+    // $('#over-percent').change(function () {
+    //     this.value = this.value >= 50 ? (this.value <= 100 ? ZhCalc.round(this.value, 2) : 100) : 50;
+    //     setLocalCache('StageGatherOverPercent', this.value);
+    //     checkOverRange(gclGatherData);
+    //     SpreadJsObj.reLoadSheetData(gclSpread.getActiveSheet());
+    // });
 
     postData(preUrl + '/load', { filter: 'ledger;pos;dealBills;spec;change;stageChange;preStageChange' }, function (result) {
         // 解析清单汇总数据

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

@@ -97,7 +97,7 @@ $(document).ready(() => {
                 const percent = formula.match(this.percentReg);
                 if (percent) {
                     for (const p of percent) {
-                        const v = math.evaluate(p.replace('%', '/100'));
+                        const v = math.evaluate(p.replace(new RegExp('%', 'gm'), '/100'));
                         formula = formula.replace(p, v);
                     }
                 }

+ 11 - 0
app/public/js/tender_list_base.js

@@ -362,6 +362,17 @@ $(document).ready(() => {
     bindTenderUrl();
     localHideList();
     tenderTreeShowLevel.initShowLevel();
+    if ($("#progress-table").length > 0) {
+        $("#progress-table").colResizable({
+            liveDrag: true,
+            gripInnerHtml:"<div class='grip'></div>",
+            draggingClass:"dragging",
+            resizeMode:'fit',
+            postbackSafe:true,
+            partialRefresh:true,
+            // headerOnly: true,
+        });
+    }
     // 分类
     $('#cate-set').on('show.bs.modal', function () {
         createTree('treeLevel');

+ 17 - 16
app/public/js/tender_list_progress.js

@@ -29,7 +29,7 @@ const tenderListSpec = (function(){
         const html = [];
         html.push('<tr pid="' + pid + '">');
         // 名称
-        html.push('<td width="25%" class="in-' + node.level + '">');
+        html.push('<td width="30%" 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> ');
             html.push((node.level === 1 ? '<b>' : ''), node.name, (node.level === 1 ? '</b>' : ''));
@@ -42,7 +42,7 @@ const tenderListSpec = (function(){
         }
         html.push('</td>');
         // 计量进度
-        html.push('<td style="width: 8%">');
+        html.push('<td style="width: 120px">');
         if (!node.cid && node.cur_flow) {
             if (node.progress) {
                 html.push(node.progress.title + ' (' + '<span class="' + node.progress.status_class +'">' + node.progress.status + '</span>' + ')');
@@ -52,7 +52,7 @@ const tenderListSpec = (function(){
         }
         html.push('</td>');
         // 当前流程
-        html.push('<td style="width: 8%">');
+        html.push('<td style="width: 230px">');
         if (!node.cid && node.cur_flow) {
             const curUser = node.cur_flow instanceof Array && node.cur_flow[0].audit_type && node.cur_flow[0].audit_type !== auditType.key.common
                 ? transFormToChinese(node.cur_flow[0].audit_order) + '审'
@@ -79,7 +79,7 @@ const tenderListSpec = (function(){
         }
         html.push('</td>');
         // 上一流程审批时间
-        html.push('<td style="width: 8%">');
+        html.push('<td style="width: 150px">');
         if (!node.cid && node.pre_flow) {
             if (node.pre_flow instanceof Array) {
                 if (node.pre_flow.length > 1) {
@@ -93,15 +93,15 @@ const tenderListSpec = (function(){
         }
         html.push('</td>');
         // 签约合同价
-        html.push('<td width="8%" class="text-right">');
+        html.push('<td width="120px" class="text-right">');
         html.push(node.contract_price ? node.contract_price : '');
         html.push('</td>');
         // 总价
-        html.push('<td width="8%" class="text-right">');
+        html.push('<td width="120px" class="text-right">');
         html.push(node.sum_tp ? node.sum_tp : '');
         html.push('</td>');
         // 截止本期累计完成/本期完成/未完成
-        html.push('<td>');
+        html.push('<td width="30%">');
         if (node.lastStage || node.stage_count > 0) {
             html.push(getProgressHtml(node.sum_tp, node.pre_gather_tp, node.gather_tp));
         } else {
@@ -113,15 +113,16 @@ const tenderListSpec = (function(){
     }
     function getTenderTreeHeaderHtml() {
         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 style="width: 25%" class="text-center">', '标段名称', '</th>');
-        html.push('<th class="text-center" style="width: 8%">', '计量进度', '</th>');
-        html.push('<th class="text-center" style="width: 8%">', '当前流程', '</th>');
-        html.push('<th class="text-center" style="width: 8%">', '上一流程审批时间', '</th>');
-        html.push('<th class="text-center" style="width: 8%">', '签约合同价', '</th>');
-        html.push('<th style="width: 8%" class="text-center">', '总价 <i class="fa fa-question-circle text-primary"  data-placement="bottom" data-toggle="tooltip" data-original-title="0号台账+截止本期数量变更"></i>', '</th>');
-        html.push('<th style="width: 35%" class="text-center">', '截止上期完成/本期完成/未完成', '</th>');
+        html.push('<table class="table table-hover table-bordered" id="progress-table">');
+        html.push('<thead style="position: sticky;left:56px;top: 0px;">', '<tr>');
+        // html.push('<thead style="left:56px;top: 34px;">', '<tr>');
+        html.push('<th style="width: 30%;min-width: 300px" class="text-center">', '标段名称', '</th>');
+        html.push('<th class="text-center" style="width: 120px">', '计量进度', '</th>');
+        html.push('<th class="text-center" style="width: 230px">', '当前流程', '</th>');
+        html.push('<th class="text-center" style="width: 150px">', '上一流程审批时间', '</th>');
+        html.push('<th class="text-center" style="width: 120px">', '签约合同价', '</th>');
+        html.push('<th style="width: 120px" class="text-center">', '总价 <i class="fa fa-question-circle text-primary"  data-placement="bottom" data-toggle="tooltip" data-original-title="0号台账+截止本期数量变更"></i>', '</th>');
+        html.push('<th style="width: 30%" class="text-center">', '截止上期完成/本期完成/未完成', '</th>');
         html.push('</tr>', '</thead>');
         return html.join('');
     }

+ 3 - 1
app/public/js/tender_showhide.js

@@ -142,7 +142,9 @@ function setTopTr() {
             }
         }
     }
-    $('.c-body table').css('margin-top', $(".c-body table>thead").height() - 4);
+    if ($("#progress-table").length === 0) {
+        $('.c-body table').css('margin-top', $(".c-body table>thead").height() - 4);
+    }
 }
 
 function doTrStatus(node, status, all = '') {

+ 21 - 8
app/router.js

@@ -241,6 +241,7 @@ module.exports = app => {
     app.post('/tender/:id/ledger/get-children', sessionAuth, tenderCheck, uncheckTenderCheck, 'ledgerController.getChildren');
     app.post('/tender/:id/ledger/update', sessionAuth, tenderCheck, uncheckTenderCheck, 'ledgerController.update');
     app.post('/tender/:id/ledger/upload-excel/:ueType', sessionAuth, tenderCheck, uncheckTenderCheck, 'ledgerController.uploadExcel');
+    app.post('/tender/:id/ledger/ybp', sessionAuth, tenderCheck, uncheckTenderCheck, 'ledgerController.uploadYbp');
     app.get('/tender/:id/ledger/download/:file', sessionAuth, tenderCheck, uncheckTenderCheck, 'ledgerController.download');
     app.post('/tender/:id/anc-gcl/update', sessionAuth, tenderCheck, uncheckTenderCheck, 'ledgerController.ancGclUpdate');
     app.post('/tender/:id/pos/update', sessionAuth, tenderCheck, uncheckTenderCheck, 'ledgerController.posUpdate');
@@ -483,7 +484,7 @@ module.exports = app => {
     app.post('/tender/:id/settle/:sorder/audit/checkCancel', sessionAuth, tenderCheck, uncheckTenderCheck, settleCheck, 'settleController.auditCheckCancel');
 
     // 报表
-    app.get('/tender/:id/report', sessionAuth, tenderCheck, uncheckTenderCheck, 'reportController.index');
+    app.get('/tender/:id/report', sessionAuth, tenderCheck, 'reportController.index');
     app.get('/tender/:id/change/:cid/report', sessionAuth, tenderCheck, uncheckTenderCheck, 'reportController.indexForChange');
     app.get('/tender/:id/change/plan/:cplnid/report', sessionAuth, tenderCheck, uncheckTenderCheck, 'reportController.indexForChangePlan');
     app.get('/tender/:id/change/project/:cprjid/report', sessionAuth, tenderCheck, uncheckTenderCheck, 'reportController.indexForChangeProject');
@@ -491,7 +492,7 @@ module.exports = app => {
     app.get('/tender/:id/measure/stage/:order/report', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'reportController.index');
     app.get('/payment/:id/safe/:did/report', sessionAuth, paymentTenderCheck, paymentDetailCheck, 'reportController.indexForPaymentSafe');
     app.get('/budget/:id/report', sessionAuth, budgetCheck, 'reportController.indexForDynamicGrandTotal');
-    app.get('/tender/:id/archiveReport', sessionAuth, tenderCheck, uncheckTenderCheck, 'reportArchiveController.index');
+    app.get('/tender/:id/archiveReport', sessionAuth, tenderCheck, 'reportArchiveController.index');
     app.post('/tender/report_api/getReportArchive', sessionAuth, 'reportArchiveController.getReportArchive');
     app.post('/tender/report_api/getReportArchive4bz', sessionAuth, 'reportArchiveController.getReportArchive4bz');
     app.get('/getArchivedFileByUUID/:uuid/:rptName', sessionAuth, 'reportArchiveController.getArchivedFileByUUID');
@@ -751,7 +752,7 @@ module.exports = app => {
     app.get('/wap/dashboard/msg/:id', sessionAuth, 'wapController.msg');
     app.get('/wap/list', sessionAuth, 'wapController.list');
     app.get('/wap/tender/:id', sessionAuth, tenderCheck, uncheckTenderCheck, 'wapController.tender');
-    app.get('/wap/tender/:id/stage/:order', sessionAuth, tenderCheck, uncheckTenderCheck, 'wapController.stage');
+    app.get('/wap/tender/:id/stage/:order', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'wapController.stage');
     app.get('/wap/tender/:id/change/:cid/info', sessionAuth, tenderCheck, uncheckTenderCheck, changeCheck, changeAuditCheck, 'wapController.change');
     app.get('/wap/tender/:id/change/plan/:cpid/info', sessionAuth, tenderCheck, uncheckTenderCheck, 'wapController.changePlan');
     app.post('/wap/tender/:id/change/approval', sessionAuth, tenderCheck, uncheckTenderCheck, tenderBuildCheck, 'wapController.changeApproval');
@@ -759,7 +760,7 @@ module.exports = app => {
     app.get('/wap/tender/:id/advance', sessionAuth, tenderCheck, uncheckTenderCheck, 'wapController.advance');
     app.get('/wap/tender/:id/advance/:order/detail', sessionAuth, tenderCheck, advanceCheck, 'wapController.advanceDetail');
     // 针对企业微信访问判断去掉wap就能直达web端
-    app.get('/wap/tender/:id/measure/stage/:order', sessionAuth, tenderCheck, uncheckTenderCheck, 'wapController.stage');
+    app.get('/wap/tender/:id/measure/stage/:order', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'wapController.stage');
     app.get('/wap/tender/:id/change', sessionAuth, tenderCheck, uncheckTenderCheck, 'wapController.changeIndex');
     app.get('/wap/tender/:id/change/:cid/information', sessionAuth, tenderCheck, uncheckTenderCheck, changeCheck, changeAuditCheck, 'wapController.change');
     app.get('/wap/tender/:id/change/project/:cpid/information', sessionAuth, tenderCheck, uncheckTenderCheck, changeProjectCheck, changeProjectAuditCheck, 'wapController.changeProject');
@@ -841,6 +842,17 @@ module.exports = app => {
     app.get('/sp/:id/info', sessionAuth, subProjectCheck, 'subProjController.info');
     app.get('/sp/:id/data', sessionAuth, subProjectCheck, 'subProjController.dataIndex');
     app.post('/sp/:id/info/save', sessionAuth, subProjectCheck, 'subProjController.saveInfo');
+    app.post('/sp/:id/load', sessionAuth, subProjectCheck, 'subProjController.load');
+    // 阶段进度
+    app.get('/sp/:id/progress', sessionAuth, subProjectCheck, 'subProjController.progress');
+    app.post('/sp/:id/progress/update', sessionAuth, subProjectCheck, 'subProjController.progressUpdate');
+    app.post('/sp/:id/progress/file/upload', sessionAuth, subProjectCheck, 'subProjController.uploadFile');
+    app.post('/sp/:id/progress/file/delete', sessionAuth, subProjectCheck, 'subProjController.deleteFile');
+    //推进记录
+    app.get('/sp/:id/push', sessionAuth, subProjectCheck, 'subProjController.push');
+    app.post('/sp/:id/push/update', sessionAuth, subProjectCheck, 'subProjController.pushUpdate');
+    app.post('/sp/:id/push/file/upload', sessionAuth, subProjectCheck, 'subProjController.uploadFile');
+    app.post('/sp/:id/push/file/delete', sessionAuth, subProjectCheck, 'subProjController.deleteFile');
     // 概算投资
     app.get('/budget', sessionAuth, 'budgetController.list');
     app.get('/budget/:id', sessionAuth, budgetCheck, 'budgetController.budgetInfo');
@@ -972,10 +984,11 @@ module.exports = app => {
     app.post('/financial/:spid/pay/save', sessionAuth, financialCheck, 'financialController.paySave');
     app.get('/financial/:spid/pay/:fpid/detail', sessionAuth, financialCheck, financialPayCheck, financialPayAuditCheck, 'financialController.payDetail');
     app.post('/financial/:spid/pay/:fpid/save', sessionAuth, financialCheck, financialPayCheck, 'financialController.payDetailSave');
-    app.post('/financial/:spid/pay/:fpid/file/upload', sessionAuth, financialCheck, financialPayCheck, financialPayAuditCheck, 'financialController.payUploadFile');
-    app.post('/financial/:spid/pay/:fpid/file/delete', sessionAuth, financialCheck, financialPayCheck, financialPayAuditCheck, 'financialController.payDeleteFile');
-    app.get('/financial/:spid/pay/:fpid/file/:fid/download', sessionAuth, financialCheck, financialPayCheck, financialPayAuditCheck, 'financialController.payDownloadFile');
+    app.post('/financial/:spid/pay/:fpid/file/upload', sessionAuth, financialCheck, financialPayCheck, 'financialController.payUploadFile');
+    app.post('/financial/:spid/pay/:fpid/file/delete', sessionAuth, financialCheck, financialPayCheck, 'financialController.payDeleteFile');
+    app.get('/financial/:spid/pay/:fpid/file/:fid/download', sessionAuth, financialCheck, financialPayCheck, 'financialController.payDownloadFile');
     app.post('/financial/:spid/pay/:fpid/audit/start', sessionAuth, financialCheck, financialPayCheck, financialPayAuditCheck, 'financialController.startPayAudit');
     app.post('/financial/:spid/pay/:fpid/audit/check', sessionAuth, financialCheck, financialPayCheck, 'financialController.checkPayAudit');
-    // app.get('/financial/:spid/summary', sessionAuth, financialCheck, 'financialController.summary');
+    app.get('/financial/:spid/summary', sessionAuth, financialCheck, 'financialController.summary');
+    app.post('/financial/:spid/summary/load', sessionAuth, financialCheck, 'financialController.summaryLoad');
 };

+ 12 - 0
app/service/account_cert.js

@@ -47,8 +47,20 @@ module.exports = app => {
             // 判断key是否在常量里,并转换为对应的值
             if (!this._.includes(profileConst.cert.postCertConst, data.key)) throw '参数有误';
             updateData[data.key] = data.value ? data.value : null;
+            if (data.key === 'type') {
+                const cert_type = this._.find(profileConst.cert.certType, { value: data.value });
+                updateData.cert_type = cert_type ? cert_type.name : '';
+            } else if (data.key === 'name') {
+                const cert_name = this._.find(profileConst.cert.certReg, { value: data.value }) || this._.find(profileConst.cert.certQual, { value: data.value });
+                updateData.cert_name = cert_name ? cert_name.name : '';
+            }
             if (data.other && this._.includes(profileConst.cert.postCertConst, data.other.key)) {
                 updateData[data.other.key] = data.other.value ? data.other.value : null;
+                if (data.other.key === 'name') {
+                    console.log(data.other.value);
+                    const cert_name = this._.find(profileConst.cert.certReg, { value: data.other.value }) || this._.find(profileConst.cert.certQual, { value: data.other.value });
+                    updateData.cert_name = cert_name ? cert_name.name : '';
+                }
             }
             return await this.db.update(this.tableName, updateData);
         }

+ 8 - 3
app/service/change.js

@@ -631,10 +631,15 @@ module.exports = app => {
          * @return {void}
          */
         async getChangeTp(tenderId) {
-            const sql = 'SELECT SUM(`total_price`) AS tp, SUM(`positive_tp`) AS p_tp, SUM(`negative_tp`) AS n_tp, SUM(`valuation_tp`) AS v_tp, SUM(`unvaluation_tp`) AS uv_tp FROM ?? WHERE tid = ? AND status = ?';
+            const sql = 'SELECT `total_price`, `positive_tp`, `negative_tp`, `valuation_tp`, `unvaluation_tp` FROM ?? WHERE tid = ? AND status = ?';
             const sqlParam = [this.tableName, tenderId, audit.change.status.checked];
-            const result = await this.db.queryOne(sql, sqlParam);
-            return result ? [result.tp, result.p_tp, result.n_tp, result.v_tp, result.uv_tp] : [0, 0, 0, 0, 0];
+            const result = await this.db.query(sql, sqlParam);
+            const tp = this.ctx.helper.sum(this._.map(result, 'total_price'));
+            const p_tp = this.ctx.helper.sum(this._.map(result, 'positive_tp'));
+            const n_tp = this.ctx.helper.sum(this._.map(result, 'negative_tp'));
+            const v_tp = this.ctx.helper.sum(this._.map(result, 'valuation_tp'));
+            const uv_tp = this.ctx.helper.sum(this._.map(result, 'unvaluation_tp'));
+            return result ? [tp, p_tp, n_tp, v_tp, uv_tp] : [0, 0, 0, 0, 0];
         }
 
         /**

+ 8 - 2
app/service/financial_pay.js

@@ -40,7 +40,10 @@ module.exports = app => {
          */
         async getListByStatus(spid, status = 0, tid = null, used = null) {
             let addSql = '';
-            if (tid) {
+            if (tid !== null) {
+                if (tid.length === 0) {
+                    return [];
+                }
                 addSql += ' AND a.tid in (' + this.ctx.helper.getInArrStrSqlFilter(tid) + ')';
             }
             if (used) {
@@ -95,7 +98,10 @@ module.exports = app => {
          */
         async getCountByStatus(spid, status = 0, tid = null, used = null) {
             let addSql = '';
-            if (tid) {
+            if (tid !== null) {
+                if (tid.length === 0) {
+                    return 0;
+                }
                 addSql += ' AND a.tid in (' + this.ctx.helper.getInArrStrSqlFilter(tid) + ')';
             }
             if (used) {

+ 3 - 2
app/service/financial_pay_audit.js

@@ -280,7 +280,7 @@ module.exports = app => {
          * @return {Promise<boolean>}
          */
         async start(fpId, times = 1) {
-            const audits = await this.getAllDataByCondition({where: {fpid: fpId, times, order: 1}});
+            const audits = await this.getAllDataByCondition({ where: { fpid: fpId, times, order: 1 } });
             if (audits.length === 0) {
                 throw '请联系管理员添加审批人';
             }
@@ -294,7 +294,7 @@ module.exports = app => {
                 await transaction.updateRows(this.tableName, updateData);
                 await transaction.update(this.ctx.service.financialPay.tableName, {
                     id: fpId, status: auditConst.status.checking,
-                    entities: await this.ctx.service.financialPayContract.getEntities(fpId),
+                    // entities: await this.ctx.service.financialPayContract.getEntities(fpId),
                 });
                 // todo 更新标段tender状态 ?
                 await transaction.commit();
@@ -391,6 +391,7 @@ module.exports = app => {
                         // 同步 期信息
                         await transaction.update(this.ctx.service.financialPay.tableName, {
                             id: fp.id, status: checkData.checkType,
+                            entities: await this.ctx.service.financialPayContract.getEntities(fp.id),
                         });
                     }
                 } else {

+ 20 - 2
app/service/financial_pay_contract.js

@@ -22,14 +22,32 @@ module.exports = app => {
             this.dataId = 'id';
         }
 
-        async getContractList(fpid) {
+        async getContractList(fpid, needCheck = false) {
             const list = await this.getAllDataByCondition({ where: { fpid }, orders: [['id', 'asc']] });
+            const updateData = [];
+            const needCheckKeys = ['c_code', 'name', 'total_price', 'entity', 'bank', 'bank_account'];
             for (const l of list) {
                 const allList = l.cid ? await this.getAllDataByCondition({ where: { spid: l.spid, cid: l.cid } }) : [];
                 l.accumulate_pay_price = this._.sumBy(allList, 'pay_price');
                 l.accumulate_settle_price = this._.sumBy(allList, 'settle_price');
                 l.files = await this.ctx.service.financialPayAtt.getAtt(l.fpid, l.id);
+                if (l.cid && needCheck) {
+                    const contractInfo = await this.ctx.service.contract.getDataById(l.cid);
+                    if (contractInfo) {
+                        const condition = {};
+                        for (const key of needCheckKeys) {
+                            if (l[key] !== contractInfo[key]) {
+                                condition[key] = contractInfo[key];
+                                l[key] = contractInfo[key];
+                            }
+                        }
+                        if (Object.keys(condition).length > 0) {
+                            updateData.push({ id: l.id, ...condition });
+                        }
+                    }
+                }
             }
+            if (updateData.length > 0) await this.db.updateRows(this.tableName, updateData);
             return list;
         }
 
@@ -56,7 +74,7 @@ module.exports = app => {
                 const insertDatas = [];
                 for (const c of contracts) {
                     insertDatas.push({
-                        spid: c.spid,
+                        spid: fp.spid,
                         tid: fp.tid,
                         fpid: fp.id,
                         cid: c.id,

+ 4 - 0
app/service/financial_transfer_tender.js

@@ -96,6 +96,10 @@ module.exports = app => {
             if (!node) {
                 throw '资金划拨标段不存在';
             }
+            if (node.is_lock) {
+                throw '资金划拨标段已锁定,无法修改';
+            }
+
             const transaction = await this.db.beginTransaction();
             try {
                 await transaction.update(this.tableName, { id: node.id, hb_tp });

+ 101 - 0
app/service/ledger.js

@@ -707,6 +707,107 @@ module.exports = app => {
             }
         }
 
+        async importYbp(tender, ybp) {
+            const YBP = require('../lib/ybp');
+            const YbpTrees = require('../lib/ybp_tree');
+            const gatherTreeSetting = {
+                id: 'ledger_id', pid: 'ledger_pid', order: 'order', full_path: 'full_path', level: 'level', rootId: -1,
+                calcFields: [ 'total_price' ],
+                calc(node, helper, decimal) {
+                    if (!node.children || node.children.length === 0) {
+                        node.total_price = helper.mul(node.quantity, node.unit_price, decimal.tp);
+                    }
+                },
+            };
+            const ybpTreeSetting = { id: 'ID', pid: 'parentID', order: 'seq', rootId: '-1' };
+            const helper = this.ctx.helper;
+
+            const ybpAnalysis = new YBP(this.ctx);
+            const ybpData = ybpAnalysis.decryptBuffer(ybp);
+            const gatherTree = new YbpTrees.YbpImportTree(gatherTreeSetting, YbpTrees.YbpImportType.flow, helper, tender.info.decimal);
+            for (const subject of ybpData.subjects) {
+                if (!subject.bills || subject.bills.length === 0) continue;
+
+                const ybpTree = new YbpTrees.YbpTree(ybpTreeSetting);
+                ybpTree.loadDatas(subject.bills);
+                const loadRelaFun = function (cur, node) {
+                    if (node.children && node.children.length > 0) return;
+
+                    const rations = subject.rations.filter(x => { return x.parentID === node.ID; });
+                    if (rations.length === 0) return;
+
+                    if (!cur.glj) cur.glj = [];
+                    for (const r of rations) {
+                        const gljs = r.gljList || r.rationGljList;
+                        if (!gljs || gljs.length === 0) continue;
+                        for (const g of gljs) {
+                            let curGlj = cur.glj.find(x => {
+                                if (x.projectGljID && g.projectGljID) return x.projectGljID === g.projectGljID;
+                                return x.code === g.code && x.name === g.name && x.unit === g.unit && x.unit_price === g.unit_price;
+                            });
+                            if (!curGlj) {
+                                curGlj = { code: g.code, name: g.name, unit: g.unit, unit_price: g.unit_price, spec: g.specs, projectGljID: g.projectGljID };
+                                cur.glj.push(curGlj);
+                            }
+                            curGlj.quantity = helper.add(curGlj.quantity, g.quantity);
+                        }
+                    }
+                };
+                gatherTree.importTree(ybpTree, subject.project.name, loadRelaFun);
+            }
+            gatherTree.sort();
+            gatherTree.calculateAll();
+
+            const bills = [], glj = [];
+            for (const n of gatherTree.nodes) {
+                const billsId = this.uuid.v4();
+                bills.push({
+                    id: billsId,
+                    tender_id: tender.id,
+                    ledger_id: n.ledger_id,
+                    ledger_pid: n.ledger_pid,
+                    level: n.level,
+                    order: n.order,
+                    full_path: n.full_path,
+                    is_leaf: !n.children || n.children.length === 0,
+                    code: n.code,
+                    b_code: n.b_code,
+                    name: n.name,
+                    unit: n.unit,
+                    unit_price: n.unit_price || 0,
+                    sgfh_qty: n.quantity || 0,
+                    sgfh_tp: n.total_price || 0,
+                    quantity: n.quantity || 0,
+                    total_price: n.total_price || 0,
+                    memo: n.source.join(';'),
+                });
+                if (!n.glj) continue;
+                for (const g of n.glj) {
+                    glj.push({
+                        tid: tender.id,
+                        lid: billsId,
+                        code: g.code || '',
+                        name: g.name || '',
+                        unit: g.unit || '',
+                        spec: g.spec || '',
+                        quantity: helper.div(g.quantity, n.quantity, 4),
+                    })
+                }
+            }
+            const conn = await this.db.beginTransaction();
+            try {
+                await conn.delete(this.ctx.service.ledger.tableName, { tender_id: tender.id });
+                await conn.delete(this.ctx.service.pos.tableName, { tid: tender.id });
+                await conn.insert(this.ctx.service.ledger.tableName, bills);
+                // if (glj.length > 0) await conn.insert(this.ctx.service.ledgerGlj.tableName, glj);
+                await conn.commit();
+                return bills;
+            } catch (err) {
+                await conn.rollback();
+                throw err;
+            }
+        }
+
         async sumLoad(lid, tenders) {
             const maxId = await this._getMaxLid(this.ctx.tender.id);
             const select = await this.getDataById(lid);

+ 12 - 0
app/service/material_list.js

@@ -285,6 +285,18 @@ module.exports = app => {
                 m_tp: tp.total_price,
                 m_tax_tp: tp.tax_total_price,
             };
+            // 计算建筑税价
+            if (this.ctx.material.is_stage_self) {
+                const materialStages = await transaction.select(this.ctx.service.materialStage.tableName, { where: { mid: this.ctx.material.id } });
+                let rate_tp = 0;
+                for (const ms of materialStages) {
+                    const ms_rate_tp = this.ctx.helper.round(this.ctx.helper.mul(ms.m_tp, 1 + this.ctx.material.rate / 100), this.ctx.material.decimal.tp);
+                    rate_tp = this.ctx.helper.add(rate_tp, ms_rate_tp);
+                }
+                updateData2.rate_tp = rate_tp;
+            } else {
+                updateData2.rate_tp = this.ctx.helper.round(this.ctx.helper.mul(tp.total_price, 1 + this.ctx.material.rate / 100), this.ctx.material.decimal.tp);
+            }
             const result = await transaction.update(this.ctx.service.material.tableName, updateData2);
             // 找出当前人并更新tp_data
             const tp_data = await this.ctx.service.materialAudit.getTpData(transaction, this.ctx.material.id);

+ 5 - 2
app/service/stage_audit.js

@@ -1565,12 +1565,13 @@ module.exports = app => {
         async getAuditStage(auditorId) {
             const sql =
                 'SELECT sa.`aid`, sa.`times`, sa.`order`, sa.`begin_time`, sa.`end_time`, sa.`tid`, sa.`sid`,' +
-                '    s.`order` As `sorder`, s.`status` As `sstatus`,' +
+                '    s.`order` As `sorder`, s.`status` As `sstatus`, s.`in_time`,' +
                 '    t.`name`, t.`project_id`, t.`type`, t.`user_id` ' +
                 '  FROM ?? AS sa ' +
                 '    Left Join ?? AS s On sa.`sid` = s.`id` ' +
                 '    Left Join ?? As t ON sa.`tid` = t.`id`' +
-                '  WHERE ((sa.`aid` = ? and sa.`status` = ?) OR (s.`user_id` = ? and sa.`status` = ? and s.`status` = ? and sa.`times` = (s.`times`-1)))' +
+                '  WHERE ((sa.`aid` = ? and sa.`status` = ?) OR (s.`user_id` = ? and sa.`status` = ? and s.`status` = ? and sa.`times` = (s.`times`-1))' +
+                '  OR (s.`user_id` = ? and s.`status` = ? and sa.`times` = s.`times`))' +
                 '  ORDER BY sa.`begin_time` DESC';
             const sqlParam = [
                 this.tableName,
@@ -1581,6 +1582,8 @@ module.exports = app => {
                 auditorId,
                 auditConst.status.checkNo,
                 auditConst.status.checkNo,
+                auditorId,
+                auditConst.status.uncheck,
             ];
             const result = await this.db.query(sql, sqlParam);
             // 过滤result中存在重复sid的值, 保留最新的一条

+ 94 - 0
app/service/sub_proj_file.js

@@ -0,0 +1,94 @@
+'use strict';
+
+/**
+ *
+ *  附件
+ * @author Ellisran
+ * @date 2019/1/11
+ * @version
+ */
+
+module.exports = app => {
+    class SubProjFile extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'sub_project_file';
+        }
+
+        _analysisData(files) {
+            const helper = this.ctx.helper;
+            const userId = this.ctx.session.sessionUser.accountId;
+            const ossPath = this.ctx.app.config.fujianOssPath;
+            files.forEach(x => {
+                x.viewpath = helper.canPreview(x.fileext) ? ossPath + x.filepath : '';
+                x.filepath = ossPath + x.filepath;
+                x.fileext_str = helper.fileExtStr(x.fileext);
+                x.canEdit = x.user_id === userId;
+            });
+        }
+
+        async getData(spid, type) {
+            const data = await this.getAllDataByCondition({
+                where: { type, spid, is_deleted: 0 },
+                orders: [['create_time', 'desc']],
+            });
+            this._analysisData(data);
+            return data;
+        }
+
+        async getFiles(condition) {
+            condition.orders = [['create_time', 'desc']];
+            const result = await this.getAllDataByCondition(condition);
+            this._analysisData(result);
+            return result;
+        }
+
+        async addFiles(spid, type, fileInfo, user) {
+            const conn = await this.db.beginTransaction();
+            const result = {};
+            try {
+                const insertData = fileInfo.map(x => {
+                    return {
+                        id: this.uuid.v4(), spid, type, rela_id: x.rela_id,
+                        user_id: user.id, user_name: user.name, user_company: user.company, user_role: user.role,
+                        filename: x.filename, fileext: x.fileext, filesize: x.filesize, filepath: x.filepath,
+                    };
+                });
+                await conn.insert(this.tableName, insertData);
+                await conn.commit();
+                result.files = { id: insertData.map(x => { return x.id; })};
+            } catch (err) {
+                await conn.rollback();
+                throw err;
+            }
+            return await this.getFiles({ where: result.files });
+        }
+
+        async delFiles(files) {
+            if (files.length === 0) return;
+
+            const fileDatas = await this.getAllDataByCondition({ where: { id: files } });
+            const result = {};
+
+            const conn = await this.db.beginTransaction();
+            try {
+                const updateData = fileDatas.map(x => { return { id: x.id, is_deleted: 1 }; });
+                if (updateData.length > 0) await conn.updateRows(this.tableName, updateData);
+                await conn.commit();
+                result.del = files;
+            } catch (err) {
+                await conn.rollback();
+                throw err;
+            }
+            return result;
+        }
+    }
+
+    return SubProjFile;
+};

+ 186 - 0
app/service/sub_proj_progress.js

@@ -0,0 +1,186 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const readOnlyFields = ['id', 'spid', 'tree_id', 'tree_pid', 'tree_order', 'tree_level', 'tree_full_path', 'tree_is_leaf'];
+const defaultData = [
+    { code: '1', name: '前期阶段', tree_id: 1, tree_pid: -1, tree_level: 1, tree_order: 1, tree_full_path: '1', tree_is_leaf: 1 },
+    { code: '2', name: '实施阶段', tree_id: 2, tree_pid: -1, tree_level: 1, tree_order: 2, tree_full_path: '2', tree_is_leaf: 1 },
+    { code: '3', name: '竣(交)工阶段', tree_id: 3, tree_pid: -1, tree_level: 1, tree_order: 3, tree_full_path: '3', tree_is_leaf: 1 },
+];
+
+module.exports = app => {
+    class SubProjProgress extends app.BaseTreeService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @param {String} tableName - 表名
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx, {
+                mid: 'spid',
+                kid: 'tree_id',
+                pid: 'tree_pid',
+                order: 'tree_order',
+                level: 'tree_level',
+                isLeaf: 'tree_is_leaf',
+                fullPath: 'tree_full_path',
+                cacheKey: false,
+                uuid: true,
+            });
+            this.tableName = 'sub_project_progress';
+        }
+
+        _getDefaultData(data, spid) {
+            data.id = this.uuid.v4();
+            data.spid = spid;
+            data.add_user_id = this.ctx.session.sessionUser.accountId;
+            data.update_user_id = this.ctx.session.sessionUser.accountId;
+        }
+
+        async init(subProj) {
+            if (!subProj) throw '阶段进度数据错误';
+
+            const insertData = [];
+            for (const b of defaultData) {
+                const bills = JSON.parse(JSON.stringify(b));
+                this._getDefaultData(bills, subProj.id);
+                insertData.push(bills);
+            }
+
+            const operate = await this.db.insert(this.tableName, insertData);
+            return operate.affectedRows === insertData.length;
+        }
+
+        async getData(subProj) {
+            let result = await this.getAllDataByCondition({ where: { spid: subProj.id } });
+            if (result.length === 0) {
+                await this.init(subProj);
+                result = await this.getAllDataByCondition({ where: { spid: subProj.id } });
+            }
+            return result;
+        }
+
+        async addChild(spid, select, count) {
+            const maxId = await this._getMaxLid(spid);
+            const children = await this.getChildrenByParentId(spid, select[this.setting.kid]);
+            const newDatas = [];
+            for (let i = 1; i < count + 1; i++) {
+                const newData = {};
+                newData[this.setting.kid] = maxId + i;
+                newData[this.setting.pid] = select[this.setting.kid];
+                newData[this.setting.level] = select[this.setting.level] + 1;
+                newData[this.setting.order] = children.length + i;
+                newData[this.setting.fullPath] = select[this.setting.fullPath] + '-' + newData[this.setting.kid];
+                newData[this.setting.isLeaf] = true;
+                this._getDefaultData(newData, spid);
+                newDatas.push(newData);
+            }
+
+            this.transaction = await this.db.beginTransaction();
+            try {
+                const result = await this.transaction.insert(this.tableName, newDatas);
+                if (children.length === 0) await this.transaction.update(this.tableName, { id: select.id, tree_is_leaf: 0 });
+                await this.transaction.commit();
+            } catch(err) {
+                this.transaction.rollback();
+                throw err;
+            }
+
+            // 查询应返回的结果
+            const resultData = {};
+            resultData.create = await this.getNextsData(spid, select[this.setting.kid], children.length);
+            if (children.length === 0) resultData.update = await this.getDataByKid(spid, select[this.setting.kid]);
+            return resultData;
+        }
+
+        async addNextSibling(spid, select, count) {
+            const maxId = await this._getMaxLid(spid);
+            const newDatas = [];
+
+            for (let i = 1; i < count + 1; i++) {
+                const newData = {};
+                newData[this.setting.kid] = maxId + i;
+                newData[this.setting.pid] = select ? select[this.setting.pid] : this.rootId;
+                newData[this.setting.level] = select ? select[this.setting.level] : 1;
+                newData[this.setting.order] = select ? select[this.setting.order] + i : i;
+                newData[this.setting.fullPath] = newData[this.setting.level] > 1
+                    ? select[this.setting.fullPath].replace('-' + select[this.setting.kid], '-' + newData[this.setting.kid])
+                    : newData[this.setting.kid] + '';
+                newData[this.setting.isLeaf] = true;
+                this._getDefaultData(newData, spid);
+                newDatas.push(newData);
+            }
+
+            this.transaction = await this.db.beginTransaction();
+            try {
+                if (select) await this._updateChildrenOrder(spid, select[this.setting.pid], select[this.setting.order] + 1, count);
+                const insertResult = await this.transaction.insert(this.tableName, newDatas);
+                if (insertResult.affectedRows !== count) throw '新增节点数据错误';
+                await this.transaction.commit();
+            } catch (err) {
+                await this.transaction.rollback();
+                this.transaction = null;
+                throw err;
+            }
+
+            if (select) {
+                const createData = await this.getChildBetween(spid, select[this.setting.pid], select[this.setting.order], select[this.setting.order] + count + 1);
+                const updateData = await this.getNextsData(spid, select[this.setting.pid], select[this.setting.order] + count);
+                return {create: createData, update: updateData};
+            } else {
+                const createData = await this.getChildBetween(spid, -1, 0, count + 1);
+                return {create: createData};
+            }
+        }
+
+        async addProgressNode(spid, targetId, count) {
+            if (!spid) return null;
+
+            const select = targetId ? await this.getDataByKid(spid, targetId) : null;
+            if (targetId && !select) throw '新增节点数据错误';
+
+            if (select[this.setting.level] === 1) {
+                return await this.addChild(spid, select, count);
+            } else {
+                return await this.addNextSibling(spid, select, count);
+            }
+        }
+
+        async _deleteRelaData(spid, deleteData) {
+            if (!this.transaction) throw '删除相关数据错误';
+            await this.transaction.update(this.ctx.service.subProjFile.tableName, {is_deleted: 1},
+                { where: { spid, type: 'progress', rela_id: deleteData.map(x => { return x.id; })} });
+        }
+
+        async updateInfos(spid, data) {
+            if (!spid) throw '数据错误';
+
+            const datas = Array.isArray(data) ? data : [data];
+            const orgDatas = await this.getAllDataByCondition({ where: { id: datas.map(x => { return x.id; })} });
+
+            const updateDatas = [];
+            for (const d of datas) {
+                const node = orgDatas.find(x => { return x.id === d.id; });
+                if (!node || node.spid !== spid) throw '提交数据错误';
+                d.update_user_id = this.ctx.session.sessionUser.accountId;
+                updateDatas.push(this._filterUpdateInvalidField(node.id, d));
+            }
+            await this.db.updateRows(this.tableName, updateDatas);
+
+            const resultData = await this.getDataById(this._.map(datas, 'id'));
+            return { update: resultData };
+        }
+    }
+
+    return SubProjProgress;
+}

+ 132 - 0
app/service/sub_proj_push.js

@@ -0,0 +1,132 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+module.exports = app => {
+    class SubProjPush extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'sub_project_push';
+        }
+
+        async getData(spid) {
+            const data = await this.getAllDataByCondition({where: { spid }, orders: [['push_order', 'asc']]});
+            return data;
+        }
+
+        async _addDatas(data) {
+            const spid = this.ctx.subProject.id;
+            const datas = data instanceof Array ? data : [data];
+            const insertData = [];
+            for (const d of datas) {
+                if (!d.push_order) throw '提交的数据错误';
+                const nd = {
+                    id: this.uuid.v4(),
+                    spid: this.ctx.subProject.id,
+                    user_id: this.ctx.session.sessionUser.accountId,
+                    update_user_id: this.ctx.session.sessionUser.accountId,
+                    push_order: d.push_order,
+                };
+                if (d.push_date !== undefined) nd.push_date = d.push_date;
+                if (d.push_content !== undefined) nd.push_content = d.push_content;
+                if (d.memo !== undefined) nd.memo = d.memo;
+                insertData.push(nd);
+            }
+            const push_order = this.ctx.helper._.min(insertData.map(x => { return x.push_order}));
+
+            const conn = await this.db.beginTransaction();
+            try {
+                await conn.query(`UPDATE ${this.tableName} SET push_order = push_order + ? WHERE spid = ? AND push_order >= ?`, [insertData.length, spid, push_order]);
+                await conn.insert(this.tableName, insertData);
+                await conn.commit();
+            } catch(err) {
+                await conn.rollback();
+                throw err;
+            }
+            const add = await this.getAllDataByCondition({ where: { spid: this.ctx.subProject.id, id: this.ctx.helper._.map(insertData, 'id') } });
+            const update = await this.db.query(`SELECT * FROM ${this.tableName} WHERE spid = ? AND push_order >= ?`, [spid, push_order + insertData.length]);
+            return { add, update };
+        }
+
+        async _delDatas (data) {
+            const spid = this.ctx.subProject.id;
+            const datas = data instanceof Array ? data : [data];
+            const orgDatas = await this.getAllDataByCondition({ where: { id: datas }});
+            const push_order = this.ctx.helper._.min(orgDatas.map(x => { return x.push_order}));
+
+            const conn = await this.db.beginTransaction();
+            try {
+                await conn.delete(this.tableName, {id: datas});
+                await conn.update(this.ctx.service.subProjFile.tableName, { is_deleted: 1}, { where: { spid: this.ctx.subProject.id, type: 'push', rela_id: datas } });
+                await conn.query(`UPDATE ${this.tableName} SET push_order = push_order - ? WHERE spid = ? AND push_order >= ?`, [datas.length, spid, push_order + datas.length]);
+                await conn.commit();
+            } catch(err) {
+                await conn.rollback();
+                throw err;
+            }
+            const update = await this.db.query(`SELECT * FROM ${this.tableName} WHERE spid = ? AND push_order >= ?`, [spid, push_order]);
+            return { del: datas, update };
+        }
+
+        async _updateDatas (data) {
+            const datas = data instanceof Array ? data : [data];
+
+            const uDatas = [];
+            for (const d of datas) {
+                const nd = { id: d.id };
+                if (d.push_order !== undefined) nd.push_order = d.push_order;
+                if (d.push_date !== undefined) nd.push_date = d.push_date;
+                if (d.push_content !== undefined) nd.push_content = d.push_content;
+                if (d.memo !== undefined) nd.memo = d.memo;
+                uDatas.push(nd);
+            }
+            if (uDatas.length > 0) {
+                await this.db.updateRows(this.tableName, uDatas);
+                return uDatas;
+            } else {
+                return [];
+            }
+        }
+
+        async updateDatas(data) {
+            const result = {add: [], del: [], update: []};
+            try {
+                if (data.add) {
+                    const addResult = await this._addDatas(data.add);
+                    result.add.push(...addResult.add);
+                    result.update.push(...addResult.update);
+                }
+                if (data.update) {
+                    result.update = await this._updateDatas(data.update);
+                }
+                if (data.del) {
+                    const delResult = await this._delDatas(data.del);
+                    result.del.push(...delResult.del);
+                    result.update.push(...delResult.update);
+                }
+                return result;
+            } catch (err) {
+                if (err.stack) {
+                    throw err;
+                } else {
+                    result.err = err.toString();
+                    return result;
+                }
+            }
+        }
+    }
+
+    return SubProjPush;
+};

+ 1 - 1
app/service/sub_project.js

@@ -358,7 +358,7 @@ module.exports = app => {
                 const removeTenderId = orgRelaTenderId.filter(x => { return relaTenderId.indexOf(x) < 0});
                 const addTenderId = relaTenderId.filter(x => { return orgRelaTenderId.indexOf(x) < 0});
                 if (removeTenderId.length > 0) await conn.update(this.ctx.service.tender.tableName, { id: removeTenderId, spid: '' });
-                if (removeTenderId.length > 0) await conn.update(this.ctx.service.tender.tableName, { id: addTenderId, spid: data.id });
+                if (addTenderId.length > 0) await conn.update(this.ctx.service.tender.tableName, { id: addTenderId, spid: data.id });
                 await conn.commit();
                 return data;
             } catch (error) {

+ 1 - 0
app/service/tender_info.js

@@ -83,6 +83,7 @@ module.exports = app => {
             if (info.decimal) {
                 info.decimal._pay_tp = info.decimal.pay ? info.decimal.payTp : info.decimal.tp;
             }
+            info.checkOverInfo = infoConst.transOverRangeCheck(this.ctx.helper, info.over_range_check);
             return info;
         }
 

+ 15 - 7
app/view/dashboard/index.ejs

@@ -128,21 +128,29 @@
                                             </tr>
                                         <% } %>
                                         <% for (const audit of auditStages) { %>
-                                            <% if (audit.sstatus !== acStage.status.checkNo) { %>
+                                            <% if (audit.sstatus === acStage.status.checkNo) { %>
                                                 <tr data-type="2">
                                                     <td><span class="bg-new-stage text-new-stage badge text-width">计量审批</span></td>
                                                     <td><a href="/tender/<%- audit.tid %>"><%- audit.name %></a> <a href="/tender/<%- audit.tid %>/measure/stage/<%- audit.sorder %>">第<%- audit.sorder %>期</a></td>
                                                     <td>第<%- audit.sorder %>期</td>
-                                                    <td><%- ctx.moment(audit.begin_time).format('YYYY/MM/DD HH:mm') %></td>
-                                                    <td><a href="/tender/<%- audit.tid %>/measure/stage/<%- audit.sorder %>" class="btn btn-outline-primary btn-sm btn-table" role="button"><% if (audit.sstatus === acStage.status.checkNoPre) { %>重新<% } %>审批</a></td>
+                                                    <td><%- ctx.moment(audit.end_time).format('YYYY/MM/DD HH:mm') %></td>
+                                                    <td><a href="/tender/<%- audit.tid %>/measure/stage/<%- audit.sorder %>" class="btn btn-outline-warning btn-sm btn-table text-warning" role="button">重新上报</a></td>
+                                                </tr>
+                                            <% } else if (audit.sstatus === acStage.status.uncheck) { %>
+                                                <tr data-type="2">
+                                                    <td><span class="bg-new-stage text-new-stage badge text-width">计量审批</span></td>
+                                                    <td><a href="/tender/<%- audit.tid %>"><%- audit.name %></a> <a href="/tender/<%- audit.tid %>/measure/stage/<%- audit.sorder %>">第<%- audit.sorder %>期</a></td>
+                                                    <td>第<%- audit.sorder %>期</td>
+                                                    <td><%- ctx.moment(audit.in_time).format('YYYY/MM/DD HH:mm') %></td>
+                                                    <td><a href="/tender/<%- audit.tid %>/measure/stage/<%- audit.sorder %>" class="btn btn-outline-primary btn-sm btn-table" role="button">上报</a></td>
                                                 </tr>
                                             <% } else { %>
                                                 <tr data-type="2">
                                                     <td><span class="bg-new-stage text-new-stage badge text-width">计量审批</span></td>
                                                     <td><a href="/tender/<%- audit.tid %>"><%- audit.name %></a> <a href="/tender/<%- audit.tid %>/measure/stage/<%- audit.sorder %>">第<%- audit.sorder %>期</a></td>
                                                     <td>第<%- audit.sorder %>期</td>
-                                                    <td><%- ctx.moment(audit.end_time).format('YYYY/MM/DD HH:mm') %></td>
-                                                    <td><a href="/tender/<%- audit.tid %>/measure/stage/<%- audit.sorder %>" class="btn btn-outline-warning btn-sm btn-table text-warning" role="button">重新上报</a></td>
+                                                    <td><%- ctx.moment(audit.begin_time).format('YYYY/MM/DD HH:mm') %></td>
+                                                    <td><a href="/tender/<%- audit.tid %>/measure/stage/<%- audit.sorder %>" class="btn btn-outline-primary btn-sm btn-table" role="button"><% if (audit.sstatus === acStage.status.checkNoPre) { %>重新<% } %>审批</a></td>
                                                 </tr>
                                             <% } %>
                                         <% } %>
@@ -289,7 +297,7 @@
                                         <% for (const af of auditFinancials) { %>
                                             <tr data-type="11">
                                                 <td><span class="bg-new-financial text-new-financial badge text-width">资金支付</span></td>
-                                                <td><a href="/financial/<%- af.spid %>/pay"><%- af.name %></a> <a href="/financial/<%- af.spid %>/pay/<%- af.fpid %>/detial"><%- af.fpcode %></a></td>
+                                                <td><a href="/financial/<%- af.spid %>/pay"><%- af.name %></a> <a href="/financial/<%- af.spid %>/pay/<%- af.fpid %>/detail"><%- af.fpcode %></a></td>
                                                 <td>支付</td>
                                                 <td><%- (
                                                             af.fpcstatus !== acFinancial.status.checkNo
@@ -518,7 +526,7 @@
                                             <% } else if(notice.type === pushType.financial && ctx.session.sessionProject.page_show.openFinancial) { %>
                                                 <tr data-type="11">
                                                     <td><span class="bg-new-financial text-new-financial badge text-width">资金支付</span></td>
-                                                    <td><a href="/financial/<%- notice.spid %>"><%- notice.name %></a> <a href="/financial/<%- notice.spid %>/pay/<%- notice.fpid %>/detail"><%- notice.code %></a></td>
+                                                    <td><a href="/financial/<%- notice.spid %>/pay"><%- notice.name %></a> <a href="/financial/<%- notice.spid %>/pay/<%- notice.fpid %>/detail"><%- notice.code %></a></td>
                                                     <td><%- notice.su_name %><%- (notice.su_role ? '-' + notice.su_role : '') %></td>
                                                     <td><%- ctx.helper.dateTran(notice.create_time, 'YYYY/MM/DD HH:mm') %></td>
                                                     <td>支付</td>

+ 2 - 2
app/view/financial/pay.ejs

@@ -52,7 +52,7 @@
                 </div>
             </div>
             <div class="d-inline-block ml-auto">
-                <% if (ctx.session.sessionUser.is_admin || fptAuditTids.length > 0) { %>
+                <% if (ctx.session.sessionUser.is_admin || fptReportTids.length > 0) { %>
                 <a href="#add-pay" data-toggle="modal" data-target="#add-pay" class="btn btn-primary btn-sm pull-right">申请支付</a>
                 <% } %>
 <!--                <a href="#batch-sp" data-toggle="modal" data-target="#batch-sp" class="btn btn-success btn-sm pull-right mr-2">批量审批</a>-->
@@ -128,7 +128,7 @@
     const is_admin = <%- ctx.session.sessionUser.is_admin %>;
     const spid = '<%- ctx.subProject.id %>';
     const tenders = JSON.parse(unescape('<%- escape(JSON.stringify(tenders)) %>'));
-    const fptAuditTids = JSON.parse(unescape('<%- escape(JSON.stringify(fptAuditTids)) %>'));
+    const fptReportTids = JSON.parse(unescape('<%- escape(JSON.stringify(fptReportTids)) %>'));
     const auditConst = JSON.parse(unescape('<%- escape(JSON.stringify(auditConst)) %>'));
     const auditType = JSON.parse(unescape('<%- escape(JSON.stringify(auditType)) %>'));
 </script>

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

@@ -101,7 +101,7 @@
                         </div>
                     </div>
                 </div>
-                <form class="modal-footer" method="post" action="<%- preUrl %>/audit/start" onsubmit="return checkStartFrom()">
+                <form class="modal-footer" method="post" action="<%- preUrl2 %>/audit/start" onsubmit="return checkStartFrom()">
                     <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
                     <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>">
                     <% if (ctx.session.sessionUser.accountId === financialPay.uid) { %>
@@ -331,7 +331,7 @@
                         </div>
                     </div>
                 </div>
-                <form class="modal-footer" method="post" action="<%- preUrl %>/audit/start" onsubmit="return checkStartFrom()">
+                <form class="modal-footer" method="post" action="<%- preUrl2 %>/audit/start" onsubmit="return checkStartFrom()">
                     <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>">
                     <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
                     <% if(financialPay.status === auditConst.status.checkNo && ctx.session.sessionUser.accountId === financialPay.uid) { %>
@@ -347,7 +347,7 @@
         <!--审批通过-->
         <div class="modal fade sp-location-list" id="sp-done" data-backdrop="static">
             <div class="modal-dialog modal-lg" role="document">
-                <form class="modal-content" action="<%- preUrl %>/audit/check" method="post" onsubmit="return auditCheck(0);">
+                <form class="modal-content" action="<%- preUrl2 %>/audit/check" method="post" onsubmit="return auditCheck(0);">
                     <div class="modal-header">
                         <h5 class="modal-title">审批通过</h5>
                     </div>
@@ -525,7 +525,7 @@
         <!--审批退回-->
         <div class="modal fade sp-location-list" id="sp-back" data-backdrop="static">
             <div class="modal-dialog modal-lg" role="document">
-                <form class="modal-content modal-lg" action="<%- preUrl %>/audit/check" method="post"
+                <form class="modal-content modal-lg" action="<%- preUrl2 %>/audit/check" method="post"
                       onsubmit="return auditCheck(1);">
                     <div class="modal-header">
                         <h5 class="modal-title">审批退回</h5>

+ 2 - 2
app/view/financial/pay_modal.ejs

@@ -1,5 +1,5 @@
 <% include ../shares/delete_hint_modal.ejs %>
-<% if (ctx.session.sessionUser.is_admin || fptAuditTids.length > 0) { %>
+<% if (ctx.session.sessionUser.is_admin || fptReportTids.length > 0) { %>
 <!-- 申请支付 -->
 <div class="modal fade show" id="add-pay" data-backdrop="static">
     <div class="modal-dialog" role="document">
@@ -12,7 +12,7 @@
                     <label>支付标段<strong class="text-danger">*</strong></label>
                     <select class="form-control form-control-sm" id="add-pay-tender">
                         <% for (const t of tenders) { %>
-                        <% if (ctx.session.sessionUser.is_admin || ctx.helper._.includes(fptAuditTids, t.id)) { %>
+                        <% if (ctx.session.sessionUser.is_admin || ctx.helper._.includes(fptReportTids, t.id)) { %>
                             <option value="<%- t.id %>"><%- t.name %></option>
                         <% } %>
                         <% } %>

+ 146 - 0
app/view/financial/summary.ejs

@@ -0,0 +1,146 @@
+<link rel="stylesheet" href="/public/css/bootstrap/bootstrap-select.min.css">
+<% include ./sub_menu.ejs %>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main  d-flex">
+            <% include ./sub_mini_menu.ejs %>
+            <div class="col-12 pl-0">
+                <div class="d-inline-block mr-2" style="width: 300px;">
+                    <div class="input-group input-group-sm pr-1">
+                        <style>
+                            .select-border {
+                                border: 1px solid #ced4da;
+                            }
+                        </style>
+                        <select class="form-control form-control-sm" multiple id="tender-select" data-style="select-border">
+                            <option value="0" selected>全部</option>
+                        </select>
+                    </div>
+                </div>
+                <div class="d-inline-block">
+                    <!-- <label for="">开始时间</label><input type="text"  class="form-control form-control-sm"> -->
+                    <div class="form-inline">
+                        <div class="form-group mx-sm-3 mb-2">
+                            <label for="inputPassword2" class="">开始日期:</label>
+                            <input class="datepicker-here form-control form-control-sm mt-0" placeholder="选择开始日期" data-view="months" data-min-view="months" data-date-format="yyyy-MM" data-language="zh" type="text" id="start-month">
+                            <label for="inputPassword2" class="ml-2">结束日期:</label>
+                            <input class="datepicker-here form-control form-control-sm  mt-0" placeholder="选择结束始日期" data-view="months" data-min-view="months" data-date-format="yyyy-MM" data-language="zh" type="text" id="end-month">
+                            <button class="btn btn-sm btn-primary ml-1" id="search-btn">查询</button>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="d-inline-block ml-auto">
+            </div>
+        </div>
+    </div>
+    <div class="content-wrap">
+        <div class="c-body">
+            <div class="sjs-height-0">
+                <div class="row mt-3 col-12">
+                    <div class="col-4 px-0">
+                        <div class="card ml-3">
+                            <div class="card-header card-white d-flex justify-content-between">
+                                <div class="card-big-htext"><span class="card-icon mr-2"></span>金额概况</div>
+                                <div class="mt-1">元</div>
+                            </div>
+                            <div class="card-body p-0">
+                                <div class="">
+                                    <table class="table table-middle">
+                                        <thead class="text-center align-middle thead-light" style="height: 50px;line-height: 25px">
+                                        <tr>
+                                            <th>累计划拨</th>
+                                            <th>累计支付</th>
+                                            <th>待支付</th>
+                                        </tr>
+                                        </thead>
+                                        <tbody class="text-center" style="height: 53.5px;">
+                                        <tr>
+                                            <td id="transfer_price">-</td>
+                                            <td id="pay_price">-</td>
+                                            <td id="pend_price">-</td>
+                                        </tr>
+                                        </tbody>
+                                    </table>
+                                </div>
+                            </div>
+                        </div>
+                        <div class="card ml-3 mt-3">
+                            <div class="card-header card-white d-flex justify-content-between">
+                                <div class="card-big-htext"><span class="card-icon mr-2"></span>支付进度</div>
+                                <div class="mt-1"></div>
+                            </div>
+                            <div class="card-body p-0">
+                                <div class="progress my-5 mx-4" id="pay_progress">
+                                </div>
+                            </div>
+                        </div>
+                        <div class="card ml-3 mt-3">
+                            <div class="card-header card-white d-flex justify-content-between">
+                                <div class="card-big-htext"><span class="card-icon mr-2"></span>资金支出明细</div>
+                                <div class="mt-1">元</div>
+                            </div>
+                            <div class="card-body p-0">
+                                <table class="table table-middle">
+                                    <thead class="text-center thead-light">
+                                    <tr>
+                                        <th>支出类型</th>
+                                        <th>支出金额</th>
+                                        <th>占比%</th>
+                                    </tr>
+                                    </thead>
+                                    <tbody id="pay_used_table">
+                                    </tbody>
+                                </table>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="col-8 px-0">
+                        <div class="ml-3">
+                            <div class="card">
+                                <div class="card-header card-white d-flex justify-content-between">
+                                    <div class="card-big-htext"><span class="card-icon mr-2"></span>标段支出概况</div>
+                                    <div class="mt-1"></div>
+                                </div>
+                                <div class="card-body p-0">
+                                    <div id="jlchart3" style="height: 300%; width: 100%;"> </div>
+                                </div>
+                            </div>
+                        </div>
+                        <div class="ml-3 mt-3 row px-0">
+                            <div class="col-6 px-0">
+                                <div class="card">
+                                    <div class="card-header card-white d-flex justify-content-between">
+                                        <div class="card-big-htext"><span class="card-icon mr-2"></span>支出明细占比</div>
+                                        <div class="mt-1"></div>
+                                    </div>
+                                    <div class="card-body p-0">
+                                        <div id="jlchart1" style="height: 335%; width: 100%;"> </div>
+                                    </div>
+                                </div>
+                            </div>
+                            <div class="col-6">
+                                <div class="card">
+                                    <div class="card-header card-white d-flex justify-content-between">
+                                        <div class="card-big-htext"><span class="card-icon mr-2"></span>小额支出占比</div>
+                                        <div class="mt-1"></div>
+                                    </div>
+                                    <div class="card-body p-0">
+                                        <div id="jlchart2" style="height: 335%; width: 100%;"> </div>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+
+            </div>
+        </div>
+    </div>
+</div>
+<script src="/public/js/bootstrap/bootstrap-select.min.js"></script>
+<script>
+    const spid = '<%- ctx.subProject.id %>';
+    const usedList = JSON.parse(unescape('<%- escape(JSON.stringify(usedList)) %>'));
+    let tenders, transferList, transferTenderList, payList;
+</script>

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

@@ -14,7 +14,7 @@
     <div class="content-wrap">
         <div class="c-body">
             <div class="sjs-height-0">
-                <table class="table table-bordered text-center">
+                <table class="table table-hover table-bordered text-center">
                     <thead>
                     <tr>
                         <th width="100px">次数</th>

+ 2 - 0
app/view/financial/transfer_tender.ejs

@@ -1,3 +1,4 @@
+<link rel="stylesheet" href="/public/css/bootstrap/bootstrap-select.min.css">
 <% include ./sub_menu.ejs %>
 <div class="panel-content">
     <div class="panel-title">
@@ -63,6 +64,7 @@
         </div>
     </div>
 </div>
+<script src="/public/js/bootstrap/bootstrap-select.min.js"></script>
 <script>
     const user_id = <%- ctx.session.sessionUser.accountId %>;
     const spid = '<%- ctx.subProject.id %>';

+ 4 - 4
app/view/financial/transfer_tender_modal.ejs

@@ -46,9 +46,9 @@
             </div>
             <div class="modal-body">
                 <table class="table table-bordered">
-                    <thead>
+                    <thead class="text-center">
                     <tr>
-                        <th width="50px">选择</th>
+                        <th width="40px">选择</th>
                         <th width="">标段名称</th>
                         <th width="150px">选择期数</th>
                     </tr>
@@ -59,9 +59,9 @@
                         <td><input type="checkbox" value="<%- t.id %>"></td>
                         <td class="text-left"><%- t.name %></td>
                         <td>
-                            <select class="form-control form-control-sm" size="3" multiple>
+                            <select class="form-control form-control-sm selectpicker" title="选择期" data-width="150px" multiple>
                                 <% for (const s of t.stages) { %>
-                                <option value="<%- s.order %>">第<%- s.order %>期</option>
+                                    <option value="<%- s.order %>">第<%- s.order %>期</option>
                                 <% } %>
                             </select>
                         </td>

+ 3 - 0
app/view/ledger/explode.ejs

@@ -42,6 +42,9 @@
                 <div class="d-inline-block ml-3">
                     <a id="exportLedger" class="btn btn-primary btn-sm" href="javascript: void(0)">导出台账Excel</a>
                 </div>
+                <div class="d-inline-block ml-1">
+                    <a href="#upload-ybp" data-toggle="modal" data-target="#upload-ybp" class="btn btn-sm btn-primary">导入计价文件</a>
+                </div>
                 <% if (syncLedgerUrl) { %>
                 <div class="d-inline-block ml-1">
                     <a id="sync-ledger" class="btn btn-primary btn-sm" href="javascript: void(0)">同步台账</a>

+ 20 - 0
app/view/ledger/explode_modal.ejs

@@ -55,6 +55,26 @@
         </div>
     </div>
 </div>
+<!--上传计价文件-->
+<div class="modal fade" id="upload-ybp" data-backdrop="static" enctype="multipart/form-data">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">导入计价文件</h5>
+            </div>
+            <div class="modal-body">
+                <div class="form-group">
+                    <label for="exampleFormControlFile1">导入计价YBPX/YUP文件</label>
+                    <input type="file" class="form-control-file" id="ybp-file">
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                <button type="button" class="btn btn-primary btn-sm" id="upload-ybp-file">确认上传</button>
+            </div>
+        </div>
+    </div>
+</div>
 <!--批量添加清单部位-->
 <div class="modal fade" id="batch" data-backdrop="static">
     <div class="modal-dialog modal-xl" role="document">

+ 2 - 2
app/view/measure/compare_modal.ejs

@@ -7,9 +7,9 @@
             </div>
             <div class="modal-body scroll-y" style="height: 600px">
                 <table class="table table-sm" >
-                    <tr><th>期</th><th width="90">选择</th></tr>
+                    <tr><th>期</th><th>计量年月</th><th width="60">选择</th></tr>
                     <% for (const s of stages) { %>
-                    <tr stage-id="<%- s.id %>"><td><%- s.order %>期</td><td><input type="checkbox"></td></tr>
+                    <tr stage-id="<%- s.id %>"><td><%- s.order %>期</td><td><%- s.s_time %></td><td><input type="checkbox"></td></tr>
                     <% } %>
                 </table>
             </div>

+ 1 - 1
app/view/shares/select_rela_tender_modal.ejs

@@ -1,6 +1,6 @@
 <!--弹出关联标段-->
 <div class="modal fade" id="select-rela" data-backdrop="static">
-    <div class="modal-dialog modal-lg" role="document">
+    <div class="modal-dialog modal-xl" role="document">
         <div class="modal-content">
             <div class="modal-header">
                 <h5 class="modal-title">选择关联标段</h5>

+ 1 - 38
app/view/stage/gather.ejs

@@ -5,44 +5,6 @@
             <% include ./stage_sub_mini_menu.ejs %>
             <div>
                 <div class="d-inline-block">
-                    <div class="dropdown">
-                        <button class="btn btn-sm btn-light dropdown-toggle text-primary" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                            超计显示
-                        </button>
-                        <div class="dropdown-menu" aria-labelledby="dropdownMenuButton" style="width:290px">
-                            <span class="dropdown-item-text text-danger">截止本期合同计量 大于</span>
-                            <form class="px-4 py-3">
-                                <div class="form-group">
-                                    <div class="custom-control custom-radio">
-                                        <input type="radio" id="customRadio1" name="customRadio" class="custom-control-input" checked="">
-                                        <label class="custom-control-label" for="customRadio1">台账数量</label>
-                                    </div>
-                                </div>
-                                <div class="form-group">
-                                    <div class="custom-control custom-radio">
-                                        <input type="radio" id="customRadio2" name="customRadio" class="custom-control-input">
-                                        <label class="custom-control-label" for="customRadio2">签约清单数量</label>
-                                    </div>
-                                </div>
-                                <div class="form-group mb-0">
-                                    <div class="custom-control custom-radio">
-                                        <input type="radio" id="customRadio3" name="customRadio" class="custom-control-input">
-                                        <label class="custom-control-label" for="customRadio3">台账数量 或 签约清单数量</label>
-                                    </div>
-                                </div>
-                            </form>
-                            <span class="dropdown-item-text text-danger">
-                            <div class="input-group input-group-sm">
-                                <input type="number" step="5" class="form-control form-control-sm m-0" placeholder="100" max="100" min="50" id="over-percent" value="100">
-                                <div class="input-group-append">
-                                    <span class="input-group-text" id="basic-addon2">%</span>
-                                </div>
-                            </div>
-                        </span>
-                        </div>
-                    </div>
-                </div>
-                <div class="d-inline-block">
                     <button href="#zjhj" class="btn btn-sm btn-light text-primary" data-toggle="modal" data-target="#zjhj">章节合计</button>
                 </div>
                 <% if (ctx.app.config.is_debug) { %>
@@ -140,5 +102,6 @@
     const chapter = JSON.parse('<%- JSON.stringify(ctx.tender.info.chapter) %>');
     const thousandth = <%- ctx.tender.info.display.thousandth %>;
     const hintOver = <%- hintOver %>;
+    const checkOverInfo = JSON.parse('<%- JSON.stringify(ctx.tender.info.checkOverInfo) %>');
     const tenderDecimal = JSON.parse(unescape('<%- escape(JSON.stringify(ctx.tender.info.decimal)) %>'));
 </script>

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

@@ -47,6 +47,7 @@
                 </div>
             </div>
             <div class="ml-auto">
+                <a class="mr-2" href="#help" data-toggle="modal" data-target="#help"> <i class="fa fa-question-circle"></i></a>
                 <% if (ctx.session.sessionUser.is_admin && ctx.tender.data.measure_type === measureType.tz.value) { %>
                 <a class="btn btn-primary btn-sm" href="#stage-stash" data-toggle="modal" data-target="#stage-stash" href="javascript: void(0)">暂存计量</a>
                 <% } %>

+ 22 - 0
app/view/stage/modal.ejs

@@ -650,6 +650,28 @@
         </div>
     </div>
 </div>
+<div class="modal fade" id="help" data-backdrop="static" aria-hidden="true" style="display: none;">
+    <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-150">
+                    <label for="">1、本期补差计算公式</label>
+                    <div class="in-2">
+                        <label for="">本期补差=本期合同补差+本期变更补差。</label>
+                        <label>本期合同补差:新单价*截止上期合同数量-截止上期合同计量金额。</label>
+                        <label for="">本期变更补差:新单价*截止上期变更数量-截止上期变更计量金额。</label>
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
+            </div>
+        </div>
+    </div>
+</div>
 <% include ./audit_modal.ejs %>
 <% include ../shares/merge_peg_modal.ejs %>
 <% include ../shares/check_modal2.ejs %>

+ 183 - 9
app/view/sub_proj/info.ejs

@@ -4,7 +4,11 @@
         <div class="title-main">
             <% include ./sp_info_mini_menu.ejs %>
             <div class="d-inline-block">
-                项目信息
+                <div class="btn-group group-tab">
+                    <a class="btn btn-sm btn-light active" href="/sp/<%- info.id %>/info">项目信息</a>
+                    <a class="btn btn-sm btn-light" href="/sp/<%- info.id %>/progress">阶段进度</a>
+                    <a class="btn btn-sm btn-light" href="/sp/<%- info.id %>/push">推进记录</a>
+                </div>
             </div>
         </div>
         <div class="ml-auto"></div>
@@ -14,6 +18,16 @@
             <div class="sjs-height-0" style="height: 570px;">
                 <div class="col-9 px-3">
                     <div>
+                        <div class="row">
+                            <div class="form-group col-6">
+                                <div class="py-2 font-weight-bold">项目概述</div>
+                                <textarea class="form-control form-control-sm" name="proj_intro" rows="5" org="<%- info.proj_intro %>" maxlength="1000" oninput="limitMaxLength(this)" onblur="changeInfo(this)"><%- info.proj_intro %></textarea>
+                            </div>
+                            <div class="col">
+                                <div class="py-2 font-weight-bold">当前状态</div>
+                                <textarea class="form-control form-control-sm" name="proj_cur_status" rows="5" org="<%- info.proj_cur_status %>" maxlength="1000" oninput="limitMaxLength(this)" onblur="changeInfo(this)"><%- info.proj_cur_status %></textarea>
+                            </div>
+                        </div>
                         <div class="py-2 font-weight-bold">项目信息</div>
                         <div class="form-group">
                             <label for="">建设项目名称:</label>
@@ -57,7 +71,17 @@
                                     </select>
                                 </div>
                             </div>
-                            <div class="col-3"></div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">建设状态:</label>
+                                    <select class="form-control form-control-sm" name="proj_status" value="<%- info.proj_status %>" org="<%- info.proj_status%>" onchange="changeInfo(this)">
+                                        <option value="">请选择</option>
+                                        <option value="在建" <%if (info.proj_status === '在建') { %>selected<% } %>>在建</option>
+                                        <option value="已建" <%if (info.proj_status === '已建') { %>selected<% } %>>已建</option>
+                                        <option value="拟建" <%if (info.proj_status === '拟建') { %>selected<% } %>>拟建</option>
+                                    </select>
+                                </div>
+                            </div>
                         </div>
                         <div class="row">
                             <div class="col-3">
@@ -87,7 +111,7 @@
                         </div>
                         <div class="py-2 font-weight-bold">立项批准(核准)情况:</div>
                         <div class="row">
-                            <div class="col-6">
+                            <div class="col-3">
                                 <div class="form-group">
                                     <label for="">部门:</label>
                                     <input type="text" class="form-control form-control-sm" name="lx_department" value="<%- info.lx_department %>" org="<%- info.lx_department %>" maxlength="100" placeholder="请输入部门名称" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
@@ -105,10 +129,22 @@
                                     <input type="text" class="form-control form-control-sm" name="lx_code" value="<%- info.lx_code %>" org="<%- info.lx_code %>" placeholder="请输入文号" maxlength="50" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
                                 </div>
                             </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">金额:</label>
+                                    <input type="text" class="form-control form-control-sm" name="lx_tp" value="<%- info.lx_tp %>" org="<%- info.lx_tp %>" maxlength="50" readonly>
+                                </div>
+                            </div>
+                            <div class="col-12">
+                                <div class="form-group">
+                                    <label for="">备注:</label>
+                                    <textarea class="form-control form-control-sm" name="lx_memo" rows="2" org="<%- info.lx_memo %>" maxlength="1000" oninput="limitMaxLength(this)" onblur="changeInfo(this)"><%- info.lx_memo %></textarea>
+                                </div>
+                            </div>
                         </div>
                         <div class="py-2 font-weight-bold">初步(修编)设计批准情况:</div>
                         <div class="row">
-                            <div class="col-6">
+                            <div class="col-3">
                                 <div class="form-group">
                                     <label for="">部门:</label>
                                     <input type="text" class="form-control form-control-sm" name="cb_department" value="<%- info.cb_department %>" org="<%- info.cb_department %>" placeholder="请输入部门名称" maxlength="100" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
@@ -126,10 +162,121 @@
                                     <input type="text" class="form-control form-control-sm" name="cb_code" value="<%- info.cb_code %>" org="<%- info.cb_code %>" placeholder="请输入文号" maxlength="50" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
                                 </div>
                             </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">金额:</label>
+                                    <input type="text" class="form-control form-control-sm" name="cb_tp" value="<%- info.cb_tp %>" org="<%- info.cb_tp %>" maxlength="50" readonly>
+                                </div>
+                            </div>
+                            <div class="col-12">
+                                <div class="form-group">
+                                    <label for="">备注:</label>
+                                    <textarea class="form-control form-control-sm" name="cb_memo" rows="2" org="<%- info.cb_memo %>" maxlength="1000" oninput="limitMaxLength(this)" onblur="changeInfo(this)"><%- info.cb_memo %></textarea>
+                                </div>
+                            </div>
+                        </div>
+                        <div class="py-2 font-weight-bold">施工图预算批复情况:</div>
+                        <div class="row">
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">部门:</label>
+                                    <input type="text" class="form-control form-control-sm" name="sgt_department" value="<%- info.sgt_department %>" org="<%- info.sgt_department %>" placeholder="请输入部门名称" maxlength="100" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
+                                </div>
+                            </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">日期:</label>
+                                    <input type="date" class="form-control form-control-sm" name="sgt_date" value="<%- info.sgt_date %>" org="<%- info.sgt_date %>" placeholder="请输入" onchange="changeInfo(this)">
+                                </div>
+                            </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">文号:</label>
+                                    <input type="text" class="form-control form-control-sm" name="sgt_code" value="<%- info.sgt_code %>" org="<%- info.sgt_code %>" placeholder="请输入文号" maxlength="50" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
+                                </div>
+                            </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">金额:</label>
+                                    <input type="text" class="form-control form-control-sm" name="sgt_tp" value="<%- info.sgt_tp %>" org="<%- info.sgt_tp %>" maxlength="50" readonly>
+                                </div>
+                            </div>
+                            <div class="col-12">
+                                <div class="form-group">
+                                    <label for="">备注:</label>
+                                    <textarea class="form-control form-control-sm" name="sgt_memo" rows="2" org="<%- info.sgt_memo %>" maxlength="1000" oninput="limitMaxLength(this)" onblur="changeInfo(this)"><%- info.sgt_memo %></textarea>
+                                </div>
+                            </div>
+                        </div>
+                        <div class="py-2 font-weight-bold">财评/造价站批复情况:</div>
+                        <div class="row">
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">部门:</label>
+                                    <input type="text" class="form-control form-control-sm" name="pf_department" value="<%- info.pf_department %>" org="<%- info.pf_department %>" placeholder="请输入部门名称" maxlength="100" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
+                                </div>
+                            </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">日期:</label>
+                                    <input type="date" class="form-control form-control-sm" name="pf_date" value="<%- info.pf_date %>" org="<%- info.pf_date %>" placeholder="请输入" onchange="changeInfo(this)">
+                                </div>
+                            </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">文号:</label>
+                                    <input type="text" class="form-control form-control-sm" name="pf_code" value="<%- info.pf_code %>" org="<%- info.pf_code %>" placeholder="请输入文号" maxlength="50" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
+                                </div>
+                            </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">金额:</label>
+                                    <input type="text" class="form-control form-control-sm" name="pf_tp" value="<%- info.pf_tp %>" org="<%- info.pf_tp %>" maxlength="50" onblur="changeInfo(this, true)">
+                                </div>
+                            </div>
+                            <div class="col-12">
+                                <div class="form-group">
+                                    <label for="">备注:</label>
+                                    <textarea class="form-control form-control-sm" name="pf_memo" rows="2" org="<%- info.pf_memo %>" maxlength="1000" oninput="limitMaxLength(this)" onblur="changeInfo(this)"><%- info.pf_memo %></textarea>
+                                </div>
+                            </div>
+                        </div>
+                        <div class="py-2 font-weight-bold">招标完成情况:</div>
+                        <div class="row">
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">部门:</label>
+                                    <input type="text" class="form-control form-control-sm" name="zb_department" value="<%- info.zb_department %>" org="<%- info.zb_department %>" placeholder="请输入部门名称" maxlength="100" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
+                                </div>
+                            </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">日期:</label>
+                                    <input type="date" class="form-control form-control-sm" name="zb_date" value="<%- info.zb_date %>" org="<%- info.zb_date %>" placeholder="请输入" onchange="changeInfo(this)">
+                                </div>
+                            </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">文号:</label>
+                                    <input type="text" class="form-control form-control-sm" name="zb_code" value="<%- info.zb_code %>" org="<%- info.zb_code %>" placeholder="请输入文号" maxlength="50" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
+                                </div>
+                            </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">金额:</label>
+                                    <input type="text" class="form-control form-control-sm" name="zb_tp" value="<%- info.zb_tp %>" org="<%- info.zb_tp %>" maxlength="50" onblur="changeInfo(this, true)">
+                                </div>
+                            </div>
+                            <div class="col-12">
+                                <div class="form-group">
+                                    <label for="">备注:</label>
+                                    <textarea class="form-control form-control-sm" name="zb_memo" rows="2" org="<%- info.zb_memo %>" maxlength="1000" oninput="limitMaxLength(this)" onblur="changeInfo(this)"><%- info.zb_memo %></textarea>
+                                </div>
+                            </div>
                         </div>
                         <div class="py-2 font-weight-bold">施工许可批复情况:</div>
                         <div class="row">
-                            <div class="col-6">
+                            <div class="col-3">
                                 <div class="form-group">
                                     <label for="">部门:</label>
                                     <input type="text" class="form-control form-control-sm" name="sg_department" value="<%- info.sg_department %>" org="<%- info.sg_department %>" placeholder="请输入部门名称" maxlength="100" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
@@ -147,10 +294,16 @@
                                     <input type="text" class="form-control form-control-sm" name="sg_code" value="<%- info.sg_code %>" org="<%- info.sg_code %>" placeholder="请输入文号" maxlength="50" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
                                 </div>
                             </div>
+                            <div class="col-12">
+                                <div class="form-group">
+                                    <label for="">备注:</label>
+                                    <textarea class="form-control form-control-sm" name="sg_memo" rows="2" org="<%- info.sg_memo %>" maxlength="1000" oninput="limitMaxLength(this)" onblur="changeInfo(this)"><%- info.sg_memo %></textarea>
+                                </div>
+                            </div>
                         </div>
                         <div class="py-2 font-weight-bold">交工验收情况:</div>
                         <div class="row">
-                            <div class="col-6">
+                            <div class="col-3">
                                 <div class="form-group">
                                     <label for="">部门:</label>
                                     <input type="text" class="form-control form-control-sm" name="jg_department" value="<%- info.jg_department %>" org="<%- info.jg_department %>" placeholder="请输入部门名称" maxlength="100" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
@@ -168,6 +321,12 @@
                                     <input type="text" class="form-control form-control-sm" name="jg_code" value="<%- info.jg_code %>" org="<%- info.jg_code %>" placeholder="请输入文号" maxlength="50" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
                                 </div>
                             </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">金额:</label>
+                                    <input type="text" class="form-control form-control-sm" name="jg_tp" value="<%- info.jg_tp %>" org="<%- info.jg_tp %>" maxlength="50" onblur="changeInfo(this, true)">
+                                </div>
+                            </div>
                         </div>
                         <div class="row">
                             <div class="col-3">
@@ -187,6 +346,12 @@
                                     </select>
                                 </div>
                             </div>
+                            <div class="col-12">
+                                <div class="form-group">
+                                    <label for="">备注:</label>
+                                    <textarea class="form-control form-control-sm" name="jg_memo" rows="2" org="<%- info.jg_memo %>" maxlength="1000" oninput="limitMaxLength(this)" onblur="changeInfo(this)"><%- info.jg_memo %></textarea>
+                                </div>
+                            </div>
                         </div>
                         <div class="py-2 font-weight-bold">单位信息:</div>
                         <div class="row">
@@ -250,17 +415,26 @@
             obj.value = obj.value.substr(0, obj.maxLength);
         }
     }
-    function changeInfo(obj) {
+    function changeInfo(obj, isNum = false) {
         const field = obj.getAttribute('name');
         if (!field) return;
         if (obj.getAttribute('org') === obj.value) return;
 
         const updateData = {};
-        updateData[field] = obj.value;
+        if (isNum) {
+            updateData[field] = _.toNumber(obj.value);
+            if (!_.isFinite(updateData[field])) {
+                toastr.error('输入的数字非法');
+                obj.focus();
+                return;
+            }
+        } else {
+            updateData[field] = obj.value;
+        }
         postData('info/save', { updateData }, function (result) {
             obj.setAttribute('org', obj.value);
         }, function () {
             obj.value = obj.getAttribute('org');
         });
-    };
+    }
 </script>

+ 107 - 0
app/view/sub_proj/progress.ejs

@@ -0,0 +1,107 @@
+<% include ./sp_info_menu.ejs %>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main d-flex">
+            <% include ./sp_info_mini_menu.ejs %>
+            <div>
+                <div class="d-inline-block">
+                    <div class="btn-group group-tab">
+                        <a class="btn btn-sm btn-light" href="/sp/<%- ctx.subProject.id %>/info">项目信息</a>
+                        <a class="btn btn-sm btn-light active" href="/sp/<%- ctx.subProject.id %>/progress">阶段进度</a>
+                        <a class="btn btn-sm btn-light" href="/sp/<%- ctx.subProject.id %>/push">推进记录</a>
+                    </div>
+                </div>
+                <div class="d-inline-block">
+                    <div class="dropdown">
+                        <button class="btn btn-sm btn-light dropdown-toggle text-primary" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+                            <i class="fa fa-list-ol"></i> 显示层级
+                        </button>
+                        <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
+                            <a class="dropdown-item" name="showLevel" tag="1" href="javascript: void(0);">第一层</a>
+                            <a class="dropdown-item" name="showLevel" tag="2" href="javascript: void(0);">第二层</a>
+                            <a class="dropdown-item" name="showLevel" tag="3" href="javascript: void(0);">第三层</a>
+                            <a class="dropdown-item" name="showLevel" tag="4" href="javascript: void(0);">第四层</a>
+                            <a class="dropdown-item" name="showLevel" tag="last" href="javascript: void(0);">最底层</a>
+                        </div>
+                    </div>
+                </div>
+                <div class="d-inline-block">
+                    <a href="javascript: void(0);" name="base-opr" type="add" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="新增"><i class="fa fa-plus" aria-hidden="true"></i></a>
+                    <a href="javascript: void(0);" name="base-opr" type="delete" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="删除"><i class="fa fa-remove" aria-hidden="true"></i></a>
+                    <a href="javascript: void(0);" name="base-opr" type="up-level" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="升级"><i class="fa fa-arrow-left" aria-hidden="true"></i></a>
+                    <a href="javascript: void(0);" name="base-opr" type="down-level" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="降级"><i class="fa fa-arrow-right" aria-hidden="true"></i></a>
+                    <a href="javascript: void(0);" name="base-opr" type="down-move" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="下移"><i class="fa fa-arrow-down" aria-hidden="true"></i></a>
+                    <a href="javascript: void(0);" name="base-opr" type="up-move" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="上移"><i class="fa fa-arrow-up" aria-hidden="true"></i></a>
+                </div>
+            </div>
+        </div>
+    </div>
+    <div class="content-wrap row pr-46">
+        <div class="c-header p-0 col-12">
+        </div>
+        <!--核心内容(两栏)-->
+        <div class="row w-100 sub-content">
+            <!--左栏-->
+            <div class="c-body" id="left-view" style="width: 100%">
+                <div id="progress-spread" class="sjs-height-1"></div>
+                <div z-index="0" style="display: none;">
+                    <input class="datepicker-here form-control form-control-sm" data-date-format="yyyy-MM-DD" data-language="zh" type="text" autocomplete="off" id="dp-input">
+                </div>
+            </div>
+            <div class="c-body" id="right-view" style="display: none; width: 33%;">
+                <div class="resize-x" id="right-spr" r-Type="width" div1="#left-view" div2="#right-view" title="调整大小" a-type="percent"><!--调整左右高度条--></div>
+                <div class="tab-content">
+                    <div id="fujian" class="tab-pane tab-select-show">
+                        <div class="sjs-bar">
+                            <ul class="nav nav-tabs">
+                                <li class="nav-item">
+                                    <a class="nav-link active" data-toggle="tab" href="#cur-att" role="tab" fujian-content="cur-att">当前节点</a>
+                                </li>
+                                <li class="nav-item">
+                                    <a class="nav-link" data-toggle="tab" href="#all-att" role="tab" fujian-content="all-att">所有附件</a>
+                                </li>
+                                <li class="nav-item ml-auto pt-1">
+                                    <button  id="batch-download-att" class="btn btn-sm btn-primary" type="curr">批量下载</button>
+                                    <!--所有附件 翻页-->
+                                    <span id="showPage" style="display: none"><a href="javascript:void(0);" class="page-select ml-3" content="pre"><i class="fa fa-chevron-left"></i></a> <span id="currentPage">1</span>/<span id="totalPage">10</span> <a href="javascript:void(0);" class="page-select mr-3" content="next"><i class="fa fa-chevron-right"></i></a></span>
+                                    <a href="#upload" data-toggle="modal" data-target="#upload" class="btn btn-sm btn-outline-primary ml-3">上传</a>
+                                </li>
+                            </ul>
+                        </div>
+                        <div class="tab-content">
+                            <div class="tab-pane active" id="cur-att">
+                                <div class="sjs-sh-3" style="overflow:auto; overflow-x:hidden;">
+                                    <table class="table table-sm table-bordered table-hover" style="word-break:break-all; table-layout: fixed">
+                                        <tr><th width="25"><input type="checkbox" class="check-all-file"><th>文件名</th><th width="80">上传</th></tr>
+                                        <tbody id="cur-att-list" class="list-table">
+                                        </tbody>
+                                    </table>
+                                </div>
+                            </div>
+                            <div class="tab-pane" id="all-att">
+                                <div class="sjs-sh-3" style="overflow:auto; overflow-x:hidden;">
+                                    <table class="table table-sm table-bordered table-hover" style="word-break:break-all; table-layout: fixed">
+                                        <tr><th width="25"><input type="checkbox" class="check-all-file"></th><th>文件名</th><th width="80">上传</th></tr>
+                                        <tbody id="all-att-list" class="list-table">
+                                        </tbody>
+                                    </table>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <!--右侧菜单-->
+        <div class="side-menu">
+            <ul class="nav flex-column right-nav" id="side-menu">
+                <li class="nav-item">
+                    <a class="nav-link" content="#fujian" href="javascript: void(0);">附件</a>
+                </li>
+            </ul>
+        </div>
+    </div>
+    <div style="display: none">
+        <img src="/public/images/ellipsis_horizontal.png" id="ellipsis-icon" />
+    </div>
+</div>

+ 2 - 0
app/view/sub_proj/progress_modal.ejs

@@ -0,0 +1,2 @@
+<% include ../shares/delete_hint_modal.ejs %>
+<% include ../shares/upload_att.ejs %>

+ 85 - 0
app/view/sub_proj/push.ejs

@@ -0,0 +1,85 @@
+<% include ./sp_info_menu.ejs %>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main d-flex">
+            <% include ./sp_info_mini_menu.ejs %>
+            <div>
+                <div class="d-inline-block">
+                    <div class="btn-group group-tab">
+                        <a class="btn btn-sm btn-light" href="/sp/<%- ctx.subProject.id %>/info">项目信息</a>
+                        <a class="btn btn-sm btn-light" href="/sp/<%- ctx.subProject.id %>/progress">阶段进度</a>
+                        <a class="btn btn-sm btn-light active" href="/sp/<%- ctx.subProject.id %>/push">推进记录</a>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+    <div class="content-wrap row pr-46">
+        <div class="c-header p-0 col-12">
+        </div>
+        <!--核心内容(两栏)-->
+        <div class="row w-100 sub-content">
+            <!--左栏-->
+            <div class="c-body" id="left-view" style="width: 100%">
+                <div id="push-spread" class="sjs-height-1"></div>
+                <div z-index="0" style="display: none;">
+                    <input class="datepicker-here form-control form-control-sm" data-date-format="yyyy-MM-DD" data-language="zh" type="text" autocomplete="off" id="dp-input">
+                </div>
+            </div>
+            <div class="c-body" id="right-view" style="display: none; width: 33%;">
+                <div class="resize-x" id="right-spr" r-Type="width" div1="#left-view" div2="#right-view" title="调整大小" a-type="percent"><!--调整左右高度条--></div>
+                <div class="tab-content">
+                    <div id="fujian" class="tab-pane tab-select-show">
+                        <div class="sjs-bar">
+                            <ul class="nav nav-tabs">
+                                <li class="nav-item">
+                                    <a class="nav-link active" data-toggle="tab" href="#cur-att" role="tab" fujian-content="cur-att">当前节点</a>
+                                </li>
+                                <li class="nav-item">
+                                    <a class="nav-link" data-toggle="tab" href="#all-att" role="tab" fujian-content="all-att">所有附件</a>
+                                </li>
+                                <li class="nav-item ml-auto pt-1">
+                                    <button  id="batch-download-att" class="btn btn-sm btn-primary" type="curr">批量下载</button>
+                                    <!--所有附件 翻页-->
+                                    <span id="showPage" style="display: none"><a href="javascript:void(0);" class="page-select ml-3" content="pre"><i class="fa fa-chevron-left"></i></a> <span id="currentPage">1</span>/<span id="totalPage">10</span> <a href="javascript:void(0);" class="page-select mr-3" content="next"><i class="fa fa-chevron-right"></i></a></span>
+                                    <a href="#upload" data-toggle="modal" data-target="#upload" class="btn btn-sm btn-outline-primary ml-3">上传</a>
+                                </li>
+                            </ul>
+                        </div>
+                        <div class="tab-content">
+                            <div class="tab-pane active" id="cur-att">
+                                <div class="sjs-sh-3" style="overflow:auto; overflow-x:hidden;">
+                                    <table class="table table-sm table-bordered table-hover" style="word-break:break-all; table-layout: fixed">
+                                        <tr><th width="25"><input type="checkbox" class="check-all-file"><th>文件名</th><th width="80">上传</th></tr>
+                                        <tbody id="cur-att-list" class="list-table">
+                                        </tbody>
+                                    </table>
+                                </div>
+                            </div>
+                            <div class="tab-pane" id="all-att">
+                                <div class="sjs-sh-3" style="overflow:auto; overflow-x:hidden;">
+                                    <table class="table table-sm table-bordered table-hover" style="word-break:break-all; table-layout: fixed">
+                                        <tr><th width="25"><input type="checkbox" class="check-all-file"></th><th>文件名</th><th width="80">上传</th></tr>
+                                        <tbody id="all-att-list" class="list-table">
+                                        </tbody>
+                                    </table>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <!--右侧菜单-->
+        <div class="side-menu">
+            <ul class="nav flex-column right-nav" id="side-menu">
+                <li class="nav-item">
+                    <a class="nav-link" content="#fujian" href="javascript: void(0);">附件</a>
+                </li>
+            </ul>
+        </div>
+    </div>
+    <div style="display: none">
+        <img src="/public/images/ellipsis_horizontal.png" id="ellipsis-icon" />
+    </div>
+</div>

+ 1 - 0
app/view/sub_proj/push_modal.ejs

@@ -0,0 +1 @@
+<% include ../shares/upload_att.ejs %>

+ 1 - 0
app/view/tender/detail.ejs

@@ -73,6 +73,7 @@
                             <a href="#bd-set-12" data-toggle="modal" data-target="#bd-set-12" class="dropdown-item">功能设置</a>
                             <a href="#bd-set-11" data-toggle="modal" data-target="#bd-set-11" class="dropdown-item">概况设置</a>
                             <a href="#bd-set-13" data-toggle="modal" data-target="#bd-set-13" class="dropdown-item">标段类型</a>
+                            <a href="#bd-set-14" data-toggle="modal" data-target="#bd-set-14" class="dropdown-item">超计判断条件</a>
                         </div>
                     </div>
                 <% } %>

+ 88 - 0
app/view/tender/detail_modal.ejs

@@ -1895,6 +1895,62 @@
         </div>
     </div>
 </div>
+<div class="modal fade" id="bd-set-14" 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="in-2">
+                    <label for="" class="mr-4">超计判断依据:</label>
+                    <div class="form-check form-check-inline ">
+                        <input class="form-check-input" name="oc-field" type="radio" id="oc-field-deal" value="deal">
+                        <label class="form-check-label" for="oc-field-deal">签约</label>
+                    </div>
+                    <div class="form-check form-check-inline">
+                        <input class="form-check-input" name="oc-field" type="radio" id="oc-field-tz" value="tz">
+                        <label class="form-check-label" for="oc-field-tz">台账</label>
+                    </div>
+                    <div class="form-check form-check-inline">
+                        <input class="form-check-input" name="oc-field" type="radio" id="oc-field-bothy" value="both">
+                        <label class="form-check-label" for="oc-field-both">签约或台账</label>
+                    </div>
+                </div>
+                <div class="in-2">
+                    <label>含计量单元的清单:</label>
+                    <div class="form-check form-check-inline ">
+                        <input class="form-check-input" name="oc-bp" type="radio" id="oc-bp-bills" value="bills">
+                        <label class="form-check-label" for="oc-bp-bills">清单</label>
+                    </div>
+                    <div class="form-check form-check-inline">
+                        <input class="form-check-input" name="oc-bp" type="radio" id="oc-bp-pos" value="pos">
+                        <label class="form-check-label" for="oc-bp-pos">计量单元</label>
+                    </div>
+                    <div class="form-check form-check-inline">
+                        <input class="form-check-input" name="oc-bp" type="radio" id="oc-bp-both" value="both">
+                        <label class="form-check-label" for="oc-bp-both">清单和计量单元</label>
+                    </div>
+                </div>
+                <div class="in-2" style="vertical-align: middle;">
+                    <label class="mr-5">数据比例:</label>
+                    <div class="form-check form-check-inline">
+                        <div class="input-group input-group-sm" style="width:100px">
+                            <input type="number" class="form-control" name="oc-percent" max="100" min="0" step="2" value="100">
+                            <div class="input-group-append">
+                                <span class="input-group-text">%</span>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
+                <button type="button" class="btn btn-sm btn-primary" onclick="post14();">确认修改</button>
+            </div>
+        </div>
+    </div>
+</div>
 <!--游客账号-->
 <div class="modal fade" id="bd-set-9" data-backdrop="static">
     <div class="modal-dialog" role="document">
@@ -2119,6 +2175,38 @@
             $('#bd-set-13').modal('hide');
         });
     }
+    const loadOverCheckProperty = function () {
+        //$('[name=oc-field]').removeAttr('checked');
+        $(`#oc-field-${property.over_range_check.field}`)[0].checked = true;
+        //$('[name=oc-bp]').removeAttr('checked');
+        $(`#oc-bp-${property.over_range_check.billsWithPos}`)[0].checked = true;
+        $('[name=oc-percent]').val(property.over_range_check.percent);
+    };
+    $('#bd-set-14').on('show.bs.modal', function () {
+        loadOverCheckProperty();
+    });
+    function post14() {
+        let percent;
+        try {
+            percent = parseInt($('[name=oc-percent]').val());
+            if (percent < 0 || percent > 100) throw '数据比例请输入0-100的整数';
+        } catch (err) {
+            toastr.error('数据比例请输入0-100的整数');
+            return;
+        }
+        const prop = {
+            over_range_check: {
+                field: $('[name=oc-field]:checked').val(),
+                billsWithPos: percent,
+                percent: $('[name=oc-bp]:checked').val(),
+            }
+        }
+        const tenderId = window.location.pathname.split('/')[2];
+        postData('/tender/' + tenderId + '/save2', prop, function (data) {
+            property.over_range_check = data.over_range_check;
+            $('#bd-set-14').modal('hide');
+        });
+    }
     $(function () {
         // 投资进度
         let timer2 = null;

+ 1 - 1
config/config.default.js

@@ -124,7 +124,7 @@ module.exports = appInfo => {
 
     // 上传设置
     config.multipart = {
-        whitelist: ['.cpd',
+        whitelist: ['.cpd', '.yup', '.ybpx',
             '.json', '.txt',
             '.xls', '.xlsx',
             '.doc', '.docx',

+ 55 - 0
config/web.js

@@ -85,6 +85,7 @@ const JsFiles = {
                 mergeFiles: [
                     '/public/js/zh_calc.js',
                     '/public/js/PinYinOrder.bundle.js',
+                    '/public/js/colResizable/colResizable-1.6.min.js',
                     '/public/js/shares/tender_list_order.js',
                     '/public/js/shares/show_level.js',
                     '/public/js/tender_showhide.js',
@@ -1166,6 +1167,41 @@ const JsFiles = {
                 ],
                 mergeFile: 'sp_info',
             },
+            progress: {
+                files: [
+                    '/public/js/datepicker/datepicker.min.js', '/public/js/datepicker/datepicker.zh.js',
+                    '/public/js/axios/axios.min.js', '/public/js/file-saver/FileSaver.js', '/public/js/js-xlsx/jszip.min.js',
+                    '/public/js/shares/aliyun-oss-sdk.min.js', '/public/js/component/menu.js',
+                    '/public/js/spreadjs/sheets/v11/gc.spread.sheets.all.11.2.2.min.js',
+                ],
+                mergeFiles: [
+                    '/public/js/div_resizer.js',
+                    '/public/js/path_tree.js',
+                    '/public/js/spreadjs_rela/spreadjs_zh.js',
+                    '/public/js/shares/tools_att.js',
+                    '/public/js/shares/ali_oss.js',
+                    '/public/js/sub_menu.js',
+                    '/public/js/sp_progress.js',
+                ],
+                mergeFile: 'sp_progress',
+            },
+            push: {
+                files: [
+                    '/public/js/datepicker/datepicker.min.js', '/public/js/datepicker/datepicker.zh.js',
+                    '/public/js/axios/axios.min.js', '/public/js/file-saver/FileSaver.js', '/public/js/js-xlsx/jszip.min.js',
+                    '/public/js/shares/aliyun-oss-sdk.min.js', '/public/js/component/menu.js',
+                    '/public/js/spreadjs/sheets/v11/gc.spread.sheets.all.11.2.2.min.js',
+                ],
+                mergeFiles: [
+                    '/public/js/div_resizer.js',
+                    '/public/js/spreadjs_rela/spreadjs_zh.js',
+                    '/public/js/shares/tools_att.js',
+                    '/public/js/shares/ali_oss.js',
+                    '/public/js/sub_menu.js',
+                    '/public/js/sp_push.js',
+                ],
+                mergeFile: 'sp_push',
+            },
             data: {
                 files: [
                     '/public/js/component/menu.js',
@@ -1709,6 +1745,25 @@ const JsFiles = {
                 ],
                 mergeFile: 'financial_pay_detail',
             },
+            summary: {
+                files: [
+                    '/public/js/echarts/echarts.min.js',
+                    '/public/js/decimal.min.js',
+                    '/public/js/math.min.js',
+                    '/public/js/component/menu.js',
+                    '/public/js/moment/moment.min.js',
+                ],
+                mergeFiles: [
+                    '/public/js/sub_menu.js',
+                    '/public/js/div_resizer.js',
+                    '/public/js/zh_calc.js',
+                    '/public/js/shares/cs_tools.js',
+                    '/public/js/datepicker/datepicker.min.js',
+                    '/public/js/datepicker/datepicker.zh.js',
+                    '/public/js/financial_summary.js',
+                ],
+                mergeFile: 'financial_summary',
+            },
         },
     },
 };

+ 1 - 0
package.json

@@ -29,6 +29,7 @@
         "gt3-sdk": "^2.0.0",
         "gulp": "^4.0.0",
         "hpack.js": "^2.1.6",
+        "js-base64": "^3.7.7",
         "js-xlsx": "^0.8.22",
         "jsonwebtoken": "^8.5.1",
         "jszip": "^3.1.3",

+ 96 - 193
sql/update.sql

@@ -1,194 +1,96 @@
-ALTER TABLE `zh_rpt_tree_node_cust`
-ADD COLUMN `tender_id` INT NULL DEFAULT -1 COMMENT '新需求,跟标段走,不跟客户走' AFTER `cust_acc_id`,
-ADD INDEX `tender` (`tender_id` ASC);
-;
-
-CREATE TABLE `zh_stage_yjcl`  (
-  `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
-  `uuid` varchar(36) NOT NULL DEFAULT '' COMMENT 'uuid',
-  `add_sid` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '新增期id',
-  `add_sorder` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '新增期序号',
-  `add_uid` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建人id',
-  `tid` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '标段id',
-  `sid` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '期id',
-  `sorder` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '期序号',
-  `name` varchar(255) NOT NULL DEFAULT '' COMMENT '名称',
-  `m_order` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '排序',
-  `spec` varchar(255) NOT NULL DEFAULT '' COMMENT '规格型号',
-  `unit` varchar(20) NOT NULL DEFAULT '' COMMENT '单位',
-  `tax` tinyint(4) UNSIGNED NOT NULL DEFAULT 0 COMMENT '税率',
-  `arrive_time` varchar(50) NOT NULL DEFAULT '' COMMENT '到场时间',
-  `source` varchar(255) NOT NULL DEFAULT '' COMMENT '材料来源',
-  `bills_code` varchar(255) NOT NULL DEFAULT '' COMMENT '单据号',
-  `location` varchar(50) NOT NULL DEFAULT '' COMMENT '存放位置',
-  `prepare_pos` varchar(255) NOT NULL DEFAULT '' COMMENT '拟用于部位',
-  `memo` varchar(1000) NOT NULL DEFAULT '' COMMENT '备注',
-  `arrive_qty` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '到场数量',
-  `arrive_tp` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '到场金额',
-  `unit_price` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '单价',
-  `ex_tax_up` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '除税单价',
-  `qty` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '本期数量',
-  `tp` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '本期金额',
-  `pre_qty` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '截止上期数量',
-  `pre_tp` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '截止上期金额',
-  `pre_used` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '上期是否使用',
-  `shistory` text NULL COMMENT '审批历史',
-  PRIMARY KEY (`id`)
-);
-
-ALTER TABLE `zh_budget_std`
-ADD COLUMN `ht_project_template_id` varchar(255) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL DEFAULT '' COMMENT '合同-项目合同模版-id列表(‘,’分隔)' AFTER `zb_bills_id`,
-ADD COLUMN `ht_tender_template_id` varchar(255) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL DEFAULT '' COMMENT '合同-标段合同模版-id列表(‘,’分隔)' AFTER `ht_project_template_id`;
-
-ALTER TABLE `zh_shenpi_audit`
-ADD COLUMN `audit_ledger_id` varchar(5000) NOT NULL DEFAULT '' COMMENT '审批台账id' AFTER `audit_order`;
-
-ALTER TABLE `zh_stage_audit`
-ADD COLUMN `audit_ledger_id` varchar(5000) NOT NULL DEFAULT '' COMMENT '审批台账id' AFTER `audit_order`,
-ADD COLUMN `audit_locked` tinyint(1) NOT NULL DEFAULT 0  COMMENT '审批锁定(仅协审用)' AFTER `audit_ledger_id`;
-
-ALTER TABLE `zh_ledger_audit`
-ADD COLUMN `audit_type` tinyint(4) NOT NULL DEFAULT 1  COMMENT '审批类型' AFTER `audit_order`,
-ADD COLUMN `audit_ledger_id` varchar(5000) NOT NULL DEFAULT '' COMMENT '审批台账id' AFTER `audit_id`;
-
-ALTER TABLE `zh_revise_audit`
-ADD COLUMN `audit_type` tinyint(4) NOT NULL DEFAULT 1 COMMENT '审批类型' AFTER `audit_order`,
-ADD COLUMN `audit_ledger_id` varchar(5000) NOT NULL DEFAULT '' COMMENT '审批台账id' AFTER `audit_type`;
-
-ALTER TABLE `zh_tender_cache`
-MODIFY COLUMN `ledger_flow_cur_uid` varchar(1000) NOT NULL DEFAULT '0' COMMENT '台账-当前流程人id' AFTER `ledger_status`,
-MODIFY COLUMN `ledger_flow_pre_uid` varchar(1000) NOT NULL DEFAULT '0' COMMENT '台账-上一流程人id' AFTER `ledger_flow_cur_info`;
-
-CREATE TABLE `zh_contract_tree` (
-  `id` varchar(100) CHARACTER SET utf8 NOT NULL COMMENT '自增id',
-  `spid` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '项目id',
-  `tid` int(11) DEFAULT NULL COMMENT '标段id',
-  `contract_type` tinyint(1) NOT NULL COMMENT '合同类型(1是支出,2是收入)',
-  `contract_id` int(11) NOT NULL COMMENT '节点id',
-  `contract_pid` int(11) NOT NULL COMMENT '父节点id',
-  `level` tinyint(4) NOT NULL COMMENT '层级',
-  `order` mediumint(4) NOT NULL DEFAULT '0' COMMENT '同级排序',
-  `full_path` varchar(255) CHARACTER SET utf8 DEFAULT '' COMMENT '层级定位辅助字段parent.full_path.contract_id',
-  `is_leaf` tinyint(1) NOT NULL COMMENT '是否叶子节点,界面显示辅助字段',
-  `code` varchar(50) CHARACTER SET utf8 DEFAULT '' COMMENT '节点编号',
-  `name` varchar(255) CHARACTER SET utf8 DEFAULT '' COMMENT '名称',
-  `unit` varchar(255) CHARACTER SET utf8 DEFAULT '' COMMENT '单位',
-  `remark` varchar(1000) CHARACTER SET utf8 DEFAULT '' COMMENT '备注',
-  PRIMARY KEY (`id`),
-  KEY `sub_id` (`spid`),
-  KEY `tid` (`tid`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT = '合同管理树结构表';
-
-CREATE TABLE `zh_contract` (
-  `id` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
-  `spid` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '项目id',
-  `tid` int(11) DEFAULT NULL COMMENT '标段id',
-  `contract_type` tinyint(1) DEFAULT NULL COMMENT '合同类型(1是支出,2是收入)',
-  `uid` int(11) DEFAULT NULL COMMENT '创建人id',
-  `contract_id` int(11) NOT NULL COMMENT '节点id',
-  `contract_pid` int(11) NOT NULL COMMENT '父节点id',
-  `level` tinyint(4) NOT NULL COMMENT '层级',
-  `is_leaf` tinyint(1) DEFAULT '1' COMMENT '是否是子节点',
-  `order` mediumint(4) NOT NULL DEFAULT '0' COMMENT '同级排序',
-  `full_path` varchar(255) CHARACTER SET utf8 DEFAULT '' COMMENT '层级定位辅助字段parent.full_path.contract_id',
-  `c_code` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '合同编号',
-  `name` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '合同名称',
-  `total_price` decimal(30,8) DEFAULT '0.00000000' COMMENT '合同金额',
-  `pay_price` decimal(30,8) DEFAULT '0.00000000' COMMENT '累计支付(回款)金额',
-  `debit_price` decimal(30,8) DEFAULT '0.00000000' COMMENT '累计扣款金额',
-  `yf_price` decimal(30,8) DEFAULT '0.00000000' COMMENT '累计应付(应回)金额',
-  `sf_price` decimal(30,8) DEFAULT '0.00000000' COMMENT '累计已付(已回)金额',
-  `party_a` varchar(255) COLLATE utf8_unicode_ci DEFAULT '' COMMENT '甲方',
-  `party_a_user` varchar(255) COLLATE utf8_unicode_ci DEFAULT '' COMMENT '甲方签约人',
-  `party_b` varchar(255) COLLATE utf8_unicode_ci DEFAULT '' COMMENT '乙方',
-  `party_b_user` varchar(255) COLLATE utf8_unicode_ci DEFAULT '' COMMENT '乙方签约人',
-  `sign_date` datetime DEFAULT NULL COMMENT '签订日期',
-  `address` varchar(255) COLLATE utf8_unicode_ci DEFAULT '' COMMENT '签约地点',
-  `entity` varchar(255) COLLATE utf8_unicode_ci DEFAULT '' COMMENT '单位',
-  `bank` varchar(255) COLLATE utf8_unicode_ci DEFAULT '' COMMENT '开户行',
-  `bank_account` varchar(255) COLLATE utf8_unicode_ci DEFAULT '' COMMENT '收款账号',
-  `settle_code` varchar(255) COLLATE utf8_unicode_ci DEFAULT '' COMMENT '结算书编号',
-  `remark` varchar(1000) COLLATE utf8_unicode_ci DEFAULT '' COMMENT '备注',
-  `exist_pay` tinyint(1) DEFAULT '0' COMMENT '是否存在合同支付(回款)',
-  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
-  PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='合同详情表';
-
-CREATE TABLE `zh_contract_pay` (
-  `id` int(11) NOT NULL AUTO_INCREMENT,
-  `spid` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '项目id',
-  `tid` int(11) DEFAULT NULL COMMENT '标段id',
-  `contract_type` tinyint(1) NOT NULL COMMENT '合同类型(1是支出,2是收入)',
-  `cid` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '合同详情id',
-  `uid` int(11) DEFAULT NULL COMMENT '创建人id',
-  `pay_time` datetime DEFAULT NULL COMMENT '支付日期',
-  `pay_price` decimal(30,8) DEFAULT '0.00000000' COMMENT '付款金额',
-  `debit_price` decimal(30,8) DEFAULT '0.00000000' COMMENT '扣款金额',
-  `yf_price` decimal(30,8) DEFAULT '0.00000000' COMMENT '应付(回)金额',
-  `sf_price` decimal(30,8) DEFAULT '0.00000000' COMMENT '实付金额',
-  `pay_type` varchar(50) COLLATE utf8_unicode_ci DEFAULT '' COMMENT '支付方式',
-  `remark` varchar(1000) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '备注',
-  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
-  PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT = '合同支付表';
-
-CREATE TABLE `zh_contract_pay_attachment` (
-  `id` int(11) NOT NULL AUTO_INCREMENT,
-  `spid` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '项目id',
-  `tid` int(11) DEFAULT NULL COMMENT '标段id',
-  `contract_type` tinyint(1) NOT NULL COMMENT '合同类型(1是支出,2是收入)',
-  `cid` varchar(100) COLLATE utf8_unicode_ci NOT NULL COMMENT '合同详情id',
-  `cpid` int(11) NOT NULL COMMENT '合同支付id',
-  `uid` int(11) NOT NULL COMMENT '上传者id',
-  `filename` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '文件名称',
-  `fileext` varchar(5) COLLATE utf8_unicode_ci NOT NULL COMMENT '文件后缀',
-  `filesize` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '文件大小',
-  `filepath` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '文件存储路径',
-  `upload_time` datetime NOT NULL COMMENT '上传时间',
-  PRIMARY KEY (`id`) USING BTREE,
-  KEY `idx_cid` (`cpid`) USING BTREE
-) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_unicode_ci COMMENT = '合同支付附件表';
-
-CREATE TABLE `zh_contract_attachment` (
-  `id` int(11) NOT NULL AUTO_INCREMENT,
-  `spid` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '项目id',
-  `tid` int(11) DEFAULT NULL COMMENT '标段id',
-  `contract_type` tinyint(1) NOT NULL COMMENT '合同类型(1是支出,2是收入)',
-  `cid` varchar(100) COLLATE utf8_unicode_ci NOT NULL COMMENT '合同详情id',
-  `uid` int(11) NOT NULL COMMENT '上传者id',
-  `filename` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '文件名称',
-  `fileext` varchar(5) COLLATE utf8_unicode_ci NOT NULL COMMENT '文件后缀',
-  `filesize` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '文件大小',
-  `filepath` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '文件存储路径',
-  `upload_time` datetime NOT NULL COMMENT '上传时间',
-  PRIMARY KEY (`id`) USING BTREE,
-  KEY `idx_cid` (`cid`) USING BTREE
-) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_unicode_ci COMMENT = '合同文件表';
-
-CREATE TABLE `zh_contract_audit` (
-  `id` int(11) NOT NULL AUTO_INCREMENT,
-  `spid` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '项目id',
-  `tid` int(11) DEFAULT NULL COMMENT '标段id',
-  `uid` int(11) NOT NULL COMMENT '用户id',
-  `permission_add` tinyint(1) DEFAULT '0' COMMENT '添加合同权限',
-  `permission_edit` tinyint(1) DEFAULT '0' COMMENT '编辑节点权限',
-  `permission_show_unit` tinyint(1) DEFAULT '0' COMMENT '查看该节点下同单位下合同权限',
-  `permission_show_node` tinyint(1) DEFAULT '0' COMMENT '查看该节点下所有合同权限',
-  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
-  PRIMARY KEY (`id`)
-) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_unicode_ci COMMENT = '合同权限用户表';
-
-CREATE TABLE `zh_contract_tree_audit` (
-  `id` int(11) NOT NULL AUTO_INCREMENT,
-  `spid` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '项目id',
-  `tid` int(11) DEFAULT NULL COMMENT '标段id',
-  `contract_type` tinyint(1) DEFAULT NULL COMMENT '合同类型(1是支出,2是收入)',
-  `contract_id` int(11) DEFAULT NULL COMMENT '节点id',
-  `uid` int(11) DEFAULT NULL COMMENT '节点授权人id',
-  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
-  PRIMARY KEY (`id`)
-) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_unicode_ci COMMENT = '合同节点授权用户表';
-
-
-Update zh_filing_template SET is_fixed = 1 WHERE tree_level = 1;
+-- 请按如下分类提交sql!!!
+-- Version todo
+-- uat todo
+-- prod todo
+
+------------------------------------
+-- 表结构
+------------------------------------
+
+ALTER TABLE `zh_sub_project_info`
+ADD COLUMN `proj_intro` text NULL COMMENT '项目概述' AFTER `project_id`,
+ADD COLUMN `proj_cur_status` text NULL COMMENT '当前状态' AFTER `proj_intro`,
+ADD COLUMN `proj_status` varchar(20) NOT NULL DEFAULT '' COMMENT '建设状态' AFTER `proj_level`,
+ADD COLUMN `lx_tp` decimal(24, 8) NOT NULL COMMENT '立项-金额' AFTER `lx_code`,
+ADD COLUMN `lx_memo` text NULL COMMENT '立项-备注' AFTER `lx_tp`,
+ADD COLUMN `cb_tp` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '初步-金额' AFTER `cb_code`,
+ADD COLUMN `cb_memo` text NULL COMMENT '初步-备注' AFTER `cb_tp`,
+ADD COLUMN `sg_tp` decimal(24, 8) NOT NULL COMMENT '施工许可批复-金额' AFTER `sg_code`,
+ADD COLUMN `sg_memo` text NULL COMMENT '施工许可批复-备注' AFTER `sg_tp`,
+ADD COLUMN `sgt_department` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '施工图-部门' AFTER `gcl_quantity`,
+ADD COLUMN `sgt_date` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '施工图-日期' AFTER `sgt_department`,
+ADD COLUMN `sgt_code` varchar(100) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '施工图-文号' AFTER `sgt_date`,
+ADD COLUMN `sgt_tp` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '施工图-金额' AFTER `sgt_code`,
+ADD COLUMN `sgt_memo` text NULL COMMENT '施工图-备注' AFTER `sgt_tp`,
+ADD COLUMN `pf_department` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '财评/造价站批复-部门' AFTER `sgt_memo`,
+ADD COLUMN `pf_date` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '财评/造价站批复-日期' AFTER `pf_department`,
+ADD COLUMN `pf_code` varchar(100) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '财评/造价站批复-文号' AFTER `pf_date`,
+ADD COLUMN `pf_tp` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '财评/造价站批复-金额' AFTER `pf_code`,
+ADD COLUMN `pf_memo` text NULL COMMENT '财评/造价站批复-备注' AFTER `pf_tp`,
+ADD COLUMN `zb_department` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '招标完成-部门' AFTER `pf_memo`,
+ADD COLUMN `zb_date` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '招标完成-日期' AFTER `zb_department`,
+ADD COLUMN `zb_code` varchar(100) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '招标完成-文号' AFTER `zb_date`,
+ADD COLUMN `zb_tp` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '招标完成-金额' AFTER `zb_code`,
+ADD COLUMN `zb_memo` text NULL COMMENT '招标完成-备注' AFTER `zb_tp`;
+
+ALTER TABLE `zh_sub_project_info`
+ADD COLUMN `jg_tp` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '交工-金额' AFTER `jg_level`,
+ADD COLUMN `jg_memo` text NULL COMMENT '交工-备注' AFTER `jg_tp`;
+
+CREATE TABLE `zh_sub_project_progress`  (
+  `id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT 'uuid',
+  `spid` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT 'uuid',
+  `tree_id` int(11) NOT NULL COMMENT '节点id',
+  `tree_pid` int(11) NOT NULL COMMENT '父节点id',
+  `tree_order` tinyint(4) NOT NULL COMMENT '同级排序',
+  `tree_level` tinyint(4) NOT NULL COMMENT '层级',
+  `tree_full_path` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '层级定位辅助字段parent.full_path.ledger_id',
+  `tree_is_leaf` tinyint(4) UNSIGNED NOT NULL DEFAULT 1 COMMENT '是否叶子节点,界面显示辅助字段',
+  `code` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '编号/序号',
+  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '阶段/项目名称',
+  `edit_progress` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '成果编制-进度',
+  `edit_date` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '成果编制-日期',
+  `edit_department` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '成果编制-部门',
+  `submit_progress` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '报审情况-进度',
+  `submit_date` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '报审情况-日期',
+  `submit_department` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '报审情况-部门',
+  `reply_progress` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '批复情况-进度',
+  `reply_date` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '批复情况-日期',
+  `reply_department` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '批复情况-部门',
+  `reply_code` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '批复情况-文号',
+  `memo` varchar(1000) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '备注',
+  `add_user_id` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建人',
+  `add_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_user_id` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '最后编辑人',
+  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后编辑时间',
+  PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_unicode_ci ROW_FORMAT = Dynamic;
+
+CREATE TABLE `zh_sub_project_file`  (
+  `id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT 'uuid',
+  `spid` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '项目id(sub_project.id)',
+  `type` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '类型(progress/push/...)',
+  `rela_id` varchar(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT 'uuid(zh_sub_project_progress.id/zh_sub_project_push.id/...)',
+  `filename` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '文件名',
+  `fileext` varchar(10) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '文件后缀',
+  `filesize` int(11) NOT NULL COMMENT '文件大小',
+  `filepath` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '文件存储路径',
+  `user_id` int(11) UNSIGNED NOT NULL COMMENT '用户id(zh_project_account.id)',
+  `user_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '用户名(缓存)',
+  `user_company` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '公司(缓存)',
+  `user_role` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '角色(缓存)',
+  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
+  `is_deleted` tinyint(4) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否删除',
+  PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_unicode_ci ROW_FORMAT = Dynamic;
+
+ALTER TABLE `calculation`.`zh_tender_info`
+ADD COLUMN `over_range_check` varchar(255) NOT NULL DEFAULT '' AFTER `s_type`;
+
+------------------------------------
+-- 表数据
+------------------------------------
+
+UPDATE zh_tender_info ti LEFT JOIN zh_tender t ON ti.tid = t.id SET ti.over_range_check = '{"field":"deal","percent":100,"billsWithPos":"bills"}' WHERE t.measure_type = 2;
+UPDATE zh_tender_info ti LEFT JOIN zh_tender t ON ti.tid = t.id SET ti.over_range_check = '{"field":"tz","percent":100,"billsWithPos":"both"}' WHERE t.measure_type = 1;

+ 373 - 0
sql/update20241030.sql

@@ -0,0 +1,373 @@
+ALTER TABLE `zh_rpt_tree_node_cust`
+ADD COLUMN `tender_id` INT NULL DEFAULT -1 COMMENT '新需求,跟标段走,不跟客户走' AFTER `cust_acc_id`,
+ADD INDEX `tender` (`tender_id` ASC);
+;
+
+CREATE TABLE `zh_stage_yjcl`  (
+  `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
+  `uuid` varchar(36) NOT NULL DEFAULT '' COMMENT 'uuid',
+  `add_sid` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '新增期id',
+  `add_sorder` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '新增期序号',
+  `add_uid` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建人id',
+  `tid` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '标段id',
+  `sid` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '期id',
+  `sorder` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '期序号',
+  `name` varchar(255) NOT NULL DEFAULT '' COMMENT '名称',
+  `m_order` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '排序',
+  `spec` varchar(255) NOT NULL DEFAULT '' COMMENT '规格型号',
+  `unit` varchar(20) NOT NULL DEFAULT '' COMMENT '单位',
+  `tax` tinyint(4) UNSIGNED NOT NULL DEFAULT 0 COMMENT '税率',
+  `arrive_time` varchar(50) NOT NULL DEFAULT '' COMMENT '到场时间',
+  `source` varchar(255) NOT NULL DEFAULT '' COMMENT '材料来源',
+  `bills_code` varchar(255) NOT NULL DEFAULT '' COMMENT '单据号',
+  `location` varchar(50) NOT NULL DEFAULT '' COMMENT '存放位置',
+  `prepare_pos` varchar(255) NOT NULL DEFAULT '' COMMENT '拟用于部位',
+  `memo` varchar(1000) NOT NULL DEFAULT '' COMMENT '备注',
+  `arrive_qty` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '到场数量',
+  `arrive_tp` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '到场金额',
+  `unit_price` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '单价',
+  `ex_tax_up` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '除税单价',
+  `qty` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '本期数量',
+  `tp` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '本期金额',
+  `pre_qty` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '截止上期数量',
+  `pre_tp` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '截止上期金额',
+  `pre_used` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '上期是否使用',
+  `shistory` text NULL COMMENT '审批历史',
+  PRIMARY KEY (`id`)
+);
+
+ALTER TABLE `zh_budget_std`
+ADD COLUMN `ht_project_template_id` varchar(255) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL DEFAULT '' COMMENT '合同-项目合同模版-id列表(‘,’分隔)' AFTER `zb_bills_id`,
+ADD COLUMN `ht_tender_template_id` varchar(255) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL DEFAULT '' COMMENT '合同-标段合同模版-id列表(‘,’分隔)' AFTER `ht_project_template_id`;
+
+ALTER TABLE `zh_shenpi_audit`
+ADD COLUMN `audit_ledger_id` varchar(5000) NOT NULL DEFAULT '' COMMENT '审批台账id' AFTER `audit_order`;
+
+ALTER TABLE `zh_stage_audit`
+ADD COLUMN `audit_ledger_id` varchar(5000) NOT NULL DEFAULT '' COMMENT '审批台账id' AFTER `audit_order`,
+ADD COLUMN `audit_locked` tinyint(1) NOT NULL DEFAULT 0  COMMENT '审批锁定(仅协审用)' AFTER `audit_ledger_id`;
+
+ALTER TABLE `zh_ledger_audit`
+ADD COLUMN `audit_type` tinyint(4) NOT NULL DEFAULT 1  COMMENT '审批类型' AFTER `audit_order`,
+ADD COLUMN `audit_ledger_id` varchar(5000) NOT NULL DEFAULT '' COMMENT '审批台账id' AFTER `audit_id`;
+
+ALTER TABLE `zh_revise_audit`
+ADD COLUMN `audit_type` tinyint(4) NOT NULL DEFAULT 1 COMMENT '审批类型' AFTER `audit_order`,
+ADD COLUMN `audit_ledger_id` varchar(5000) NOT NULL DEFAULT '' COMMENT '审批台账id' AFTER `audit_type`;
+
+ALTER TABLE `zh_tender_cache`
+MODIFY COLUMN `ledger_flow_cur_uid` varchar(1000) NOT NULL DEFAULT '0' COMMENT '台账-当前流程人id' AFTER `ledger_status`,
+MODIFY COLUMN `ledger_flow_pre_uid` varchar(1000) NOT NULL DEFAULT '0' COMMENT '台账-上一流程人id' AFTER `ledger_flow_cur_info`;
+
+CREATE TABLE `zh_contract_tree` (
+  `id` varchar(100) CHARACTER SET utf8 NOT NULL COMMENT '自增id',
+  `spid` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '项目id',
+  `tid` int(11) DEFAULT NULL COMMENT '标段id',
+  `contract_type` tinyint(1) NOT NULL COMMENT '合同类型(1是支出,2是收入)',
+  `contract_id` int(11) NOT NULL COMMENT '节点id',
+  `contract_pid` int(11) NOT NULL COMMENT '父节点id',
+  `level` tinyint(4) NOT NULL COMMENT '层级',
+  `order` mediumint(4) NOT NULL DEFAULT '0' COMMENT '同级排序',
+  `full_path` varchar(255) CHARACTER SET utf8 DEFAULT '' COMMENT '层级定位辅助字段parent.full_path.contract_id',
+  `is_leaf` tinyint(1) NOT NULL COMMENT '是否叶子节点,界面显示辅助字段',
+  `code` varchar(50) CHARACTER SET utf8 DEFAULT '' COMMENT '节点编号',
+  `name` varchar(255) CHARACTER SET utf8 DEFAULT '' COMMENT '名称',
+  `unit` varchar(255) CHARACTER SET utf8 DEFAULT '' COMMENT '单位',
+  `remark` varchar(1000) CHARACTER SET utf8 DEFAULT '' COMMENT '备注',
+  PRIMARY KEY (`id`),
+  KEY `sub_id` (`spid`),
+  KEY `tid` (`tid`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT = '合同管理树结构表';
+
+CREATE TABLE `zh_contract` (
+  `id` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
+  `spid` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '项目id',
+  `tid` int(11) DEFAULT NULL COMMENT '标段id',
+  `contract_type` tinyint(1) DEFAULT NULL COMMENT '合同类型(1是支出,2是收入)',
+  `uid` int(11) DEFAULT NULL COMMENT '创建人id',
+  `contract_id` int(11) NOT NULL COMMENT '节点id',
+  `contract_pid` int(11) NOT NULL COMMENT '父节点id',
+  `level` tinyint(4) NOT NULL COMMENT '层级',
+  `is_leaf` tinyint(1) DEFAULT '1' COMMENT '是否是子节点',
+  `order` mediumint(4) NOT NULL DEFAULT '0' COMMENT '同级排序',
+  `full_path` varchar(255) CHARACTER SET utf8 DEFAULT '' COMMENT '层级定位辅助字段parent.full_path.contract_id',
+  `c_code` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '合同编号',
+  `name` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '合同名称',
+  `total_price` decimal(30,8) DEFAULT '0.00000000' COMMENT '合同金额',
+  `pay_price` decimal(30,8) DEFAULT '0.00000000' COMMENT '累计支付(回款)金额',
+  `debit_price` decimal(30,8) DEFAULT '0.00000000' COMMENT '累计扣款金额',
+  `yf_price` decimal(30,8) DEFAULT '0.00000000' COMMENT '累计应付(应回)金额',
+  `sf_price` decimal(30,8) DEFAULT '0.00000000' COMMENT '累计已付(已回)金额',
+  `party_a` varchar(255) COLLATE utf8_unicode_ci DEFAULT '' COMMENT '甲方',
+  `party_a_user` varchar(255) COLLATE utf8_unicode_ci DEFAULT '' COMMENT '甲方签约人',
+  `party_b` varchar(255) COLLATE utf8_unicode_ci DEFAULT '' COMMENT '乙方',
+  `party_b_user` varchar(255) COLLATE utf8_unicode_ci DEFAULT '' COMMENT '乙方签约人',
+  `sign_date` datetime DEFAULT NULL COMMENT '签订日期',
+  `address` varchar(255) COLLATE utf8_unicode_ci DEFAULT '' COMMENT '签约地点',
+  `entity` varchar(255) COLLATE utf8_unicode_ci DEFAULT '' COMMENT '单位',
+  `bank` varchar(255) COLLATE utf8_unicode_ci DEFAULT '' COMMENT '开户行',
+  `bank_account` varchar(255) COLLATE utf8_unicode_ci DEFAULT '' COMMENT '收款账号',
+  `settle_code` varchar(255) COLLATE utf8_unicode_ci DEFAULT '' COMMENT '结算书编号',
+  `remark` varchar(1000) COLLATE utf8_unicode_ci DEFAULT '' COMMENT '备注',
+  `exist_pay` tinyint(1) DEFAULT '0' COMMENT '是否存在合同支付(回款)',
+  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='合同详情表';
+
+CREATE TABLE `zh_contract_pay` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `spid` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '项目id',
+  `tid` int(11) DEFAULT NULL COMMENT '标段id',
+  `contract_type` tinyint(1) NOT NULL COMMENT '合同类型(1是支出,2是收入)',
+  `cid` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '合同详情id',
+  `uid` int(11) DEFAULT NULL COMMENT '创建人id',
+  `pay_time` datetime DEFAULT NULL COMMENT '支付日期',
+  `pay_price` decimal(30,8) DEFAULT '0.00000000' COMMENT '付款金额',
+  `debit_price` decimal(30,8) DEFAULT '0.00000000' COMMENT '扣款金额',
+  `yf_price` decimal(30,8) DEFAULT '0.00000000' COMMENT '应付(回)金额',
+  `sf_price` decimal(30,8) DEFAULT '0.00000000' COMMENT '实付金额',
+  `pay_type` varchar(50) COLLATE utf8_unicode_ci DEFAULT '' COMMENT '支付方式',
+  `remark` varchar(1000) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '备注',
+  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT = '合同支付表';
+
+CREATE TABLE `zh_contract_pay_attachment` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `spid` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '项目id',
+  `tid` int(11) DEFAULT NULL COMMENT '标段id',
+  `contract_type` tinyint(1) NOT NULL COMMENT '合同类型(1是支出,2是收入)',
+  `cid` varchar(100) COLLATE utf8_unicode_ci NOT NULL COMMENT '合同详情id',
+  `cpid` int(11) NOT NULL COMMENT '合同支付id',
+  `uid` int(11) NOT NULL COMMENT '上传者id',
+  `filename` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '文件名称',
+  `fileext` varchar(5) COLLATE utf8_unicode_ci NOT NULL COMMENT '文件后缀',
+  `filesize` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '文件大小',
+  `filepath` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '文件存储路径',
+  `upload_time` datetime NOT NULL COMMENT '上传时间',
+  PRIMARY KEY (`id`) USING BTREE,
+  KEY `idx_cid` (`cpid`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_unicode_ci COMMENT = '合同支付附件表';
+
+CREATE TABLE `zh_contract_attachment` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `spid` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '项目id',
+  `tid` int(11) DEFAULT NULL COMMENT '标段id',
+  `contract_type` tinyint(1) NOT NULL COMMENT '合同类型(1是支出,2是收入)',
+  `cid` varchar(100) COLLATE utf8_unicode_ci NOT NULL COMMENT '合同详情id',
+  `uid` int(11) NOT NULL COMMENT '上传者id',
+  `filename` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '文件名称',
+  `fileext` varchar(5) COLLATE utf8_unicode_ci NOT NULL COMMENT '文件后缀',
+  `filesize` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '文件大小',
+  `filepath` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '文件存储路径',
+  `upload_time` datetime NOT NULL COMMENT '上传时间',
+  PRIMARY KEY (`id`) USING BTREE,
+  KEY `idx_cid` (`cid`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_unicode_ci COMMENT = '合同文件表';
+
+CREATE TABLE `zh_contract_audit` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `spid` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '项目id',
+  `tid` int(11) DEFAULT NULL COMMENT '标段id',
+  `uid` int(11) NOT NULL COMMENT '用户id',
+  `permission_add` tinyint(1) DEFAULT '0' COMMENT '添加合同权限',
+  `permission_edit` tinyint(1) DEFAULT '0' COMMENT '编辑节点权限',
+  `permission_show_unit` tinyint(1) DEFAULT '0' COMMENT '查看该节点下同单位下合同权限',
+  `permission_show_node` tinyint(1) DEFAULT '0' COMMENT '查看该节点下所有合同权限',
+  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
+  PRIMARY KEY (`id`)
+) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_unicode_ci COMMENT = '合同权限用户表';
+
+CREATE TABLE `zh_contract_tree_audit` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `spid` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '项目id',
+  `tid` int(11) DEFAULT NULL COMMENT '标段id',
+  `contract_type` tinyint(1) DEFAULT NULL COMMENT '合同类型(1是支出,2是收入)',
+  `contract_id` int(11) DEFAULT NULL COMMENT '节点id',
+  `uid` int(11) DEFAULT NULL COMMENT '节点授权人id',
+  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
+  PRIMARY KEY (`id`)
+) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_unicode_ci COMMENT = '合同节点授权用户表';
+
+CREATE TABLE `zh_financial_audit` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `spid` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '项目id',
+  `uid` int(11) NOT NULL COMMENT '用户id',
+  `permission_transfer_show` tinyint(1) DEFAULT '1' COMMENT '查看划拨权限',
+  `permission_transfer_add` tinyint(1) DEFAULT '0' COMMENT '添加划拨权限',
+  `permission_transfer_file` tinyint(1) DEFAULT '0' COMMENT '划拨文件权限',
+  `permission_pay_show` tinyint(1) DEFAULT '1' COMMENT '查看支付权限',
+  `permission_pay_file` tinyint(1) DEFAULT '0' COMMENT '支付文件权限',
+  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='资金监管权限用户表';
+
+CREATE TABLE `zh_financial_pay` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `spid` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '项目id',
+  `tid` int(11) NOT NULL COMMENT '标段id',
+  `code` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '支付编号',
+  `create_time` datetime DEFAULT NULL COMMENT '申请时间',
+  `uid` int(11) DEFAULT NULL COMMENT '申请人',
+  `used` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '资金用途',
+  `total_price` decimal(30,8) DEFAULT '0.00000000' COMMENT '支付金额',
+  `small_expenses_tp` decimal(30,8) DEFAULT '0.00000000' COMMENT '小额支出支付金额',
+  `times` tinyint(3) DEFAULT '1' COMMENT '次数',
+  `status` tinyint(2) DEFAULT NULL COMMENT '审批状态',
+  `entity` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '申请支付单位',
+  `bank` varchar(255) COLLATE utf8_unicode_ci DEFAULT '' COMMENT '单位开户行',
+  `bank_account` varchar(255) COLLATE utf8_unicode_ci DEFAULT '' COMMENT '支付账号',
+  `remark` text COLLATE utf8_unicode_ci COMMENT '备注',
+  `final_auditor_str` varchar(255) COLLATE utf8_unicode_ci DEFAULT '',
+  `entities` varchar(5000) COLLATE utf8_unicode_ci DEFAULT '' COMMENT '收款单位',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='资金支付表';
+
+CREATE TABLE `zh_financial_pay_attachment` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `spid` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '项目id',
+  `fpid` int(11) NOT NULL COMMENT '申请支付id',
+  `fpcid` int(11) DEFAULT NULL COMMENT '支付明细id',
+  `uid` int(11) NOT NULL COMMENT '上传者id',
+  `filename` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '文件名称',
+  `fileext` varchar(5) COLLATE utf8_unicode_ci NOT NULL COMMENT '文件后缀',
+  `filesize` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '文件大小',
+  `filepath` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '文件存储路径',
+  `upload_time` datetime NOT NULL COMMENT '上传时间',
+  `type` tinyint(1) DEFAULT '0' COMMENT '文件类型(0为普通,1为发票)',
+  `bill` varchar(255) COLLATE utf8_unicode_ci DEFAULT '' COMMENT '发票号',
+  PRIMARY KEY (`id`) USING BTREE,
+  KEY `idx_trid` (`fpid`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='资金支付文件表';
+
+CREATE TABLE `zh_financial_pay_audit` (
+  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
+  `spid` varchar(100) COLLATE utf8_unicode_ci NOT NULL COMMENT '项目id',
+  `tid` int(11) NOT NULL COMMENT '标段id',
+  `fpid` int(11) NOT NULL COMMENT '资金支付id',
+  `aid` int(11) NOT NULL COMMENT '审批人id',
+  `order` int(11) NOT NULL COMMENT '审批顺序',
+  `times` int(11) NOT NULL DEFAULT '1' COMMENT '审批次数',
+  `status` int(1) NOT NULL COMMENT '审批状态',
+  `begin_time` datetime DEFAULT NULL COMMENT '开始审批时间',
+  `end_time` datetime DEFAULT NULL COMMENT '结束审批时间',
+  `opinion` varchar(1000) CHARACTER SET utf8 DEFAULT NULL COMMENT '审批意见',
+  `audit_type` tinyint(4) unsigned NOT NULL DEFAULT '1' COMMENT '审批类型(1个人,2会签,3或签)',
+  `audit_order` tinyint(4) unsigned NOT NULL COMMENT '审批顺序',
+  PRIMARY KEY (`id`),
+  KEY `idx_sid_order_times` (`fpid`,`order`,`times`),
+  KEY `idx_sid_status_aid_order_times` (`fpid`,`status`,`aid`,`order`,`times`),
+  KEY `idx_sid_times_status` (`fpid`,`times`,`status`),
+  KEY `idx_tid_aid` (`tid`,`aid`),
+  KEY `idx_sid_times_aid` (`fpid`,`times`,`aid`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='资金支付审批表';
+
+CREATE TABLE `zh_financial_pay_contract` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `spid` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '项目id',
+  `tid` int(11) DEFAULT NULL COMMENT '标段id',
+  `fpid` int(11) DEFAULT NULL COMMENT '申请支付id',
+  `cid` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '合同id',
+  `c_code` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '合同编号',
+  `name` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '合同名称',
+  `total_price` decimal(30,8) DEFAULT NULL COMMENT '合同金额',
+  `entity` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '收款单位',
+  `bank` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '开户行',
+  `bank_account` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '开户行账号',
+  `small_expenses` tinyint(1) DEFAULT '0' COMMENT '是否小额支出',
+  `pay_price` decimal(30,8) DEFAULT NULL COMMENT '支付金额',
+  `settle_price` decimal(30,8) DEFAULT NULL COMMENT '结算金额',
+  `pay_type` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '支付方式',
+  `bill` tinyint(1) DEFAULT '0' COMMENT '是否需要发票',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='资金支付明细表';
+
+CREATE TABLE `zh_financial_pay_tender` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `spid` varchar(100) COLLATE utf8_unicode_ci NOT NULL COMMENT '项目id',
+  `tid` int(11) NOT NULL COMMENT '标段id',
+  `name` varchar(255) COLLATE utf8_unicode_ci DEFAULT '' COMMENT '开户名称',
+  `bank` varchar(255) COLLATE utf8_unicode_ci DEFAULT '' COMMENT '开户银行',
+  `bank_account` varchar(255) COLLATE utf8_unicode_ci DEFAULT '' COMMENT '开户账号',
+  `contact` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '联系人',
+  `phone` varchar(20) COLLATE utf8_unicode_ci DEFAULT '' COMMENT '联系电话',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='资金支付标段数据表';
+
+CREATE TABLE `zh_financial_pay_tender_audit` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `spid` varchar(100) COLLATE utf8_unicode_ci NOT NULL COMMENT '项目id',
+  `tid` int(11) NOT NULL COMMENT '标段id',
+  `uid` int(11) NOT NULL COMMENT '用户id',
+  `is_report` tinyint(1) DEFAULT '0' COMMENT '是否为填报人',
+  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='资金支付标段人员权限表';
+
+CREATE TABLE `zh_financial_transfer` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `spid` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '项目id',
+  `t_time` varchar(20) COLLATE utf8_unicode_ci NOT NULL COMMENT '划拨年月',
+  `total_price` decimal(30,8) DEFAULT '0.00000000' COMMENT '划拨金额',
+  `uid` int(11) NOT NULL COMMENT '填报人',
+  `is_lock` tinyint(1) DEFAULT '0' COMMENT '是否已锁定',
+  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
+  `remark` varchar(1000) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '备注',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='资金划拨表';
+
+CREATE TABLE `zh_financial_transfer_attachment` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `spid` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '项目id',
+  `trid` int(11) NOT NULL COMMENT '划拨年月id',
+  `uid` int(11) NOT NULL COMMENT '上传者id',
+  `filename` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '文件名称',
+  `fileext` varchar(5) COLLATE utf8_unicode_ci NOT NULL COMMENT '文件后缀',
+  `filesize` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '文件大小',
+  `filepath` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '文件存储路径',
+  `upload_time` datetime NOT NULL COMMENT '上传时间',
+  PRIMARY KEY (`id`) USING BTREE,
+  KEY `idx_trid` (`trid`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='资金划拨文件表';
+
+CREATE TABLE `zh_financial_transfer_tender` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `spid` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '项目id',
+  `trid` int(11) NOT NULL COMMENT '资金划拨id',
+  `tid` int(11) NOT NULL COMMENT '标段id',
+  `sorder` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '期数,以逗号分隔',
+  `uid` int(11) NOT NULL COMMENT '创建人id',
+  `is_lock` tinyint(1) DEFAULT '0' COMMENT '是否锁定',
+  `total_price` decimal(30,8) DEFAULT '0.00000000' COMMENT '合同金额',
+  `contract_tp` decimal(30,8) DEFAULT '0.00000000' COMMENT '本期合同计量',
+  `qc_tp` decimal(30,8) DEFAULT '0.00000000' COMMENT '本期数量变更计量',
+  `pc_tp` decimal(30,8) DEFAULT '0.00000000' COMMENT '本期补差',
+  `yf_tp` decimal(30,8) DEFAULT '0.00000000' COMMENT '本期应付',
+  `sf_tp` decimal(30,8) DEFAULT '0.00000000' COMMENT '本期实付',
+  `hb_tp` decimal(30,8) DEFAULT '0.00000000' COMMENT '本期划拨',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='资金划拨标段表';
+
+CREATE TABLE `zh_financial_transfer_tender_attachment` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `spid` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '项目id',
+  `trid` int(11) NOT NULL COMMENT '划拨年月id',
+  `ftid` int(11) NOT NULL COMMENT '划拨标段id',
+  `uid` int(11) NOT NULL COMMENT '上传者id',
+  `filename` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '文件名称',
+  `fileext` varchar(5) COLLATE utf8_unicode_ci NOT NULL COMMENT '文件后缀',
+  `filesize` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '文件大小',
+  `filepath` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '文件存储路径',
+  `upload_time` datetime NOT NULL COMMENT '上传时间',
+  PRIMARY KEY (`id`) USING BTREE,
+  KEY `idx_trid` (`trid`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='资金划拨文件表';
+
+
+ALTER TABLE `zh_account_cert`
+ADD COLUMN `cert_type` varchar(255) NULL DEFAULT '' COMMENT '证书分类字符' AFTER `type`,
+ADD COLUMN `cert_name` varchar(255) NULL DEFAULT '' COMMENT '证书名称字符' AFTER `name`;
+
+-- update请放在最后
+
+Update zh_filing_template SET is_fixed = 1 WHERE tree_level = 1;