Browse Source

Merge remote-tracking branch 'remotes/origin/uat'

MaiXinRong 4 năm trước cách đây
mục cha
commit
a780341067
100 tập tin đã thay đổi với 3909 bổ sung1159 xóa
  1. 1 10
      app.js
  2. 38 27
      app/base/base_bills_service.js
  3. 2 2
      app/base/base_controller.js
  4. 12 1
      app/base/base_service.js
  5. 2 2
      app/base/base_tree_service.js
  6. 40 0
      app/const/project_log.js
  7. 6 6
      app/const/spread.js
  8. 5 6
      app/const/standard.js
  9. 3 1
      app/controller/advance_controller.js
  10. 5 0
      app/controller/change_controller.js
  11. 1 1
      app/controller/deal_bills_controller.js
  12. 7 4
      app/controller/ledger_controller.js
  13. 54 8
      app/controller/report_controller.js
  14. 4 1
      app/controller/revise_controller.js
  15. 175 1
      app/controller/schedule_controller.js
  16. 33 0
      app/controller/setting_controller.js
  17. 0 2
      app/controller/spss_controller.js
  18. 15 5
      app/controller/stage_controller.js
  19. 10 5
      app/controller/stage_extra_controller.js
  20. 1 0
      app/controller/standard_lib_controller.js
  21. 2 2
      app/controller/template_controller.js
  22. 16 4
      app/controller/tender_controller.js
  23. 2 2
      app/extend/context.js
  24. 112 1
      app/extend/helper.js
  25. 2 2
      app/lib/bills_pos_convert.js
  26. 338 0
      app/lib/gcl_gather.js
  27. 473 289
      app/lib/rpt_data_analysis.js
  28. 71 0
      app/lib/stage_im.js
  29. 1 1
      app/middleware/revise_audit_check.js
  30. 1 0
      app/middleware/stage_check.js
  31. 3 2
      app/middleware/tender_check.js
  32. 2 2
      app/middleware/uncheck_tender_check.js
  33. 9 9
      app/public/css/main.css
  34. 10 10
      app/public/css/main_s.css
  35. BIN
      app/public/files/template/ledger/导入分项清单EXCEL格式.xlsx
  36. 10 5
      app/public/js/advance_audit.js
  37. 80 9
      app/public/js/change_information_set.js
  38. 45 33
      app/public/js/gcl_gather.js
  39. 19 86
      app/public/js/ledger.js
  40. 60 2
      app/public/js/ledger_bwtz.js
  41. 1 1
      app/public/js/ledger_gather.js
  42. 6 0
      app/public/js/path_tree.js
  43. 5 5
      app/public/js/revise.js
  44. 2 2
      app/public/js/revise_gcl_compare.js
  45. 54 63
      app/public/js/schedule_plan.js
  46. 598 0
      app/public/js/schedule_stage_gcl.js
  47. 254 0
      app/public/js/schedule_stage_tp.js
  48. 24 1
      app/public/js/se_bonus.js
  49. 29 0
      app/public/js/se_other.js
  50. 6 6
      app/public/js/shares/cs_tools.js
  51. 45 37
      app/public/js/shares/gcl_gather_compare.js
  52. 34 0
      app/public/js/shares/new_tag.js
  53. 43 46
      app/public/js/shenpi.js
  54. 5 5
      app/public/js/spreadjs_rela/spreadjs_zh.js
  55. 238 180
      app/public/js/stage.js
  56. 63 1
      app/public/js/stage_bwtz.js
  57. 1 1
      app/public/js/stage_compare.js
  58. 2 2
      app/public/js/stage_gather.js
  59. 4 0
      app/public/report/js/jpc_output.js
  60. 3 4
      app/public/report/js/rpt_custom.js
  61. 1 0
      app/public/report/js/rpt_jspdf.js
  62. 12 0
      app/public/report/js/rpt_main.js
  63. 8 0
      app/public/report/js/rpt_print.js
  64. 18 10
      app/public/report/js/rpt_signature.js
  65. 115 12
      app/reports/rpt_component/jpc_cross_tab.js
  66. 12 5
      app/router.js
  67. 4 0
      app/service/change.js
  68. 36 0
      app/service/change_audit_list.js
  69. 3 2
      app/service/ledger.js
  70. 5 5
      app/service/ledger_revise.js
  71. 3 62
      app/service/login_logging.js
  72. 4 0
      app/service/material.js
  73. 6 4
      app/service/pos.js
  74. 53 0
      app/service/project_log.js
  75. 6 3
      app/service/report.js
  76. 16 0
      app/service/report_memory.js
  77. 4 4
      app/service/revise_audit.js
  78. 4 1
      app/service/revise_bills.js
  79. 1 0
      app/service/revise_pos.js
  80. 54 49
      app/service/rpt_gather_memory.js
  81. 63 1
      app/service/schedule_ledger_month.js
  82. 45 0
      app/service/schedule_month.js
  83. 85 0
      app/service/schedule_stage.js
  84. 3 0
      app/service/stage.js
  85. 2 0
      app/service/stage_audit.js
  86. 10 9
      app/service/stage_bills.js
  87. 3 11
      app/service/stage_bills_final.js
  88. 38 31
      app/service/stage_change.js
  89. 84 0
      app/service/stage_change_final.js
  90. 1 0
      app/service/stage_pos.js
  91. 1 0
      app/service/stage_pos_final.js
  92. 13 8
      app/service/tender.js
  93. 45 7
      app/service/tender_info.js
  94. 3 3
      app/view/layout/menu.ejs
  95. 21 0
      app/view/ledger/bwtz.ejs
  96. 2 1
      app/view/ledger/explode_modal.ejs
  97. 0 1
      app/view/ledger/gather.ejs
  98. 7 1
      app/view/report/index.ejs
  99. 31 34
      app/view/report/rpt_all_popup.ejs
  100. 0 0
      app/view/revise/gcl_compare.ejs

+ 1 - 10
app.js

@@ -74,22 +74,13 @@ module.exports = app => {
 
     //压缩前端js
     app.jsFiles = { common: JsFiles.commonFiles };
-    if (!fs.existsSync(app.baseDir + '/app' + JsFiles.webPath)) {
-        fs.mkdirSync(app.baseDir + '/app' + JsFiles.webPath);
-    }
     for (const c in JsFiles.controller) {
         const controller = JsFiles.controller[c];
         app.jsFiles[c] = {};
         for (const a in controller) {
             const action = controller[a];
             if (app.config.min && action.mergeFiles && action.mergeFile.length > 0) {
-                const minFileName = JsFiles.webPath + action.mergeFile + '.' + app.config.version + '.min.js';
-                let code = '';
-                for (const f of action.mergeFiles) {
-                    code = code + fs.readFileSync(app.baseDir + '/app' + f, 'utf8');
-                }
-                fs.writeFileSync(app.baseDir + '/app' + minFileName, Uglyfy.minify(code, {mangle: true}).code);
-                app.jsFiles[c][a] = action.files.concat([minFileName]);
+                app.jsFiles[c][a] = action.files.concat([JsFiles.webPath + action.mergeFile + '.' + app.config.version + '.min.js']);
             } else {
                 app.jsFiles[c][a] = action.files.concat(action.mergeFiles || []);
             }

+ 38 - 27
app/base/base_bills_service.js

@@ -17,7 +17,6 @@ const upFields = ['unit_price'];
 const qtyFields = ['sgfh_qty', 'sjcl_qty', 'qtcl_qty', 'quantity', 'deal_qty', 'dgn_qty1', 'dgn_qty2'];
 const tpFields = ['sgfh_tp', 'sjcl_tp', 'qtcl_tp', 'total_price', 'deal_tp'];
 const measureType = require('../const/tender').measureType;
-const math = require('mathjs');
 const billsUtils = require('../lib/bills_utils');
 
 class BaseBillsSerivce extends TreeService {
@@ -71,9 +70,10 @@ class BaseBillsSerivce extends TreeService {
     async addBillsNode(tenderId, selectId, data, reviseId) {
         if (data) {
             if (reviseId) data.crid = reviseId;
+            data.check_calc = 1;
             return await this.addNode(tenderId, selectId, data);
         } else {
-            return await this.addNode(tenderId, selectId, {crid: reviseId});
+            return await this.addNode(tenderId, selectId, {crid: reviseId, check_calc: 1});
         }
     }
 
@@ -104,6 +104,7 @@ class BaseBillsSerivce extends TreeService {
         data.order = children.length + 1;
         data.full_path = selectData.full_path + '-' + data.ledger_id;
         data.is_leaf = true;
+        data.check_calc = 1;
         if (reviseId) data.crid = reviseId;
 
         this.transaction = await this.db.beginTransaction();
@@ -192,6 +193,7 @@ class BaseBillsSerivce extends TreeService {
         if (data.is_leaf === undefined) {
             data.is_leaf = true;
         }
+        data.check_calc = 1;
         if (reviseId) data.crid = reviseId;
         const result = await this.transaction.insert(this.tableName, data);
 
@@ -365,6 +367,7 @@ class BaseBillsSerivce extends TreeService {
      * @return {Promise<*>}
      */
     async updateCalc(tenderId, data) {
+        const helper = this.ctx.helper;
         // 简单验证数据
         if (tenderId <= 0 || !this.ctx.tender) {
             throw '标段不存在';
@@ -390,27 +393,31 @@ class BaseBillsSerivce extends TreeService {
                     throw '提交数据错误';
                 }
                 let updateData;
-                if (row.unit) {
+
+                // 更新单位或单价,全部数据都应重算
+                if (row.unit !== undefined || row.unit_price !== undefined) {
                     if (row.sgfh_qty === undefined) { row.sgfh_qty = updateNode.sgfh_qty; }
                     if (row.sjcl_qty === undefined) { row.sjcl_qty = updateNode.sjcl_qty; }
                     if (row.qtcl_qty === undefined) { row.qtcl_qty = updateNode.qtcl_qty; }
                     if (row.deal_qty === undefined) { row.deal_qty = updateNode.deal_qty; }
                 }
+
+                // 项目节、工程量清单相关
                 if (row.b_code) {
                     row.dgn_qty1 = null;
                     row.dgn_qty2 = null;
                     row.code = null;
                 }
-                if (row.code) {
-                    row.b_code = null;
-                }
+                if (row.code) row.b_code = null;
+
                 if (this._checkCalcField(row)) {
                     let calcData = JSON.parse(JSON.stringify(row));
-                    const precision = this.ctx.helper.findPrecision(info.precision, row.unit ? row.unit : updateNode.unit);
+                    calcData.check_calc = 1;
+                    const precision = helper.findPrecision(info.precision, row.unit ? row.unit : updateNode.unit);
                     // 数量保留小数位数
-                    this.ctx.helper.checkFieldPrecision(calcData, qtyFields, precision.value);
+                    helper.checkFieldPrecision(calcData, qtyFields, precision.value);
                     // 单位保留小数位数
-                    this.ctx.helper.checkFieldPrecision(calcData, upFields, info.decimal.up);
+                    helper.checkFieldPrecision(calcData, upFields, info.decimal.up);
                     // 未提交单价则读取数据库单价
                     if (row.unit_price === undefined) calcData.unit_price = updateNode.unit_price;
                     // 计算
@@ -420,29 +427,29 @@ class BaseBillsSerivce extends TreeService {
                         if (row.sjcl_qty === undefined) calcData.sjcl_qty = updateNode.sjcl_qty;
                         if (row.qtcl_qty === undefined) calcData.qtcl_qty = updateNode.qtcl_qty;
                         if (row.deal_qty === undefined) calcData.deal_qty = updateNode.deal_qty;
-                        calcData.quantity = this.ctx.helper.sum([calcData.sgfh_qty, calcData.sjcl_qty, calcData.qtcl_qty]);
-                        calcData.sgfh_tp = this.ctx.helper.mul(calcData.sgfh_qty, calcData.unit_price, info.decimal.tp);
-                        calcData.sjcl_tp = this.ctx.helper.mul(calcData.sjcl_qty, calcData.unit_price, info.decimal.tp);
-                        calcData.qtcl_tp = this.ctx.helper.mul(calcData.qtcl_qty, calcData.unit_price, info.decimal.tp);
-                        calcData.total_price = this.ctx.helper.mul(calcData.quantity, calcData.unit_price, info.decimal.tp);
-                        calcData.deal_tp = this.ctx.helper.mul(calcData.deal_qty, calcData.unit_price, info.decimal.tp);
+                        calcData.quantity = helper.sum([calcData.sgfh_qty, calcData.sjcl_qty, calcData.qtcl_qty]);
+                        calcData.sgfh_tp = helper.mul(calcData.sgfh_qty, calcData.unit_price, info.decimal.tp);
+                        calcData.sjcl_tp = helper.mul(calcData.sjcl_qty, calcData.unit_price, info.decimal.tp);
+                        calcData.qtcl_tp = helper.mul(calcData.qtcl_qty, calcData.unit_price, info.decimal.tp);
+                        calcData.total_price = helper.mul(calcData.quantity, calcData.unit_price, info.decimal.tp);
+                        calcData.deal_tp = helper.mul(calcData.deal_qty, calcData.unit_price, info.decimal.tp);
                     } else if (row.sgfh_tp !== undefined || row.sjcl_tp !== undefined || row.qtcl_tp !== undefined || row.deal_tp !== undefined) {
                         calcData.sgfh_qty = null;
                         calcData.sjcl_qty = null;
                         calcData.qtcl_qty = null;
                         calcData.quantity = null;
                         calcData.deal_qty = null;
-                        calcData.sgfh_tp = (row.sgfh_tp !== undefined) ? this.ctx.helper.round(calcData.row.sgfh_tp, info.decimal.tp) : updateNode.sgfh_tp;
-                        calcData.sjcl_tp = (row.sgfh_tp !== undefined) ? this.ctx.helper.round(calcData.row.sjcl_tp, info.decimal.tp) : updateNode.sjcl_tp;
-                        calcData.qtcl_tp = (row.sgfh_tp !== undefined) ? this.ctx.helper.round(calcData.row.qtcl_tp, info.decimal.tp) : updateNode.qtcl_tp;
-                        calcData.total_price = this.ctx.helper.sum([calcData.sgfh_tp, calcData.sjcl_tp, calcData.qtcl_tp]);
-                        calcData.deal_tp = (row.deal_tp !== undefined) ? this.ctx.helper.round(calcData.row.deal_tp, info.decimal.tp) : updateNode.deal_tp;
+                        calcData.sgfh_tp = helper.round(row.sgfh_tp !== undefined ? calcData.row.sgfh_tp : updateNode.sgfh_tp, info.decimal.tp);
+                        calcData.sjcl_tp = helper.round(row.sgfh_tp !== undefined ? calcData.row.sjcl_tp : updateNode.sjcl_tp, info.decimal.tp);
+                        calcData.qtcl_tp = helper.round(row.sgfh_tp !== undefined ? calcData.row.qtcl_tp : updateNode.qtcl_tp, info.decimal.tp);
+                        calcData.total_price = helper.sum([calcData.sgfh_tp, calcData.sjcl_tp, calcData.qtcl_tp]);
+                        calcData.deal_tp = helper.round(row.deal_tp !== undefined ? calcData.row.deal_tp : updateNode.deal_tp, info.decimal.tp);
                     } else if (row.unit_price !== undefined) {
-                        calcData.sgfh_tp = this.ctx.helper.mul(calcData.sgfh_qty, calcData.unit_price, info.decimal.tp);
-                        calcData.sjcl_tp = this.ctx.helper.mul(calcData.sjcl_qty, calcData.unit_price, info.decimal.tp);
-                        calcData.qtcl_tp = this.ctx.helper.mul(calcData.qtcl_qty, calcData.unit_price, info.decimal.tp);
-                        calcData.total_price = this.ctx.helper.mul(calcData.quantity, calcData.unit_price, info.decimal.tp);
-                        calcData.deal_tp = this.ctx.helper.mul(calcData.deal_qty, calcData.unit_price, info.decimal.tp);
+                        calcData.sgfh_tp = helper.mul(calcData.sgfh_qty, calcData.unit_price, info.decimal.tp);
+                        calcData.sjcl_tp = helper.mul(calcData.sjcl_qty, calcData.unit_price, info.decimal.tp);
+                        calcData.qtcl_tp = helper.mul(calcData.qtcl_qty, calcData.unit_price, info.decimal.tp);
+                        calcData.total_price = helper.mul(calcData.quantity, calcData.unit_price, info.decimal.tp);
+                        calcData.deal_tp = helper.mul(calcData.deal_qty, calcData.unit_price, info.decimal.tp);
                     }
                     updateData = this._filterUpdateInvalidField(updateNode.id, calcData);
                 } else {
@@ -463,8 +470,9 @@ class BaseBillsSerivce extends TreeService {
 
     // 统计方法
     async addUp(condition) {
+        if (!condition.tender_id) throw new TypeError('statistical lacks necessary parameter');
         const sql = 'SELECT Sum(total_price) As total_price, Sum(deal_tp) As deal_tp' +
-            '  FROM ' + this.tableName + this.ctx.helper.whereSql(condition);
+            '  FROM ' + this.departTableName(condition.tender_id) + this.ctx.helper.whereSql(condition);
         const result = await this.db.queryOne(sql);
         return result;
     }
@@ -536,7 +544,7 @@ class BaseBillsSerivce extends TreeService {
     _calcExpr(data, field, expr, defaultValue, precision) {
         if (expr) {
             try {
-                data[field] = this.ctx.helper.round(math.eval(expr), precision.value);
+                data[field] = this.ctx.helper.round(this.ctx.helper.calcExpr(expr), precision.value);
             } catch (err) {
             }
         } else {
@@ -592,11 +600,13 @@ class BaseBillsSerivce extends TreeService {
                     sgfh_expr: d.sgfh_expr,
                     sjcl_expr: d.sjcl_expr,
                     qtcl_expr: d.qtcl_expr,
+                    check_calc: 1,
                 };
                 for (const c of d.children) {
                     c.ledger_pid = newBills.ledger_id;
                 }
                 const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, newBills.unit);
+                newBills.deal_qty = this.ctx.helper.round(d.deal_qty, precision.value);
                 if (d.pos && d.pos.length > 0) {
                     for (const pos of d.pos) {
                         const newPos = {
@@ -637,6 +647,7 @@ class BaseBillsSerivce extends TreeService {
                 newBills.sjcl_tp = this.ctx.helper.mul(newBills.qtcl_qty, newBills.unit_price, tpDecimal);
                 newBills.qtcl_tp = this.ctx.helper.mul(newBills.sjcl_qty, newBills.unit_price, tpDecimal);
                 newBills.total_price = this.ctx.helper.mul(newBills.quantity, newBills.unit_price, tpDecimal);
+                newBills.deal_tp = this.ctx.helper.mul(newBills.deal_qty, newBills.unit_price, tpDecimal);
                 if (defaultData) this.ctx.helper._.assignIn(newBills, defaultData);
                 pbd.push(newBills);
             }

+ 2 - 2
app/base/base_controller.js

@@ -154,9 +154,9 @@ class BaseController extends Controller {
 
     ajaxErrorBody(error, msg) {
         if (error.stack) {
-            return {err: 2, msg: msg, data: null};
+            return {err: 4, msg: msg, data: null};
         } else {
-            return {err: 1, msg: error.toString(), data: null};
+            return {err: 3, msg: error.toString(), data: null};
         }
     }
 }

+ 12 - 1
app/base/base_service.js

@@ -26,6 +26,7 @@ class BaseService extends Service {
         this.transaction = null;
         this.sqlBuilder = null;
         this._ = this.app._;
+        this.depart = 0;
     }
 
     /**
@@ -44,7 +45,17 @@ class BaseService extends Service {
      * @return {String} - 返回表名
      */
     get tableName() {
-        return this._table;
+        if (this.depart <= 0) return this._table;
+
+        if (!this.ctx.tender) throw  new TypeError('tableName error: Without Tender');
+        return this._table + '_' + (this.ctx.tender.id % this.depart);
+    }
+
+    departTableName(key) {
+        if (this.depart <= 0) return this._table;
+
+        if (!key) throw  new TypeError('tableName error: Without DepartKey');
+        return this._table + '_' + (key % this.depart);
     }
 
     /**

+ 2 - 2
app/base/base_tree_service.js

@@ -74,10 +74,10 @@ class TreeService extends Service {
                 operate: '<=',
                 value: level,
             });
-            const [sql, sqlParam] = this.sqlBuilder.build(this.tableName);
+            const [sql, sqlParam] = this.sqlBuilder.build(this.departTableName(mid));
             return await this.db.query(sql, sqlParam);
         } else {
-            return await this.db.select(this.tableName, {
+            return await this.db.select(this.departTableName(mid), {
                 where: this.getCondition({mid: mid})
             });
         }

+ 40 - 0
app/const/project_log.js

@@ -0,0 +1,40 @@
+'use strict';
+
+/**
+ * 项目操作日志
+ *
+ * @author ELlisran
+ * @date 2021/01/12
+ * @version
+ */
+// 操作模块
+const type = {
+    all: 0,
+    tender: 1,
+    stage: 2,
+    change: 3,
+    material: 4,
+};
+
+const type_list = [
+    { code: 'all', type: type.all, name: '全部模块' },
+    { code: 'tender', type: type.tender, name: '标段' },
+    { code: 'stage', type: type.stage, name: '计量期' },
+    { code: 'change', type: type.change, name: '变更令' },
+    { code: 'material', type: type.material, name: '材料调差' },
+];
+// 操作状态
+const status = {
+    delete: 1, // 删除
+};
+const status_list = [
+    '',
+    '删除',
+];
+
+module.exports = {
+    type,
+    type_list,
+    status,
+    status_list,
+};

+ 6 - 6
app/const/spread.js

@@ -197,7 +197,7 @@ const stageTz = {
             //{title: '累计完成率(%)', colSpan: '1', rowSpan: '2', field: 'percent', hAlign: 0, width: 100, readOnly: true, type: 'Number'},
             {title: '备注', colSpan: '1', rowSpan: '2', field: 'memo', hAlign: 0, width: 100, formatter: '@', cellType: 'ellipsisAutoTip'},
             {title: '总额计量', colSpan: '1', rowSpan: '2', field: 'is_tp', hAlign: 1, width: 60, cellType: 'checkbox'},
-            {title: '交工状态', colSpan: '1', rowSpan: '2', field: 'gxby', hAlign: 1, width: 80, formatter: '@', readOnly: true},
+            {title: '工序报验', colSpan: '1', rowSpan: '2', field: 'gxby', hAlign: 1, width: 80, formatter: '@', readOnly: true},
             {title: '档案管理', colSpan: '1', rowSpan: '2', field: 'dagl', hAlign: 1, width: 80, formatter: '@', readOnly: true},
         ],
         emptyRows: 0,
@@ -222,7 +222,7 @@ const stageTz = {
             {title: '|完成', colSpan: '|1', rowSpan: '|1', field: 'end_gather_qty', hAlign: 2, width: 60, type: 'Number', readOnly: true},
             {title: '本期批注', colSpan: '1', rowSpan: '2', field: 'postil', hAlign: 0, width: 80, formatter: '@', cellType: 'autoTip'},
             {title: '图册号', colSpan: '1', rowSpan: '2', field: 'drawing_code', hAlign: 0, width: 80, formatter: '@'},
-            {title: '交工状态', colSpan: '1', rowSpan: '2', field: 'gxby', hAlign: 1, width: 80, formatter: '@', readOnly: true},
+            {title: '工序报验', colSpan: '1', rowSpan: '2', field: 'gxby', hAlign: 1, width: 80, formatter: '@', readOnly: true},
             {title: '档案管理', colSpan: '1', rowSpan: '2', field: 'dagl', hAlign: 1, width: 80, formatter: '@', readOnly: true},
         ],
         emptyRows: 3,
@@ -268,7 +268,7 @@ const stageCl = {
             {title: '图(册)号', colSpan: '1', rowSpan: '2', field: 'drawing_code', hAlign: 0, width: 80, formatter: '@', readOnly: true},
             {title: '备注', colSpan: '1', rowSpan: '2', field: 'memo', hAlign: 0, width: 100, formatter: '@', cellType: 'ellipsisAutoTip'},
             {title: '总额计量', colSpan: '1', rowSpan: '2', field: 'is_tp', hAlign: 1, width: 60, cellType: 'checkbox'},
-            {title: '交工状态', colSpan: '1', rowSpan: '2', field: 'gxby', hAlign: 1, width: 80, formatter: '@', readOnly: true},
+            {title: '工序报验', colSpan: '1', rowSpan: '2', field: 'gxby', hAlign: 1, width: 80, formatter: '@', readOnly: true},
             {title: '档案管理', colSpan: '1', rowSpan: '2', field: 'dagl', hAlign: 1, width: 80, formatter: '@', readOnly: true},
         ],
         emptyRows: 0,
@@ -297,7 +297,7 @@ const stageCl = {
             {title: '本期批注', colSpan: '1', rowSpan: '2', field: 'postil', hAlign: 0, width: 80, formatter: '@', cellType: 'autoTip'},
             {title: '图册号', colSpan: '1', rowSpan: '2', field: 'drawing_code', hAlign: 0, width: 80, formatter: '@'},
             {title: '添加期数', colSpan: '1', rowSpan: '2', field: 'add_stage_order', hAlign:1, width: 80, readOnly: true},
-            {title: '交工状态', colSpan: '1', rowSpan: '2', field: 'gxby', hAlign: 1, width: 80, formatter: '@', readOnly: true},
+            {title: '工序报验', colSpan: '1', rowSpan: '2', field: 'gxby', hAlign: 1, width: 80, formatter: '@', readOnly: true},
             {title: '档案管理', colSpan: '1', rowSpan: '2', field: 'dagl', hAlign: 1, width: 80, formatter: '@', readOnly: true},
         ],
         emptyRows: 20,
@@ -343,7 +343,7 @@ const stageNoCl = {
             {title: '图(册)号', colSpan: '1', rowSpan: '2', field: 'drawing_code', hAlign: 0, width: 80, formatter: '@', readOnly: true},
             {title: '备注', colSpan: '1', rowSpan: '2', field: 'memo', hAlign: 0, width: 100, formatter: '@', cellType: 'ellipsisAutoTip'},
             {title: '总额计量', colSpan: '1', rowSpan: '2', field: 'is_tp', hAlign: 1, width: 60, cellType: 'checkbox'},
-            {title: '交工状态', colSpan: '1', rowSpan: '2', field: 'gxby', hAlign: 1, width: 80, formatter: '@', readOnly: true},
+            {title: '工序报验', colSpan: '1', rowSpan: '2', field: 'gxby', hAlign: 1, width: 80, formatter: '@', readOnly: true},
             {title: '档案管理', colSpan: '1', rowSpan: '2', field: 'dagl', hAlign: 1, width: 80, formatter: '@', readOnly: true},
         ],
         emptyRows: 0,
@@ -369,7 +369,7 @@ const stageNoCl = {
             {title: '本期批注', colSpan: '1', rowSpan: '2', field: 'postil', hAlign: 0, width: 80, formatter: '@', cellType: 'autoTip'},
             {title: '图册号', colSpan: '1', rowSpan: '2', field: 'drawing_code', hAlign: 0, width: 80, formatter: '@'},
             {title: '添加期数', colSpan: '1', rowSpan: '2', field: 'add_stage_order', hAlign:1, width: 80, readOnly: true},
-            {title: '交工状态', colSpan: '1', rowSpan: '2', field: 'gxby', hAlign: 1, width: 80, formatter: '@', readOnly: true},
+            {title: '工序报验', colSpan: '1', rowSpan: '2', field: 'gxby', hAlign: 1, width: 80, formatter: '@', readOnly: true},
             {title: '档案管理', colSpan: '1', rowSpan: '2', field: 'dagl', hAlign: 1, width: 80, formatter: '@', readOnly: true},
         ],
         emptyRows: 20,

+ 5 - 6
app/const/standard.js

@@ -26,17 +26,16 @@ const nodeType = [
     {text: '其他建安工程', value: 14},
 ];
 
-const chapterFilter = [];
 const jrg = nodeType.find(x => {
     return x.text === '计日工';
 });
-if (jrg) {
-    chapterFilter.push({node_type: jrg.value});
-    chapterFilter.push({field: 'name', part: jrg.text});
-}
+const zlj = nodeType.find(x => {
+    return x.text === '暂列金额';
+});
 
 
 module.exports = {
     nodeType,
-    chapterFilter,
+    jrg,
+    zlj,
 };

+ 3 - 1
app/controller/advance_controller.js

@@ -365,13 +365,14 @@ module.exports = app => {
         async upload(ctx) {
             let stream;
             try {
-                this._checkAdvanceFileCanModify(ctx);
+                // this._checkAdvanceFileCanModify(ctx);
                 const parts = this.ctx.multipart({
                     autoFields: true,
                 });
                 const files = [];
                 const create_time = Date.parse(new Date()) / 1000;
                 let idx = 0;
+                const extra_upload = ctx.advance.status === auditConst.status.checked;
                 while ((stream = await parts()) !== undefined) {
                     if (!stream.filename) {
                         // 如果没有传入直接返回
@@ -401,6 +402,7 @@ module.exports = app => {
                         filesize: ctx.helper.bytesToSize(idx === 'isString' ? parts.field.size : parts.field.size[idx]),
                         filename: file.name,
                         fileext: file.ext,
+                        extra_upload,
                     };
                     return newFile;
                 });

+ 5 - 0
app/controller/change_controller.js

@@ -746,6 +746,11 @@ module.exports = app => {
                         // 取所有工料表
                         responseData.data = await ctx.service.changeAuditList.getList(ctx.change.cid);
                         break;
+                    case 'remove_list':
+                        await ctx.service.changeAuditList.removeLedgerListDatas(data.updateData);
+                        // 取所有工料表
+                        responseData.data = await ctx.service.changeAuditList.getList(ctx.change.cid);
+                        break;
                     case 'info':
                         await ctx.service.change.saveInfo(data.updateData);
                         // 取所有工料表

+ 1 - 1
app/controller/deal_bills_controller.js

@@ -116,7 +116,7 @@ module.exports = app => {
                 if (stream) {
                     await sendToWormhole(stream);
                 }
-                ctx.body = {err: 1, msg: err.toString(), data: null};
+                this.ajaxErrorBody(err, '导入数据失败');
             }
         }
 

+ 7 - 4
app/controller/ledger_controller.js

@@ -630,8 +630,8 @@ module.exports = app => {
             if (file) {
                 try {
                     let fileName;
-                    if (file === '导入分项清单EXCEL格式.xls') {
-                        fileName = path.join(this.app.baseDir, 'app', 'public', 'files', 'template', 'ledger', '导入分项清单EXCEL格式.xls');
+                    if (file === '导入分项清单EXCEL格式.xlsx') {
+                        fileName = path.join(this.app.baseDir, 'app', 'public', 'files', 'template', 'ledger', '导入分项清单EXCEL格式.xlsx');
                         ctx.body = await fs.readFileSync(fileName);
                     } else if (file === '台账分解.xlsx') {
                         const create_time = Date.parse(new Date()) / 1000;
@@ -699,7 +699,6 @@ module.exports = app => {
                 const renderData = {
                     tender: ctx.tender.data,
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.ledger.gather),
-                    chapterFilter: stdConst.chapterFilter,
                 };
 
                 await this.layout('ledger/gather.ejs', renderData);
@@ -720,7 +719,11 @@ module.exports = app => {
                 const posData = this.ctx.tender.data.measure_type === measureType.tz.value
                     ? await ctx.service.pos.getPosData({ tid: ctx.tender.id }) : [];
                 const dealBills = await ctx.service.dealBills.getAllDataByCondition({ where: { tender_id: this.ctx.tender.id } });
-                ctx.body = { err: 0, msg: '', data: { bills: billsData, pos: posData, dealBills } };
+                const zlj = JSON.parse(JSON.stringify(stdConst.zlj));
+                zlj.deal_bills_tp = ctx.tender.info.deal_param.zanLiePrice;
+                ctx.body = { err: 0, msg: '', data: {
+                    bills: billsData, pos: posData, dealBills, spec: {zlj: zlj, jrg: stdConst.jrg},
+                }};
             } catch (err) {
                 this.log(err);
                 ctx.body = { err: 1, msg: err.toString(), data: [] };

+ 54 - 8
app/controller/report_controller.js

@@ -326,9 +326,13 @@ module.exports = app => {
             for (const page of pageRst.items) {
                 page[JV.PROP_WATERMARK_CELLS] = [];
             }
+            let waterMarkStr = null;
             if (params.stage_status !== 3 && params.closeWatermark === 0) {
                 // 加水印
                 fillWaterMark([pageRst]);
+                if (params.needWaterMark) {
+                    waterMarkStr = await getWatermarkPicData(pageRst, this.app.baseDir);
+                }
             }
             // console.log(pageRst);
             // const roleRel = (params.stage_status === 3) ? (await ctx.service.roleRptRel.getRoleRptRelByDetailIds(params.tender_id, params.rpt_tpl_id)) : [];
@@ -338,7 +342,7 @@ module.exports = app => {
             // console.log('after role stage!');
             // console.log(roleRel);
             await encodeSignatureDataUri(roleRel, this.app.baseDir);
-            await encodeDummySignatureDataUri(pageRst, this.app.baseDir);
+            await encodeDummySignatureDataUri(pageRst, this.app.baseDir); // 注意草图数据量问题!
             const stageFlow = await ctx.service.stageAudit.getAuditGroupByListWithOwner(params.stage_id, params.stage_times);
 
             // console.log('encodeSignatureDataUri!');
@@ -351,6 +355,7 @@ module.exports = app => {
                 customDefine: rptTpl[JV.NODE_CUSTOM_DEFINE],
                 stageFlow,
                 customSelect,
+                waterMarkStr,
             };
         }
 
@@ -426,9 +431,13 @@ module.exports = app => {
                     page[JV.PROP_WATERMARK_CELLS] = [];
                 }
             }
+            let waterMarkStr = null;
             if (params.stage_status !== 3 && params.closeWatermark === 0) {
                 // 加水印
                 fillWaterMark(pageRstArr);
+                if (params.needWaterMark) {
+                    waterMarkStr = await getWatermarkPicData(pageRstArr[0], this.app.baseDir);
+                }
             }
             const stgAudit = await ctx.service.stageAudit.getStageAudit(params.stage_id, params.stage_times);
             const roleRel = (params.stage_status === 3) ? (await ctx.service.roleRptRel.getRoleRptRelByDetailIds(params.tender_id, params.rpt_ids, params.stage_id)) : [];
@@ -440,10 +449,10 @@ module.exports = app => {
             // console.log(roleRel);
             await encodeSignatureDataUri(roleRel, this.app.baseDir);
             for (const pageRst of pageRstArr) {
-                await encodeDummySignatureDataUri(pageRst, this.app.baseDir);
+                await encodeDummySignatureDataUri(pageRst, this.app.baseDir); // 注意草图数据量问题!
             }
             // console.log(pageRstArr);
-            ctx.body = { data: pageRstArr, signatureRelInfo: roleRel, stageAudit: stgAudit };
+            ctx.body = { data: pageRstArr, signatureRelInfo: roleRel, stageAudit: stgAudit, waterMarkStr };
             ctx.status = 201;
         }
 
@@ -492,10 +501,14 @@ module.exports = app => {
                     page[JV.PROP_WATERMARK_CELLS] = [];
                 }
             }
+            let waterMarkStr = null;
             if (params.stage_status !== 3 && params.closeWatermark === 0) {
             // if (params.stage_status !== 3 && params.closeWatermark === 0 && params.option === JV.PAGING_OPTION_NORMAL) {
                 // 加水印(注意:还得看用户设置是否需要加水印)
                 fillWaterMark(pageRstArr);
+                if (params.needWaterMark) {
+                    waterMarkStr = await getWatermarkPicData(pageRstArr[0], this.app.baseDir);
+                }
             }
 
             await this.ctx.helper.recursiveMkdirSync(this.app.baseDir + '/app/public/download');
@@ -519,7 +532,7 @@ module.exports = app => {
             }
             // fsUtil.writeObjToFile(pageRstArr, 'D:/GitHome/temp/计量导出pageArr.js');
             const uuidRst = await Promise.all(runnableRst);
-            ctx.body = { data: uuidRst };
+            ctx.body = { data: uuidRst, waterMarkStr };
             ctx.status = 201;
         }
 
@@ -548,10 +561,14 @@ module.exports = app => {
                     page[JV.PROP_WATERMARK_CELLS] = [];
                 }
             }
+            let waterMarkStr = null;
             if (params.stage_status !== 3 && params.closeWatermark === 0) {
             // if (params.stage_status !== 3 && params.closeWatermark === 0 && params.option === JV.PAGING_OPTION_NORMAL) {
                 // 加水印
                 fillWaterMark(pageRstArr);
+                if (params.needWaterMark) {
+                    waterMarkStr = await getWatermarkPicData(pageRstArr[0], this.app.baseDir);
+                }
             }
             await this.ctx.helper.recursiveMkdirSync(this.app.baseDir + '/app/public/download');
             const runnableRst = [];
@@ -598,7 +615,7 @@ module.exports = app => {
             }
             runnableRst.push(getExcelByPageData(pageRstArr, params.rptName, rptRoleRelArr));
             const uuidRst = await Promise.all(runnableRst);
-            ctx.body = { data: uuidRst };
+            ctx.body = { data: uuidRst, waterMarkStr };
             ctx.status = 201;
         }
 
@@ -1246,6 +1263,34 @@ async function encodeDummySignatureDataUri(pageRst, baseDir) {
                     }
                 }
             }
+            // 考虑优化,水印只需要一张即可,不需要每页都设置
+            // for (const signature of page[JV.PROP_WATERMARK_CELLS]) {
+            //     if (signature.path !== '') {
+            //         const filePath = baseDir + '/app' + signature.path;
+            //         try {
+            //             const res = await isFileExisted(filePath);
+            //             if (res) {
+            //                 const bData = fs.readFileSync(filePath);
+            //                 const base64Str = bData.toString('base64');
+            //                 const datauri = 'data:image/png;base64,' + base64Str;
+            //                 signature.pic = datauri;
+            //                 signature.path = '';
+            //             } else {
+            //                 console.log('文件不存在:' + filePath);
+            //             }
+            //         } catch (err) {
+            //             console.error(err);
+            //         }
+            //     }
+            // }
+        }
+    }
+}
+
+async function getWatermarkPicData(pageRst, baseDir) {
+    let rst = '';
+    if (pageRst) {
+        for (const page of pageRst.items) {
             for (const signature of page[JV.PROP_WATERMARK_CELLS]) {
                 if (signature.path !== '') {
                     const filePath = baseDir + '/app' + signature.path;
@@ -1254,9 +1299,8 @@ async function encodeDummySignatureDataUri(pageRst, baseDir) {
                         if (res) {
                             const bData = fs.readFileSync(filePath);
                             const base64Str = bData.toString('base64');
-                            const datauri = 'data:image/png;base64,' + base64Str;
-                            signature.pic = datauri;
-                            signature.path = '';
+                            rst = 'data:image/png;base64,' + base64Str;
+                            break;
                         } else {
                             console.log('文件不存在:' + filePath);
                         }
@@ -1264,9 +1308,11 @@ async function encodeDummySignatureDataUri(pageRst, baseDir) {
                         console.error(err);
                     }
                 }
+                if (rst !== null) break;
             }
         }
     }
+    return rst;
 }
 
 async function createExportRequestJob(ctx) {

+ 4 - 1
app/controller/revise_controller.js

@@ -991,6 +991,10 @@ module.exports = app => {
                         return await this._loadLastStagePosData(ctx);
                     }
                 case 'dealBills': return await ctx.service.dealBills.getAllDataByCondition({where: {tender_id: ctx.tender.id}});
+                case 'spec':
+                    const spec = {zlj: stdConst.zlj, jrg: stdConst.jrg};
+                    spec.zlj.deal_bills_tp = ctx.tender.info.deal_param.zanLiePrice;;
+                    return spec;
             }
         }
 
@@ -1021,7 +1025,6 @@ module.exports = app => {
             const renderData = {
                 revise,
                 jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.revise.gclCompare),
-                chapterFilter: stdConst.chapterFilter,
             };
             await this.layout('revise/gcl_compare.ejs', renderData);
         }

+ 175 - 1
app/controller/schedule_controller.js

@@ -77,12 +77,57 @@ module.exports = app => {
             const tender = ctx.tender;
             const schedule = await ctx.service.schedule.getDataByCondition({ tid: tender.id });
             const scheduleMonth = await ctx.service.scheduleMonth.getAllDataByCondition({ where: { tid: tender.id }, orders: [['yearmonth', 'asc']] });
+            const stageOrderList = await ctx.service.stage.getAllDataByCondition({ columns: ['id', 's_time', 'order'], where: { tid: tender.id } });
+            const scheduleStage = await ctx.service.scheduleStage.getAllDataByCondition({ where: { tid: tender.id }, orders: [['order', 'desc']] });
+            let curScheduleStage = scheduleStage.length > 0 ? _.maxBy(scheduleStage, 'order') : null;
+            let slmList = [];
+            let nextSlmList = [];
+            let endSlmList = [];
+            let yearSlmList = [];
+            let curYearStageData = [];
+            if (ctx.params.order && scheduleStage.length > 0) {
+                curScheduleStage = _.find(scheduleStage, { order: parseInt(ctx.params.order) });
+            }
+            if (curScheduleStage) {
+                const newSS = _.sortBy(scheduleStage, 'yearmonth');
+                const nowScheduleStage = _.findIndex(newSS, { yearmonth: curScheduleStage.yearmonth });
+                slmList = await ctx.service.scheduleLedgerMonth.getAllDataByCondition({ where: { tid: tender.id, yearmonth: curScheduleStage.yearmonth } });
+                const nextScheduleStage = nowScheduleStage >= 0 && nowScheduleStage + 1 <= newSS.length - 1 ? newSS[nowScheduleStage + 1] : null;
+                if (nextScheduleStage) nextSlmList = await ctx.service.scheduleLedgerMonth.getAllDataByCondition({ where: { tid: tender.id, yearmonth: nextScheduleStage.yearmonth } });
+                if (nowScheduleStage === 0) {
+                    endSlmList = slmList;
+                } else if (nowScheduleStage > 0) {
+                    const endYearmonthCollection = _.map(_.take(newSS, nowScheduleStage + 1), 'yearmonth');
+                    endSlmList = await ctx.service.scheduleLedgerMonth.getConllectionList(tender.id, endYearmonthCollection);
+                }
+                const yearConllection = _.map(_.filter(newSS, function(item) {
+                    return item.yearmonth.indexOf(curScheduleStage.yearmonth.split('-')[0]) !== -1;
+                }), 'yearmonth');
+                yearSlmList = await ctx.service.scheduleLedgerMonth.getConllectionList(tender.id, yearConllection);
+                // 获取本年完成计量数据
+                const curStage = _.find(stageOrderList, { order: curScheduleStage.order });
+                const stageList = _.filter(stageOrderList, function(item) {
+                    return item.s_time.indexOf(curStage.s_time.split('-')[0]) !== -1;
+                });
+                const stageIdList = _.map(_.filter(stageList, function(item) {
+                    return _.find(newSS, { order: item.order });
+                }), 'id');
+                curYearStageData = await ctx.service.stageBills.getStagesData(ctx.tender.id, stageIdList.join(','));
+            }
             const renderData = {
                 tender: tender.data,
                 tenderInfo: tender.info,
                 schedule,
                 scheduleMonth,
                 measureType,
+                stageOrderList,
+                scheduleStage,
+                curScheduleStage,
+                slmList,
+                nextSlmList,
+                endSlmList,
+                yearSlmList,
+                curYearStageData,
                 scheduleLedgerList: await this._getSelectedLedgerList(ctx),
                 jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.schedule.stageTp),
             };
@@ -93,12 +138,16 @@ module.exports = app => {
             const tender = ctx.tender;
             const schedule = await ctx.service.schedule.getDataByCondition({ tid: tender.id });
             const scheduleMonth = await ctx.service.scheduleMonth.getAllDataByCondition({ where: { tid: tender.id }, orders: [['yearmonth', 'asc']] });
+            const scheduleStage = await ctx.service.scheduleStage.getAllDataByCondition({ where: { tid: tender.id }, orders: [['order', 'desc']] });
+            const curScheduleStage = scheduleStage.length > 0 ? _.maxBy(scheduleStage, 'order') : null;
             const renderData = {
                 tender: tender.data,
                 tenderInfo: tender.info,
                 schedule,
                 scheduleMonth,
                 measureType,
+                scheduleStage,
+                curScheduleStage,
                 scheduleLedgerList: await this._getSelectedLedgerList(ctx),
                 jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.schedule.stageGcl),
             };
@@ -106,7 +155,66 @@ module.exports = app => {
         }
 
         /**
-         * 获取部位明细数据(Ajax)
+         * 获取金额模式下台账数据(Ajax)
+         *
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async loadTpLedgerData(ctx) {
+            try {
+                const tender = ctx.tender;
+                // const ledgerData = await ctx.controller.stage._getStageLedgerData(ctx);
+                // console.log(ledgerData);
+                const stageOrderList = await ctx.service.stage.getAllDataByCondition({ columns: ['id', 's_time', 'order'], where: { tid: tender.id } });
+                const scheduleStage = await ctx.service.scheduleStage.getAllDataByCondition({ where: { tid: tender.id }, orders: [['order', 'desc']] });
+                const curScheduleStage = _.find(scheduleStage, { order: parseInt(ctx.params.order) });
+                let slmList = [];
+                let nextSlmList = [];
+                let endSlmList = [];
+                let yearSlmList = [];
+                let curYearStageData = [];
+                if (curScheduleStage) {
+                    const newSS = _.sortBy(scheduleStage, 'yearmonth');
+                    const nowScheduleStage = _.findIndex(newSS, { yearmonth: curScheduleStage.yearmonth });
+                    slmList = await ctx.service.scheduleLedgerMonth.getAllDataByCondition({ where: { tid: tender.id, yearmonth: curScheduleStage.yearmonth } });
+                    const nextScheduleStage = nowScheduleStage >= 0 && nowScheduleStage + 1 <= newSS.length - 1 ? newSS[nowScheduleStage + 1] : null;
+                    if (nextScheduleStage) nextSlmList = await ctx.service.scheduleLedgerMonth.getAllDataByCondition({ where: { tid: tender.id, yearmonth: nextScheduleStage.yearmonth } });
+                    if (nowScheduleStage === 0) {
+                        endSlmList = slmList;
+                    } else if (nowScheduleStage > 0) {
+                        const endYearmonthCollection = _.map(_.take(newSS, nowScheduleStage + 1), 'yearmonth');
+                        endSlmList = await ctx.service.scheduleLedgerMonth.getConllectionList(tender.id, endYearmonthCollection);
+                    }
+                    const yearConllection = _.map(_.filter(newSS, function(item) {
+                        return item.yearmonth.indexOf(curScheduleStage.yearmonth.split('-')[0]) !== -1;
+                    }), 'yearmonth');
+                    yearSlmList = await ctx.service.scheduleLedgerMonth.getConllectionList(tender.id, yearConllection);
+                    // 获取本年完成计量数据
+                    const curStage = _.find(stageOrderList, { order: curScheduleStage.order });
+                    const stageList = _.filter(stageOrderList, function(item) {
+                        return item.s_time.indexOf(curStage.s_time.split('-')[0]) !== -1;
+                    });
+                    const stageIdList = _.map(_.filter(stageList, function(item) {
+                        return _.find(newSS, { order: item.order });
+                    }), 'id');
+                    curYearStageData = await ctx.service.stageBills.getStagesData(tender.id, stageIdList.join(','));
+                }
+                ctx.body = { err: 0, msg: '', data: {
+                    // ledgerData,
+                    slmList,
+                    nextSlmList,
+                    endSlmList,
+                    yearSlmList,
+                    curYearStageData,
+                } };
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: [] };
+            }
+        }
+
+        /**
+         * 获取台账数据(Ajax)
          *
          * @param ctx
          * @return {Promise<void>}
@@ -179,6 +287,72 @@ module.exports = app => {
                 ctx.body = { err: 1, msg: err.toString(), data: null };
             }
         }
+
+        /**
+         * 计量进度金额模式计算方式提交(Ajax)
+         *
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async saveStageTp(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const responseData = {
+                    err: 0,
+                    msg: '',
+                    data: {},
+                };
+                switch (data.type) {
+                    case 'add_stage':
+                        responseData.data = await ctx.service.scheduleStage.add(data.postData);
+                        break;
+                    case 'del_stage':
+                        responseData.data = await ctx.service.scheduleStage.del(data.postData);
+                        break;
+                    case 'reload_stage':
+                        responseData.data = await ctx.service.scheduleStage.changeOrder(data.postData);
+                        break;
+                    default: throw '参数有误';
+                }
+                ctx.body = responseData;
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+        }
+
+        /**
+         * 计量进度工程量模式计算方式提交(Ajax)
+         *
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async saveStageGcl(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const responseData = {
+                    err: 0,
+                    msg: '',
+                    data: {},
+                };
+                switch (data.type) {
+                    case 'add_stage':
+                        responseData.data = await ctx.service.scheduleMonth.addStageUsed(data.postData);
+                        break;
+                    case 'del_stage':
+                        responseData.data = await ctx.service.scheduleMonth.delStageUsed(data.postData);
+                        break;
+                    case 'ledger_edit':
+                        responseData.data = await ctx.service.scheduleLedgerMonth.saveSj(data.postData);
+                        break;
+                    default: throw '参数有误';
+                }
+                ctx.body = responseData;
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+        }
     }
 
     return ScheduleController;

+ 33 - 0
app/controller/setting_controller.js

@@ -13,6 +13,7 @@ const settingConst = require('../const/setting.js');
 const settingMenu = require('../../config/menu').settingMenu;
 const accountGroup = require('../const/account_group').group;
 const permission = require('../const/account_permission').permission;
+const projectLog = require('../const/project_log');
 
 module.exports = app => {
 
@@ -99,6 +100,7 @@ module.exports = app => {
 
                 // 过滤数据
                 ctx.service.projectAccount.searchFilter(ctx.request.query, projectId);
+                ctx.sort = ['id', 'desc'];
                 const total = await ctx.service.projectAccount.getCountWithBuilder();
 
                 // 获取数据规则
@@ -195,6 +197,7 @@ module.exports = app => {
                 // const rule = ctx.service.projectAccount.rule('updateUser');
                 // const frontRule = ctx.helper.validateConvert(rule);
                 const page = ctx.page;
+                ctx.sort = ['id', 'desc'];
                 const total = await ctx.service.projectAccount.count({ project_id: projectId });
                 // 获取项目用户列表
                 // const accountData = await ctx.service.projectAccount.getAllDataByCondition({
@@ -618,6 +621,9 @@ module.exports = app => {
                 if (!projectData) {
                     throw '没有对应的项目数据';
                 }
+                if (ctx.session.sessionUser.is_admin === 0) {
+                    throw '没有访问权限';
+                }
                 const showList = await ctx.service.settingShow.getList(projectData.page_path);
                 const renderData = { projectData, showList };
                 await this.layout('setting/show.ejs', renderData);
@@ -646,6 +652,33 @@ module.exports = app => {
                 ctx.body = { err: 1, msg: error.toString(), data: null };
             }
         }
+
+        async logs(ctx) {
+            try {
+                // 获取项目数据
+                const projectId = ctx.session.sessionProject.id;
+                const projectData = await ctx.service.project.getDataById(projectId);
+                if (projectData === null) {
+                    throw '没有对应的项目数据';
+                }
+                if (ctx.session.sessionUser.is_admin === 0) {
+                    throw '没有访问权限';
+                }
+                const settingType = ctx.params.type ? parseInt(ctx.params.type) : 0;
+                const logs = await ctx.service.projectLog.getLogs(projectId, settingType);
+                const renderData = {
+                    projectData,
+                    officeList,
+                    projectLog,
+                    settingType,
+                    logs,
+                };
+                await this.layout('setting/logs.ejs', renderData);
+            } catch (error) {
+                console.log(error);
+                ctx.redirect('/dashboard');
+            }
+        }
     }
 
     return SettingController;

+ 0 - 2
app/controller/spss_controller.js

@@ -87,7 +87,6 @@ module.exports = app => {
         }
 
         async _getStageData(tid, sorder) {
-            console.log(tid);
             const data = await this._getTzData(tid, true);
             const stage = await this._checkStage(tid, sorder);
             const bills = await this.ctx.service.stageBills.getAuditorStageData(tid, stage.id, stage.curTimes, stage.curOrder);
@@ -173,7 +172,6 @@ module.exports = app => {
         async loadCompareStage(ctx) {
             try {
                 const data = JSON.parse(ctx.request.body.data);
-                console.log(data);
                 const responseData = {err: 0, msg: '', data: {}};
                 responseData.data.tender1 = await this._getStageData(data.tid1, data.sorder1);
                 responseData.data.tender2 = await this._getStageData(data.tid2, data.sorder2);

+ 15 - 5
app/controller/stage_controller.js

@@ -324,12 +324,19 @@ module.exports = app => {
                             responseData.data.dealBills = await ctx.service.dealBills.getAllDataByCondition({
                                 where: { tender_id: this.ctx.tender.id },
                             });
+                            break;
                         case 'tag':
                             responseData.data.tags = await ctx.service.ledgerTag.getDatas(ctx.tender.id, ctx.stage.id);
+                            break;
                         case 'cooperation':
                             responseData.data.cooperation = await this.ctx.service.ledgerCooperation.getValidData(
                                 ctx.tender.id, ctx.session.sessionUser.accountId);
                             break;
+                        case 'spec':
+                            const spec = {zlj: JSON.parse(JSON.stringify(stdConst.zlj)), jrg: stdConst.jrg};
+                            spec.zlj.deal_bills_tp = ctx.tender.info.deal_param.zanLiePrice;
+                            responseData.data.spec = spec;
+                            break;
                     }
                 }
 
@@ -1178,7 +1185,6 @@ module.exports = app => {
                 [renderData.gclSpread, renderData.leafXmjSpread] = this._getGatherSpreadSetting();
 
                 renderData.jsFiles = this.app.jsFiles.common.concat(this.app.jsFiles.stage.gather);
-                renderData.chapterFilter = stdConst.chapterFilter;
                 await this.layout('stage/gather.ejs', renderData, 'stage/gather_modal.ejs');
             } catch (err) {
                 this.log(err);
@@ -1195,7 +1201,9 @@ module.exports = app => {
             try {
                 await this._getStageAuditViewData(ctx);
                 const renderData = await this._getDefaultRenderData(ctx);
-                if (ctx.stage.times !== ctx.stage.curTimes) {
+                if (ctx.stage.status === auditConst.status.checkNo && !ctx.stage.readOnly) {
+                    renderData.compareAuditors = ctx.stage.auditHistory[ctx.stage.curTimes - 2];
+                } else if (ctx.stage.times !== ctx.stage.curTimes) {
                     renderData.compareAuditors = ctx.stage.auditHistory[ctx.stage.curTimes - 1];
                 } else {
                     renderData.compareAuditors = ctx.stage.auditors;
@@ -1224,8 +1232,10 @@ module.exports = app => {
                 }
                 for (const order of data.roles) {
                     const data = { order, bills: [], pos: [] };
-                    data.bills = await ctx.service.stageBills.getAuditorStageData(ctx.tender.id, ctx.stage.id, ctx.stage.curTimes, order);
-                    data.pos = await ctx.service.stagePos.getAuditorStageData(ctx.tender.id, ctx.stage.id, ctx.stage.curTimes, order);
+                    const compareTimes = ctx.stage.status === auditConst.status.checkNo && !ctx.stage.readOnly
+                        ? ctx.stage.curTimes - 1 : ctx.stage.curTimes;
+                    data.bills = await ctx.service.stageBills.getAuditorStageData(ctx.tender.id, ctx.stage.id, compareTimes, order);
+                    data.pos = await ctx.service.stagePos.getAuditorStageData(ctx.tender.id, ctx.stage.id, compareTimes, order);
                     result.roles.push(data);
                 }
                 ctx.body = { err: 0, msg: '', data: result };
@@ -1503,7 +1513,7 @@ module.exports = app => {
             };
             let stream;
             try {
-                this._checkStageCanModifyRe(ctx);
+                // this._checkStageCanModifyRe(ctx);
                 stream = await ctx.getFileStream({ requireFile: false });
                 let fileData = {};
                 if (stream.filename !== undefined) {

+ 10 - 5
app/controller/stage_extra_controller.js

@@ -203,13 +203,13 @@ module.exports = app => {
                 const parts = ctx.multipart({ autoFields: true });
                 let index = 0;
                 const create_time = Date.parse(new Date()) / 1000;
-                let bonus;
-                while ((stream = await parts()) !== undefined) {
+                let stream = await parts();
+                const bonus = await ctx.service.stageBonus.getStageDataById(parts.field.bonus_id);
+                if (!bonus || bonus.sid !== ctx.stage.id) throw '该奖罚金,当前不允许上传附件';
+                while (stream !== undefined) {
                     if (!stream.filename) {
                         throw '未发现上传文件!';
                     }
-                    if (!bonus) bonus = await ctx.service.stageBonus.getStageDataById(parts.field.bonus_id);
-                    if (!bonus || bonus.sid !== ctx.stage.id) throw '该奖罚金,当前不允许上传附件';
 
                     const fileInfo = path.parse(stream.filename);
                     const dirName = 'app/public/upload/extra/' + moment().format('YYYYMMDD');
@@ -229,6 +229,11 @@ module.exports = app => {
                         in_time: moment(create_time * 1000).format('YYYY-MM-DD'),
                     });
                     ++index;
+                    if (Array.isArray(parts.field.size) && index < parts.field.size.length) {
+                        stream = await parts();
+                    } else {
+                        stream = undefined;
+                    }
                 }
                 const result = await ctx.service.stageBonus.updateDatas({
                     update: [
@@ -315,4 +320,4 @@ module.exports = app => {
     }
 
     return StageExtraController;
-};
+};

+ 1 - 0
app/controller/standard_lib_controller.js

@@ -34,6 +34,7 @@ module.exports = app => {
                         throw '查询的标准清单不存在';
                 }
             } catch (error) {
+                this.log(error);
                 responseData.err = 1;
                 responseData.msg = error;
             }

+ 2 - 2
app/controller/template_controller.js

@@ -25,8 +25,8 @@ module.exports = app => {
                 try {
                     let fileName;
                     switch (file) {
-                        case '导入分项清单EXCEL格式.xls':
-                            fileName = path.join(this.app.baseDir, 'app', 'public', 'files', 'template', 'ledger', '导入分项清单EXCEL格式.xls');
+                        case '导入分项清单EXCEL格式.xlsx':
+                            fileName = path.join(this.app.baseDir, 'app', 'public', 'files', 'template', 'ledger', '导入分项清单EXCEL格式.xlsx');
                             break;
                         case '导入工程量清单EXCEL格式.xls':
                             fileName = path.join(this.app.baseDir, 'app', 'public', 'files', 'template', 'ledger', '导入工程量清单EXCEL格式.xls');

+ 16 - 4
app/controller/tender_controller.js

@@ -164,12 +164,14 @@ module.exports = app => {
                 };
                 await this.layout(view, renderData, modal);
             } catch (err) {
+                console.log(err);
                 this.log(err);
                 this.ctx.redirect('/dashboard');
             }
         }
 
         async _list(view, renderData, modal = '', list_status = '') {
+            console.log(1);
             try {
                 renderData.tenderList = await this.ctx.service.tender.getList(list_status, renderData.userPermission);
 
@@ -229,6 +231,7 @@ module.exports = app => {
          * @return {void}
          */
         async listInfo(ctx) {
+            console.log('listInfo');
             this.jsFiles = this.app.jsFiles.tender.info;
             await this._listDetail('tender/info.ejs', 'tender/modal.ejs');
         }
@@ -240,6 +243,7 @@ module.exports = app => {
          * @return {Promise<void>}
          */
         async listProgress(ctx) {
+            console.log('listProgress');
             this.jsFiles = this.app.jsFiles.tender.progress;
             await this._listDetail('tender/progress.ejs', 'tender/modal.ejs');
         }
@@ -251,6 +255,7 @@ module.exports = app => {
          * @return {Promise<void>}
          */
         async listManage(ctx) {
+            console.log('listManager');
             this.jsFiles = this.app.jsFiles.tender.manage;
             // 先判断权限
             // 获取用户新建标段权利
@@ -518,12 +523,12 @@ module.exports = app => {
                 if (!tenderId) {
                     throw '当前未打开标段';
                 }
-                const tender = await ctx.service.tender.getTender(tenderId);
-                if (!tender) {
+                await ctx.service.tender.checkTender(tenderId);
+                if (!ctx.tender) {
                     throw '标段数据错误';
                 }
-                if (!tender.measure_type) {
-                    await ctx.service.tender.setTenderType(tender, parseInt(type));
+                if (!ctx.tender.measure_type) {
+                    await ctx.service.tender.setTenderType(ctx.tender, parseInt(type));
                 }
                 ctx.redirect('/tender/' + tenderId);
             } catch (error) {
@@ -966,6 +971,13 @@ module.exports = app => {
 
         async billsTag(ctx) {
             try {
+                if (ctx.stage) {
+                    if (ctx.stage.users.indexOf(this.ctx.session.sessionUser.accountId) < 0)
+                        throw '您无权进行该操作';
+                } else {
+                    if (ctx.tender.ledgerUsers.indexOf(this.ctx.session.sessionUser.accountId) < 0)
+                        throw '您无权进行该操作';
+                }
                 const data = JSON.parse(ctx.request.body.data);
                 const result = await ctx.service.ledgerTag.update(data);
                 ctx.body = { err: 0, msg: '', data: result };

+ 2 - 2
app/extend/context.js

@@ -11,5 +11,5 @@
 const moment = require('moment');
 
 module.exports = {
-    moment: moment,
-};
+    moment,
+};

+ 112 - 1
app/extend/helper.js

@@ -18,6 +18,9 @@ const Decimal = require('decimal.js');
 Decimal.set({ precision: 50, defaults: true });
 const SMS = require('../lib/sms');
 const WX = require('../lib/wechat');
+const timesLen = 100;
+const UAParser = require('ua-parser-js');
+const math = require('mathjs');
 
 module.exports = {
     _,
@@ -548,7 +551,6 @@ module.exports = {
         // 检查文件夹是否存在,不存在则直接创建文件夹
         const pathName = path.dirname(fileName);
         if (!fs.existsSync(pathName)) {
-            console.log('11111');
             await this.recursiveMkdirSync(pathName);
         }
         await fs.writeFileSync(fileName, buffer);
@@ -569,6 +571,14 @@ module.exports = {
         await this.saveBufferFile(buffer, fileName);
     },
 
+    async copyFileSync(source, target) {
+        const pathName = path.dirname(target);
+        if (!fs.existsSync(pathName)) {
+            await this.recursiveMkdirSync(pathName);
+        }
+        await fs.copyFileSync(source, target);
+    },
+
     /**
      * 检查code是否是指标模板数据
      * @param {String} code
@@ -1185,6 +1195,8 @@ module.exports = {
             },
         };
         for (const b of bills) {
+            if (!b.check_calc) continue;
+
             const checkData = {}, calcData = {};
             for (const f of field) {
                 checkData[f.tp] = b[f.tp] || 0;
@@ -1254,4 +1266,103 @@ module.exports = {
         const counts = (arr, value) => arr.reduce((a, v) => { return value.indexOf(v) !== -1 ? a + 1 : a + 0; }, 0);
         return counts(array, val);
     },
+
+    filterLastestData(data, keyFields) {
+        const dataIndex = {};
+        for (const d of data) {
+            let key = 'd';
+            for (const kf of keyFields) {
+                key = key + '.' + (d[kf] || '');
+            }
+
+            const di = dataIndex[key];
+            if (di) {
+                if ((di.times * timesLen + di.order) < (d.times * timesLen + d.order)) dataIndex[key] = d;
+            } else {
+                dataIndex[key] = d;
+            }
+        }
+        const result = [];
+        for (const prop in dataIndex) {
+            result.push(dataIndex[prop]);
+        }
+        return result;
+    },
+
+    calcExpr(expr) {
+        const validExpr = expr.replace('=', '').replace('%', '/100');
+        return math.eval(validExpr);
+    },
+
+    /**
+     * 创建登录日志
+     * @return {Boolean} 日志是否创建成功
+     * @param {Number} type - 登录类型
+     * @param {Number} status - 是否显示记录
+     */
+    async getUserIPMsg() {
+        const { ctx } = this;
+        const ip = ctx.request.ip ? ctx.request.ip : '';
+        const ipInfo = await this.getIpInfoFromApi(ip);
+        const parser = new UAParser(ctx.header['user-agent']);
+        const osInfo = parser.getOS();
+        const cpuInfo = parser.getCPU();
+        const browserInfo = parser.getBrowser();
+        const ipMsg = {
+            os: `${osInfo.name} ${osInfo.version} ${cpuInfo.architecture}`,
+            browser: `${browserInfo.name} ${browserInfo.version}`,
+            ip,
+            address: ipInfo,
+        };
+        return ipMsg;
+    },
+
+    /**
+     * 根据ip请求获取详细地址
+     * @param {String} a_ip - ip地址
+     * @return {String} 详细地址
+     */
+    async getIpInfoFromApi(a_ip = '') {
+        if (!a_ip) return '';
+        if (a_ip === '127.0.0.1' || a_ip === '::1' || a_ip.indexOf('192.168') !== -1) return '服务器本机访问';
+        const { ip = '', region = '', city = '', isp = '' } = await this.sendIpRequest(a_ip);
+        let address = '';
+        region && (address += region + '省');
+        city && (address += city + '市 ');
+        isp && (address += isp + ' ');
+        ip && (address += `(${ip})`);
+        return address;
+    },
+
+    /**
+     * 发送请求获取详细地址
+     * @param {String} ip - ip地址
+     * @return {Object} the result of request
+     * @private
+     */
+    async sendIpRequest(ip) {
+        return new Promise(resolve => {
+            this.ctx.curl(`https://api01.aliyun.venuscn.com/ip?ip=${ip}`, {
+                dateType: 'json',
+                encoding: 'utf8',
+                timeout: 2000,
+                headers: {
+                    Authorization: 'APPCODE 85c64bffe70445c4af9df7ae31c7bfcc',
+                },
+            }).then(({ status, data }) => {
+                if (status === 200) {
+                    const result = JSON.parse(data.toString()).data;
+                    if (!result.ip) {
+                        resolve({});
+                    } else {
+                        resolve(result);
+                    }
+                } else {
+                    resolve({});
+                }
+            }).catch(() => {
+                resolve({});
+            });
+        });
+    },
 };

+ 2 - 2
app/lib/bills_pos_convert.js

@@ -69,7 +69,7 @@ class BillsPosConvert {
 
     _convertXmj(data) {
         const xmj = this.resultTree.addData(data, ['ledger_id', 'ledger_pid', 'order', 'level', 'tender_id', 'full_path',
-            'code', 'name', 'unit', 'dgn_qty1', 'dgn_qty2', 'drawing_code', 'postil', 'memo', 'gxby_status', 'dagl_status']);
+            'code', 'name', 'unit', 'dgn_qty1', 'dgn_qty2', 'drawing_code', 'postil', 'memo', 'gxby_status', 'dagl_status', 'dagl_url']);
         return xmj;
     }
     // v1
@@ -125,7 +125,7 @@ class BillsPosConvert {
             for (const p of pos) {
                 const posUnit = xmj.unitTree.addNode({pos_name: p.name,
                     b_code: null, name: '', unit: null, unit_price: null,
-                    gxby_status: p.gxby_status, dagl_status: p.dagl_status});
+                    gxby_status: p.gxby_status, dagl_status: p.dagl_status, dagl_url: p.dagl_url});
                 if (p.drawing_code && posUnit.drawing_code.indexOf(p.drawing_code) < 0)
                     posUnit.drawing_code.push(p.drawing_code);
                 if (p.memo) posUnit.memo.push(p.memo);

+ 338 - 0
app/lib/gcl_gather.js

@@ -0,0 +1,338 @@
+'use strict';
+
+/**
+ *
+ * 清单汇总
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const mergeChar = ';';
+const Ledger = require('./ledger');
+
+const gclGatherModel = class {
+
+    /**
+     * 构造函数
+     * 
+     * @param {Object} ctx - egg 全局变量 
+     */
+    constructor(ctx) {
+        this.ctx = ctx;
+        this._ = ctx.helper._;
+        // mainData
+        this.billsTree = new Ledger.billsTree(this.ctx, {
+            id: 'ledger_id',
+            pid: 'ledger_pid',
+            order: 'order',
+            level: 'level',
+            rootId: -1,
+            keys: ['id', 'tender_id', 'ledger_id']
+        });
+        this.pos = new Ledger.pos({
+            id: 'id', ledgerId: 'lid', order: 'order'
+        });
+    }
+
+    /**
+     * 根据node新增工程量清单
+     * 
+     * @param {Object}} node 
+     * @returns {Object}
+     */
+    newGclNode(node) {
+        const gcl = {
+            id: this.gclList.length + 1,
+            b_code: node.b_code,
+            name: node.name,
+            unit: node.unit,
+            unit_price: node.unit_price,
+            leafXmjs: [],
+        };
+        this.gclList.push(gcl);
+        return gcl;
+    }
+
+    /**
+     * 获取node对应的工程量清单
+     * 
+     * @param {Object} node
+     * @returns {Object}  
+     */
+    getGclNode(node) {
+        const helper = this.ctx.helper;
+        const gcl = this.gclList.find(function (g) {
+            return g.b_code === node.b_code &&
+                (g.name || node.name ? g.name === node.name : true) &&
+                (g.unit || node.unit ? g.unit === node.unit : true) &&
+                helper.checkZero(helper.sub(g.unit_price, node.unit_price));
+        });
+        if (gcl) {
+            return gcl;
+        } else {
+            return this.newGclNode(node);
+        }
+    }
+
+    /**
+     * 检查 text 是否是Peg
+     * e.g. K123+000(true) Kab+123(false) K123.234+234(false) K12+324.234(true)
+     *
+     * @param text
+     * @returns {*}
+     * @constructor
+     */
+    CheckPeg(text) {
+        const pegReg = /[a-zA-Z]?[kK][0-9]+[++][0-9]{3}([.][0-9]+)?/;
+        return pegReg.test(text);
+    }
+
+    /**
+     * 基于node向上查找桩号节点(特别的,对于路基工程等,桩号节点应该在计量单元中)
+     * 
+     * @param {Object} node - 清单树节点 
+     */
+    getPegNode(node) {
+        if (node) {
+            if (this.CheckPeg(node.name)) {
+                return node;
+            } else {
+                const parent = this.billsTree.getParent(node);
+                return parent ? this.getPegNode(parent) : null;
+            }
+        }
+    }
+
+    /**
+     * 获取节点的第N层父节点
+     *
+     * @param node - 节点(检索起点)
+     * @param level - 第N层
+     * @returns {*}
+     */
+    getNodeByLevel(node, level) {
+        let cur = node;
+        while (cur && cur.level > level) {
+            cur = this.billsTree.getParent(cur);
+        }
+        return cur;
+    }
+
+    /**
+     * 获取 单位工程
+     *
+     * @param xmj - 计量单元(最底层项目节)
+     * @returns {string}
+     */
+    getDwgc(peg, xmj) {
+        if (peg) {
+            return peg.name;
+        } else {
+            const node = this.getNodeByLevel(xmj, 2);
+            return node ? node.name : '';
+        }
+    }
+
+    /**
+     * 获取 分部工程
+     *
+     * @param peg - 桩号节点
+     * @param xmj - 计量单元(最底层项目节)
+     * @returns {string}
+     */
+    getFbgc(peg, xmj) {
+        if (peg && peg.id !== xmj.id) {
+            const node = this.getNodeByLevel(xmj, peg.level + 1);
+            return node ? node.name : '';
+        } else {
+            const node = this.getNodeByLevel(xmj, 3);
+            return node ? node.name : '';
+        }
+    }
+
+    /**
+     * 获取 分项工程
+     *
+     * @param peg - 桩号节点
+     * @param xmj - 计量单元(最底层项目节)
+     * @returns {string}
+     */
+    getFxgc (peg, xmj) {
+        if (!peg) {
+            const node = this.getNodeByLevel(xmj, 4);
+            return node ? node.name : '';
+        } else if (peg.id === xmj.id) {
+            if (xmj.level > 4) {
+                let value = '';
+                for (let level = 4; level < xmj.level; level++) {
+                    const node = this.getNodeByLevel(xmj, level);
+                    value = value === '' ? node.name : value + mergeChar + node.name;
+                }
+                return value;
+            } else {
+                return '';
+            }
+        } else {
+            if (peg.level + 2 < xmj.level) {
+                let value = '';
+                for (let level = peg.level + 2; level < xmj.level; level++) {
+                    const node = this.getNodeByLevel(xmj, level);
+                    value = value === '' ? node.name : value + mergeChar + node.name;
+                }
+                return value;
+            } else {
+                return '';
+            }
+        }
+    }
+
+    /**
+     * 生成缓存数据(缓存仅为了不用每次都运算分部工程等)
+     * 
+     * @param {Object}} leafXmj - 清单树节点 
+     * @returns {Object} 最底层项目节缓存数据
+     */
+    newCacheLeafXmj(leafXmj) {
+        const peg = this.getPegNode(leafXmj);
+        const cacheLX = {
+            id: leafXmj.id,
+            code: leafXmj.code,
+            jldy: leafXmj.name,
+            fbgc: this.getFbgc(peg, leafXmj),
+            fxgc: this.getFxgc(peg, leafXmj),
+            dwgc: this.getDwgc(peg, leafXmj),
+            drawing_code: leafXmj.drawing_code,
+        };
+        this.leafXmjs.push(cacheLX);
+        return cacheLX;
+    }
+
+    /**
+     * 获取缓存数据(有缓存则直接读取,无则生成缓存)
+     * 
+     * @param {Object} leafXmj - 最底层项目节 
+     * @returns {Object} 最底层项目缓存数据
+     */
+    getCacheLeafXmj(leafXmj) {
+        const cacheLX = this.leafXmjs.find(lx => { return lx.id === leafXmj.id; });
+        if (!cacheLX) {
+            return this.newCacheLeafXmj(leafXmj);
+        } else {
+            return cacheLX;
+        }
+    }
+
+    /**
+     * 汇总工程量清单数据
+     * 
+     * @param {Object} node 最底层工程量清单树节点 
+     * @param {*} leafXmj node所属的最底层项目节
+     */
+    loadGatherGclNode(node, leafXmj) {
+        const helper = this.ctx.helper;
+        const gcl = this.getGclNode(node);
+        for (const prop in node) {
+            if (prop === 'quantity' || prop === 'total_price' || prop.indexOf('qty') >= 0 || prop.indexOf('tp') >= 0) {
+                gcl[prop] = this.ctx.helper.add(gcl[prop], node[prop]);
+            }
+        }
+        const cacheLeafXmj = this.getCacheLeafXmj(leafXmj);
+        const posRange = this.pos.getLedgerPos(node.id);
+
+        const loadLeafXmj = function (detail, calcSource) {
+            const dx = helper._.assign({}, cacheLeafXmj);
+            dx.gcl_id = gcl.id;
+            if (detail.name !== node.name) {
+                dx.bwmx = detail.name;
+                dx.mx_id = detail.id;
+            }
+            if (detail.drawing_code) {
+                dx.drawing_code = detail.drawing_code;
+            }
+            for (const prop in calcSource) {
+                if (prop === 'quantity' || prop.indexOf('qty') > 0) dx[prop] = calcSource[prop];
+            }
+            gcl.leafXmjs.push(dx);
+        };
+
+        if (posRange && posRange.length > 0) {
+            for (const pr of posRange) {
+                loadLeafXmj(pr, pr);
+            }
+        } else {
+            loadLeafXmj(leafXmj, node);
+        }
+    }
+
+    /**
+     * 递归生成工程量清单汇总数据
+     * 
+     * @param {Array<Object>} nodes 清单子节点
+     * @param {Object} leafXmj 最底层项目节 
+     */
+    recursiveGatherGclData(nodes, leafXmj) {
+        for (const node of nodes) {
+            if (node.b_code) {
+                if (node.children.length > 0) {
+                    this.recursiveGatherGclData(node.children, leafXmj);
+                } else {
+                    this.loadGatherGclNode(node, leafXmj);
+                }
+            } else if (node.children.length > 0) {
+                this.recursiveGatherGclData(node.children, node);
+            }
+        }
+    }
+
+    gatherDealBillsData(deal) {
+        if (deal && deal.length > 0) {
+            for (const node of deal) {
+                node.b_code = node.code;
+                const gcl = this.getGclNode(node);
+                if (!node.quantity || !node.unit_price) continue;
+                gcl.deal_bills_qty = node.quantity;
+                gcl.deal_bills_tp = node.total_price;
+            }
+        }
+    }
+
+    convertResultData() {
+        this.leafXmjs = [];
+        for (const gcl of this.gclList) {
+            if (gcl.leafXmjs.length === 0) return;
+            for (const lx of gcl.leafXmjs) {
+                this.leafXmjs.push(lx);
+            }
+        }
+    }
+
+    /**
+     * 汇总
+     * 
+     * @param {Array<Object>} bills 清单数据 
+     * @param {Array<Object>} pos 计量单元数据
+     */
+    gather(bills, pos, deal) {
+        const helper = this.ctx.helper;
+        this.billsTree.loadDatas(bills);
+        this.pos.loadDatas(pos);
+
+        this.gclList = [];
+        this.leafXmjs = [];
+
+        this.recursiveGatherGclData(this.billsTree.children, null);
+        this.gatherDealBillsData(deal);
+        this.gclList.sort(function (a, b) {
+            return helper.compareCode(a.b_code, b.b_code);
+        });
+
+        this.convertResultData();
+        return [this.gclList, this.leafXmjs];
+    }
+};
+
+module.exports = {
+    gclGather: gclGatherModel,
+};

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 473 - 289
app/lib/rpt_data_analysis.js


+ 71 - 0
app/lib/stage_im.js

@@ -242,6 +242,72 @@ class StageIm {
             return result;
         }
     }
+    /**
+     * 获取 单位工程
+     *
+     * @param xmj - 计量单元(最底层项目节)
+     * @returns {string}
+     */
+    _getDwgc(peg, xmj) {
+        if (peg) {
+            return peg.name;
+        } else {
+            const node = this._getNodeByLevel(xmj, 2);
+            return node ? node.name : '';
+        }
+    }
+    /**
+     * 获取 分部工程
+     *
+     * @param peg - 桩号节点
+     * @param xmj - 计量单元(最底层项目节)
+     * @returns {string}
+     */
+    _getFbgc(peg, xmj) {
+        if (peg && peg.id !== xmj.id) {
+            const node = this._getNodeByLevel(xmj, peg.level + 1);
+            return node ? node.name : '';
+        } else {
+            const node = this._getNodeByLevel(xmj, 3);
+            return node ? node.name : '';
+        }
+    }
+    /**
+     * 获取 分项工程
+     *
+     * @param peg - 桩号节点
+     * @param xmj - 计量单元(最底层项目节)
+     * @returns {string}
+     */
+    _getFxgc(peg, xmj) {
+        if (!peg) {
+            const node = this._getNodeByLevel(xmj, 4);
+            return node ? node.name : '';
+        } else if (peg.id === xmj.id) {
+            if (xmj.level > 4) {
+                let value = '';
+                for (let level = 4; level < xmj.level; level++) {
+                    const node = this._getNodeByLevel(xmj, level);
+                    value = value === '' ? node.name : value + mergeChar + node.name;
+                }
+                return value;
+            } else {
+                return '';
+            }
+        } else {
+            if (peg.level + 2 < xmj.level) {
+                let value = '';
+                for (let level = peg.level + 2; level < xmj.level; level++) {
+                    const node = this._getNodeByLevel(xmj, level);
+                    value = value === '' ? node.name : value + mergeChar + node.name;
+                }
+                return value;
+            } else {
+                return '';
+            }
+        }
+    }
+
 
     _checkCustomDetail(im) {
         const self = this;
@@ -457,6 +523,7 @@ class StageIm {
             pre_jl: node.pre_gather_tp, pre_contract_jl: node.pre_contract_tp, pre_qc_jl: node.pre_qc_tp,
             end_jl: node.end_gather_tp, end_contract_jl: node.end_contract_tp, end_qc_jl: node.end_qc_tp,
             peg: peg ? this._getPegStr(peg.name) : '', drawing_code: this._getDrawingCode(node),
+            dwgc: this._getDwgc(peg, node), fbgc: this._getFbgc(peg, node), fxgc: this._getFxgc(peg, node),
             position: '',
             lIndex: nodeIndex,
         };
@@ -564,6 +631,7 @@ class StageIm {
             lIndex: index,
             bw: bw, jldy: node.name,
             changes: [], gclBills: [],
+            dwgc: this._getDwgc(peg, node), fbgc: this._getFbgc(peg, node), fxgc: this._getFxgc(peg, node),
         };
         this._checkCustomDetail(im);
         return im;
@@ -746,6 +814,7 @@ class StageIm {
                     peg: peg ? this._getPegStr(peg.name) : '',
                     position: '',
                     lIndex: nodeIndex,
+                    dwgc: this._getDwgc(peg, node), fbgc: this._getFbgc(peg, node), fxgc: this._getFxgc(peg, node),
                 };
                 if (this.ctx.stage.im_gather && node.check) {
                     im.check = true;
@@ -817,6 +886,7 @@ class StageIm {
                         end_jl: pp.end_gather_qty, end_contract_jl: pp.end_contract_qty, end_qc_jl: pp.end_qc_qty,
                         bw,
                         peg: this._checkPeg(pp.name) ? this._getPegStr(pp.name) : (peg ? this._getPegStr(peg.name) : ''),
+                        dwgc: this._getDwgc(peg, node), fbgc: this._getFbgc(peg, node), fxgc: this._getFxgc(peg, node),
                         xm: pp.name, jldy: pp.name,
                         drawing_code: pp.drawing_code,
                         changes: [],
@@ -859,6 +929,7 @@ class StageIm {
                     end_tp: p.end_gather_tp, end_contract_tp: p.end_contract_tp, end_qc_tp: p.end_qc_tp,
                     bw,
                     peg: peg ? this._getPegStr(peg.name) : '',
+                    dwgc: this._getDwgc(peg, node), fbgc: this._getFbgc(peg, node), fxgc: this._getFxgc(peg, node),
                     xm: node.name,
                     drawing_code: this._getDrawingCode(p),
                     changes: [],

+ 1 - 1
app/middleware/revise_audit_check.js

@@ -29,7 +29,7 @@ module.exports = options => {
             if ((revise.status === status.uncheck || revise.status === status.checkNo) && this.tender.info.shenpi.revise !== shenpiConst.sp_status.sqspr) {
                 const shenpi_status = this.tender.info.shenpi.revise;
                 // 进一步比较审批流是否与审批流程设置的相同,不同则替换为固定审批流或固定的终审
-                const auditList = yield this.service.reviseAudit.getAllDataByCondition({ where: { tender_id: this.tender.id, times: revise.times } });
+                const auditList = yield this.service.reviseAudit.getAllDataByCondition({ where: { rid: revise.id, times: revise.times } });
                 const auditIdList = _.map(auditList, 'audit_id');
                 if (shenpi_status === shenpiConst.sp_status.gdspl) {
                     const shenpiList = yield this.service.shenpiAudit.getAllDataByCondition({ where: { tid: this.tender.id, sp_type: shenpiConst.sp_type.revise, sp_status: shenpi_status } });

+ 1 - 0
app/middleware/stage_check.js

@@ -63,6 +63,7 @@ module.exports = options => {
             const accountId = this.session.sessionUser.accountId,
                 auditorIds = _.map(stage.auditors, 'aid'),
                 shareIds = [];
+            stage.users = stage.status === status.uncheck ? [stage.user_id] : [stage.user_id, ...auditorIds];
             const permission = this.session.sessionUser.permission;
             if (accountId === stage.user_id) { // 原报
                 if (stage.curAuditor) {

+ 3 - 2
app/middleware/tender_check.js

@@ -71,6 +71,7 @@ module.exports = options => {
             tender.ledgerReadOnly = this.session.sessionUser.accountId !== tender.data.user_id ||
                 tender.data.ledger_status === auditConst.status.checking || tender.data.ledger_status === auditConst.status.checked;
             tender.advanceAuditorsId = advanceAuditorsId;
+            tender.ledgerUsers = tender.ledger_status === auditConst.status.uncheck ? [tender.data.user_id] : [tender.data.user_id, ...auditorsId];
             this.tender = tender;
             this.session.sessionProject.page_show = yield this.service.project.getPageshow(this.session.sessionProject.id);
             yield next;
@@ -93,9 +94,9 @@ module.exports = options => {
             }
             if (this.helper.isAjax(this.request)) {
                 if (err.stack) {
-                    this.body = {err: 2, msg: '标段数据未知错误', data: null};
+                    this.body = {err: 4, msg: '标段数据未知错误', data: null};
                 } else {
-                    this.body = {err: 1, msg: err.toString(), data: null};
+                    this.body = {err: 3, msg: err.toString(), data: null};
                 }
             } else {
                 if (this.helper.isWap(this.request)) {

+ 2 - 2
app/middleware/uncheck_tender_check.js

@@ -49,9 +49,9 @@ module.exports = options => {
             }
             if (this.helper.isAjax(this.request)) {
                 if (err.stack) {
-                    this.body = {err: 2, msg: '标段数据未知错误', data: null};
+                    this.body = {err: 4, msg: '标段数据未知错误', data: null};
                 } else {
-                    this.body = {err: 1, msg: err.toString(), data: null};
+                    this.body = {err: 3, msg: err.toString(), data: null};
                 }
             } else {
                 if (this.helper.isWap(this.request)) {

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

@@ -51,6 +51,15 @@ font-size: .875rem;
 .input-group-text .group-checkbox[type="checkbox"],.input-group-text .group-checkbox[type="radio"]{
   margin-top: .3rem;
 }
+.custom-control-label::before {
+  top:.1rem;
+}
+.custom-control-label::after{
+  top:.1rem;
+}
+.custom-switch .custom-control-label::after{
+  top:.25rem;
+}
 .custom-control {
   min-height: 1.2rem
 }
@@ -73,12 +82,6 @@ font-size: .875rem;
 .custom-control-warning-label{
   color:#da9500;
 }
-.custom-control-label::before{
-  top:.15rem;
-}
-.custom-control-label::after{
-  top:.15rem;
-}
 /*
 .btn.disabled, .btn:disabled {
   opacity:.4
@@ -1086,9 +1089,6 @@ legend {
   padding:.2em .4em;
   top:0;
 }
-.custom-switch .custom-control-label::after{
-  top:.25rem;
-}
 .search-group {
   position: relative;
 }

+ 10 - 10
app/public/css/main_s.css

@@ -51,6 +51,15 @@ font-size: .875rem;
 .input-group-text .group-checkbox[type="checkbox"],.input-group-text .group-checkbox[type="radio"]{
   margin-top: .3rem;
 }
+.custom-control-label::before {
+  top:.1rem;
+}
+.custom-control-label::after{
+  top:.1rem;
+}
+.custom-switch .custom-control-label::after{
+  top:.25rem;
+}
 .custom-control {
   min-height: 1.2rem
 }
@@ -68,17 +77,11 @@ font-size: .875rem;
 }
 .custom-control-warning-input:checked ~ .custom-control-warning-label::before{
   border-color:#da9500 ;
-  background-color:#da9500 
+  background-color:#da9500
 }
 .custom-control-warning-label{
   color:#da9500;
 }
-.custom-control-label::before{
-  top:.15rem;
-}
-.custom-control-label::after{
-  top:.15rem;
-}
 /*
 .btn.disabled, .btn:disabled {
   opacity:.4
@@ -1089,9 +1092,6 @@ legend {
   padding:.2em .4em;
   top:0;
 }
-.custom-switch .custom-control-label::after{
-  top:.25rem;
-}
 .search-group {
   position: relative;
 }

BIN
app/public/files/template/ledger/导入分项清单EXCEL格式.xlsx


+ 10 - 5
app/public/js/advance_audit.js

@@ -335,15 +335,20 @@ $(document).ready(function () {
     })
     function handleFileList(files = []) {
         $('#file-content').empty()
-        const { uncheck, checkNo } = auditConst.status
+        // const { uncheck, checkNo } = auditConst.status
         const newFiles = files.map(file => {
             let showDel = false;
             if (file.uid === cur_uid) {
-                if (!curAuditor) {
-                    advance.status === uncheck && cur_uid === advance.uid && (showDel = true)
-                    advance.status === checkNo && cur_uid === advance.uid && (showDel = true)
+                // if (!curAuditor) {
+                //     advance.status === uncheck && cur_uid === advance.uid && (showDel = true)
+                //     advance.status === checkNo && cur_uid === advance.uid && (showDel = true)
+                // } else {
+                //     curAuditor.audit_id === cur_uid && (showDel = true)
+                // }
+                if (advance.status === auditConst.status.checked) {
+                    showDel = Boolean(file.extra_upload )
                 } else {
-                    curAuditor.audit_id === cur_uid && (showDel = true)
+                    showDel = true
                 }
             }
             return {...file, showDel}

+ 80 - 9
app/public/js/change_information_set.js

@@ -16,10 +16,34 @@ function sortByCode(a, b) {
     for (let i = 0; i < code1length; i ++) {
         if (i+1 <= code2length) {
             if (code1[i] != code2[i]) {
-                if (!/^\d+$/.test(code1[i])) {
-                    return code1[i].charCodeAt() - code2[i].charCodeAt();
-                } else {
+                if (/^\d+$/.test(code1[i]) && /^\d+$/.test(code2[i])) {
                     return parseInt(code1[i]) - parseInt(code2[i]);
+                } else if (!/^\d+$/.test(code1[i]) && /^\d+$/.test(code2[i])) {
+                    return 1;
+                } else if (/^\d+$/.test(code1[i]) && !/^\d+$/.test(code2[i])) {
+                    return -1;
+                } else {
+                    const str1length = code1[i].length;
+                    const str2length = code2[i].length;
+                    for (let j = 0; j < str1length; j++) {
+                        if (j+1 <= str2length) {
+                            if (code1[i].charAt(j) != code2[i].charAt(j)) {
+                                return code1[i].charAt(j).charCodeAt() - code2[i].charAt(j).charCodeAt();
+                            }  else if (j+1 == str1length && code1[i].charAt(j) == code2[i].charAt(j)) {
+                                if (str1length == str2length) {
+                                    return 0;
+                                } else {
+                                    return str1length - str2length;
+                                }
+                            }
+                        } else {
+                            if (j+1 >= str1length) {
+                                return 1;
+                            } else {
+                                return -1;
+                            }
+                        }
+                    }
                 }
             } else if (i+1 == code1length && code1[i] == code2[i]) {
                 if (code1length == code2length) {
@@ -176,10 +200,11 @@ $(document).ready(() => {
         resetXmjSpread: function(data = null) {
             const xmj = [];
             if (data && data.lid != 0 && data.xmj_code !== '' && data.xmj_code !== null) {
-                if (data.bwmx === data.xmj_jldy) {
-                    data.bwmx = '';
+                const newData = JSON.parse(JSON.stringify(data));
+                if (newData.bwmx === newData.xmj_jldy) {
+                    newData.bwmx = '';
                 }
-                xmj.push(data);
+                xmj.push(newData);
             }
             SpreadJsObj.loadSheetData(xmjSpread.getActiveSheet(), SpreadJsObj.DataType.Data, xmj);
         },
@@ -369,6 +394,7 @@ $(document).ready(() => {
             }
         }
         changeListData = gclGatherData.concat(dealBillList).sort(sortByCode);
+        console.log(changeListData);
         // 先加载台账数据
         let listHtml = '';
         let list_index = 1;
@@ -398,6 +424,41 @@ $(document).ready(() => {
         SpreadJsObj.initSpreadSettingEvents(changeSpreadSetting, changeCol);
         SpreadJsObj.initSheet(changeSpreadSheet, changeSpreadSetting);
         SpreadJsObj.loadSheetData(changeSpreadSheet, SpreadJsObj.DataType.Data, changeList);
+        // changeSpreadSheet.options.protectionOptions = {
+        //     allowSort: true,
+        //     allowFilter: true
+        // };
+        // var option = changeSpreadSheet.options.protectionOptions;
+        // changeSpreadSheet.rowFilter(new GC.Spread.Sheets.Filter.HideRowFilter(new GC.Spread.Sheets.Range(-1, 0, -1, changeSpreadSetting.cols.length)));
+        // // changeSpreadSheet.rowFilter(new GC.Spread.Sheets.Filter.HideRowFilter(new GC.Spread.Sheets.Range(-1, 0, -1, 3)));
+        // const filter = changeSpreadSheet.rowFilter();
+        // filter.filterButtonVisible(false);
+        // filter.filterButtonVisible(0, true);
+        // filter.filterButtonVisible(2, true);
+        // filter.filterDialogVisibleInfo({
+        //     sortByValue: true,         //SortByValue item is visible.
+        //     sortByColor: false,          //SortByColor item is visible.
+        //     filterByColor: false,        //FilterByColor item is visible.
+        //     filterByValue: false,        //FilterByValue item is visible.
+        //     listFilterArea: false       //ListFilterArea item is visible.
+        // });
+        // function compareList(obj1, obj2) {
+        //     console.log(obj1, obj2);
+        //     var list = ["", '204-1-b', '合计'];
+        //     var index1 = list.indexOf(obj1), index2 = list.indexOf(obj2);
+        //     if (index1 > index2) {
+        //         return 1;
+        //     } else if (index1 < index2) {
+        //         return -1;
+        //     } else {
+        //         return 0;
+        //     }
+        // }
+        // changeSpreadSheet.sortRange(0, 0, changeSpreadSetting.cols.length, 1, true, [{index: 0, ascending: true, compareFunction: compareList}]);
+        // changeSpreadSheet.bind(GC.Spread.Sheets.Events.RangeSorting, function (e, info) {
+        //     info.compareFunction = compareList;
+        // });
+        // filter.sortColumn(0, true);
         changeSpreadObj.makeSjsFooter();
         changeSpreadObj.resetXmjSpread(SpreadJsObj.getSelectObject(changeSpreadSheet));
     });
@@ -745,6 +806,7 @@ function tableDataRemake(changeListData) {
     $('#code-list').html('');
     // 根据已添加的清单显示
     if (changeList.length > 0 && changeList[0]) {
+        const removeList = [];
         for (const [index,clinfo] of changeList.entries()) {
             if (clinfo.lid != 0) {
                 let listinfo = changeListData.find(function (item) {
@@ -755,7 +817,8 @@ function tableDataRemake(changeListData) {
                     listinfo = changeListData[clinfo.lid - 1];
                     if (listinfo === undefined) {
                         toastr.warning('台账清单列表已不存在'+ clinfo.code +',已更新变更清单列表');
-                        changeList.splice(index, 1);
+                        // changeList.splice(index, 1);
+                        removeList.push(clinfo);
                         continue;
                     }
                     $('#table-list-select tr[data-index="'+ clinfo.lid +'"]').addClass('table-success');
@@ -774,7 +837,8 @@ function tableDataRemake(changeListData) {
                                 (leafInfo.bwmx !== undefined ? leafInfo.bwmx : (leafInfo.jldy !== undefined ? leafInfo.jldy : '')) + '*;*' + (leafInfo.quantity !== null ? leafInfo.quantity : 0);
                         } else {
                             toastr.warning('台账清单列表已不存在'+ clinfo.code +',已更新变更清单列表');
-                            changeList.splice(index, 1);
+                            // changeList.splice(index, 1);
+                            removeList.push(clinfo);
                             continue;
                         }
                     } else {
@@ -804,7 +868,8 @@ function tableDataRemake(changeListData) {
                                 (leafInfo.bwmx !== undefined ? leafInfo.bwmx : (leafInfo.jldy !== undefined ? leafInfo.jldy : '')) + '*;*' + (leafInfo.quantity !== null ? leafInfo.quantity : 0);
                         } else {
                             toastr.warning('台账清单列表已不存在'+ clinfo.code +',已更新变更清单列表');
-                            changeList.splice(index, 1);
+                            // changeList.splice(index, 1);
+                            removeList.push(clinfo);
                             continue;
                         }
                     } else {
@@ -821,6 +886,12 @@ function tableDataRemake(changeListData) {
                 }
             }
         }
+        if(removeList.length > 0) {
+            _.pullAll(changeList, removeList);
+            postData(window.location.pathname + '/save', { type:'remove_list', updateData: removeList }, function (result) {
+            }, function () {
+            });
+        }
     }
 }
 

+ 45 - 33
app/public/js/gcl_gather.js

@@ -420,8 +420,8 @@ const gclGatherModel = (function () {
         }
     }
 
-    function _getCalcChapter(chapter) {
-        const gclChapter = [], otherChapter = [];
+    function _getCalcChapter(chapter, option) {
+        const gclChapter = [], otherChapter = {}, gclChapterFilter = [];
         let serialNo = 1;
         for (const c of chapter) {
             const cc = { code: c.code, name: c.name, cType: 1 };
@@ -429,11 +429,23 @@ const gclGatherModel = (function () {
             cc.filter = '^[^0-9]*' + c.code.substr(0, c.code.length - 2) + '[0-9]{2}-';
             gclChapter.push(cc);
         }
-        gclChapter.push({ name: '未计入章节清单合计', cType: 21, serialNo: serialNo++, });
-        otherChapter.push({ name: '清单小计(A)', cType: 11, serialNo: serialNo++ });
-        otherChapter.push({ name: '非清单项费用(B)', cType: 31, serialNo: serialNo++ });
-        otherChapter.push({ name: '合计(C=A+B)', cType: 41, serialNo: serialNo });
-        return [gclChapter, otherChapter];
+        gclChapter.push({ name: '未计入章节清单合计', cType: 21, serialNo: serialNo+1 });
+
+        otherChapter.hj = { name: '合计(C=A+B+Z)', cType: 41, serialNo: serialNo+5, deal_bills_tp: option.zlj.deal_bills_tp };
+        gclChapterFilter.push({node_type: option.jrg.value});
+        gclChapterFilter.push({field: 'name', part: option.jrg.text});
+        const zlChapter = {
+            name: '暂列金额(Z)', cType: 32, serialNo: serialNo+4,
+            deal_bills_tp: option.zlj.deal_bills_tp, match: [], matchPath: []
+        };
+        zlChapter.match.push({node_type: option.zlj.value});
+        zlChapter.match.push({field: 'name', part: option.zlj.text});
+        otherChapter.zlj = zlChapter;
+
+        otherChapter.qd = { name: '清单小计(A)', cType: 11, serialNo: serialNo+2 };
+        otherChapter.fqd = { name: '非清单项费用(B)', cType: 31, serialNo: serialNo+3 };
+
+        return [gclChapter, otherChapter, gclChapterFilter];
     }
 
     function _gatherChapterFields(chapter, data, fields) {
@@ -466,52 +478,52 @@ const gclGatherModel = (function () {
         return false;
     }
 
-    function gatherChapterData(chapter, fields, filter = []) {
-        const filterPath = [];
-        const checkFilterPath = function (data) {
+    function gatherChapterData(chapter, option, fields) {
+        const chapterFilterPath = [];
+        const checkFilterPath = function (data, filterPath) {
             for (const fp of filterPath) {
                 if (data.full_path.indexOf(fp + '-') === 0 || data.full_path === fp) return true;
             }
             return false;
         };
 
-        const [gclChapter, otherChapter] = _getCalcChapter(chapter, filter);
+        const [gclChapter, otherChapter, gclChapterFilter] = _getCalcChapter(chapter, option);
         for (const d of gsTree.nodes) {
-            if (_checkFilter(d, filter)) {
-                filterPath.push(d.full_path);
-            }
+            if (_checkFilter(d, gclChapterFilter)) chapterFilterPath.push(d.full_path);
+            if (_checkFilter(d, otherChapter.zlj.match)) otherChapter.zlj.matchPath.push(d.full_path);
             if (d.children && d.children.length > 0) continue;
 
-            for (const c of otherChapter) {
-                if (c.cType === 41) {
-                    gatherfields(c, d, fields);
+            if (checkFilterPath(d,otherChapter.zlj.matchPath)) {
+                gatherfields(otherChapter.zlj, d, fields);
+                gatherfields(otherChapter.hj, d, fields);
+            } else {
+                gatherfields(otherChapter.hj, d, fields);
+                if (d.b_code) {
+                    gatherfields(otherChapter.qd, d, fields);
                 }
-                if (c.cType === 11 && (d.b_code)) {
-                    gatherfields(c, d, fields);
+                if (!d.b_code || d.b_code === '') {
+                    gatherfields(otherChapter.fqd, d, fields);
                 }
-                if (c.cType === 31 && (!d.b_code || d.b_code === '')) {
+
+                if (d.b_code) {
+                    const c = checkFilterPath(d, chapterFilterPath)
+                        ? gclChapter.find(x => { return x.cType === 21})
+                        : _getGclChapter(gclChapter, d);
                     gatherfields(c, d, fields);
                 }
             }
-
-            if (d.b_code) {
-                const c = checkFilterPath(d)
-                    ? gclChapter.find(x => { return x.cType === 21})
-                    : _getGclChapter(gclChapter, d);
-                gatherfields(c, d, fields);
-            }
         }
         for (const d of deal) {
             if (!d.quantity || !d.unit_price) continue;
-            for (const c of otherChapter) {
-                if (c.cType === 41 || c.cType === 11) {
-                    c.deal_bills_tp = ZhCalc.add(c.deal_bills_tp, d.total_price);
-                }
-            }
+            otherChapter.hj.deal_bills_tp = ZhCalc.add(otherChapter.hj.deal_bills_tp, d.total_price);
+            otherChapter.qd.deal_bills_tp = ZhCalc.add(otherChapter.qd.deal_bills_tp, d.total_price);
             const c = _getGclChapter(gclChapter, d);
             c.deal_bills_tp = ZhCalc.add(c.deal_bills_tp, d.total_price);
         }
-        return gclChapter.concat(otherChapter);
+
+        const result = gclChapter.concat([otherChapter.hj, otherChapter.zlj, otherChapter.qd, otherChapter.fqd]);
+        result.sort((x, y) => {return x.serialNo - y.serialNo});
+        return result;
     }
 
     return {

+ 19 - 86
app/public/js/ledger.js

@@ -1218,11 +1218,10 @@ $(document).ready(function() {
     //     const $obj = $('<div>' + item.name +)
     // };
     // 右键菜单
-    let addTagShare = true;
+    const addTag = newTag({ledgerSheet: ledgerSpread.getActiveSheet(), billsTag});
     const billsContextMenuOptions = {
         selector: '#ledger-spread',
         build: function ($trigger, e) {
-            addTagShare = true;
             const target = SpreadJsObj.safeRightClickSelection($trigger, e, ledgerSpread);
             return target.hitTestType === spreadNS.SheetArea.viewport || target.hitTestType === spreadNS.SheetArea.rowHeader;
         },
@@ -1295,7 +1294,7 @@ $(document).ready(function() {
                 return !readOnly;
             }
         };
-        billsContextMenuOptions.items.sprBase = '-----------';
+        billsContextMenuOptions.items.sprBase = '----';
     }
     billsContextMenuOptions.items.copyBlock = {
         name: '复制整块',
@@ -1411,7 +1410,7 @@ $(document).ready(function() {
                 return !readOnly;
             }
         };
-        billsContextMenuOptions.items.sprBlock = '-----------';
+        billsContextMenuOptions.items.sprBlock = '----';
     }
     if (!readOnly) {
         billsContextMenuOptions.items.sortChildren = {
@@ -1441,7 +1440,7 @@ $(document).ready(function() {
                 }
             };
         }
-        billsContextMenuOptions.items.sprSort = '-----------';
+        billsContextMenuOptions.items.sprSort = '----';
     }
     if (!readOnly) {
         billsContextMenuOptions.items.batchInsert = {
@@ -1520,7 +1519,7 @@ $(document).ready(function() {
                 return !readOnly;
             }
         };
-        billsContextMenuOptions.items.sprBatch = '-----------';
+        billsContextMenuOptions.items.sprBatch = '----';
     }
     if (!readOnly) {
         billsContextMenuOptions.items.importExcel = {
@@ -1533,7 +1532,7 @@ $(document).ready(function() {
                 importExcel.doImport({
                     template: {
                         hint: '0号台账',
-                        url: '/template/导入分项清单EXCEL格式.xls',
+                        url: '/template/导入分项清单EXCEL格式.xlsx',
                     },
                     filter: true,
                     callback: function (sheet, filter) {
@@ -1605,83 +1604,18 @@ $(document).ready(function() {
                 return !readOnly;
             }
         };
-        billsContextMenuOptions.items.sprImport = '-----------';
+        billsContextMenuOptions.items.sprImport = '----';
     }
     billsContextMenuOptions.items.tag = {
         name: '书签',
-        items: {
-            tagShare: {
-                name: '参与人可见',
-                type: 'checkbox',
-                selected: true,
-                events: {
-                    change: function () {
-                        addTagShare = this.checked;
-                    }
-                }
-            },
-            tagSpr: '--------------',
-            tagPrimary: {
-                icon: 'fa-tag text-primary mt-2 mb-2',
-                name: '靛青',
-                callback: function (key, opt, menu, e) {
-                    const node = SpreadJsObj.getSelectObject(ledgerSpread.getActiveSheet());
-                    postData(window.location.pathname + '/tag', {add: { color: '#007bff', lid: node.id, share: addTagShare }}, function (data) {
-                        if (data.add) data.add.node = node;
-                        billsTag.updateDatasAndShow(data);
-                        SpreadJsObj.repaintNodesRowHeader(ledgerSpread.getActiveSheet(), node);
-                    });
-                },
-            },
-            tagSuccess: {
-                icon: 'fa-tag text-success mt-2 mb-2',
-                name: '果绿',
-                callback: function (key, opt) {
-                    const node = SpreadJsObj.getSelectObject(ledgerSpread.getActiveSheet());
-                    postData(window.location.pathname + '/tag', {add: { color: '#28a745', lid: node.id, share: addTagShare }}, function (data) {
-                        if (data.add) data.add.node = node;
-                        billsTag.updateDatasAndShow(data);
-                        SpreadJsObj.repaintNodesRowHeader(ledgerSpread.getActiveSheet(), node);
-                    });
-                },
-            },
-            tagDanger: {
-                icon: 'fa-tag text-danger mt-2 mb-2',
-                name: '朱砂',
-                callback: function (key, opt) {
-                    const node = SpreadJsObj.getSelectObject(ledgerSpread.getActiveSheet());
-                    postData(window.location.pathname + '/tag', {add: { color: '#dc3545', lid: node.id, share: addTagShare }}, function (data) {
-                        if (data.add) data.add.node = node;
-                        billsTag.updateDatasAndShow(data);
-                        SpreadJsObj.repaintNodesRowHeader(ledgerSpread.getActiveSheet(), node);
-                    });
-                },
-            },
-            tagWarning: {
-                icon: 'fa-tag text-warning mt-2 mb-2',
-                name: '姜黄',
-                callback: function (key, opt) {
-                    const node = SpreadJsObj.getSelectObject(ledgerSpread.getActiveSheet());
-                    postData(window.location.pathname + '/tag', {add: { color: '#da9500', lid: node.id, share: addTagShare }}, function (data) {
-                        if (data.add) data.add.node = node;
-                        billsTag.updateDatasAndShow(data);
-                        SpreadJsObj.repaintNodesRowHeader(ledgerSpread.getActiveSheet(), node);
-                    });
-                },
-            },
-            tagInfo: {
-                icon: 'fa-tag text-info mt-2 mb-2',
-                name: '天蓝',
-                callback: function (key, opt) {
-                    const node = SpreadJsObj.getSelectObject(ledgerSpread.getActiveSheet());
-                    postData(window.location.pathname + '/tag', {add: { color: '#17a2b8', lid: node.id, share: addTagShare }}, function (data) {
-                        if (data.add) data.add.node = node;
-                        billsTag.updateDatasAndShow(data);
-                        SpreadJsObj.repaintNodesRowHeader(ledgerSpread.getActiveSheet(), node);
-                    });
-                },
-            }
+        callback: function (key, opt, menu, e) {
+            const node = SpreadJsObj.getSelectObject(ledgerSpread.getActiveSheet());
+            addTag.do(node);
         },
+        disabled: function (key, opt) {
+            const node = SpreadJsObj.getSelectObject(ledgerSpread.getActiveSheet());
+            return !node;
+        }
     };
 
     $.contextMenu(billsContextMenuOptions);
@@ -2238,6 +2172,10 @@ $(document).ready(function() {
     postData(window.location.pathname + '/load', {}, function (data) {
         ledgerTree.loadDatas(data.bills);
         treeCalc.calculateAll(ledgerTree);
+        for (const t of data.tags) {
+            t.node = ledgerTree.datas.find(x => {return x.id === t.lid});
+        }
+        billsTag.loadDatas(data.tags);
         SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), 'tree', ledgerTree);
         SpreadJsObj.loadTopAndSelect(ledgerSpread.getActiveSheet(), ckBillsSpread);
 
@@ -2249,11 +2187,6 @@ $(document).ready(function() {
         treeOperationObj.loadExprToInput(ledgerSpread.getActiveSheet());
 
         checkList.loadHisCheckData();
-
-        for (const t of data.tags) {
-            t.node = ledgerTree.datas.find(x => {return x.id === t.lid});
-        }
-        billsTag.loadDatas(data.tags);
     }, null, true);
 
     $.divResizer({
@@ -2767,7 +2700,7 @@ $(document).ready(function() {
                                 self.OprObj.delete(self.sheet);
                             },
                         },
-                        sprEdit: '---------',
+                        sprEdit: '----',
                         apply: {
                             name: '应用全部清单单价至台账',
                             icon: 'fa-magic',

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

@@ -22,12 +22,58 @@ $(document).ready(() => {
     if (thousandth) sjsSettingObj.setTpThousandthFormat(xmjSpreadSetting);
     SpreadJsObj.initSheet(unitSheet, unitSpreadSetting);
 
+    const filterUnitTree = createNewPathTree('filter', {
+        id: 'ledger_id',
+        pid: 'ledger_pid',
+        order: 'order',
+        level: 'level',
+        rootId: -1,
+        fullPath: 'full_path',
+    });
+
     const unitTreeObj = {
+        getFilterUnitTree: function (unitTree) {
+            const filter = $('#unit-filter').val();
+            if (!filter || unitTree.nodes.length === 0) return unitTree;
+            filterUnitTree.clearDatas();
+            const filterPath = [];
+            const checkFullPath = function (checkPath, valuePath) {
+                if (valuePath.indexOf(checkPath + '-') >= 0) return true;
+
+                const pathArray = checkPath.split('-');
+                const tmpArray = [];
+                for (let i = 0, iLen = pathArray.length; i < iLen; i++) {
+                    tmpArray.push(pathArray.slice(0, i+1).join('-'));
+                }
+                for (const ta of tmpArray) {
+                    if (ta === valuePath) return true;
+                }
+                return false;
+            };
+            for (const node of unitTree.nodes) {
+                if ((node.code && node.code.indexOf(filter) >= 0) || (node.b_code && node.b_code.indexOf(filter) >= 0)
+                    || (node.name && node.name.indexOf(filter) >= 0) || (node.pos_name && node.pos_name.indexOf(filter) >= 0))
+                    filterPath.push(node.full_path);
+            }
+            for (const node of unitTree.nodes) {
+                for (const fp of filterPath) {
+                    if (checkFullPath(fp, node.full_path)) {
+                        filterUnitTree.addData(node, ['id', 'ledger_id', 'ledger_pid', 'order', 'level', 'full_path', 'pos_name',
+                            'code', 'b_code', 'name', 'unit', 'unit_price', 'quantity', 'total_price', 'drawing_code_merge', 'memo_merge']);
+                        break;
+                    }
+                }
+            }
+            filterUnitTree.sortTreeNode();
+            return filterUnitTree;
+        },
         loadCurUnitData: function () {
             const node = SpreadJsObj.getSelectObject(xmjSheet);
             SpreadJsObj.resetTopAndSelect(unitSheet);
             if (node && node.unitTree) {
-                SpreadJsObj.loadSheetData(unitSheet, SpreadJsObj.DataType.Tree, node.unitTree);
+                const relaTree = unitTreeObj.getFilterUnitTree(node.unitTree);
+                if ($('#unit-show-1')[0].checked) relaTree.expandByLevel(1);
+                SpreadJsObj.loadSheetData(unitSheet, SpreadJsObj.DataType.Tree, relaTree);
             } else {
                 SpreadJsObj.initSheet(unitSheet, unitSpreadSetting);
             }
@@ -55,7 +101,6 @@ $(document).ready(() => {
                 n.unitTree.loadDatas(n.unitTreeData);
             }
         }
-        console.log(xmjTree);
         SpreadJsObj.loadSheetData(xmjSheet, SpreadJsObj.DataType.Tree, xmjTree);
         unitTreeObj.loadCurUnitData();
     });
@@ -170,6 +215,19 @@ $(document).ready(() => {
         xmjSpread.refresh();
         unitSpread.refresh();
     });
+    $('#unit-filter').bind('keydown', function (e) {
+        const evt = window.event || e;
+        if (e.keyCode == 13) unitTreeObj.loadCurUnitData();
+    });
+    $('#unit-show-1').bind('change', function () {
+        if (this.checked) {
+            unitSheet.zh_tree.expandByLevel(1);
+            SpreadJsObj.refreshTreeRowVisible(unitSheet);
+        } else {
+            unitSheet.zh_tree.expandByCustom(() => { return true; });
+            SpreadJsObj.refreshTreeRowVisible(unitSheet);
+        }
+    });
 
     // 显示层次
     (function (select, sheet) {

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

@@ -176,7 +176,7 @@ $(document).ready(() => {
         checkCompareData();
         loadLeafXmjData(0);
 
-        const chapterData = gclGatherModel.gatherChapterData(chapter, ['total_price'], filter);
+        const chapterData = gclGatherModel.gatherChapterData(chapter, data.spec, ['total_price']);
         for (const c of chapterData) {
             c.compare_tp = ZhCalc.sub(c.total_price, c.deal_bills_tp);
         }

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

@@ -1407,6 +1407,12 @@ const createNewPathTree = function (type, setting) {
     }
 
     class FilterTree extends BaseTree {
+        clearDatas() {
+            this.items = {};
+            this.nodes = [];
+            this.datas = [];
+            this.children = [];
+        }
         addData(data, fields) {
             const item = {};
             for (const prop in data) {

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

@@ -1049,7 +1049,7 @@ $(document).ready(() => {
                 return !(valid && first && sameParent && !(first.level === 1 && first.node_type) && !nodeUsed);
             }
         };
-        billsContextMenuOptions.items.sprBase = '-----------';
+        billsContextMenuOptions.items.sprBase = '----';
     }
     billsContextMenuOptions.items.copyBlock = {
         name: '复制整块',
@@ -1159,7 +1159,7 @@ $(document).ready(() => {
                 return !readOnly;
             }
         };
-        billsContextMenuOptions.items.sprBlock = '-----------';
+        billsContextMenuOptions.items.sprBlock = '----';
     }
     if (!readOnly) {
         billsContextMenuOptions.items.batchInsert = {
@@ -1217,7 +1217,7 @@ $(document).ready(() => {
                 $('#batch').modal('show');
             }
         };
-        billsContextMenuOptions.items.sprBatch = '-----------';
+        billsContextMenuOptions.items.sprBatch = '----';
         billsContextMenuOptions.items.importGclBills2Xmj = {
             name: '导入工程量清单至项目节',
             icon: 'fa-file-excel-o',
@@ -1316,7 +1316,7 @@ $(document).ready(() => {
             } else {
                 $('#pos-expr').val('').attr('readOnly', true);
                 $('#pos-expr').removeAttr('data-row');
-            }            
+            }
         },
         /**
          * 加载计量单元 根据当前台账选择节点
@@ -1326,7 +1326,7 @@ $(document).ready(() => {
             if (node) {
                 const posData = pos.getLedgerPos(node.id) || [];
                 SpreadJsObj.loadSheetData(posSheet, 'data', posData);
-                posSheet.zh_setting.readOnly = posData.length === 0 && node.used;
+                posSheet.zh_setting.readOnly = readOnly || (node.used && posData.length === 0);
             } else {
                 SpreadJsObj.loadSheetData(posSheet, 'data', []);
                 posSheet.zh_setting.readOnly = true;

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

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

+ 54 - 63
app/public/js/schedule_plan.js

@@ -53,7 +53,7 @@ $(function () {
     const monthsCols = [];
     if(scheduleMonth.length > 0) {
         for (const sm of scheduleMonth) {
-            const readOnly = sm.sj_gcl !== null || sm.sj_tp !== null;
+            const readOnly = sm.stage_used !== 0;
             const yearmonth = sm.yearmonth.split('-')[0] + '年' + parseInt(sm.yearmonth.split('-')[1]) + '月';
             const cols = {title: yearmonth + '|计划工程量', colSpan: '2|1', rowSpan: '1|1', field: sm.yearmonth+'_gcl', hAlign: 2, width: 90, type: 'Number', readOnly: readOnly ? readOnly : 'readOnly.gcl'};
             const cols2 = {title: '|计划金额(万元)', colSpan: '|1', rowSpan: '|1', field: sm.yearmonth+'_tp', hAlign: 2, width: 90, type: 'Number', readOnly: readOnly ? readOnly : 'readOnly.tp'};
@@ -256,84 +256,75 @@ $(function () {
             const hint = {
                 cellError: {type: 'error', msg: '粘贴内容超出了表格范围'},
                 numberExpr: {type: 'error', msg: '不能粘贴其它非数字类型字符'},
-                numberCan: {type: 'error', msg: '请粘贴大于0并且小于3位小数的浮点数'},
             };
             const range = info.cellRange;
-            const sortData = info.sheet.zh_data || [];
-            if (info.cellRange.row + info.cellRange.rowCount > sortData.length) {
+            if (range.rowCount > 1 || range.colCount > 1) {
                 toastMessageUniq(hint.cellError);
-                SpreadJsObj.reLoadSheetHeader(materialMonthSpread.getActiveSheet());
-                SpreadJsObj.reLoadSheetData(materialMonthSpread.getActiveSheet());
+                SpreadJsObj.reLoadSheetHeader(ledgerSpread.getActiveSheet());
+                SpreadJsObj.reLoadSheetData(ledgerSpread.getActiveSheet());
                 return;
             }
-            if (sortData.length > 0 && range.col + range.colCount > 4 + months.length) {
-                toastMessageUniq(hint.cellError);
-                SpreadJsObj.reLoadSheetHeader(materialMonthSpread.getActiveSheet());
-                SpreadJsObj.reLoadSheetData(materialMonthSpread.getActiveSheet());
+            const select = SpreadJsObj.getSelectObject(info.sheet);
+            const col = info.sheet.zh_setting.cols[range.col];
+            const validText = is_numeric(info.pasteData.text) ? parseFloat(info.pasteData.text) : (info.pasteData.text ? trimInvalidChar(info.pasteData.text) : null);
+            const orgValue = select[col.field];
+            if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === ''))) {
+                SpreadJsObj.reLoadRowData(info.sheet, info.row);
                 return;
             }
-            const data = [];
-            for (let iRow = 0; iRow < range.rowCount; iRow++) {
-                let bPaste = true;
-                const curRow = range.row + iRow;
-                const materialMonthData = sortData[curRow];
-                const hintRow = range.rowCount > 1 ? curRow : '';
-                let sameCol = 0;
-                for (let iCol = 0; iCol < range.colCount; iCol++) {
-                    const curCol = range.col + iCol;
-                    const colSetting = info.sheet.zh_setting.cols[curCol];
-                    if (!colSetting) continue;
-
-                    let validText = info.sheet.getText(curRow, curCol);
-                    validText = is_numeric(validText) ? parseFloat(validText) : (validText ? trimInvalidChar(validText) : null);
-                    const orgValue = sortData[curRow][colSetting.field];
-                    if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === ''))) {
-                        sameCol++;
-                        if (range.colCount === sameCol)  {
-                            bPaste = false;
-                        }
-                        continue;
-                    }
-                    const num = parseFloat(validText);
-                    if (isNaN(validText)) {
-                        toastMessageUniq(getPasteHint(hint.numberExpr, hintRow));
-                        bPaste = false;
-                        continue;
-                    }
-                    if (validText !== null && (num < 0 || !/^\d+(\.\d{1,3})?$/.test(num))) {
-                        toastMessageUniq(getPasteHint(hint.numberCan, hintRow));
-                        bPaste = false;
-                        continue;
-                    }
-                    materialMonthData[colSetting.field] = validText;
-                    sortData[curRow][colSetting.field] = validText;
+            if (isNaN(validText)) {
+                toastr.error('不能粘贴其它非数字类型字符');
+                SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
+                return;
+            }
+            const yearmonth = col.field.split('_')[0];
+            const mode = col.field.split('_')[1];
+            let plan_gcl = 0;
+            let plan_tp = 0;
+            // 判断输入位数,提示
+            if (mode === 'tp') {
+                const reg = new RegExp('^([-]?)\\d+(\\.\\d{0,'+ parseInt(tenderInfo.decimal.tp) +'})?$');
+                if (validText !== null && (!reg.test(validText))) {
+                    toastr.error('粘贴的金额小数位数不能大于' + tenderInfo.decimal.tp + '位');
+                    SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
+                    return;
                 }
-                if (bPaste) {
-                    data.push(materialMonthData);
-                } else {
-                    SpreadJsObj.reLoadRowData(info.sheet, curRow);
+                plan_gcl = select.dgn_price && select.dgn_price !== 0 ? ZhCalc.round(ZhCalc.div(validText, select.dgn_price), tenderInfo.decimal.up) : 0;
+                plan_tp = validText;
+            } else {
+                const reg = new RegExp('^([-]?)\\d+(\\.\\d{0,'+ parseInt(tenderInfo.decimal.up) +'})?$');
+                if (validText !== null && (!reg.test(validText))) {
+                    toastr.error('粘贴的工程量小数位数不能大于' + tenderInfo.decimal.up + '位');
+                    SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
+                    return;
                 }
+                plan_gcl = validText;
+                plan_tp = select.dgn_price && select.dgn_price !== 0 ? ZhCalc.round(ZhCalc.mul(validText, select.dgn_price), tenderInfo.decimal.tp) : 0;
             }
-            if (data.length === 0) {
+            select[col.field] = validText;
+            const updateData = {
+                lid: select.ledger_id,
+                yearmonth,
+                plan_gcl,
+                plan_tp,
+            };
+            postData(window.location.pathname + '/save', {type: 'ledger_edit', postData: updateData}, function (result) {
+                if (mode === 'tp') {
+                    select[yearmonth + '_gcl'] = plan_gcl;
+                } else {
+                    select[yearmonth + '_tp'] = plan_tp;
+                }
                 SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
-                return;
-            }
-            // // 更新至服务器
-            // postData(window.location.pathname + '/month/save', { type:'paste', updateData: data }, function (result) {
-            //     SpreadJsObj.reLoadSheetData(materialMonthSpread.getActiveSheet());
-            //     materialBillsData = result.materialBillsData;
-            //     SpreadJsObj.loadSheetData(materialSpread.getActiveSheet(), SpreadJsObj.DataType.Data, materialBillsData);
-            //     m_tp = result.m_tp;
-            //     resetTpTable();
-            // }, function () {
-            //     SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
-            //     return;
-            // });
+            },function () {
+                select[col.field] = orgValue;
+                SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
+            })
         },
     };
 
     ledgerSpread.bind(spreadNS.Events.EditEnded, ledgerSpreadObj.editEnded);
     SpreadJsObj.addDeleteBind(ledgerSpread, ledgerSpreadObj.deletePress);
+    ledgerSpread.bind(spreadNS.Events.ClipboardPasted, ledgerSpreadObj.clipboardPasted);
 
     // 进度计算方式选择
     $('.mode-select').on('click', function () {

+ 598 - 0
app/public/js/schedule_stage_gcl.js

@@ -0,0 +1,598 @@
+/**
+ * 进度台账相关js
+ *
+ * @author Ellisran
+ * @date 2020/11/6
+ * @version
+ */
+function getTenderId() {
+    return window.location.pathname.split('/')[2];
+}
+$(function () {
+    autoFlashHeight();
+    if (monthList.length === 0 && selectedLedgerList.length !== 0) {
+        $('#second').modal('show');
+    }
+    // 初始化台账
+    const ledgerSpread = SpreadJsObj.createNewSpread($('#ledger-spread')[0]);
+    const treeSetting = {
+        id: 'ledger_id',
+        pid: 'ledger_pid',
+        order: 'order',
+        level: 'level',
+        rootId: -1,
+        fullPath: 'full_path',
+        //treeCacheKey: 'ledger_bills_fold' + '_' + getTenderId(),
+        // markFoldKey: 'bills-fold',
+        // markFoldSubKey: window.location.pathname.split('/')[2],
+    };
+    const ledgerTree = createNewPathTree('filter', treeSetting);
+
+    const static_cols = [
+        {title: '编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 145, formatter: '@', readOnly: true, cellType: 'tree'},
+        {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 185, formatter: '@', readOnly: true},
+        {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 50, formatter: '@', readOnly: true},
+        {title: '经济指标', colSpan: '1', rowSpan: '2', field: 'dgn_price', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+        {title: '总设计|工程量', colSpan: '2|1', rowSpan: '1|1', field: 'dgn_qty1', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '|金额(万元)', colSpan: '|1', rowSpan: '|1', field: 'total_price', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+    ];
+
+    const ledgerSpreadSetting = {
+        emptyRows: 0,
+        headRows: 2,
+        headRowHeight: [25, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        // readOnly: true,
+        localCache: {
+            key: 'ledger-bills',
+            colWidth: true,
+        }
+    };
+    const monthsCols = [];
+    if(scheduleMonth.length > 0) {
+        for (const sm of scheduleMonth) {
+            if (sm.stage_gcl_used === 1) {
+                const yearmonth = sm.yearmonth.split('-')[0] + '年' + parseInt(sm.yearmonth.split('-')[1]) + '月';
+                const cols = {title: yearmonth + '|计划工程量', colSpan: '4|1', rowSpan: '1|1', field: sm.yearmonth+'_plan_gcl', hAlign: 2, width: 90, type: 'Number', readOnly: true};
+                const cols2 = {title: '|计划金额(万元)', colSpan: '|1', rowSpan: '|1', field: sm.yearmonth+'_plan_tp', hAlign: 2, width: 90, type: 'Number', readOnly: true};
+                const cols3 = {title: '|计量工程量', colSpan: '|1', rowSpan: '|1', field: sm.yearmonth+'_sj_gcl', hAlign: 2, width: 90, type: 'Number', readOnly: 'readOnly.gcl'};
+                const cols4 = {title: '|计量金额(万元)', colSpan: '|1', rowSpan: '|1', field: sm.yearmonth+'_sj_tp', hAlign: 2, width: 90, type: 'Number', readOnly : true};
+                monthsCols.push(cols);
+                monthsCols.push(cols2);
+                monthsCols.push(cols3);
+                monthsCols.push(cols4);
+            }
+        }
+    }
+    const spreadHeaderCols = static_cols.concat(monthsCols);
+    ledgerSpreadSetting.cols = spreadHeaderCols;
+
+    const ledgerCol = {
+        readOnly: {
+            gcl: function (data) {
+                return !data.is_leaf;
+            },
+        },
+    };
+
+    sjsSettingObj.setFxTreeStyle(ledgerSpreadSetting, sjsSettingObj.FxTreeStyle.jz);
+    if (thousandth) sjsSettingObj.setTpThousandthFormat(ledgerSpreadSetting);
+    SpreadJsObj.initSpreadSettingEvents(ledgerSpreadSetting, ledgerCol);
+    SpreadJsObj.initSheet(ledgerSpread.getActiveSheet(), ledgerSpreadSetting);
+    SpreadJsObj.selChangedRefreshBackColor(ledgerSpread.getActiveSheet());
+
+    postData('/tender/' + getTenderId() + '/schedule/ledger/load', {}, function (data) {
+        // let treeData = [];
+        // for(const sl of selectedLedgerList) {
+        //     const one = _.find(data, { 'ledger_id' : sl });
+        //     treeData.push(one);
+        // }
+        // treeData = setLeafData(treeData);
+        // console.log(treeData);
+        // let treeData = data;
+        const calcList = ['total_price'];
+        const showList = ['ledger_id', 'ledger_pid', 'order', 'level', 'tender_id', 'full_path',
+            'code', 'name', 'unit', 'dgn_qty1', 'dgn_qty2', 'dgn_price', 'quantity', 'total_price'];
+        for (const m of scheduleMonth) {
+            if (m.stage_gcl_used === 1) {
+                showList.push(m.yearmonth + '_plan_tp');
+                showList.push(m.yearmonth + '_plan_gcl');
+                showList.push(m.yearmonth + '_sj_tp');
+                showList.push(m.yearmonth + '_sj_gcl');
+            }
+            // calcList.push(m + '_tp');
+            // calcList.push(m + '_gcl');
+        }
+        const baseLedgerTree = createNewPathTree('base', {
+            id: 'ledger_id',
+            pid: 'ledger_pid',
+            order: 'order',
+            level: 'level',
+            rootId: -1,
+            fullPath: 'full_path',
+            calcFields: calcList,
+            calcFun: function (node) {
+                node.dgn_price = ZhCalc.round(ZhCalc.div(node.total_price, node.dgn_qty1), 2);
+            }
+        });
+        const newLedgerList = setMonthToLedger(data.bills, data.slm);
+        baseLedgerTree.loadDatas(newLedgerList);
+        treeCalc.calculateAll(baseLedgerTree);
+        console.log(baseLedgerTree);
+        for (const d of baseLedgerTree.nodes) {
+            if (!d.b_code) {
+                const one = _.find(selectedLedgerList, function (item) {
+                    return item === d.ledger_id;
+                });
+                if(one) {
+                    ledgerTree.addData(d, showList);
+                }
+            }
+        }
+        console.log(ledgerTree);
+        ledgerTree.sortTreeNode(true);
+        // console.log(ledgerTree);
+        SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), SpreadJsObj.DataType.Tree, ledgerTree);
+    }, null, true);
+
+    const ledgerSpreadObj = {
+        refreshTree: function (sheet, data) {
+            SpreadJsObj.massOperationSheet(sheet, function () {
+                const tree = sheet.zh_tree;
+                // 处理删除
+                if (data.delete) {
+                    data.delete.sort(function (x, y) {
+                        return y.deleteIndex - x.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);
+                            }
+                        }
+                    }
+                }
+            });
+        },
+        editEnded: function (e, info) {
+            if (info.sheet.zh_setting) {
+                const select = SpreadJsObj.getSelectObject(info.sheet);
+                const col = info.sheet.zh_setting.cols[info.col];
+                const validText = is_numeric(info.editingText) ? parseFloat(info.editingText) : (info.editingText ? trimInvalidChar(info.editingText) : null);
+                const orgValue = select[col.field];
+                if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === ''))) {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    return;
+                }
+                if (isNaN(validText)) {
+                    toastr.error('不能输入其它非数字类型字符');
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    return;
+                }
+                const yearmonth = col.field.split('_')[0];
+                // 判断输入位数,提示
+                const reg = new RegExp('^([-]?)\\d+(\\.\\d{0,'+ parseInt(tenderInfo.decimal.up) +'})?$');
+                if (validText !== null && (!reg.test(validText))) {
+                    toastr.error('输入工程量小数位数不能大于' + tenderInfo.decimal.up + '位');
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    return;
+                }
+                const sj_gcl = validText;
+                const sj_tp = select.dgn_price && select.dgn_price !== 0 ? ZhCalc.round(ZhCalc.mul(validText, select.dgn_price), tenderInfo.decimal.tp) : 0;
+                select[col.field] = validText;
+                const updateData = {
+                    lid: select.ledger_id,
+                    yearmonth,
+                    sj_gcl,
+                    sj_tp,
+                };
+                console.log(updateData);
+                postData(window.location.pathname + '/save', {type: 'ledger_edit', postData: updateData}, function (result) {
+                    select[yearmonth + '_sj_tp'] = sj_tp;
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                },function () {
+                    select[col.field] = orgValue;
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                })
+            }
+        },
+        deletePress: function (sheet) {
+            return;
+        },
+        clipboardPasted(e, info) {
+            const hint = {
+                cellError: {type: 'error', msg: '粘贴内容超出了表格范围'},
+                numberExpr: {type: 'error', msg: '不能粘贴其它非数字类型字符'},
+            };
+            const range = info.cellRange;
+            if (range.rowCount > 1 || range.colCount > 1) {
+                toastMessageUniq(hint.cellError);
+                SpreadJsObj.reLoadSheetHeader(ledgerSpread.getActiveSheet());
+                SpreadJsObj.reLoadSheetData(ledgerSpread.getActiveSheet());
+                return;
+            }
+            const select = SpreadJsObj.getSelectObject(info.sheet);
+            const col = info.sheet.zh_setting.cols[range.col];
+            const validText = is_numeric(info.pasteData.text) ? parseFloat(info.pasteData.text) : (info.pasteData.text ? trimInvalidChar(info.pasteData.text) : null);
+            const orgValue = select[col.field];
+            if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === ''))) {
+                SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                return;
+            }
+            if (isNaN(validText)) {
+                toastr.error('不能粘贴其它非数字类型字符');
+                SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
+                return;
+            }
+            const yearmonth = col.field.split('_')[0];
+            // 判断输入位数,提示
+            const reg = new RegExp('^([-]?)\\d+(\\.\\d{0,'+ parseInt(tenderInfo.decimal.up) +'})?$');
+            if (validText !== null && (!reg.test(validText))) {
+                toastr.error('粘贴的工程量小数位数不能大于' + tenderInfo.decimal.up + '位');
+                SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
+                return;
+            }
+            const sj_gcl = validText;
+            const sj_tp = select.dgn_price && select.dgn_price !== 0 ? ZhCalc.round(ZhCalc.mul(validText, select.dgn_price), tenderInfo.decimal.tp) : 0;
+            select[col.field] = validText;
+            const updateData = {
+                lid: select.ledger_id,
+                yearmonth,
+                sj_gcl,
+                sj_tp,
+            };
+            console.log(updateData);
+            postData(window.location.pathname + '/save', {type: 'ledger_edit', postData: updateData}, function (result) {
+                select[yearmonth + '_sj_tp'] = sj_tp;
+                SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
+            },function () {
+                select[col.field] = orgValue;
+                SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
+            })
+        },
+    };
+
+    ledgerSpread.bind(spreadNS.Events.EditEnded, ledgerSpreadObj.editEnded);
+    SpreadJsObj.addDeleteBind(ledgerSpread, ledgerSpreadObj.deletePress);
+    ledgerSpread.bind(spreadNS.Events.ClipboardPasted, ledgerSpreadObj.clipboardPasted);
+
+
+    // 下方金额模式的sjs
+    const huizongSpread = SpreadJsObj.createNewSpread($('#huizong-spread')[0]);
+    const huizongTreeSetting = {
+        id: 'ledger_id',
+        pid: 'ledger_pid',
+        order: 'order',
+        level: 'level',
+        rootId: -1,
+        fullPath: 'full_path',
+        calcFields: ['plan_gcl', 'plan_tp', 'next_plan_gcl', 'next_plan_tp', 'end_plan_gcl', 'end_plan_tp', 'year_plan_gcl', 'year_plan_tp'],
+    };
+
+    const huizong_static_cols = [
+        {title: '编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 145, formatter: '@', readOnly: true, cellType: 'tree'},
+        {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 185, formatter: '@', readOnly: true},
+        {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 50, formatter: '@', readOnly: true},
+        {title: '经济指标', colSpan: '1', rowSpan: '2', field: 'dgn_price', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+        {title: '总设计|工程量', colSpan: '2|1', rowSpan: '1|1', field: 'dgn_qty1', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '|金额(万元)', colSpan: '|1', rowSpan: '|1', field: 'total_price', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '自开工至本月计划完成|工程量', colSpan: '2|1', rowSpan: '1|1', field: 'end_plan_gcl', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '|金额(万元)', colSpan: '|1', rowSpan: '|1', field: 'end_plan_tp', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '截止本月完成计量|工程量', colSpan: '2|1', rowSpan: '1|1', field: 'end_gather_qty', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '|金额(万元)', colSpan: '|1', rowSpan: '|1', field: 'end_gather_tp', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '本年计划完成|工程量', colSpan: '2|1', rowSpan: '1|1', field: 'year_plan_gcl', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '|金额(万元)', colSpan: '|1', rowSpan: '|1', field: 'year_plan_tp', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '本年累计完成|工程量', colSpan: '2|1', rowSpan: '1|1', field: 'year_contract_qty', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '|金额(万元)', colSpan: '|1', rowSpan: '|1', field: 'year_gather_tp', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '本月计划完成|工程量', colSpan: '2|1', rowSpan: '1|1', field: 'plan_gcl', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '|金额(万元)', colSpan: '|1', rowSpan: '|1', field: 'plan_tp', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '下月计划|工程量', colSpan: '2|1', rowSpan: '1|1', field: 'next_plan_gcl', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '|金额(万元)', colSpan: '|1', rowSpan: '|1', field: 'next_plan_tp', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+    ];
+
+    const huizongSpreadSetting = {
+        emptyRows: 0,
+        headRows: 2,
+        headRowHeight: [25, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        // readOnly: true,
+        localCache: {
+            key: 'ledger-bills',
+            colWidth: true,
+        }
+    };
+    huizongSpreadSetting.cols = huizong_static_cols;
+
+    sjsSettingObj.setFxTreeStyle(huizongSpreadSetting, sjsSettingObj.FxTreeStyle.jz);
+    if (thousandth) sjsSettingObj.setTpThousandthFormat(huizongSpreadSetting);
+    SpreadJsObj.initSheet(huizongSpread.getActiveSheet(), huizongSpreadSetting);
+    SpreadJsObj.selChangedRefreshBackColor(huizongSpread.getActiveSheet());
+
+    const huizongObj = {
+        setSjs: function (order) {
+            postData('/tender/' + getTenderId() + '/measure/stage/' + order + '/load', { filter: 'ledger' }, function (data) {
+                postData('/tender/' + getTenderId() + '/schedule/stage/' + order + '/load', {}, function (data2) {
+                    const calcList = ['year_contract_qty', 'year_gather_tp',
+                        'contract_qty', 'end_gather_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'total_price', 'contract_tp', 'qc_tp', 'gather_tp',
+                        'pre_contract_tp', 'pre_qc_tp', 'pre_gather_tp', 'end_contract_tp', 'end_qc_tp', 'end_gather_tp', 'end_correct_tp'];
+                    const showList = ['plan_gcl', 'plan_tp', 'next_plan_gcl', 'next_plan_tp', 'end_plan_gcl', 'end_plan_tp', 'year_plan_gcl', 'year_plan_tp',
+                        'year_contract_qty', 'year_gather_tp',
+                        'contract_qty', 'end_gather_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'ledger_id', 'ledger_pid', 'order', 'level', 'tender_id', 'full_path',
+                        'code', 'name', 'unit', 'dgn_qty1', 'dgn_qty2', 'dgn_price', 'quantity', 'total_price', 'contract_tp', 'qc_tp', 'gather_tp',
+                        'pre_contract_tp', 'pre_qc_tp', 'pre_gather_tp', 'end_contract_tp', 'end_qc_tp', 'end_gather_tp', 'end_correct_tp'];
+                    const baseLedgerTreeSetting = {
+                        id: 'ledger_id',
+                        pid: 'ledger_pid',
+                        order: 'order',
+                        level: 'level',
+                        rootId: -1,
+                        fullPath: 'full_path',
+                        calcFields: calcList,
+                    };
+                    baseLedgerTreeSetting.updateFields = ['year_contract_qty', 'year_gather_tp', 'contract_qty', 'end_gather_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'postil', 'used', 'contract_expr'];
+                    baseLedgerTreeSetting.calcFun = function (node) {
+                        if (!node.children || node.children.length === 0) {
+                            node.pre_gather_qty = ZhCalc.add(node.pre_contract_qty, node.pre_qc_qty);
+                            node.gather_qty = ZhCalc.add(node.contract_qty, node.qc_qty);
+                            node.end_contract_qty = ZhCalc.add(node.pre_contract_qty, node.contract_qty);
+                            node.end_qc_qty = ZhCalc.add(node.pre_qc_qty, node.qc_qty);
+                            node.end_gather_qty = ZhCalc.add(node.pre_gather_qty, node.gather_qty);
+                        }
+                        node.pre_gather_tp = ZhCalc.add(node.pre_contract_tp, node.pre_qc_tp);
+                        node.gather_tp = ZhCalc.add(node.contract_tp, node.qc_tp);
+                        node.end_contract_tp = ZhCalc.add(node.pre_contract_tp, node.contract_tp);
+                        node.end_qc_tp = ZhCalc.add(node.pre_qc_tp, node.qc_tp);
+                        node.end_gather_tp = ZhCalc.add(node.pre_gather_tp, node.gather_tp);
+                        node.end_final_tp = ZhCalc.add(node.end_qc_tp, node.total_price);
+                        if (!node.children || node.children.length === 0) {
+                            if (node.end_contract_qty) {
+                                node.end_correct_tp = ZhCalc.add(node.end_qc_tp, ZhCalc.mul(node.end_contract_qty, node.unit_price, tenderInfo.decimal.tp));
+                            } else {
+                                node.end_correct_tp = node.end_gather_tp;
+                            }
+                        }
+                        node.end_gather_percent = ZhCalc.mul(ZhCalc.div(node.end_gather_tp, node.end_final_tp), 100, 2);
+                        node.end_correct_percent = ZhCalc.mul(ZhCalc.div(node.end_correct_tp, node.end_final_tp), 100, 2);
+                        node.final_dgn_price = ZhCalc.round(ZhCalc.div(node.end_gather_tp, ZhCalc.add(node.deal_dgn_qty1, node.c_dgn_qty1)), tenderInfo.decimal.up);
+                        node.dgn_price = ZhCalc.round(ZhCalc.div(node.total_price, node.dgn_qty1), 2);
+                    };
+                    const baseLedgerTree = createNewPathTree('base', baseLedgerTreeSetting);
+                    const newLedgerList = setTpMonthToLedger(data.ledgerData, data2.slmList, data2.nextSlmList, data2.endSlmList, data2.yearSlmList, data2.curYearStageData);
+                    baseLedgerTree.loadDatas(newLedgerList);
+                    treeCalc.calculateAll(baseLedgerTree);
+                    const huizongTree = createNewPathTree('filter', huizongTreeSetting);
+                    for (const d of baseLedgerTree.nodes) {
+                        if (!d.b_code) {
+                            const one = _.find(selectedLedgerList, function (item) {
+                                return item === d.ledger_id;
+                            });
+                            if (one) {
+                                huizongTree.addData(d, showList);
+                            }
+                        }
+                    }
+                    huizongTree.sortTreeNode(true);
+                    treeCalc.calculateAll(huizongTree);
+                    console.log(huizongTree);
+                    SpreadJsObj.loadSheetData(huizongSpread.getActiveSheet(), SpreadJsObj.DataType.Tree, huizongTree);
+                }, null, true);
+            }, null, true);
+        }
+    };
+
+    if (curScheduleStage && curScheduleStage.order) {
+        huizongObj.setSjs(curScheduleStage.order);
+    }
+
+    // 汇总切换
+    $('body').on('click', '.change-tp', function () {
+        const order = parseInt($(this).data('order'));
+        $('#dropdownMenuButton').text($(this).text());
+        let html = '';
+        for (const ss of scheduleStage) {
+            if (ss.order !== order)
+            html += '<a class="dropdown-item change-tp" data-order="'+ ss.order +'" href="javascript:void(0);">'+ ss.yearmonth.split('-')[0] + '年' + parseInt(ss.yearmonth.split('-')[1]) +'月(第'+ ss.order +'期)</a>';
+        }
+        $('.dropdown-menu').html(html);
+        huizongObj.setSjs(order);
+    });
+
+    // 月份添加
+    $('#add-month').click(function () {
+        const id = parseInt($('#month-select').val());
+        if(id === 0) {
+            toastr.error('请选择计划进度月');
+            return;
+        }
+        const _self = $(this);
+        postData(window.location.pathname + '/save', {type: 'add_stage', postData: { id: id }}, function (result) {
+            _self.addClass('disabled').attr('disabled', true);
+            toastr.success('创建成功');
+            setTimeout(function () {
+                window.location.reload();
+            }, 500)
+        })
+    });
+
+    $('#month-table input[type="checkbox"]').click(function () {
+        const selectedMonth = [];
+        $('#month-table input:checkbox:checked').each(function () {
+            selectedMonth.push('「' + $(this).parents('td').siblings('td').text() + '」');
+        });
+        if(selectedMonth.length > 0) {
+            $('#del-month-list').text(selectedMonth.join(''));
+            $('#del-month-list').parent().show();
+            $('#del-month').removeAttr('disabled');
+        } else {
+            $('#del-month-list').parent().hide();
+            $('#del-month').attr('disabled', true);
+        }
+    });
+
+    $('#del-month').click(function () {
+        const selectedMonth = [];
+        $('#month-table input:checkbox:checked').each(function () {
+            selectedMonth.push($(this).parents('td').siblings().text());
+        });
+        if (selectedMonth.length === 0) {
+            toastr.error('请选择删除的计划周期');
+            return;
+        }
+        const _self = $(this);
+        postData(window.location.pathname + '/save', {type: 'del_stage', postData: selectedMonth}, function (result) {
+            _self.addClass('disabled').attr('disabled', true);
+            toastr.success('删除成功');
+            setTimeout(function () {
+                window.location.reload();
+            }, 500)
+
+        })
+    });
+
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        key: 'menu.1.0.0',
+        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            autoFlashHeight();
+            ledgerSpread.refresh();
+            huizongSpread.refresh();
+        }
+    });
+
+    $.divResizer({
+        select: '#main-resize',
+        callback: function () {
+            ledgerSpread.refresh();
+            let bcontent = $(".bcontent-wrap") ? $(".bcontent-wrap").height() : 0;
+            $(".sp-wrap").height(bcontent-30);
+            huizongSpread.refresh();
+            window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();
+        }
+    });
+});
+function setLeafData(tree) {
+    const newtree = [];
+    for (const t of tree) {
+        const child = _.find(tree, { 'ledger_pid': t.ledger_id });
+        if (!child && !t.is_leaf) {
+            t.is_leaf = true;
+        }
+        newtree.push(t);
+    }
+    return newtree;
+}
+
+function setMonthToLedger(ledgerList, slm) {
+    if (slm.length > 0) {
+        for(const s of slm) {
+            const index = _.findIndex(ledgerList, { 'ledger_id': s.lid });
+            const canCalc = _.find(scheduleMonth, { 'yearmonth': s.yearmonth, 'stage_gcl_used': 1});
+            if (index && index !== -1 && canCalc) {
+                ledgerList[index][s.yearmonth + '_plan_tp'] = s.plan_tp;
+                ledgerList[index][s.yearmonth + '_plan_gcl'] = s.plan_gcl;
+                ledgerList[index][s.yearmonth + '_sj_tp'] = s.sj_tp;
+                ledgerList[index][s.yearmonth + '_sj_gcl'] = s.sj_gcl;
+            }
+        }
+    }
+    return ledgerList;
+}
+function setTpMonthToLedger(ledgerList, slm, nextSlm, endSlm, yearSlm, yearLedgerData) {
+    if (slm.length > 0) {
+        for(const s of slm) {
+            const index = _.findIndex(ledgerList, { 'ledger_id': s.lid });
+            if (index && index !== -1) {
+                ledgerList[index].plan_tp = s.plan_tp;
+                ledgerList[index].plan_gcl = s.plan_gcl;
+            }
+        }
+    }
+    if (nextSlm.length > 0) {
+        for (const ns of nextSlm) {
+            const index = _.findIndex(ledgerList, {'ledger_id': ns.lid});
+            if (index && index !== -1) {
+                ledgerList[index].next_plan_tp = ns.plan_tp;
+                ledgerList[index].next_plan_gcl = ns.plan_gcl;
+            }
+        }
+    }
+    if (endSlm.length > 0) {
+        for (const es of endSlm) {
+            const index = _.findIndex(ledgerList, {'ledger_id': es.lid});
+            if (index && index !== -1) {
+                ledgerList[index].end_plan_tp = es.plan_tp;
+                ledgerList[index].end_plan_gcl = es.plan_gcl;
+            }
+        }
+    }
+    if (yearSlm.length > 0) {
+        for (const ys of yearSlm) {
+            const index = _.findIndex(ledgerList, {'ledger_id': ys.lid});
+            if (index && index !== -1) {
+                ledgerList[index].year_plan_tp = ys.plan_tp;
+                ledgerList[index].year_plan_gcl = ys.plan_gcl;
+            }
+        }
+    }
+    if (yearLedgerData.length > 0) {
+        for (const yl of yearLedgerData) {
+            const index = _.findIndex(ledgerList, {'id': yl.lid});
+            if (index && index !== -1) {
+                ledgerList[index].year_contract_qty = ZhCalc.add(yl.contract_qty, yl.qc_qty);
+                ledgerList[index].year_gather_tp = ZhCalc.add(yl.contract_tp, yl.qc_tp);
+            }
+        }
+    }
+    return ledgerList;
+}
+const is_numeric = (value) => {
+    if (typeof(value) === 'object') {
+        return false;
+    } else {
+        return !Number.isNaN(Number(value)) && value.toString().trim() !== '';
+    }
+};

+ 254 - 0
app/public/js/schedule_stage_tp.js

@@ -0,0 +1,254 @@
+/**
+ * 进度台账相关js
+ *
+ * @author Ellisran
+ * @date 2020/11/6
+ * @version
+ */
+function getTenderId() {
+    return window.location.pathname.split('/')[2];
+}
+$(function () {
+    autoFlashHeight();
+    if (monthList.length === 0 && selectedLedgerList.length !== 0) {
+        $('#second').modal('show');
+    }
+    // 初始化台账
+    const ledgerSpread = SpreadJsObj.createNewSpread($('#ledger-spread')[0]);
+    const treeSetting = {
+        id: 'ledger_id',
+        pid: 'ledger_pid',
+        order: 'order',
+        level: 'level',
+        rootId: -1,
+        fullPath: 'full_path',
+        calcFields: ['plan_gcl', 'plan_tp', 'next_plan_gcl', 'next_plan_tp', 'end_plan_gcl', 'end_plan_tp', 'year_plan_gcl', 'year_plan_tp'],
+    };
+    const ledgerTree = createNewPathTree('filter', treeSetting);
+
+    const static_cols = [
+        {title: '编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 145, formatter: '@', readOnly: true, cellType: 'tree'},
+        {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 185, formatter: '@', readOnly: true},
+        {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 50, formatter: '@', readOnly: true},
+        {title: '经济指标', colSpan: '1', rowSpan: '2', field: 'dgn_price', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+        {title: '总设计|工程量', colSpan: '2|1', rowSpan: '1|1', field: 'dgn_qty1', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '|金额(万元)', colSpan: '|1', rowSpan: '|1', field: 'total_price', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '自开工至本月计划完成|工程量', colSpan: '2|1', rowSpan: '1|1', field: 'end_plan_gcl', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '|金额(万元)', colSpan: '|1', rowSpan: '|1', field: 'end_plan_tp', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '截止本期完成计量|工程量', colSpan: '2|1', rowSpan: '1|1', field: 'end_gather_qty', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '|金额(万元)', colSpan: '|1', rowSpan: '|1', field: 'end_gather_tp', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '本年计划完成|工程量', colSpan: '2|1', rowSpan: '1|1', field: 'year_plan_gcl', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '|金额(万元)', colSpan: '|1', rowSpan: '|1', field: 'year_plan_tp', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '本年累计完成|工程量', colSpan: '2|1', rowSpan: '1|1', field: 'year_contract_qty', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '|金额(万元)', colSpan: '|1', rowSpan: '|1', field: 'year_gather_tp', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '本月计划完成|工程量', colSpan: '2|1', rowSpan: '1|1', field: 'plan_gcl', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '|金额(万元)', colSpan: '|1', rowSpan: '|1', field: 'plan_tp', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '本期完成计量|工程量', colSpan: '2|1', rowSpan: '1|1', field: 'contract_qty', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '|金额(万元)', colSpan: '|1', rowSpan: '|1', field: 'gather_tp', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '下月计划|工程量', colSpan: '2|1', rowSpan: '1|1', field: 'next_plan_gcl', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+        {title: '|金额(万元)', colSpan: '|1', rowSpan: '|1', field: 'next_plan_tp', hAlign: 2, width: 70, type: 'Number', readOnly: true},
+    ];
+
+    const ledgerSpreadSetting = {
+        emptyRows: 0,
+        headRows: 2,
+        headRowHeight: [25, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        // readOnly: true,
+        localCache: {
+            key: 'ledger-bills',
+            colWidth: true,
+        }
+    };
+    ledgerSpreadSetting.cols = static_cols;
+
+    sjsSettingObj.setFxTreeStyle(ledgerSpreadSetting, sjsSettingObj.FxTreeStyle.jz);
+    if (thousandth) sjsSettingObj.setTpThousandthFormat(ledgerSpreadSetting);
+    SpreadJsObj.initSheet(ledgerSpread.getActiveSheet(), ledgerSpreadSetting);
+    SpreadJsObj.selChangedRefreshBackColor(ledgerSpread.getActiveSheet());
+
+    if (curScheduleStage && curScheduleStage.order) {
+        postData('/tender/' + getTenderId() + '/measure/stage/' + curScheduleStage.order + '/load', { filter: 'ledger' }, function (data) {
+            const calcList = ['year_contract_qty', 'year_gather_tp',
+                'contract_qty', 'end_gather_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'total_price', 'contract_tp', 'qc_tp', 'gather_tp',
+                'pre_contract_tp', 'pre_qc_tp', 'pre_gather_tp', 'end_contract_tp', 'end_qc_tp', 'end_gather_tp', 'end_correct_tp'];
+            const showList = ['plan_gcl', 'plan_tp', 'next_plan_gcl', 'next_plan_tp', 'end_plan_gcl', 'end_plan_tp', 'year_plan_gcl', 'year_plan_tp',
+                'year_contract_qty', 'year_gather_tp',
+                'contract_qty', 'end_gather_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'ledger_id', 'ledger_pid', 'order', 'level', 'tender_id', 'full_path',
+                'code', 'name', 'unit', 'dgn_qty1', 'dgn_qty2', 'dgn_price', 'quantity', 'total_price', 'contract_tp', 'qc_tp', 'gather_tp',
+                'pre_contract_tp', 'pre_qc_tp', 'pre_gather_tp', 'end_contract_tp', 'end_qc_tp', 'end_gather_tp', 'end_correct_tp'];
+            const baseLedgerTreeSetting = {
+                id: 'ledger_id',
+                pid: 'ledger_pid',
+                order: 'order',
+                level: 'level',
+                rootId: -1,
+                fullPath: 'full_path',
+                calcFields: calcList,
+            };
+            baseLedgerTreeSetting.updateFields = ['year_contract_qty', 'year_gather_tp', 'contract_qty', 'end_gather_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'postil', 'used', 'contract_expr'];
+            baseLedgerTreeSetting.calcFun = function (node) {
+                if (!node.children || node.children.length === 0) {
+                    node.pre_gather_qty = ZhCalc.add(node.pre_contract_qty, node.pre_qc_qty);
+                    node.gather_qty = ZhCalc.add(node.contract_qty, node.qc_qty);
+                    node.end_contract_qty = ZhCalc.add(node.pre_contract_qty, node.contract_qty);
+                    node.end_qc_qty = ZhCalc.add(node.pre_qc_qty, node.qc_qty);
+                    node.end_gather_qty = ZhCalc.add(node.pre_gather_qty, node.gather_qty);
+                }
+                node.pre_gather_tp = ZhCalc.add(node.pre_contract_tp, node.pre_qc_tp);
+                node.gather_tp = ZhCalc.add(node.contract_tp, node.qc_tp);
+                node.end_contract_tp = ZhCalc.add(node.pre_contract_tp, node.contract_tp);
+                node.end_qc_tp = ZhCalc.add(node.pre_qc_tp, node.qc_tp);
+                node.end_gather_tp = ZhCalc.add(node.pre_gather_tp, node.gather_tp);
+                node.end_final_tp = ZhCalc.add(node.end_qc_tp, node.total_price);
+                if (!node.children || node.children.length === 0) {
+                    if (node.end_contract_qty) {
+                        node.end_correct_tp = ZhCalc.add(node.end_qc_tp, ZhCalc.mul(node.end_contract_qty, node.unit_price, tenderInfo.decimal.tp));
+                    } else {
+                        node.end_correct_tp = node.end_gather_tp;
+                    }
+                }
+                node.end_gather_percent = ZhCalc.mul(ZhCalc.div(node.end_gather_tp, node.end_final_tp), 100, 2);
+                node.end_correct_percent = ZhCalc.mul(ZhCalc.div(node.end_correct_tp, node.end_final_tp), 100, 2);
+                node.final_dgn_price = ZhCalc.round(ZhCalc.div(node.end_gather_tp, ZhCalc.add(node.deal_dgn_qty1, node.c_dgn_qty1)), tenderInfo.decimal.up);
+                node.dgn_price = ZhCalc.round(ZhCalc.div(node.total_price, node.dgn_qty1), 2);
+            };
+            const baseLedgerTree = createNewPathTree('base', baseLedgerTreeSetting);
+            const newLedgerList = setMonthToLedger(data.ledgerData, slmList, nextSlmList, endSlmList, yearSlmList, curYearStageData);
+            baseLedgerTree.loadDatas(newLedgerList);
+            treeCalc.calculateAll(baseLedgerTree);
+            for (const d of baseLedgerTree.nodes) {
+                if (!d.b_code) {
+                    const one = _.find(selectedLedgerList, function (item) {
+                        return item === d.ledger_id;
+                    });
+                    if(one) {
+                        ledgerTree.addData(d, showList);
+                    }
+                }
+            }
+            ledgerTree.sortTreeNode(true);
+            treeCalc.calculateAll(ledgerTree);
+            SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), SpreadJsObj.DataType.Tree, ledgerTree);
+        }, null, true);
+    }
+
+    $('#add-stage').click(function () {
+        const yearmonth = $('#month_list').val();
+        const order = parseInt($('#stage_list').val());
+        if (yearmonth === '0') {
+            toastr.error('请选择计划进度月');
+            return;
+        }
+        if (order === 0) {
+            toastr.error('请选择计量期');
+            return;
+        }
+        const _self = $(this);
+        postData('/tender/'+ getTenderId() + '/schedule/stage/save', {type: 'add_stage', postData: { yearmonth, order }}, function (result) {
+            _self.addClass('disabled').attr('disabled', true);
+            toastr.success('新增成功');
+            setTimeout(function () {
+                window.location.reload();
+            }, 500)
+        })
+    });
+
+    $('#reload-stage').click(function () {
+        const order = parseInt($('#reload-stage-list').val());
+        const id = parseInt($(this).data('id'));
+        if (order === 0) {
+            toastr.error('请选择计量期');
+            return;
+        }
+        const _self = $(this);
+        postData('/tender/'+ getTenderId() + '/schedule/stage/save', {type: 'reload_stage', postData: { id, order }}, function (result) {
+            _self.addClass('disabled').attr('disabled', true);
+            toastr.success('重新生成成功');
+            setTimeout(function () {
+                window.location.href = '/tender/'+ getTenderId() + '/schedule/stage/order/' + order;
+            }, 500)
+        })
+    });
+
+    $('#del-stage').click(function () {
+        const id = parseInt($(this).data('id'));
+        const _self = $(this);
+        postData('/tender/'+ getTenderId() + '/schedule/stage/save', {type: 'del_stage', postData: { id }}, function (result) {
+            _self.addClass('disabled').attr('disabled', true);
+            toastr.success('删除成功');
+            setTimeout(function () {
+                window.location.href = '/tender/'+ getTenderId() + '/schedule/stage';
+            }, 500)
+        })
+    });
+
+    $.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');
+            }
+            ledgerSpread.refresh();
+            autoFlashHeight();
+        }
+    });
+});
+
+function setMonthToLedger(ledgerList, slm, nextSlm, endSlm, yearSlm, yearLedgerData) {
+    if (slm.length > 0) {
+        for(const s of slm) {
+            const index = _.findIndex(ledgerList, { 'ledger_id': s.lid });
+            if (index && index !== -1) {
+                ledgerList[index].plan_tp = s.plan_tp;
+                ledgerList[index].plan_gcl = s.plan_gcl;
+            }
+        }
+    }
+    if (nextSlm.length > 0) {
+        for (const ns of nextSlm) {
+            const index = _.findIndex(ledgerList, {'ledger_id': ns.lid});
+            if (index && index !== -1) {
+                ledgerList[index].next_plan_tp = ns.plan_tp;
+                ledgerList[index].next_plan_gcl = ns.plan_gcl;
+            }
+        }
+    }
+    if (endSlm.length > 0) {
+        for (const es of endSlm) {
+            const index = _.findIndex(ledgerList, {'ledger_id': es.lid});
+            if (index && index !== -1) {
+                ledgerList[index].end_plan_tp = es.plan_tp;
+                ledgerList[index].end_plan_gcl = es.plan_gcl;
+            }
+        }
+    }
+    if (yearSlm.length > 0) {
+        for (const ys of yearSlm) {
+            const index = _.findIndex(ledgerList, {'ledger_id': ys.lid});
+            if (index && index !== -1) {
+                ledgerList[index].year_plan_tp = ys.plan_tp;
+                ledgerList[index].year_plan_gcl = ys.plan_gcl;
+            }
+        }
+    }
+    if (yearLedgerData.length > 0) {
+        for (const yl of yearLedgerData) {
+            const index = _.findIndex(ledgerList, {'id': yl.lid});
+            if (index && index !== -1) {
+                ledgerList[index].year_contract_qty = ZhCalc.add(yl.contract_qty, yl.qc_qty);
+                ledgerList[index].year_gather_tp = ZhCalc.add(yl.contract_tp, yl.qc_tp);
+            }
+        }
+    }
+    return ledgerList;
+}

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

@@ -183,7 +183,7 @@ $(document).ready(() => {
                     toastr.error('仅支持office文档、图片、压缩包格式,请勿上传' + fileext + '格式文件。');
                     return false;
                 }
-                formData.append('size[]', file.size);
+                formData.append('size', file.size);
                 formData.append('file[]', file);
             }
             postDataWithFile('upload/file', formData, function (data) {
@@ -270,12 +270,31 @@ $(document).ready(() => {
             });
             return cur.indexOf(bonusData) === cur.length - 1;
         }
+        sum () {
+            const result = {
+                tp: 0,
+            };
+            for (const d of this.data) {
+                result.tp = ZhCalc.add(result.tp, d.tp);
+            }
+            return result;
+        }
     }
     const bonusObj = new Bonus();
+    const refreshSum = function () {
+        const sum = bonusObj.sum();
+        const html = [];
+        const getTrHtml = function (name, value) {
+            return '<tr><td>' + name + '</td><td class="text-right">' + (!checkZero(value) ? value : '') + ' </td></tr>';
+        };
+        html.push(getTrHtml('金额', sum.tp));
+        $('#sum').html(html.join(' '));
+    };
 
     postData(window.location.pathname + '/load', null, function (result) {
         bonusObj.loadDatas(result);
         SpreadJsObj.loadSheetData(bonusSheet, SpreadJsObj.DataType.Data, bonusObj.data);
+        refreshSum();
     });
 
     if (!readOnly) {
@@ -322,6 +341,7 @@ $(document).ready(() => {
                     postData(window.location.pathname + '/update', {update: datas}, function (result) {
                         bonusObj.loadUpdateData(result);
                         SpreadJsObj.reLoadSheetData(bonusSheet);
+                        refreshSum();
                     }, function () {
                         SpreadJsObj.reLoadSheetData(bonusSheet);
                     });
@@ -357,6 +377,7 @@ $(document).ready(() => {
                     postData(window.location.pathname + '/update', {del: datas}, function (result) {
                         bonusObj.loadUpdateData(result);
                         SpreadJsObj.reLoadSheetData(sheet);
+                        refreshSum();
                     }, function () {
                         SpreadJsObj.reLoadSheetData(sheet);
                     });
@@ -394,6 +415,7 @@ $(document).ready(() => {
                 postData(window.location.pathname + '/update', data, function (result) {
                     bonusObj.loadUpdateData(result);
                     SpreadJsObj.reLoadSheetData(info.sheet);
+                    refreshSum();
                 }, function () {
                     SpreadJsObj.reLoadRowData(info.sheet, info.row);
                 });
@@ -457,6 +479,7 @@ $(document).ready(() => {
                     postData(window.location.pathname + '/update', updateData, function (result) {
                         bonusObj.loadUpdateData(result);
                         SpreadJsObj.reLoadSheetData(info.sheet);
+                        refreshSum();
                     });
                 } else {
                     SpreadJsObj.reLoadSheetData(info.sheet);

+ 29 - 0
app/public/js/se_other.js

@@ -162,12 +162,37 @@ $(document).ready(() => {
             this.calculateAll();
             this.resortData();
         }
+        sum () {
+            const result = {
+                total_price: 0,
+                tp: 0,
+                end_tp: 0,
+            };
+            for (const d of this.data) {
+                result.total_price = ZhCalc.add(result.total_price, d.total_price);
+                result.tp = ZhCalc.add(result.tp, d.tp);
+                result.end_tp = ZhCalc.add(result.end_tp, d.end_tp);
+            }
+            return result;
+        }
     }
     const seOtherObj = new SeOther();
+    const refreshSum = function () {
+        const sum = seOtherObj.sum();
+        const html = [];
+        const getTrHtml = function (name, value) {
+            return '<tr><td>' + name + '</td><td class="text-right">' + (!checkZero(value) ? value : '') + ' </td></tr>';
+        };
+        html.push(getTrHtml('金额', sum.total_price));
+        html.push(getTrHtml('本期金额', sum.tp));
+        html.push(getTrHtml('截止本期金额', sum.end_tp));
+        $('#sum').html(html.join(' '));
+    };
 
     postData(window.location.pathname + '/load', null, function (result) {
         seOtherObj.loadDatas(result);
         SpreadJsObj.loadSheetData(otherSheet, SpreadJsObj.DataType.Data, seOtherObj.data);
+        refreshSum();
     });
 
     if (!readOnly) {
@@ -211,6 +236,7 @@ $(document).ready(() => {
                     postData(window.location.pathname + '/update', {update: datas}, function (result) {
                         seOtherObj.loadUpdateData(result);
                         SpreadJsObj.reLoadSheetData(otherSheet);
+                        refreshSum();
                     }, function () {
                         SpreadJsObj.reLoadSheetData(otherSheet);
                     });
@@ -245,6 +271,7 @@ $(document).ready(() => {
                     postData(window.location.pathname + '/update', {del: datas}, function (result) {
                         seOtherObj.loadUpdateData(result);
                         SpreadJsObj.reLoadSheetData(otherSheet);
+                        refreshSum();
                     }, function () {
                         SpreadJsObj.reLoadSheetData(otherSheet);
                     });
@@ -282,6 +309,7 @@ $(document).ready(() => {
                 postData(window.location.pathname + '/update', data, function (result) {
                     seOtherObj.loadUpdateData(result);
                     SpreadJsObj.reLoadSheetData(info.sheet);
+                    refreshSum();
                 }, function () {
                     SpreadJsObj.reLoadRowData(info.sheet, info.row);
                 });
@@ -362,6 +390,7 @@ $(document).ready(() => {
                     postData(window.location.pathname + '/update', updateData, function (result) {
                         seOtherObj.loadUpdateData(result);
                         SpreadJsObj.reLoadSheetData(info.sheet);
+                        refreshSum();
                     });
                 } else {
                     SpreadJsObj.reLoadSheetData(info.sheet);

+ 6 - 6
app/public/js/shares/cs_tools.js

@@ -502,7 +502,7 @@ const showSelectTab = function(select, spread, afterShow) {
             '                                <div class="input-group-prepend">\n' +
             filter.join('') +
             '                                </div>' +
-            '                                <input id="searchKeyword" type="text" class="form-control" placeholder="可查找 ' + setting.searchRangeStr + '" aria-label="Recipient\'s username" aria-describedby="button-addon2">\n' +
+            '                                <input id="searchKeyword" type="text" class="form-control" autocomplete="off" placeholder="可查找 ' + setting.searchRangeStr + '" aria-label="Recipient\'s username" aria-describedby="button-addon2">\n' +
             '                                <div class="input-group-append">\n' +
             '                                    <button class="btn btn-outline-secondary" type="button"">搜索</button>\n' +
             '                                </div>\n' +
@@ -618,7 +618,7 @@ const showSelectTab = function(select, spread, afterShow) {
         html.push('</div>', '</div>');
         // 搜索框
         html.push('<div class="input-group input-group-sm">');
-        html.push('<input type="text" class="form-control" placeholder="可查找 项目节编号 / 清单编号 /名称" id="bills-tag-keyword">');
+        html.push('<input type="text" class="form-control" placeholder="可查找 项目节编号 / 清单编号 /名称" id="bills-tag-keyword" autocomplete="off">');
         html.push('<div class="input-group-append">', '<div class="input-group-cancel">',
             '<a href="javascript: void(0);" id="bills-tag-clear" class="text-danger"><i class="fa fa-times-circle" title="移除搜索结果"></i></a>', '</div>',
             '<button class="btn btn-outline-secondary" type="button" id="bills-tag-search">搜索</button>', '</div>');
@@ -635,7 +635,7 @@ const showSelectTab = function(select, spread, afterShow) {
         };
 
         const getTagEditHtml = function(tag) {
-            const tagClass = classIndexes.find(x => {return x.color === tag.color});
+            const tagClass = classIndexes.find(x => {return x.color === tag.color}) || {};
             const tagHtml = [];
             tagHtml.push('<div name="tag-edit">');
             tagHtml.push('<div class="card-header p-2"><div class="dropdown">');
@@ -652,7 +652,7 @@ const showSelectTab = function(select, spread, afterShow) {
             tagHtml.push('</div>');
 
             tagHtml.push('<div class="card-body p-2">');
-            tagHtml.push('<p class="card-text">', '<textarea class="form-control" id="tag-comment">', tag.comment, '</textarea>', '</p>');
+            tagHtml.push('<p class="card-text">', '<textarea class="form-control form-control-sm p-1" id="tag-comment">', tag.comment, '</textarea>', '</p>');
 
             tagHtml.push('<div class="d-flex justify-content-between">');
             // 参与人可见
@@ -673,7 +673,7 @@ const showSelectTab = function(select, spread, afterShow) {
         };
 
         const getTagDisplayHtml = function (tag) {
-            const tagClass = classIndexes.find(x => {return x.color === tag.color});
+            const tagClass = classIndexes.find(x => {return x.color === tag.color}) || {};
             const tagHtml = [];
             tagHtml.push('<div name="tag-view">');
             tagHtml.push('<div class="card-header p-2"><div class="dropdown">');
@@ -834,7 +834,7 @@ const showSelectTab = function(select, spread, afterShow) {
         const getBillsTagsInfo = function (id) {
             const billsTags = billsIndexes[id] || [];
             return billsTags.length > 0 ? billsTags.map(x => {
-                const tagClass = classIndexes.find(tc => {return tc.color === x.color});
+                const tagClass = classIndexes.find(tc => {return tc.color === x.color}) || {};
                 return {color: x.color, comment: x.comment, tagClass: tagClass.tagClass};
             }) : undefined;
         };

+ 45 - 37
app/public/js/shares/gcl_gather_compare.js

@@ -11,7 +11,7 @@
 
 const gclCompareModel = (function () {
     const leafXmjs = [], mergeChar = ';';
-    let gclList, gclChapter, otherChapter, chapterfilter;
+    let gclList, gclChapter, otherChapter, gclChapterFilter;
     let ledgerSetting, gsTree;
 
     function gatherfields(obj, src, fields, prefix = '') {
@@ -257,8 +257,11 @@ const gclCompareModel = (function () {
         }
     }
 
-    function _getCalcChapter(chapter) {
-        const gclChapter = [], otherChapter = [];
+    function _getCalcChapter(chapter, option) {
+        gclChapter = [];
+        otherChapter = [];
+        gclChapterFilter = [];
+
         let serialNo = 1;
         for (const c of chapter) {
             const cc = { code: c.code, name: c.name, cType: 1 };
@@ -266,11 +269,22 @@ const gclCompareModel = (function () {
             cc.filter = '^[^0-9]*' + c.code.substr(0, c.code.length - 2) + '[0-9]{2}-';
             gclChapter.push(cc);
         }
-        gclChapter.push({ name: '未计入章节清单合计', cType: 21, serialNo: serialNo++, });
-        otherChapter.push({ name: '清单小计(A)', cType: 11, serialNo: serialNo++ });
-        otherChapter.push({ name: '非清单项费用(B)', cType: 31, serialNo: serialNo++ });
-        otherChapter.push({ name: '合计(C=A+B)', cType: 41, serialNo: serialNo });
-        return [gclChapter, otherChapter];
+        gclChapter.push({ name: '未计入章节清单合计', cType: 21, serialNo: serialNo+1 });
+
+        otherChapter.hj = { name: '合计(C=A+B+Z)', cType: 41, serialNo: serialNo+5, deal_bills_tp: option.zlj.deal_bills_tp };
+        gclChapterFilter.push({node_type: option.jrg.value});
+        gclChapterFilter.push({field: 'name', part: option.jrg.text});
+        const zlChapter = {
+            name: '暂列金额(Z)', cType: 32, serialNo: serialNo+4,
+            deal_bills_tp: option.zlj.deal_bills_tp, match: [], matchPath: []
+        };
+        zlChapter.match.push({node_type: option.zlj.value});
+        zlChapter.match.push({field: 'name', part: option.zlj.text});
+        otherChapter.zlj = zlChapter;
+
+        otherChapter.qd = { name: '清单小计(A)', cType: 11, serialNo: serialNo+2 };
+        otherChapter.fqd = { name: '非清单项费用(B)', cType: 31, serialNo: serialNo+3 };
+        return[gclChapter, otherChapter, gclChapterFilter];
     }
 
     function _gatherChapterFields(chapter, data, fields) {
@@ -304,8 +318,8 @@ const gclCompareModel = (function () {
     }
 
     function _gatherChapter() {
-        const filterPath = [];
-        const checkFilterPath = function (data) {
+        const chapterFilterPath = [];
+        const checkFilterPath = function (data, filterPath) {
             for (const fp of filterPath) {
                 if (data.full_path.indexOf(fp + '-') === 0 || data.full_path === fp) return true;
             }
@@ -313,38 +327,35 @@ const gclCompareModel = (function () {
         };
 
         for (const d of gsTree.nodes) {
-            if (_checkFilter(d, chapterfilter)) {
-                filterPath.push(d.full_path);
-            }
+            if (_checkFilter(d, gclChapterFilter)) chapterFilterPath.push(d.full_path);
+            if (_checkFilter(d, otherChapter.zlj.match)) otherChapter.zlj.matchPath.push(d.full_path);
             if (d.children && d.children.length > 0) continue;
 
-            for (const c of otherChapter) {
-                if (c.cType === 41) {
-                    gatherfields(c, d, ledgerSetting.chapterFields, ledgerSetting.prefix);
+            if (checkFilterPath(d,otherChapter.zlj.matchPath)) {
+                gatherfields(otherChapter.zlj, d, ledgerSetting.chapterFields, ledgerSetting.prefix);
+                gatherfields(otherChapter.hj, d, ledgerSetting.chapterFields, ledgerSetting.prefix);
+            } else {
+                gatherfields(otherChapter.hj, d, ledgerSetting.chapterFields, ledgerSetting.prefix);
+                if (d.b_code) {
+                    gatherfields(otherChapter.qd, d, ledgerSetting.chapterFields, ledgerSetting.prefix);
                 }
-                if (c.cType === 11 && (d.b_code)) {
-                    gatherfields(c, d, ledgerSetting.chapterFields, ledgerSetting.prefix);
+                if (!d.b_code || d.b_code === '') {
+                    gatherfields(otherChapter.fqd, d, ledgerSetting.chapterFields, ledgerSetting.prefix);
                 }
-                if (c.cType === 31 && (!d.b_code || d.b_code === '')) {
+
+                if (d.b_code) {
+                    const c = checkFilterPath(d, chapterFilterPath)
+                        ? gclChapter.find(x => { return x.cType === 21})
+                        : _getGclChapter(gclChapter, d);
                     gatherfields(c, d, ledgerSetting.chapterFields, ledgerSetting.prefix);
                 }
             }
-            for (const fp of filterPath) {
-                if (d.full_path.indexOf(fp + '-') === 0 || d.full_path === fp) continue;
-            }
-            if (d.b_code) {
-                const c = checkFilterPath(d)
-                    ? gclChapter.find(x => { return x.cType === 21})
-                    : _getGclChapter(gclChapter, d);
-                gatherfields(c, d, ledgerSetting.chapterFields, ledgerSetting.prefix);
-            }
         }
     }
 
-    function init (gclData, chapter, filter) {
+    function init (gclData, chapter, option) {
         gclList = gclData;
-        [gclChapter, otherChapter] = _getCalcChapter(chapter);
-        chapterfilter = filter || [];
+        [gclChapter, otherChapter, gclChapterFilter] = _getCalcChapter(chapter, option);
     }
 
     /**
@@ -433,11 +444,8 @@ const gclCompareModel = (function () {
                 gcl.deal_bills_qty = node.quantity || 0;
                 gcl.deal_bills_tp = node.total_price || 0;
 
-                for (const c of otherChapter) {
-                    if (c.cType === 41 || c.cType === 11) {
-                        c.deal_bills_tp = ZhCalc.add(c.deal_bills_tp, node.total_price);
-                    }
-                }
+                otherChapter.hj.deal_bills_tp = ZhCalc.add(otherChapter.hj.deal_bills_tp, node.total_price);
+                otherChapter.qd.deal_bills_tp = ZhCalc.add(otherChapter.qd.deal_bills_tp, node.total_price);
                 const c = _getGclChapter(gclChapter, node);
                 c.deal_bills_tp = ZhCalc.add(c.deal_bills_tp, node.total_price);
             }
@@ -469,7 +477,7 @@ const gclCompareModel = (function () {
     }
 
     function chapterData () {
-        return gclChapter.concat(otherChapter);
+        return gclChapter.concat([otherChapter.qd, otherChapter.fqd, otherChapter.zlj, otherChapter.hj]);
     }
 
     return {

+ 34 - 0
app/public/js/shares/new_tag.js

@@ -0,0 +1,34 @@
+const newTag = function (setting) {
+    const billsTag = setting.billsTag;
+    const billsSheet = setting.ledgerSheet;
+    let relaNode;
+
+    const addTag = function (node) {
+        relaNode = node;
+        const code = (node.code || '') + (node.b_code || '');
+        $('#addtag-info').html(code + ' / ' + node.name);
+        $('#addtag').modal('show');
+        $('#addtag-content').val('');
+    };
+    $('[name=addtag-color]').click(function () {
+        $('[name=addtag-color]').removeClass('active');
+        $(this).addClass('active');
+    });
+    $('#addtag-ok').click(function () {
+        const data = {
+            add: {
+                color: $('.active[name=addtag-color]').attr('tag-color'),
+                lid: relaNode.id,
+                share: $('#addtag-share')[0].checked,
+                comment: $('#addtag-content').val(),
+            }
+        };
+        postData(window.location.pathname + '/tag', data, result => {
+            if (result.add) result.add.node = relaNode;
+            billsTag.updateDatasAndShow(result);
+            SpreadJsObj.repaintNodesRowHeader(billsSheet, relaNode);
+            $('#addtag').modal('hide');
+        });
+    });
+    return {do: addTag}
+};

+ 43 - 46
app/public/js/shenpi.js

@@ -363,7 +363,19 @@ $(document).ready(function () {
                     _self.parents('ul').append('<li class="pl-3"><a href="javascript:void(0);" class="add-audit"><i class="fa fa-plus"></i> 添加流程</a></li>');
                 }
                 _self.parents('.spr-span').html('<span class="d-inline-block"></span>\n' +
-                    '<span class="d-inline-block"><span class="badge badge-light">'+ user.name +' <a href="javascript:void(0);" class="remove-audit btn-sm text-danger px-1" title="移除" data-id="'+ user.id +'"><i class="fa fa-remove"></i></a></span> </span>');
+                    '<span class="d-inline-block"><span class="badge badge-light">'+ user.name +' <span class="dropdown">\n' +
+                    '                                                            <a href="javascript:void(0);" class="btn-sm text-danger px-1" title="移除" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><i class="fa fa-remove"></i></a>\n' +
+                    '                                                            <div class="dropdown-menu">\n' +
+                    '                                                                <a class="dropdown-item" href="javascript:void(0);">确认移除审批人?</a>\n' +
+                    '                                                                <div class="dropdown-divider"></div>\n' +
+                    '                                                                <div class="px-2 py-1 text-center">\n' +
+                    '                                                                    <button class="remove-audit btn btn-sm btn-danger" data-id="' + user.id + '">移除</button>\n' +
+                    '                                                                    <button class="btn btn-sm btn-secondary">取消</button>\n' +
+                    '                                                                </div>\n' +
+                    '                                                            </div>\n' +
+                    '                                                        </span> ' +
+                    '                                            </span></span></span>\n');
+                    // <a href="javascript:void(0);" class="remove-audit btn-sm text-danger px-1" title="移除" data-id="'+ user.id +'"><i class="fa fa-remove"></i></a></span> </span>');
             });
         }
     });
@@ -425,8 +437,19 @@ $(document).ready(function () {
             '                                            <span class="col-auto">'+ i +'审</span>\n' +
             '                                            <span class="col-7 spr-span">\n' +
             '                                            <span class="d-inline-block"></span>\n' +
-            '                                            <span class="d-inline-block"><span class="badge badge-light">'+ audit.name +' <a href="javascript:void(0);" class="remove-audit btn-sm text-danger px-1" title="移除" data-id="'+ audit.audit_id +'"><i class="fa fa-remove"></i></a></span> </span>\n' +
-            '                                            </span>\n' +
+            '                                            <span class="d-inline-block"><span class="badge badge-light">'+ audit.name +' <span class="dropdown">\n' +
+            '                                                            <a href="javascript:void(0);" class="btn-sm text-danger px-1" title="移除" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><i class="fa fa-remove"></i></a>\n' +
+            '                                                            <div class="dropdown-menu">\n' +
+            '                                                                <a class="dropdown-item" href="javascript:void(0);">确认移除审批人?</a>\n' +
+            '                                                                <div class="dropdown-divider"></div>\n' +
+            '                                                                <div class="px-2 py-1 text-center">\n' +
+            '                                                                    <button class="remove-audit btn btn-sm btn-danger" data-id="' + audit.audit_id + '">移除</button>\n' +
+            '                                                                    <button class="btn btn-sm btn-secondary">取消</button>\n' +
+            '                                                                </div>\n' +
+            '                                                            </div>\n' +
+            '                                                        </span> ' +
+            // '<a href="javascript:void(0);" class="remove-audit btn-sm text-danger px-1" title="移除" data-id="'+ audit.audit_id +'"><i class="fa fa-remove"></i></a></span> </span>\n' +
+            '                                            </span></span></span>\n' +
             '                                        </li>';
     }
 
@@ -645,7 +668,7 @@ $(document).ready(function () {
         },
         setAllRightPwd: function(uid) {
             selects = [];
-            for (const l of ledgerTree.datas) {
+            for (const l of ledgerTree.nodes) {
                 const coo = _.find(ledger_cooperation_list, { 'ledger_id': l.ledger_id, 'user_id': parseInt(uid) });
                 if (l.pwd && !coo) {
                     delete l.pwd;
@@ -781,7 +804,7 @@ $(document).ready(function () {
                         flag = false;
                     }
                     $('#cooperation-num').text(ledger_cooperation_list.length);
-                    setLeftTable(ledgerTree.datas, ledger_cooperation_list, user_id, $('#stage_audits option:selected').text());
+                    setLeftTable(ledgerTree.nodes, ledger_cooperation_list, user_id, $('#stage_audits option:selected').text());
                     selects = [select];
                     // if(select) {
                     //     setAllChildrenCanEdit(select, flag);
@@ -822,7 +845,7 @@ $(document).ready(function () {
         // 更新新的多人协同表格信息
         const newUidList = [];
         $('.stage_div ul li').each(function (k, v) {
-            const uid = $(v).find('a').data('id');
+            const uid = $(v).find('button').eq(0).data('id');
             if(uid) newUidList.push(uid);
         });
         const oldUidList = [];
@@ -841,7 +864,7 @@ $(document).ready(function () {
             }
             $('#stage_audits').html(newhtml);
             if(ledger_data) {
-                setLeftTable(ledgerTree.datas, ledger_cooperation_list, cur_uid, yb.name + '(原报)');
+                setLeftTable(ledgerTree.nodes, ledger_cooperation_list, cur_uid, yb.name + '(原报)');
                 ledgerSpreadObj.setAllRightPwd(cur_uid);
             }
         }
@@ -850,14 +873,14 @@ $(document).ready(function () {
                 ledger_data = true;
                 const ledgerList = setRightData(data.ledgerList, data.ledgerCooperationList);
                 ledgerTree.loadDatas(ledgerList);
-                ledger_cooperation_list = data.ledgerCooperationList;
-                const yb = _.find(accountList, { 'id': cur_uid });
-                setLeftTable(ledgerList, ledger_cooperation_list, cur_uid, yb.name + '(原报)');
                 // treeCalc.calculateAll(ledgerTree);
                 selects = [];
                 // updateByCanEdit(ledgerTree.nodes, _.filter(ledger_cooperation_list, { user_id: cur_uid }), false);
                 ledgerTree.loadPostData({update: selects});
                 console.log(ledgerTree);
+                ledger_cooperation_list = data.ledgerCooperationList;
+                const yb = _.find(accountList, { 'id': cur_uid });
+                setLeftTable(ledgerTree.nodes, ledger_cooperation_list, cur_uid, yb.name + '(原报)');
                 SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), SpreadJsObj.DataType.Tree, ledgerTree);
                 ledgerSpreadObj.setFontColor();
             }, null, true);
@@ -868,7 +891,7 @@ $(document).ready(function () {
     $('#stage_audits').change(function () {
         const uid = $(this).val();
         const title = $("#stage_audits option:selected").text();
-        setLeftTable(ledgerTree.datas, ledger_cooperation_list, uid, title);
+        setLeftTable(ledgerTree.nodes, ledger_cooperation_list, uid, title);
         ledgerSpreadObj.setAllRightPwd(uid);
     });
 
@@ -884,8 +907,8 @@ $(document).ready(function () {
         postData('/tender/' + cur_tenderid + '/shenpi/audit/save', data, function (result) {
             const lcindex = _.findIndex(ledger_cooperation_list, { ledger_id, user_id });
             ledger_cooperation_list.splice(lcindex, 1);
-            setLeftTable(ledgerTree.datas, ledger_cooperation_list, user_id, $('#stage_audits option:selected').text());
-            const select = _.find(ledgerTree.datas, { ledger_id });
+            setLeftTable(ledgerTree.nodes, ledger_cooperation_list, user_id, $('#stage_audits option:selected').text());
+            const select = _.find(ledgerTree.nodes, { ledger_id });
             delete select.pwd;
             // const refreshNode = ledgerTree.loadPostData({update: select});
             selects = [select];
@@ -946,8 +969,8 @@ $(document).ready(function () {
         postData('/tender/' + cur_tenderid + '/shenpi/audit/save', data, function (result) {
             const lcindex = _.findIndex(ledger_cooperation_list, { ledger_id, user_id });
             ledger_cooperation_list.splice(lcindex, 1, result);
-            setLeftTable(ledgerTree.datas, ledger_cooperation_list, user_id, $('#stage_audits option:selected').text());
-            const select = _.find(ledgerTree.datas, { ledger_id });
+            setLeftTable(ledgerTree.nodes, ledger_cooperation_list, user_id, $('#stage_audits option:selected').text());
+            const select = _.find(ledgerTree.nodes, { ledger_id });
             select.pwd = validText;
             const refreshNode = ledgerTree.loadPostData({update: select});
             ledgerSpreadObj.refreshTree(ledgerSpread.getActiveSheet(), refreshNode);
@@ -1063,36 +1086,6 @@ function setAllChildrenCanEdit(ledgerInfo, flag) {
         }
     }
 }
-// 编号排序,多重判断
-function sortByCode(a, b) {
-    let code1 = a.code.split('-');
-    let code2 = b.code.split('-');
-    let code1length = code1.length;
-    let code2length = code2.length;
-    for (let i = 0; i < code1length; i ++) {
-        if (i+1 <= code2length) {
-            if (code1[i] != code2[i]) {
-                if (!/^\d+$/.test(code1[i])) {
-                    return code1[i].charCodeAt() - code2[i].charCodeAt();
-                } else {
-                    return parseInt(code1[i]) - parseInt(code2[i]);
-                }
-            } else if (i+1 == code1length && code1[i] == code2[i]) {
-                if (code1length == code2length) {
-                    return 0;
-                } else {
-                    return code1length - code2length;
-                }
-            }
-        } else {
-            if (i+1 >= code1length) {
-                return 1;
-            } else {
-                return -1;
-            }
-        }
-    }
-}
 function setLeftTable(ledgerList, coolist, uid, title) {
     $('#stage_audit').text(title);
     const showCooList = _.filter(coolist, { 'user_id': parseInt(uid) });
@@ -1113,7 +1106,11 @@ function setLeftTable(ledgerList, coolist, uid, title) {
     }
 
     let html = '';
-    for (const sc of showCooList.sort(sortByCode)) {
+    // showCooList 需要根据右边台账顺序进行排序
+    showCooList.sort(function (a, b) {
+        return _.findIndex(ledgerList, { ledger_id: a.ledger_id }) - _.findIndex(ledgerList, { ledger_id: b.ledger_id });
+    });
+    for (const sc of showCooList) {
         const pichtml = sc.sign_path ? `<img src="/${sc.sign_path}" width="60"><input type="file" data-id="${sc.id}" class="upload-img-file" style="display: none;"><a href="javascript: void(0);" class="d-inline-flex upload-img">更改</a>`
             : `<img src="" style="display: none" width="60"><input type="file" data-id="${sc.id}" class="upload-img-file" style="display: none;"><a href="javascript: void(0);" class="btn btn-outline-primary btn-sm upload-img">上传签名</a>`;
         html += `<tr>` +

+ 5 - 5
app/public/js/spreadjs_rela/spreadjs_zh.js

@@ -852,7 +852,7 @@ const SpreadJsObj = {
     },
     reLoadNodesData: function (sheet, nodes) {
         this.beginMassOperation(sheet);
-        nodes = nodes instanceof Array ? nodes : [nodes];rectangle
+        nodes = nodes instanceof Array ? nodes : [nodes];
         const sortData = sheet.zh_dataType === 'tree' ? sheet.zh_tree.nodes : sheet.zh_data;
         for (const node of nodes) {
             this._loadRowData(sheet, node, sortData.indexOf(node));
@@ -1856,7 +1856,7 @@ const SpreadJsObj = {
                 const [col, img] = this.getActiveImage(options.sheet, options.row, options.col);
 
                 const indent = col.indent ? col.indent : 10;
-                if (style.hAlign === spreadNS.HorizontalAlign.right) {
+                if (style.hAlign === spreadNS.HorizontalAlign.right || col.imgAlign === spreadNS.HorizontalAlign.right) {
                     if (img) {
                         if (style.backColor) {
                             canvas.save();
@@ -1958,7 +1958,7 @@ const SpreadJsObj = {
 
                 const halfX = img.width / 2, halfY = img.height / 2;
                 const indent = col.indent ? col.indent : 10;
-                const centerX = hitinfo.cellStyle.hAlign === spreadNS.HorizontalAlign.right
+                const centerX = (hitinfo.cellStyle.hAlign === spreadNS.HorizontalAlign.right || col.imgAlign === spreadNS.HorizontalAlign.right)
                     ? hitinfo.cellRect.x + hitinfo.cellRect.width - indent -halfX
                     : hitinfo.cellRect.x + indent + halfX;
                 const centerY = hitinfo.cellRect.y + hitinfo.cellRect.height / 2;
@@ -1975,7 +1975,7 @@ const SpreadJsObj = {
 
                 const halfX = img.width / 2, halfY = img.height / 2;
                 const indent = col.indent ? col.indent : 10;
-                const centerX = hitinfo.cellStyle.hAlign === spreadNS.HorizontalAlign.right
+                const centerX = (hitinfo.cellStyle.hAlign === spreadNS.HorizontalAlign.right || col.imgAlign === spreadNS.HorizontalAlign.right)
                     ? hitinfo.cellRect.x + hitinfo.cellRect.width - indent -halfX
                     : hitinfo.cellRect.x + indent + halfX;
                 const centerY = hitinfo.cellRect.y + hitinfo.cellRect.height / 2;
@@ -1998,7 +1998,7 @@ const SpreadJsObj = {
 
                 const halfX = img.width / 2, halfY = img.height / 2;
                 const indent = col.indent ? col.indent : 10;
-                const centerX = hitinfo.cellStyle.hAlign === spreadNS.HorizontalAlign.right
+                const centerX = (hitinfo.cellStyle.hAlign === spreadNS.HorizontalAlign.right || col.imgAlign === spreadNS.HorizontalAlign.right)
                     ? hitinfo.cellRect.x + hitinfo.cellRect.width - indent -halfX
                     : hitinfo.cellRect.x + indent + halfX;
                 const centerY = hitinfo.cellRect.y + hitinfo.cellRect.height / 2;

+ 238 - 180
app/public/js/stage.js

@@ -31,7 +31,6 @@ function getExprInfo (field) {
  */
 function customColDisplay () {
     const defaultSetting = [
-        { title: '签约合同', fields: ['deal_qty', 'deal_tp'], visible: true },
         { title: '本期计量合同', fields: ['contract_qty', 'contract_tp'], visible: true },
         { title: '本期数量变更', fields: ['qc_qty', 'qc_tp', 'qc_bgl'], visible: true },
         { title: '本期完成计量', fields: ['gather_qty', 'gather_tp'], visible: true },
@@ -43,11 +42,18 @@ function customColDisplay () {
         { title: '备注', fields: ['memo'], visible: true },
         { title: '总额计量', fields: ['is_tp'], visible: true},
     ];
-    if (checkTzMeasureType()) {
-        defaultSetting.splice(0, 1);
+    if (!checkTzMeasureType()) {
+        defaultSetting.unshift({ title: '签约合同', fields: ['deal_qty', 'deal_tp'], visible: true }, { title: '台账', fields: ['quantity', 'total_price'], visible: true});
     }
     const settingStr = Cookies.get(ckColSetting);
-    return settingStr ? JSON.parse(settingStr) : defaultSetting;
+    if (settingStr) {
+        const customSetting = JSON.parse(settingStr);
+        for (const ds of defaultSetting) {
+            const cs = customSetting.find(x => {return x.title === ds.title});
+            if (cs) ds.visible = cs.visible;
+        }
+    }
+    return defaultSetting;
 }
 
 /**
@@ -168,6 +174,44 @@ function getDaglText(data) {
 
 
 $(document).ready(() => {
+    const exportExcelSetting = {
+        cols: [
+            {title: '项目节编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 145, formatter: '@', cellType: 'tree'},
+            {title: '清单编号', colSpan: '1', rowSpan: '2', field: 'b_code', hAlign: 0, width: 70, formatter: '@'},
+            {title: '计量单元', colSpan: '1', rowSpan: '2', field: 'pos_code', hAlign: 0, width: 70, formatter: '@'},
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 185, formatter: '@'},
+            {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 60, formatter: '@', cellType: 'unit'},
+            {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, type: 'Number'},
+            {title: '台账|数量', colSpan: '2|1', rowSpan: '1|1', field: 'quantity', hAlign: 2, width: 60, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'total_price', hAlign: 2, width: 60, type: 'Number'},
+            {title: '本期合同计量|数量', colSpan: '2|1', rowSpan: '1|1', field: 'contract_qty', hAlign: 2, width: 60, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'contract_tp', hAlign: 2, width: 60, type: 'Number'},
+            {title: '本期数量变更|数量', colSpan: '2|1', rowSpan: '1|1', field: 'qc_qty', hAlign: 2, width: 60, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'qc_tp', hAlign: 2, width: 60, type: 'Number'},
+            {title: '本期完成计量|数量', colSpan: '2|1', rowSpan: '1|1', field: 'gather_qty', hAlign: 2, width: 60, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'gather_tp', hAlign: 2, width: 60, type: 'Number'},
+            {title: '截止本期合同计量|数量', colSpan: '2|1', rowSpan: '1|1', field: 'end_contract_qty', hAlign: 2, width: 60, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_contract_tp', hAlign: 2, width: 60, type: 'Number'},
+            {title: '截止本期数量变更|数量', colSpan: '2|1', rowSpan: '1|1', field: 'end_qc_qty', hAlign: 2, width: 60, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_qc_tp', hAlign: 2, width: 60, type: 'Number'},
+            {title: '截止本期完成计量|数量', colSpan: '3|1', rowSpan: '1|1', field: 'end_gather_qty', hAlign: 2, width: 60, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_gather_tp', hAlign: 2, width: 60, type: 'Number'},
+            {title: '|完成率(%)', colSpan: '|1', rowSpan: '|1', field: 'end_gather_percent', hAlign: 2, width: 80, type: 'Number'},
+            {title: '合同|项目节数量1',  colSpan: '2|1', rowSpan: '1|1', field: 'deal_dgn_qty1', hAlign: 2, width: 60, type: 'Number'},
+            {title: '|项目节数量2',  colSpan: '|1', rowSpan: '|1', field: 'deal_dgn_qty2', hAlign: 2, width: 60, type: 'Number'},
+            {title: '变更|项目节数量1',  colSpan: '2|1', rowSpan: '1|1', field: 'c_dgn_qty1', hAlign: 2, width: 60, type: 'Number'},
+            {title: '|项目节数量2',  colSpan: '|1', rowSpan: '|1', field: 'c_dgn_qty2', hAlign: 2, width: 60, type: 'Number'},
+            {title: '经济指标',  colSpan: '1', rowSpan: '2', field: 'final_dgn_price', hAlign: 2, width: 60, type: 'Number'},
+            {title: '本期批注', colSpan: '1', rowSpan: '2', field: 'postil', hAlign: 0, width: 100, formatter: '@', cellType: 'autoTip'},
+            {title: '图(册)号', colSpan: '1', rowSpan: '2', field: 'drawing_code', hAlign: 0, width: 80, formatter: '@'},
+            {title: '备注', colSpan: '1', rowSpan: '2', field: 'memo', hAlign: 0, width: 100, formatter: '@', cellType: 'ellipsisAutoTip'},
+        ],
+        headRows: 2,
+        headRowHeight: [25, 25],
+        defaultRowHeight: 21,
+        headerFont: 'bold 10px 微软雅黑',
+        font: '10px 微软雅黑'
+    };
     let detail, searchLedger, checkedChanges;
     const checkOption = {
         sibling: { enable: 0 },
@@ -555,13 +599,18 @@ $(document).ready(() => {
     };
     const ratioCol = ledgerSpreadSetting.cols.find(x => {return x.field === 'end_gather_percent' || x.field === 'end_correct_percent'});
     ratioCol.field = tenderInfo.display.stage.correct ? 'end_correct_percent' : 'end_gather_percent';
-    ledgerSpreadSetting.imageClick = function (data) {
-        if (data.children && data.children.length > 0 || data.lock) return;
+    ledgerSpreadSetting.imageClick = function (data, hitinfo) {
+        const col = hitinfo.sheet.zh_setting.cols[hitinfo.col];
+        if (col.field === 'dagl') {
+            data.dagl_url && window.open(data.dagl_url);
+        } else if (col.field === 'qc_qty') {
+            if (data.children && data.children.length > 0 || data.lock) return;
 
-        const nodePos = stagePos.getLedgerPos(data.id);
-        if (nodePos && nodePos.length > 0) return;
+            const nodePos = stagePos.getLedgerPos(data.id);
+            if (nodePos && nodePos.length > 0) return;
 
-        changesObj.loadChanges({bills: data});
+            changesObj.loadChanges({bills: data});
+        }
     };
     ledgerSpreadSetting.dgnUpFields = ['deal_dgn_qty1', 'deal_dgn_qty2', 'c_dgn_qty1', 'c_dgn_qty2'];
     ledgerSpreadSetting.getColor = function (sheet, data, row, col, defaultColor) {
@@ -604,6 +653,15 @@ $(document).ready(() => {
     sjsSettingObj.setFxTreeStyle(ledgerSpreadSetting, sjsSettingObj.FxTreeStyle.jz);
     sjsSettingObj.setPropValue(ledgerSpreadSetting, ['gxby'], 'getValue', getGxbyText);
     sjsSettingObj.setPropValue(ledgerSpreadSetting, ['dagl'], 'getValue', getDaglText);
+    const lDaglCol = _.find(ledgerSpreadSetting.cols, {field: 'dagl'});
+    if (lDaglCol) {
+        lDaglCol.getValue = getDaglText;
+        lDaglCol.cellType = 'activeImageBtn';
+        lDaglCol.normalImg = '#rela-file-icon';
+        lDaglCol.indent = 5;
+        lDaglCol.imgAlign = 2;
+        lDaglCol.showImage = function (data) { return data && data.dagl_url; }
+    }
     if (thousandth) sjsSettingObj.setTpThousandthFormat(ledgerSpreadSetting);
     ledgerSpreadSetting.headColWidth = [50];
     ledgerSpreadSetting.rowHeader = [
@@ -656,10 +714,15 @@ $(document).ready(() => {
     spCol.showImage = function (data) {
         return data !== undefined && data !== null;
     };
-    posSpreadSetting.imageClick = function (data) {
-        const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
-        if (node.lock) return;
-        changesObj.loadChanges({bills: node, pos: data});
+    posSpreadSetting.imageClick = function (data, hitinfo) {
+        const col = hitinfo.sheet.zh_setting.cols[hitinfo.col];
+        if (col.field === 'dagl') {
+            data.dagl_url && window.open(data.dagl_url);
+        } else if (col.field === 'qc_qty') {
+            const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
+            if (node.lock) return;
+            changesObj.loadChanges({bills: node, pos: data});
+        }
     };
     posSpreadSetting.getColor = function (sheet, data, row, col, defaultColor) {
         if (data) {
@@ -683,6 +746,15 @@ $(document).ready(() => {
     if (thousandth) sjsSettingObj.setTpThousandthFormat(posSpreadSetting);
     sjsSettingObj.setPropValue(posSpreadSetting, ['gxby'], 'getValue', getGxbyText);
     sjsSettingObj.setPropValue(posSpreadSetting, ['dagl'], 'getValue', getDaglText);
+    const pDaglCol = _.find(posSpreadSetting.cols, {field: 'dagl'});
+    if (pDaglCol) {
+        pDaglCol.getValue = getDaglText;
+        pDaglCol.cellType = 'activeImageBtn';
+        pDaglCol.normalImg = '#rela-file-icon';
+        pDaglCol.indent = 5;
+        pDaglCol.imgAlign = 2;
+        pDaglCol.showImage = function (data) { return data && data.dagl_url; }
+    }
     SpreadJsObj.initSheet(spSpread.getActiveSheet(), posSpreadSetting);
 
     const billsTag = $.billsTag({
@@ -1224,6 +1296,45 @@ $(document).ready(() => {
                 stagePosSpreadObj.loadCurPosData();
                 $('#calc-by-ratio').modal('hide');
             });
+        },
+        exportExcel: function (filename, nodes) {
+            const exportNodesData = function (data, nodes) {
+                for (const node of nodes) {
+                    data.push({
+                        code: node.code, b_code: node.b_code, name: node.name, unit: node.unit,
+                        unit_price: node.unit_price, quantity: node.quantity, total_price: node.total_price,
+                        contract_qty: node.contract_qty, contract_tp: node.contract_tp,
+                        qc_qty: node.qc_qty, qc_tp: node.qc_tp,
+                        gather_qty: node.gather_qty, gather_tp: node.gather_tp,
+                        end_contract_qty: node.end_contract_qty, end_contract_tp: node.end_contract_tp,
+                        end_qc_qty: node.end_qc_qty, end_qc_tp: node.end_qc_tp,
+                        end_gather_qty: node.end_gather_qty, end_gather_tp: node.end_gather_tp, end_gather_percent: node.end_gather_percent,
+                        deal_dgn_qty1: node.deal_dgn_qty1, deal_dgn_qty2: node.deal_dgn_qty2,
+                        c_dgn_qty1: node.c_dgn_qty1, c_dgn_qty2: node.c_dgn_qty2,
+                        final_dgn_price: node.final_dgn_price,
+                        postil: node.postil, drawing_code: node.drawing_code, memo: node.memo,
+                    });
+                    if (node.children && node.children.length > 0) {
+                        exportNodesData(data, node.children);
+                    } else {
+                        const posRange = stagePos.getLedgerPos(node.id);
+                        if (posRange && posRange.length > 0) {
+                            for (const [i, p] of posRange.entries()) {
+                                data.push({
+                                    pos_code: (i + 1) + '', name: p.name,
+                                    quantity: p.quantity,
+                                    contract_qty: p.contract_qty, qc_qty: p.qc_qty, gather_qty: p.gather_qty,
+                                    end_contract_qty: p.end_contract_qty, end_qc_qty: p.end_qc_qty, end_gather_qty: p.end_gather_qty,
+                                    drawing_code: p.drawing_code, memo: p.memo, postil: p.postil,
+                                });
+                            }
+                        }
+                    }
+                }
+            };
+            const data = [];
+            exportNodesData(data, nodes ? nodes : stageTree.children);
+            SpreadExcelObj.exportSimpleXlsxSheet(exportExcelSetting, data, filename);
         }
     };
     slSpread.bind(spreadNS.Events.EditEnded, stageTreeSpreadObj.editEnded);
@@ -1283,11 +1394,10 @@ $(document).ready(() => {
         });
     }
     stageTreeSpreadObj.loadExprToInput(slSpread.getActiveSheet());
-    let addTagShare = true;
+    const addTag = newTag({ledgerSheet: slSpread.getActiveSheet(), billsTag});
     $.contextMenu({
         selector: '#stage-ledger',
         build: function ($trigger, e) {
-            addTagShare = true;
             const target = SpreadJsObj.safeRightClickSelection($trigger, e, slSpread);
             return target.hitTestType === spreadNS.SheetArea.viewport || target.hitTestType === spreadNS.SheetArea.rowHeader;
         },
@@ -1324,82 +1434,30 @@ $(document).ready(() => {
                     }
                 },
             },
+            tagSpr: '----',
             tag: {
                 name: '书签',
-                items: {
-                    tagShare: {
-                        name: '参与人可见',
-                        type: 'checkbox',
-                        selected: true,
-                        events: {
-                            change: function () {
-                                addTagShare = this.checked;
-                            }
-                        }
-                    },
-                    tagSpr: '--------------',
-                    tagPrimary: {
-                        icon: 'fa-tag text-primary mt-2 mb-2',
-                        name: '靛青',
-                        callback: function (key, opt, menu, e) {
-                            const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
-                            postData(window.location.pathname + '/tag', {add: { color: '#007bff', lid: node.id, share: addTagShare }}, function (data) {
-                                if (data.add) data.add.node = node;
-                                billsTag.updateDatasAndShow(data);
-                                SpreadJsObj.repaintNodesRowHeader(slSpread.getActiveSheet(), node);
-                            });
-                        },
-                    },
-                    tagSuccess: {
-                        icon: 'fa-tag text-success mt-2 mb-2',
-                        name: '果绿',
-                        callback: function (key, opt) {
-                            const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
-                            postData(window.location.pathname + '/tag', {add: { color: '#28a745', lid: node.id, share: addTagShare }}, function (data) {
-                                if (data.add) data.add.node = node;
-                                billsTag.updateDatasAndShow(data);
-                                SpreadJsObj.repaintNodesRowHeader(slSpread.getActiveSheet(), node);
-                            });
-                        },
-                    },
-                    tagDanger: {
-                        icon: 'fa-tag text-danger mt-2 mb-2',
-                        name: '朱砂',
-                        callback: function (key, opt) {
-                            const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
-                            postData(window.location.pathname + '/tag', {add: { color: '#dc3545', lid: node.id, share: addTagShare }}, function (data) {
-                                if (data.add) data.add.node = node;
-                                billsTag.updateDatasAndShow(data);
-                                SpreadJsObj.repaintNodesRowHeader(slSpread.getActiveSheet(), node);
-                            });
-                        },
-                    },
-                    tagWarning: {
-                        icon: 'fa-tag text-warning mt-2 mb-2',
-                        name: '姜黄',
-                        callback: function (key, opt) {
-                            const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
-                            postData(window.location.pathname + '/tag', {add: { color: '#da9500', lid: node.id, share: addTagShare }}, function (data) {
-                                if (data.add) data.add.node = node;
-                                billsTag.updateDatasAndShow(data);
-                                SpreadJsObj.repaintNodesRowHeader(slSpread.getActiveSheet(), node);
-                            });
-                        },
-                    },
-                    tagInfo: {
-                        icon: 'fa-tag text-info mt-2 mb-2',
-                        name: '天蓝',
-                        callback: function (key, opt) {
-                            const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
-                            postData(window.location.pathname + '/tag', {add: { color: '#17a2b8', lid: node.id, share: addTagShare }}, function (data) {
-                                if (data.add) data.add.node = node;
-                                billsTag.updateDatasAndShow(data);
-                                SpreadJsObj.repaintNodesRowHeader(slSpread.getActiveSheet(), node);
-                            });
-                        },
-                    }
+                callback: function (key, opt, menu, e) {
+                    const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
+                    addTag.do(node);
                 },
+                disabled: function (key, opt) {
+                    const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
+                    return !node;
+                }
             },
+            exportSpr: '----',
+            exportSelectNodeXlsx: {
+                name: '导出选中节点至Excel',
+                callback: function (key, opt, menu, e) {
+                    const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
+                    stageTreeSpreadObj.exportExcel($('.sidebar-title').attr('data-original-title') + `计量台账(${node.name || ''}).xlsx`, [node]);
+                },
+                disabled: function (key, opt) {
+                    const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
+                    return !node;
+                }
+            }
         }
     });
 
@@ -1833,6 +1891,10 @@ $(document).ready(() => {
             if (stageTree.pwd.length > 0) $('#cooperationCount').parent().show();
             reloadCooperationHtml();
         }
+        for (const t of result.tags) {
+            t.node = stageTree.datas.find(x => {return x.id === t.lid});
+        }
+        billsTag.loadDatas(result.tags);
         // 加载部位明细
         stagePos.loadDatas(result.posData);
         stagePos.calculateAll();
@@ -1844,10 +1906,6 @@ $(document).ready(() => {
         stageIm.init(stage, imType, tenderInfo.decimal);
         stageIm.loadData(result.ledgerData, result.posData, result.detailData, result.changeData);
 
-        for (const t of result.tags) {
-            t.node = stageTree.datas.find(x => {return x.id === t.lid});
-        }
-        billsTag.loadDatas(result.tags);
         errorList.loadHisErrorData();
         checkList.loadHisCheckData();
     }, null, true);
@@ -2874,6 +2932,39 @@ $(document).ready(() => {
                     }
                 });
             }
+            function upload(formData) {
+                if (formData.length < 1) {
+                    return;
+                }
+                postDataWithFile(window.location.pathname + '/detail/add-img', formData, function (result) {
+                    const html = [];
+                    html.push('<div class="img-item">');
+                    html.push('<div class="img-bar">');
+                    html.push('<a href="javascript: void(0);" class="text-danger" title="删除"><i class="fa fa-remove" style="font-size: 24px"></i></a>');
+                    html.push('</div>');
+                    html.push('<div class="focus" style="width:100%; height:100%"><img src="', '/' + result, '" id="draggable" style="width:100%; height:100%"></div>');
+                    html.push('</div>');
+                    $('.img-view').append(html.join(''));
+                    $('.img-bar').click(removeImageItem);
+                    setdraggrable();
+                    $('#upload-img-file').val('');
+                });
+            }
+            function dragFile(e) {
+                var file = null,
+                    data = e.dataTransfer.types;
+                for (var i = 0; i < data.length; i += 1) {
+                    if (data[i] === 'Files') {
+                        file = e.dataTransfer.files; //获取文件
+                        break;
+                    }
+                }
+                if (file && file[0].type.indexOf('image') !== -1) {
+                    var formData = new FormData();
+                    formData.append('file', file[0]); //上传单个文件的添加方式
+                    upload(formData); //upload 异步上传
+                }
+            }
             // 移动图片
             const moveImageItem = function (ev) {
                 const item = this;
@@ -2934,6 +3025,42 @@ $(document).ready(() => {
             $('#upload-img').click(function () {
                 $('#upload-img-file').trigger('click');
             });
+            // 拖拽上传
+            //拖拽上传文件
+            const dragbox = document.querySelector('#upload-img');
+            dragbox.addEventListener('dragover', function(e) {
+                e.preventDefault(); // 必须阻止默认事件
+            }, false);
+            dragbox.addEventListener('drop', function(e) {
+                e.preventDefault(); // 阻止默认事件
+                dragFile(e);
+            }, false);
+            //拖拽上传文件
+            const dragbox2 = document.querySelector('.img-view');
+            dragbox2.addEventListener('dragover', function(e) {
+                e.preventDefault(); // 必须阻止默认事件
+            }, false);
+            dragbox2.addEventListener('drop', function(e) {
+                e.preventDefault(); // 阻止默认事件
+                dragFile(e);
+            }, false);
+            // 粘贴上传
+            $(".img-view").on('paste', function(ev) {
+                let file = null;
+                const data = (event.clipboardData || window.clipboardData).items;
+                for (let i = 0; i < data.length; i += 1) {
+                    if ((data[i].kind == 'file') && (data[i].type.match('^image/'))) {
+                        file = data[i].getAsFile(); //读取图片文件
+                        break;
+                    }
+                }
+                if (file) {
+                    const formData = new FormData();
+                    formData.append('file', file); //上传单个文件的添加方式
+                    upload(formData); //异步上传文件
+                }
+            });
+
             $('#upload-img-file').change(function () {
                 const file = this.files[0];
                 const ext = file.name.toLowerCase().split('.').splice(-1)[0];
@@ -2945,19 +3072,7 @@ $(document).ready(() => {
                 if ($(this).val()) {
                     const formData = new FormData();
                     formData.append('file', this.files[0]);
-                    postDataWithFile(window.location.pathname + '/detail/add-img', formData, function (result) {
-                        const html = [];
-                        html.push('<div class="img-item">');
-                        html.push('<div class="img-bar">');
-                        html.push('<a href="javascript: void(0);" class="text-danger" title="删除"><i class="fa fa-remove" style="font-size: 24px"></i></a>');
-                        html.push('</div>');
-                        html.push('<div class="focus" style="width:100%; height:100%"><img src="', '/' + result, '" id="draggable" style="width:100%; height:100%"></div>');
-                        html.push('</div>');
-                        $('.img-view').append(html.join(''));
-                        $('.img-bar').click(removeImageItem);
-                        setdraggrable();
-                        $('#upload-img-file').val('');
-                    });
+                    upload(formData);
                 }
             });
 
@@ -3187,15 +3302,26 @@ $(document).ready(() => {
             this.changeSheet = this.changeSpread.getActiveSheet();
             SpreadJsObj.initSheet(this.changeSheet, this.changeSpreadSetting);
 
+            const getTipText = function (data) {
+                if (!data) return '';
+
+                const tips = [];
+                if (data.xmj_dwgc) tips.push(data.xmj_dwgc);
+                if (data.xmj_fbgc) tips.push(data.xmj_fbgc);
+                if (data.xmj_fxgc) tips.push(data.xmj_fxgc);
+                if (data.xmj_jldy) tips.push(data.xmj_jldy);
+                return tips.join('-');
+            };
+
             this.changeBillsSpreadSetting = {
                 cols: [
-                    {title: '清单编号', colSpan: '1', rowSpan: '1', field: 'code', hAlign: 0, width: 80, formatter: '@'},
-                    {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 150, type: 'Number'},
-                    {title: '单位', colSpan: '1', rowSpan: '1', field: 'unit', hAlign: 1, width: 50, formatter: '@'},
-                    {title: '单价', colSpan: '1', rowSpan: '1', field: 'unit_price', hAlign: 2, width: 60, type: 'Number'},
-                    {title: '数量', colSpan: '1', rowSpan: '1', field: 'qty', hAlign: 2, width: 60, formatter: '@'},
-                    {title: '金额', colSpan: '1', rowSpan: '1', field: 'tp', hAlign: 2, width: 60, formatter: '@'},
-                    {title: '变更部位', colSpan: '1', rowSpan: '1', field: 'bwmx', hAlign: 0, width: 100, formatter: '@'},
+                    {title: '清单编号', colSpan: '1', rowSpan: '1', field: 'code', hAlign: 0, width: 80, formatter: '@', cellType: 'tip', getTip: getTipText},
+                    {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 150, type: 'Number', cellType: 'tip', getTip: getTipText},
+                    {title: '单位', colSpan: '1', rowSpan: '1', field: 'unit', hAlign: 1, width: 50, formatter: '@', cellType: 'tip', getTip: getTipText},
+                    {title: '单价', colSpan: '1', rowSpan: '1', field: 'unit_price', hAlign: 2, width: 60, type: 'Number', cellType: 'tip', getTip: getTipText},
+                    {title: '数量', colSpan: '1', rowSpan: '1', field: 'qty', hAlign: 2, width: 60, formatter: '@', cellType: 'tip', getTip: getTipText},
+                    {title: '金额', colSpan: '1', rowSpan: '1', field: 'tp', hAlign: 2, width: 60, formatter: '@', cellType: 'tip', getTip: getTipText},
+                    {title: '变更部位', colSpan: '1', rowSpan: '1', field: 'bwmx', hAlign: 0, width: 100, formatter: '@', cellType: 'tip', getTip: getTipText},
                 ],
                 emptyRows: 0,
                 headRows: 1,
@@ -3862,75 +3988,7 @@ $(document).ready(() => {
     });
 
     $('#exportExcel').click(function () {
-        const data = [];
-        const setting = {
-            cols: [
-                {title: '项目节编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 145, formatter: '@', cellType: 'tree'},
-                {title: '清单编号', colSpan: '1', rowSpan: '2', field: 'b_code', hAlign: 0, width: 70, formatter: '@'},
-                {title: '计量单元', colSpan: '1', rowSpan: '2', field: 'pos_code', hAlign: 0, width: 70, formatter: '@'},
-                {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 185, formatter: '@'},
-                {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 60, formatter: '@', cellType: 'unit'},
-                {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, type: 'Number'},
-                {title: '台账|数量', colSpan: '2|1', rowSpan: '1|1', field: 'quantity', hAlign: 2, width: 60, type: 'Number'},
-                {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'total_price', hAlign: 2, width: 60, type: 'Number'},
-                {title: '本期合同计量|数量', colSpan: '2|1', rowSpan: '1|1', field: 'contract_qty', hAlign: 2, width: 60, type: 'Number'},
-                {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'contract_tp', hAlign: 2, width: 60, type: 'Number'},
-                {title: '本期数量变更|数量', colSpan: '2|1', rowSpan: '1|1', field: 'qc_qty', hAlign: 2, width: 60, type: 'Number'},
-                {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'qc_tp', hAlign: 2, width: 60, type: 'Number'},
-                {title: '本期完成计量|数量', colSpan: '2|1', rowSpan: '1|1', field: 'gather_qty', hAlign: 2, width: 60, type: 'Number'},
-                {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'gather_tp', hAlign: 2, width: 60, type: 'Number'},
-                {title: '截止本期合同计量|数量', colSpan: '2|1', rowSpan: '1|1', field: 'end_contract_qty', hAlign: 2, width: 60, type: 'Number'},
-                {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_contract_tp', hAlign: 2, width: 60, type: 'Number'},
-                {title: '截止本期数量变更|数量', colSpan: '2|1', rowSpan: '1|1', field: 'end_qc_qty', hAlign: 2, width: 60, type: 'Number'},
-                {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_qc_tp', hAlign: 2, width: 60, type: 'Number'},
-                {title: '截止本期完成计量|数量', colSpan: '3|1', rowSpan: '1|1', field: 'end_gather_qty', hAlign: 2, width: 60, type: 'Number'},
-                {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_gather_tp', hAlign: 2, width: 60, type: 'Number'},
-                {title: '|完成率(%)', colSpan: '|1', rowSpan: '|1', field: 'end_gather_percent', hAlign: 2, width: 80, type: 'Number'},
-                {title: '合同|项目节数量1',  colSpan: '2|1', rowSpan: '1|1', field: 'deal_dgn_qty1', hAlign: 2, width: 60, type: 'Number'},
-                {title: '|项目节数量2',  colSpan: '|1', rowSpan: '|1', field: 'deal_dgn_qty2', hAlign: 2, width: 60, type: 'Number'},
-                {title: '变更|项目节数量1',  colSpan: '2|1', rowSpan: '1|1', field: 'c_dgn_qty1', hAlign: 2, width: 60, type: 'Number'},
-                {title: '|项目节数量2',  colSpan: '|1', rowSpan: '|1', field: 'c_dgn_qty2', hAlign: 2, width: 60, type: 'Number'},
-                {title: '经济指标',  colSpan: '1', rowSpan: '2', field: 'final_dgn_price', hAlign: 2, width: 60, type: 'Number'},
-                {title: '本期批注', colSpan: '1', rowSpan: '2', field: 'postil', hAlign: 0, width: 100, formatter: '@', cellType: 'autoTip'},
-                {title: '图(册)号', colSpan: '1', rowSpan: '2', field: 'drawing_code', hAlign: 0, width: 80, formatter: '@'},
-                {title: '备注', colSpan: '1', rowSpan: '2', field: 'memo', hAlign: 0, width: 100, formatter: '@', cellType: 'ellipsisAutoTip'},
-            ],
-            headRows: 2,
-            headRowHeight: [25, 25],
-            defaultRowHeight: 21,
-            headerFont: 'bold 10px 微软雅黑',
-            font: '10px 微软雅黑'
-        };
-        for (const node of stageTree.nodes) {
-            data.push({
-                code: node.code, b_code: node.b_code, name: node.name, unit: node.unit,
-                unit_price: node.unit_price, quantity: node.quantity, total_price: node.total_price,
-                contract_qty: node.contract_qty, contract_tp: node.contract_tp,
-                qc_qty: node.qc_qty, qc_tp: node.qc_tp,
-                gather_qty: node.gather_qty, gather_tp: node.gather_tp,
-                end_contract_qty: node.end_contract_qty, end_contract_tp: node.end_contract_tp,
-                end_qc_qty: node.end_qc_qty, end_qc_tp: node.end_qc_tp,
-                end_gather_qty: node.end_gather_qty, end_gather_tp: node.end_gather_tp, end_gather_percent: node.end_gather_percent,
-                deal_dgn_qty1: node.deal_dgn_qty1, deal_dgn_qty2: node.deal_dgn_qty2,
-                c_dgn_qty1: node.c_dgn_qty1, c_dgn_qty2: node.c_dgn_qty2,
-                final_dgn_price: node.final_dgn_price,
-                postil: node.postil, drawing_code: node.drawing_code, memo: node.memo,
-            });
-            const posRange = stagePos.getLedgerPos(node.id);
-            if (posRange && posRange.length > 0) {
-                for (const [i, p] of posRange.entries()) {
-                    data.push({
-                        pos_code: (i + 1) + '', name: p.name,
-                        quantity: p.quantity,
-                        contract_qty: p.contract_qty, qc_qty: p.qc_qty, gather_qty: p.gather_qty,
-                        end_contract_qty: p.end_contract_qty, end_qc_qty: p.end_qc_qty, end_gather_qty: p.end_gather_qty,
-                        drawing_code: p.drawing_code, memo: p.memo
-                    });
-                }
-            }
-        }
-
-        SpreadExcelObj.exportSimpleXlsxSheet(setting, data, $('.sidebar-title').attr('data-original-title') + "计量台账.xlsx");
+        stageTreeSpreadObj.exportExcel($('.sidebar-title').attr('data-original-title') + "计量台账.xlsx", stageTree.children);
     });
 
     $('#cbr-check-all').click(function () {

+ 63 - 1
app/public/js/stage_bwtz.js

@@ -29,12 +29,58 @@ $(document).ready(() => {
     unitSheet.frozenColumnCount(5);
     unitSheet.options.frozenlineColor = '#93b5e4';
 
+    const filterUnitTree = createNewPathTree('filter', {
+        id: 'ledger_id',
+        pid: 'ledger_pid',
+        order: 'order',
+        level: 'level',
+        rootId: -1,
+        fullPath: 'full_path',
+    });
+
     const unitTreeObj = {
+        getFilterUnitTree: function (unitTree) {
+            const filter = $('#unit-filter').val();
+            if (!filter || unitTree.nodes.length === 0) return unitTree;
+            filterUnitTree.clearDatas();
+            const filterPath = [];
+            const checkFullPath = function (checkPath, valuePath) {
+                if (valuePath.indexOf(checkPath + '-') >= 0) return true;
+
+                const pathArray = checkPath.split('-');
+                const tmpArray = [];
+                for (let i = 0, iLen = pathArray.length; i < iLen; i++) {
+                    tmpArray.push(pathArray.slice(0, i+1).join('-'));
+                }
+                for (const ta of tmpArray) {
+                    if (ta === valuePath) return true;
+                }
+                return false;
+            };
+            for (const node of unitTree.nodes) {
+                if ((node.code && node.code.indexOf(filter) >= 0) || (node.b_code && node.b_code.indexOf(filter) >= 0)
+                    || (node.name && node.name.indexOf(filter) >= 0) || (node.pos_name && node.pos_name.indexOf(filter) >= 0))
+                    filterPath.push(node.full_path);
+            }
+            for (const node of unitTree.nodes) {
+                for (const fp of filterPath) {
+                    if (checkFullPath(fp, node.full_path)) {
+                        filterUnitTree.addData(node, ['id', 'ledger_id', 'ledger_pid', 'order', 'level', 'full_path', 'pos_name',
+                            'code', 'b_code', 'name', 'unit', 'unit_price', 'quantity', 'total_price', 'drawing_code_merge', 'memo_merge']);
+                        break;
+                    }
+                }
+            }
+            filterUnitTree.sortTreeNode();
+            return filterUnitTree;
+        },
         loadCurUnitData: function () {
             const node = SpreadJsObj.getSelectObject(xmjSheet);
             SpreadJsObj.resetTopAndSelect(unitSheet);
             if (node && node.unitTree) {
-                SpreadJsObj.loadSheetData(unitSheet, SpreadJsObj.DataType.Tree, node.unitTree);
+                const relaTree = unitTreeObj.getFilterUnitTree(node.unitTree);
+                if ($('#unit-show-1')[0].checked) relaTree.expandByLevel(1);
+                SpreadJsObj.loadSheetData(unitSheet, SpreadJsObj.DataType.Tree, relaTree);
             } else {
                 SpreadJsObj.initSheet(unitSheet, unitSpreadSetting);
             }
@@ -210,6 +256,22 @@ $(document).ready(() => {
         xmjSpread.refresh();
         unitSpread.refresh();
     });
+
+
+    $('#unit-filter').bind('keydown', function (e) {
+        const evt = window.event || e;
+        if (e.keyCode == 13) unitTreeObj.loadCurUnitData();
+    });
+    $('#unit-show-1').bind('change', function () {
+        if (!unitSheet.zh_tree) return;
+        if (this.checked) {
+            unitSheet.zh_tree.expandByLevel(1);
+            SpreadJsObj.refreshTreeRowVisible(unitSheet);
+        } else {
+            unitSheet.zh_tree.expandByCustom(() => { return true; });
+            SpreadJsObj.refreshTreeRowVisible(unitSheet);
+        }
+    });
     // 显示层次
     (function (select, sheet) {
         $(select).click(function () {

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

@@ -210,7 +210,7 @@ $(document).ready(function () {
                     compareRoles.push(order + 1);
                 }
             }
-            setLocalCache(scCacheKey, compareRoles.join(','));
+            // setLocalCache(scCacheKey, compareRoles.join(','));
             initSpreadSettingWithRoles(compareRoles);
             SpreadJsObj.initSheet(ledgerSpread.getActiveSheet(), ledgerSpreadSetting);
             treeCalc.calculateAll(scTree);

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

@@ -114,7 +114,7 @@ $(document).ready(function () {
         SpreadJsObj.reLoadSheetData(gclSpread.getActiveSheet());
     });
 
-    postData(preUrl + '/load', { filter: 'ledger;pos;dealBills' }, function (result) {
+    postData(preUrl + '/load', { filter: 'ledger;pos;dealBills;spec' }, function (result) {
         // 解析清单汇总数据
         gclGatherModel.loadLedgerData(result.ledgerData);
         gclGatherModel.loadPosData(result.posData);
@@ -126,7 +126,7 @@ $(document).ready(function () {
         SpreadJsObj.loadSheetData(gclSpread.getActiveSheet(), SpreadJsObj.DataType.Data, gclGatherData);
         loadLeafXmjData(0);
         // 章节合计
-        const chapterData = gclGatherModel.gatherChapterData(chapter, ['total_price', 'contract_tp', 'qc_tp', 'pre_contract_tp', 'pre_qc_tp'], filter);
+        const chapterData = gclGatherModel.gatherChapterData(chapter, result.spec, ['total_price', 'contract_tp', 'qc_tp', 'pre_contract_tp', 'pre_qc_tp']);
         for (const c of chapterData) {
             c.gather_tp = ZhCalc.add(c.contract_tp, c.qc_tp);
             c.end_contract_tp = ZhCalc.add(c.contract_tp, c.pre_contract_tp);

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

@@ -359,6 +359,9 @@ let JpcCanvasOutput = {
                 img.onload = function() {
                     private_drawImage(cell, control, img);
                 };
+                img.onerror = function() {
+                    console.log('cell.path error: ' + cell.path);
+                }
             } else if (cell.pic) {
                 const img = new Image();
                 img.src = cell.pic;
@@ -475,6 +478,7 @@ let JpcCanvasOutput = {
                 if (page.watermark_cells && page.watermark_cells.length > 0) {
                     for (let k = 0; k < page.watermark_cells.length; k++) {
                         let cell = page.watermark_cells[k];
+                        cell.pic = COMMON_WATER_MARK_PIC_DATA;
                         private_drawSignatureCell(cell, fonts, styles, controls, newPageMergeBand);
                     }
                 }

+ 3 - 4
app/public/report/js/rpt_custom.js

@@ -200,10 +200,9 @@ const rptCustomObj = (function () {
         for (const [i, s] of setting.select.entries()) {
             const obj = $('#sf-' + i);
             const s = select[i];
-            const sf = s ? stageFlow.find(function (x) {
+            obj[0].selectedIndex = s ? stageFlow.findIndex(function (x) {
                 return x.order === s.order && x.aid === s.aid;
-            }) : null;
-            obj[0].selectedIndex = sf ? sf.order : -1;
+            }) : -1;
         }
         if (asSelect.length === 0 || !checkAsSelectValid(stageFlow, asSelect)) {
             $('#audit-select').modal('show');
@@ -397,7 +396,7 @@ const rptCustomObj = (function () {
                 $('#audit-select-hint').html('未选择' + s.attributes['sf-title'].value).show();
                 return;
             }
-            data.audit_select.push(sf);
+            data.audit_select.push({...sf, sort: s.selectedIndex});
         }
         $('#audit-select-hint').hide();
         postData('/report/cDefine', data, function (result) {

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

@@ -103,6 +103,7 @@ let JpcJsPDFHelper = {
                 }
                 if (PAGE_SHOW['closeWatermark'] === 0) {
                     for (let cell of page.watermark_cells) {
+                        cell.pic = COMMON_WATER_MARK_PIC_DATA;
                         _drawWatermark(doc, ctx, cell, controls);
                     }
                 }

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

@@ -353,11 +353,17 @@ let zTreeOprObj = {
     },
     requestNormalReport: function (params) {
         let me = zTreeOprObj;
+        if (COMMON_WATER_MARK_PIC_DATA === null || COMMON_WATER_MARK_PIC_DATA === '') {
+            params.needWaterMark = true;
+        } else {
+            params.needWaterMark = false;
+        }
         $.bootstrapLoading.start();
         CommonAjax.postXsrfEx("/tender/report_api/getReport", params, 300000, true, getCookie('csrfToken'),
             function(result){
                 $.bootstrapLoading.end();
                 let pageRst = result.data;
+                if (params.needWaterMark) COMMON_WATER_MARK_PIC_DATA = result.waterMarkStr;
                 STAGE_AUDIT = result.stageAudit;
                 STAGE_AUDIT_ORG = result.stageAuditOrg;
                 STAGE_FLOW = result.stageFlow;
@@ -684,10 +690,16 @@ let rptControlObj = {
                 await rptCustomObj.getCustomSelect(params);
                 delete params.orientation; // 打印时有勾选的话,不需要提供方向
                 $.bootstrapLoading.start();
+                if (COMMON_WATER_MARK_PIC_DATA === null || COMMON_WATER_MARK_PIC_DATA === '') {
+                    params.needWaterMark = true;
+                } else {
+                    params.needWaterMark = false;
+                }
                 CommonAjax.postXsrfEx("/tender/report_api/getMultiReports", params, WAIT_TIME_EXPORT, true, getCookie('csrfToken'),
                     function(result){
                         // closeWaitingView();
                         $.bootstrapLoading.end();
+                        if (params.needWaterMark) COMMON_WATER_MARK_PIC_DATA = result.waterMarkStr;
                         STAGE_AUDIT = result.stageAudit;
                         let pageSize = rptControlObj.getCurrentPageSize();
                         for (const signatureRel of result.signatureRelInfo) {

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

@@ -13,12 +13,18 @@ let rptPrintHelper = {
             if (chkNodes.length > 0) {
                 delete params.orientation; // 打印时有勾选的话,不需要提供方向
             }
+            if (COMMON_WATER_MARK_PIC_DATA === null || COMMON_WATER_MARK_PIC_DATA === '') {
+                params.needWaterMark = true;
+            } else {
+                params.needWaterMark = false;
+            }
             CommonAjax.postXsrfEx("/tender/report_api/getMultiReports", params, 60000, true, getCookie('csrfToken'),
                 function(result){
                     const signatureRelArr = [];
                     for (const signatureRel of result.signatureRelInfo) {
                         signatureRelArr.push(JSON.parse(signatureRel.rel_content));
                     }
+                    if (params.needWaterMark) COMMON_WATER_MARK_PIC_DATA = result.waterMarkStr;
                     for (let idx = 0; idx < result.data.length; idx++) {
                         let singleSignatureRelArr = [];
                         for (let rIdx = 0; rIdx < result.signatureRelInfo.length; rIdx++) {
@@ -58,6 +64,7 @@ let rptPrintHelper = {
                     sessionStorage.orientation = rptControlObj.getCurrentOrientation();
                     sessionStorage.scaleFactor = 1;
                     sessionStorage.closeWaterMark = getCloseWatermark();
+                    sessionStorage.waterMarkStr = COMMON_WATER_MARK_PIC_DATA;
                     if (sessionStorage.pageSize === 'A3') {
                         window.open('/printReport/A3');
                     } else {
@@ -118,6 +125,7 @@ let rptPrintHelper = {
             }
             if (closeWaterMark === 0) {
                 for (let cell of page.watermark_cells) {
+                    cell.pic = sessionStorage.waterMarkStr;
                     svgPageArr.push(buildSignatureCellSvg(cell, styles, controls, page[JV.PROP_PAGE_MERGE_BORDER], pagesData[JV.BAND_PROP_MERGE_BAND],
                         offsetX - actAreaOffsetX, offsetY - actAreaOffsetY, adjustY, canvas, isHtoV, pixelSize, actAreaArr[idx]));
                 }

+ 18 - 10
app/public/report/js/rpt_signature.js

@@ -10,6 +10,7 @@ let rptSignatureHelper = {
     currentSelectedESignAccDom: null,
     currentSelectedESignAccIdx: -1,
     currentSelectedESignAccName: null,
+    currentSelectedESignParentDivId: '',
     originalRoleRelList: [],
     buildSelectableAccount: function () {
         //PRJ_ACCOUNT_LIST
@@ -123,13 +124,18 @@ let rptSignatureHelper = {
             rptSignatureHelper.cleanOldSignature(rptSignatureHelper.currentSelectedESignAccName);
             // 2.1 dom element
             const elementsStrArr = [];
+            let dftDate = _getSignDateByAllScenarios(userAcc.id);
+            if (dftDate !== '' && dftDate.length > 20) {
+                dftDate = (new Date(dftDate)).Format('yyyy-MM-dd');
+            }
             if (directAcc) {
                 rptSignatureHelper.pushDomElementByUser(elementsStrArr, userAcc.name, userAcc.role);
                 // 还有ROLE_REL_LIST
                 let roleRelObj = {};
                 roleRelObj.signature_name = rptSignatureHelper.currentSelectedESignAccName;
                 roleRelObj.sign_path = dftSignSrc;
-                roleRelObj.sign_date = '';
+                // roleRelObj.sign_date = '';
+                roleRelObj.sign_date = dftDate;
                 roleRelObj.sign_date_format = 'yyyy年M月d日';
                 roleRelObj.user_name = userAcc.name;
                 roleRelObj.acc_id = userAcc.id;
@@ -143,7 +149,8 @@ let rptSignatureHelper = {
                 let roleRelObj = {};
                 roleRelObj.signature_name = rptSignatureHelper.currentSelectedESignAccName;
                 roleRelObj.sign_path = dftSignSrc;
-                roleRelObj.sign_date = '';
+                // roleRelObj.sign_date = '';
+                roleRelObj.sign_date = dftDate;
                 roleRelObj.sign_date_format = 'yyyy年M月d日';
                 roleRelObj.user_name = userAcc.name;
                 roleRelObj.acc_id = userAcc.id;
@@ -191,6 +198,7 @@ let rptSignatureHelper = {
         // let body = $('#eSignatureBodyDiv');
         if (pageRst !== null) {
             let body = $('#' + signatureDivId);
+            rptSignatureHelper.currentSelectedESignParentDivId = signatureDivId;
             body.empty();
             const signature_cells = [];
             const singatureNameArr = [];
@@ -227,7 +235,7 @@ let rptSignatureHelper = {
                                 //角色
                                 rptSignatureHelper.pushDomElementByRole(elementsStrArr, role_rel.role_name, role_rel.user_name);
                             }
-                            const idSuffixStr = 'dtp_' + role_rel.signature_name;
+                            const idSuffixStr = 'dtp_' + role_rel.signature_name + '_' + signatureDivId;
                             elementsStrArr.push('<div class="">');
                             if (role_rel.sign_date !== '') {
                                 const dt = new Date(role_rel.sign_date);
@@ -282,7 +290,7 @@ let rptSignatureHelper = {
         // rptSignatureHelper.pushDatePickerDom(elementsStrArr);
     },
     pushDatePickerDom: function (elementsStrArr, userAccId) {
-        let idSuffixStr = 'dtp_' + rptSignatureHelper.currentSelectedESignAccName;
+        let idSuffixStr = 'dtp_' + rptSignatureHelper.currentSelectedESignAccName + '_' + rptSignatureHelper.currentSelectedESignParentDivId;
         elementsStrArr.push('<div class="">');
         // 日期控件存在页面高度不过高无法选中bug,先不用
         // elementsStrArr.push('<input id="' + idSuffixStr + '" class="datepicker-here form-control form-control-sm mt-0" placeholder="选择签名日期" data-language="zh" data-position="right bottom" type="text" readonly="true"');
@@ -379,14 +387,14 @@ let rptSignatureHelper = {
     resetSignAudit: function () {
         rptSignatureHelper.mergeSignAudit(zTreeOprObj.currentRptPageRst, ROLE_REL_LIST, STAGE_AUDIT);
     },
-    resetSignDate: function() {
+    resetSignDate: function(signatureDivId) {
         for (const page of zTreeOprObj.currentRptPageRst.items) {
             if (page.signature_date_cells) {
                 for (const sCell of page.signature_date_cells) {
                     sCell.Value = _getSignDateDftName();
                     for (let idx = 0; idx < ROLE_REL_LIST.length; idx++) {
                         const role_rel = ROLE_REL_LIST[idx];
-                        const idSuffixStr = 'dtp_' + role_rel.signature_name;
+                        const idSuffixStr = 'dtp_' + role_rel.signature_name + '_' + signatureDivId;
                         let dtDom = $('#' + idSuffixStr);
                         if (dtDom.length === 1) {
                             const dtStr = dtDom[0].value;
@@ -407,10 +415,10 @@ let rptSignatureHelper = {
             }
         }
     },
-    setupAfterSelectMultiTenders: function (selectedTenders) {
+    setupAfterSelectMultiTenders: function (selectedTenders, signatureDivId) {
         //跨标段选择,有不少要注意的交互:
         //0. 签名日期
-        rptSignatureHelper.resetSignDate();
+        rptSignatureHelper.resetSignDate(signatureDivId);
         rptSignatureHelper.resetSignAudit();
         //1. 重刷page
         if (current_stage_status === 3) {
@@ -448,9 +456,9 @@ let rptSignatureHelper = {
             }
         );
     },
-    setupAfterSelectSignature: function () {
+    setupAfterSelectSignature: function (signatureDivId) {
         //0. 签名日期
-        rptSignatureHelper.resetSignDate();
+        rptSignatureHelper.resetSignDate(signatureDivId);
         rptSignatureHelper.resetSignAudit();
         if (current_stage_status === 3) {
             //1. 重刷page

+ 115 - 12
app/reports/rpt_component/jpc_cross_tab.js

@@ -12,6 +12,7 @@ const JpcTextHelper = require('./helper/jpc_helper_text');
 const JpcCommonOutputHelper = require('./helper/jpc_helper_common_output');
 const JpcAreaHelper = require('./helper/jpc_helper_area');
 const $FS_UTIL = require('../../public/js/fsUtil');
+const bc = require('../../lib/base_calc.js');
 
 const JpcCrossTabSrv = function() {};
 
@@ -106,6 +107,9 @@ JpcCrossTabSrv.prototype.createNew = function() {
             }
         }
     }
+    function _addRowSumValue() {
+        // 此方法是专门为交叉行(拓展)合计用
+    }
     function private_SortAndOptimize(rptTpl, dataObj, dataSeq, sortTab, rstFieldsIdx, $CURRENT_RPT) {
         const result = [];
         const tab = rptTpl[JV.NODE_CROSS_INFO][sortTab];
@@ -200,14 +204,16 @@ JpcCrossTabSrv.prototype.createNew = function() {
         me.dispSerialIdxLst_Row = [];
         me.col_sum_fields_idx = [];
         me.col_sum_fields_value_total = [];
-        me.dispSumValueLst_Col = [];
+        me.row_sum_fields_value_total = [];
+        me.dispSumValueLst_Col = []; // 这个是横向合计(一列列累加)
+        me.dispSumValueLst_Row = []; // 这个是纵向合计(一行行累加)
         me.page_seg_map = [];
         me.row_fields_idx = [];
         me.row_fields_adhoc_idx = [];
         me.col_fields_idx = [];
         me.content_fields_idx = [];
         me.row_extension_fields_idx = [];
-        me.row_sum_extension_fields_idx = [];
+        me.row_sum_extension_fields_idx = []; // 类似col_sum_fields_idx,放在‘交叉行拓展合计’中
         me.crsOrient = JV.PAGE_ORIENTATION_V_FIRST;
         me.pageStatusLst = [];
         me.paging_option = JV.PAGING_OPTION_NORMAL;
@@ -257,7 +263,8 @@ JpcCrossTabSrv.prototype.createNew = function() {
                             if (isNaN(vTtl)) {
                                 vTtl = 0;
                             }
-                            rowGrandTotal[di] = rowGrandTotal[di] + 1.0 * vTtl;
+                            //rowGrandTotal[di] = rowGrandTotal[di] + 1.0 * vTtl;
+                            rowGrandTotal[di] = bc.add(rowGrandTotal[di], vTtl);
                         }
                     }
                     me.col_sum_fields_value_total[i].push(rowGrandTotal);
@@ -265,6 +272,42 @@ JpcCrossTabSrv.prototype.createNew = function() {
             }
 
         }
+        const row_data_fields = [];
+        for (let i = 0; i < me.col_sum_fields_idx.length; i++) {
+            let data_field = null;
+            if (typeof me.col_sum_fields_idx[i] === 'object') {
+                const exField = JE.F(me.col_sum_fields_idx[i][JV.PROP_ID], $CURRENT_RPT);
+                if (exField) {
+                    data_field = exField[JV.PROP_AD_HOC_DATA];
+                }
+            } else {
+                data_field = data_details[me.col_sum_fields_idx[i]];
+            }
+            row_data_fields.push(data_field);
+        }
+        for (let i = 0; i < me.sortedColSequence.length; i++) {
+            if (me.sortedColSequence[i].length > 0) {
+                me.row_sum_fields_value_total.push([]);
+                for (let j = 0; j < me.sortedColSequence[i].length; j++) {
+                    const rowGrandTotal = [];
+                    for (let di = 0; di < row_data_fields.length; di++) {
+                        rowGrandTotal.push(0.0);
+                        for (let k = 0; k < me.sortedColSequence[i][j].length; k++) {
+                            // 3. start to sum
+                            let vTtl = parseFloat(JpcFieldHelper.getValue(row_data_fields[di], me.sortedColSequence[i][j][k]));
+                            if (isNaN(vTtl)) {
+                                vTtl = 0;
+                            }
+                            // rowGrandTotal[di] = rowGrandTotal[di] + 1.0 * vTtl;
+                            rowGrandTotal[di] = bc.add(rowGrandTotal[di], vTtl);
+                        }
+                    }
+                    me.row_sum_fields_value_total[i].push(rowGrandTotal);
+                }
+            }
+        }
+        // console.log('me.row_sum_fields_value_total');
+        // console.log(me.row_sum_fields_value_total);
     };
     JpcCrossTabResult.preSetupPages = function(rptTpl, defProperties, option) {
         const me = this;
@@ -356,7 +399,7 @@ JpcCrossTabSrv.prototype.createNew = function() {
                             me.pageStatusLst.push(pageStatus.slice(0));
                             pageIdx++;
                             private_addTabValue(me.dispValueIdxLst_Row, currentSortedRowSequence, segIdx, counterRowRec, maxRowRec, me.dispSerialIdxLst_Row, me.col_sum_fields_value_total, me.dispSumValueLst_Col);
-                            private_addTabValue(me.dispValueIdxLst_Col, currentSortedColSequence, segIdx, counterColRec, maxColRec, null, null, null);
+                            private_addTabValue(me.dispValueIdxLst_Col, currentSortedColSequence, segIdx, counterColRec, maxColRec, null, me.row_sum_fields_value_total, me.dispSumValueLst_Row);
                             private_addContentValue(me.dispValueIdxLst_Content, currentSortedContentSequence, segIdx, counterRowRec, maxRowRec, counterColRec, maxColRec, me.page_seg_map, pageIdx);
                         }
                     }
@@ -385,7 +428,7 @@ JpcCrossTabSrv.prototype.createNew = function() {
                                 counterRowRec = 0;
                             }
                             private_addTabValue(me.dispValueIdxLst_Row, currentSortedRowSequence, segIdx, counterRowRec, maxRowRec, me.dispSerialIdxLst_Row, me.col_sum_fields_value_total, me.dispSumValueLst_Col);
-                            private_addTabValue(me.dispValueIdxLst_Col, currentSortedColSequence, segIdx, counterColRec, maxColRec, null, null, null);
+                            private_addTabValue(me.dispValueIdxLst_Col, currentSortedColSequence, segIdx, counterColRec, maxColRec, null, me.row_sum_fields_value_total, me.dispSumValueLst_Row);
                             private_addContentValue(me.dispValueIdxLst_Content, currentSortedContentSequence, segIdx, counterRowRec, maxRowRec, counterColRec, maxColRec, me.page_seg_map, pageIdx);
                         }
                     }
@@ -419,7 +462,9 @@ JpcCrossTabSrv.prototype.createNew = function() {
         // 5. 交叉行拓展合计
         rst = rst.concat(me.outputPreviewSumTabExt(rptTpl, bands, controls, $CURRENT_RPT, customizeCfg, unitFactor));
         // 6. 交叉列合计
-        rst = rst.concat(me.outputPreviewTabSum(rptTpl, bands, controls, $CURRENT_RPT, customizeCfg, maxRowRec, JV.NODE_CROSS_COL_SUM, unitFactor));
+        rst = rst.concat(me.outputPreviewTabSumCol(rptTpl, bands, controls, $CURRENT_RPT, customizeCfg, maxRowRec, JV.NODE_CROSS_COL_SUM, unitFactor));
+        // 6.1 交叉行合计
+        rst = rst.concat(me.outputPreviewTabSumRow(rptTpl, bands, controls, $CURRENT_RPT, customizeCfg, maxColRec, JV.NODE_CROSS_ROW_SUM, unitFactor));
         // 7. 离散
         rst = rst.concat(JpcDiscreteHelper.outputPreviewDiscreteInfo(rptTpl[JV.NODE_CROSS_INFO][JV.NODE_DISCRETE_INFO], bands, unitFactor, pageStatus));
         return rst;
@@ -435,9 +480,12 @@ JpcCrossTabSrv.prototype.createNew = function() {
     JpcCrossTabResult.outputPreviewContent = function(rptTpl, bands, controls, $CURRENT_RPT, customizeCfg, maxRowRec, maxColRec, unitFactor) {
         return this.private_OutputPreviewCommon(rptTpl, bands, controls, $CURRENT_RPT, customizeCfg, maxRowRec, maxColRec, rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_CONTENT], unitFactor);
     };
-    JpcCrossTabResult.outputPreviewTabSum = function(rptTpl, bands, controls, $CURRENT_RPT, customizeCfg, maxRowRec, tabNodeName, unitFactor) {
+    JpcCrossTabResult.outputPreviewTabSumCol = function(rptTpl, bands, controls, $CURRENT_RPT, customizeCfg, maxRowRec, tabNodeName, unitFactor) {
         return this.private_OutputPreviewCommon(rptTpl, bands, controls, $CURRENT_RPT, customizeCfg, maxRowRec, 1, rptTpl[JV.NODE_CROSS_INFO][tabNodeName], unitFactor);
     };
+    JpcCrossTabResult.outputPreviewTabSumRow = function(rptTpl, bands, controls, $CURRENT_RPT, customizeCfg, maxColRec, tabNodeName, unitFactor) {
+        return this.private_OutputPreviewCommon(rptTpl, bands, controls, $CURRENT_RPT, customizeCfg, 1, maxColRec, rptTpl[JV.NODE_CROSS_INFO][tabNodeName], unitFactor);
+    };
     JpcCrossTabResult.outputPreviewTabExt = function(rptTpl, bands, controls, $CURRENT_RPT, customizeCfg, maxColRec, unitFactor) {
         // 交叉行拓展
         return this.private_OutputPreviewCommon(rptTpl, bands, controls, $CURRENT_RPT, customizeCfg, 1, maxColRec, rptTpl[JV.NODE_CROSS_INFO][JV.NODE_CROSS_ROW_EXT], unitFactor);
@@ -503,7 +551,7 @@ JpcCrossTabSrv.prototype.createNew = function() {
              tabRstLst.push(me.outputContent(rptTpl, dataObj, page, bands, unitFactor, controls));
              //2.4 Sum-Tab Row
              //2.4 Sum-tab Col
-             tabRstLst.push(me.outputTabSum(rptTpl, dataObj, page, bands, unitFactor, JV.NODE_CROSS_COL_SUM, controls));
+             tabRstLst.push(me.outputTabSumCol(rptTpl, dataObj, page, bands, unitFactor, JV.NODE_CROSS_COL_SUM, controls));
              //2.x row tab ext
              tabRstLst.push(me.outputTabExt(rptTpl, dataObj, page, bands, unitFactor, controls));
              tabRstLst.push(me.outputSumTabExt(rptTpl, dataObj, page, bands, unitFactor, segIdx, controls));
@@ -525,8 +573,10 @@ JpcCrossTabSrv.prototype.createNew = function() {
             //  2.3 Content-Tab
             tabRstLst.push(me.outputContent(rptTpl, dataObj, page, bands, unitFactor, controls, $CURRENT_RPT, customizeCfg));
             //  2.4 Sum-Tab Row
+            // 现在要加咯,
+            tabRstLst.push(me.outputTabSumRow(rptTpl, dataObj, page, bands, unitFactor, JV.NODE_CROSS_ROW_SUM, controls, $CURRENT_RPT, customizeCfg));
             //  2.4 Sum-tab Col
-            tabRstLst.push(me.outputTabSum(rptTpl, dataObj, page, bands, unitFactor, JV.NODE_CROSS_COL_SUM, controls, $CURRENT_RPT, customizeCfg));
+            tabRstLst.push(me.outputTabSumCol(rptTpl, dataObj, page, bands, unitFactor, JV.NODE_CROSS_COL_SUM, controls, $CURRENT_RPT, customizeCfg));
             //  2.x row tab ext
             tabRstLst.push(me.outputTabExt(rptTpl, dataObj, page, bands, unitFactor, controls, $CURRENT_RPT, customizeCfg));
             tabRstLst.push(me.outputSumTabExt(rptTpl, dataObj, page, bands, unitFactor, segIdx, controls, $CURRENT_RPT, customizeCfg));
@@ -719,7 +769,53 @@ JpcCrossTabSrv.prototype.createNew = function() {
         }
         return rst;
     };
-    JpcCrossTabResult.outputTabSum = function(rptTpl, dataObj, page, bands, unitFactor, tabNodeName, controls, $CURRENT_RPT, customizeCfg) {
+    JpcCrossTabResult.outputTabSumRow = function(rptTpl, dataObj, page, bands, unitFactor, tabNodeName, controls, $CURRENT_RPT, customizeCfg) {
+        const me = this;
+        const rst = [];
+        const tab = rptTpl[JV.NODE_CROSS_INFO][tabNodeName];
+        const band = tab ? bands[tab[JV.PROP_BAND_NAME]] : null;
+        if (band) {
+            const pageStatus = me.pageStatusLst[page - 1];
+            if (pageStatus[band[JV.BAND_PROP_DISPLAY_TYPE]] === true) {
+                const tab_fields = tab[JV.PROP_CROSS_FIELDS];
+                for (let i = 0; i < me.dispSumValueLst_Row[page - 1].length; i++) {
+                    if (i === 0) {
+                        for (let tfIdx = 0; tfIdx < tab_fields.length; tfIdx++) {
+                            const map_data_field = JE.F(tab_fields[tfIdx][JV.PROP_FIELD_ID], $CURRENT_RPT);
+                            JpcFieldHelper.resetFormat(tab_fields[tfIdx], map_data_field, customizeCfg);
+                        }
+                    }
+                    if (me.dispSumValueLst_Row[page - 1][i] !== null) {
+                        for (let j = 0; j < me.dispSumValueLst_Row[page - 1][i].length; j++) {
+                            const tab_field = tab_fields[j];
+                            const val = me.dispSumValueLst_Row[page - 1][i][j];
+                            const cellItem = JpcCommonOutputHelper.createCommonOutput(tab_field, val, controls);
+                            cellItem[JV.PROP_AREA] = JpcAreaHelper.outputArea(tab_field[JV.PROP_AREA], band, unitFactor, 1, 0, me.dispSumValueLst_Row[page - 1].length, i, 1, 0, true, false);
+                            // 方向不同,需要调整下
+                            rst.push(cellItem);
+                        }
+                    } else {
+                        let sumL = 1;
+                        for (let si = 0; si < me.dispSumValueLst_Row.length; si++) {
+                            if (me.dispSumValueLst_Row[si][0] !== null) {
+                                sumL = me.dispSumValueLst_Row[si][0].length;
+                                break;
+                            }
+                        }
+                        for (let j = 0; j < sumL; j++) {
+                            const tab_field = tab_fields[j];
+                            const val = null;
+                            const cellItem = JpcCommonOutputHelper.createCommonOutput(tab_field, val, controls);
+                            cellItem[JV.PROP_AREA] = JpcAreaHelper.outputArea(tab_field[JV.PROP_AREA], band, unitFactor, 1, 0, me.dispSumValueLst_Row[page - 1].length, i, 1, 0, true, false);
+                            rst.push(cellItem);
+                        }
+                    }
+                }
+            }
+        }
+        return rst;
+    };
+    JpcCrossTabResult.outputTabSumCol = function(rptTpl, dataObj, page, bands, unitFactor, tabNodeName, controls, $CURRENT_RPT, customizeCfg) {
         const me = this;
         const rst = [];
         const tab = rptTpl[JV.NODE_CROSS_INFO][tabNodeName];
@@ -845,9 +941,16 @@ JpcCrossTabSrv.prototype.createNew = function() {
                 for (let di = 0; di < data_fields.length; di++) {
                     rowGrandTotal[di] = 0.0;
                     // 3. start to sum
+                    // 这里的合计实际是章合计来的,需要seg level全部累加
                     for (let i = 0; i < me.sortedColSequence[segIdx].length; i++) {
-                        // me.sortedColSequence[segIdx][i][0] //this is the data field value index!
-                        rowGrandTotal[di] = rowGrandTotal[di] + 1.0 * JpcFieldHelper.getValue(data_fields[di], me.sortedColSequence[segIdx][i][0]);
+                        for (let j = 0; j < me.sortedColSequence[segIdx][i].length; j++) {
+                            // rowGrandTotal[di] = rowGrandTotal[di] + 1.0 * JpcFieldHelper.getValue(data_fields[di], me.sortedColSequence[segIdx][i][j]);
+                            let vTtl = parseFloat(JpcFieldHelper.getValue(data_fields[di], me.sortedColSequence[segIdx][i][j]));
+                            if (isNaN(vTtl)) {
+                                vTtl = 0;
+                            }
+                            rowGrandTotal[di] = bc.add(rowGrandTotal[di], vTtl);
+                        }
                     }
                 }
                 // 4. output

+ 12 - 5
app/router.js

@@ -80,6 +80,9 @@ module.exports = app => {
     app.post('/setting/category/update', sessionAuth, 'settingController.updateCategory');
     app.post('/setting/category/value', sessionAuth, 'settingController.setCategoryValue');
     app.post('/setting/category/level', sessionAuth, 'settingController.resetCategoryLevel');
+    // 操作日志
+    app.get('/setting/logs', sessionAuth, 'settingController.logs');
+    app.get('/setting/logs/type/:type', sessionAuth, 'settingController.logs');
 
     // 项目相关
     app.get('/project/info', sessionAuth, 'projectController.info');
@@ -112,11 +115,11 @@ module.exports = app => {
     app.post('/tender/:id/save', sessionAuth, tenderCheck, 'tenderController.saveTenderInfo');
     app.post('/tender/rule', sessionAuth, 'tenderController.rule');
     app.post('/tender/:id/rule/first', sessionAuth, tenderCheck, 'tenderController.ruleFirst');
-    app.get('/tender/:id/shenpi', sessionAuth, tenderCheck, uncheckTenderCheck, 'tenderController.shenpiSet');
-    app.post('/tender/:id/shenpi/save', sessionAuth, tenderCheck, uncheckTenderCheck, 'tenderController.saveTenderInfoShenpi');
-    app.post('/tender/:id/shenpi/audit/save', sessionAuth, tenderCheck, uncheckTenderCheck, 'tenderController.saveShenpiAudit');
-    app.post('/tender/:id/shenpi/ledger/load', sessionAuth, tenderCheck, uncheckTenderCheck, 'tenderController.loadLedgerData');
-    app.post('/tender/:id/shenpi/save-sign', sessionAuth, tenderCheck, uncheckTenderCheck, 'tenderController.saveCooperateSign');
+    app.get('/tender/:id/shenpi', sessionAuth, tenderCheck, 'tenderController.shenpiSet');
+    app.post('/tender/:id/shenpi/save', sessionAuth, tenderCheck, 'tenderController.saveTenderInfoShenpi');
+    app.post('/tender/:id/shenpi/audit/save', sessionAuth, tenderCheck, 'tenderController.saveShenpiAudit');
+    app.post('/tender/:id/shenpi/ledger/load', sessionAuth, tenderCheck, 'tenderController.loadLedgerData');
+    app.post('/tender/:id/shenpi/save-sign', sessionAuth, tenderCheck, 'tenderController.saveCooperateSign');
     app.post('/tender/:id/copy-setting', sessionAuth, tenderCheck, 'tenderController.copyTender');
 
     // 预付款
@@ -442,7 +445,11 @@ module.exports = app => {
     app.get('/tender/:id/schedule/plan', sessionAuth, tenderCheck, uncheckTenderCheck, 'scheduleController.plan');
     app.post('/tender/:id/schedule/plan/save', sessionAuth, tenderCheck, uncheckTenderCheck, 'scheduleController.savePlan');
     app.get('/tender/:id/schedule/stage', sessionAuth, tenderCheck, uncheckTenderCheck, 'scheduleController.stageTp');
+    app.get('/tender/:id/schedule/stage/order/:order', sessionAuth, tenderCheck, uncheckTenderCheck, 'scheduleController.stageTp');
+    app.post('/tender/:id/schedule/stage/save', sessionAuth, tenderCheck, uncheckTenderCheck, 'scheduleController.saveStageTp');
     app.get('/tender/:id/schedule/stage/gcl', sessionAuth, tenderCheck, uncheckTenderCheck, 'scheduleController.stageGcl');
+    app.post('/tender/:id/schedule/stage/gcl/save', sessionAuth, tenderCheck, uncheckTenderCheck, 'scheduleController.saveStageGcl');
+    app.post('/tender/:id/schedule/stage/:order/load', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'scheduleController.loadTpLedgerData');
 
     // 书签
     app.post('/tender/:id/ledger/tag', sessionAuth, tenderCheck, uncheckTenderCheck, 'tenderController.billsTag');

+ 4 - 0
app/service/change.js

@@ -17,6 +17,7 @@ const SMS = require('../lib/sms');
 const SmsAliConst = require('../const/sms_alitemplate');
 const wxConst = require('../const/wechat_template');
 const pushType = require('../const/audit').pushType;
+const projectLogConst = require('../const/project_log');
 
 module.exports = app => {
     class Change extends app.BaseService {
@@ -1157,6 +1158,7 @@ module.exports = app => {
             this.transaction = await this.db.beginTransaction();
             let result = false;
             try {
+                const changeInfo = await this.getDataByCondition({ cid });
                 // 先删除清单,审批人列表
                 await this.transaction.delete(this.ctx.service.changeAuditList.tableName, { cid });
                 await this.transaction.delete(this.ctx.service.changeAudit.tableName, { cid });
@@ -1170,6 +1172,8 @@ module.exports = app => {
                 }
                 // 最后删除变更令
                 await this.transaction.delete(this.tableName, { cid });
+                // 记录删除日志
+                await this.ctx.service.projectLog.addProjectLog(this.transaction, projectLogConst.type.change, projectLogConst.status.delete, changeInfo.code);
                 await this.transaction.commit();
                 result = true;
             } catch (e) {

+ 36 - 0
app/service/change_audit_list.js

@@ -183,6 +183,42 @@ module.exports = app => {
             }
         }
 
+        /**
+         * 台账数据清单 清除部分并重新算原设计总金额
+         * @param {Object} datas 内容
+         * @return {void}
+         */
+        async removeLedgerListDatas(datas) {
+            if (!this.ctx.tender || !this.ctx.change) {
+                throw '数据错误';
+            }
+            // 判断是否可修改
+            // 判断t_type是否为费用
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 先删除原本的台账清单数据
+                // const sql = 'DELETE FROM ?? WHERE cid = ? and lid != "0"';
+                // const sqlParam = [this.tableName, this.ctx.change.cid];
+                // await transaction.query(sql, sqlParam);
+                // const insertDatas = [];
+                for (const data of datas) {
+                    // data.tid = this.ctx.tender.id;
+                    // data.cid = this.ctx.change.cid;
+                    // data.spamount = data.camount;
+                    // data.samount = '';
+                    // insertDatas.push(data);
+                    await transaction.delete(this.tableName, { id: data.id });
+                }
+                // if (insertDatas.length > 0) await transaction.insert(this.tableName, insertDatas);
+                await this.calcCamountSum(transaction);
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
         async calcCamountSum(transaction) {
             // const sql = 'SELECT SUM(ROUND(`camount`*`unit_price`, )) as total_price FROM ?? WHERE cid = ?';
             // const sqlParam = [this.tableName, this.change.cid];

+ 3 - 2
app/service/ledger.js

@@ -52,6 +52,7 @@ module.exports = app => {
                 keyPre: 'ledger_bills_maxLid:',
                 uuid: true,
             }, 'pos');
+            this.depart = 10;
             this.tableName = 'ledger';
         }
 
@@ -555,7 +556,7 @@ module.exports = app => {
             if (!lastChild) {
                 result.ledger.update = await this.getDataByIds([selectData.id]);
             }
-            result.pos = await this.ctx.service.pos.getPosData({ lid: newIds });
+            result.pos = await this.ctx.service.pos.getPosData({tid: tenderId, lid: newIds });
             return result;
         }
 
@@ -622,7 +623,7 @@ module.exports = app => {
             // 查询应返回的结果
             result.ledger.create = await this.getDataByIds(newIds);
             result.ledger.update = await this.getNextsData(selectData.tender_id, selectData.ledger_pid, selectData.order + data.length);
-            result.pos = await this.ctx.service.pos.getPosData({ lid: newIds });
+            result.pos = await this.ctx.service.pos.getPosData({tid: tenderId, lid: newIds });
             return result;
         }
 

+ 5 - 5
app/service/ledger_revise.js

@@ -99,11 +99,11 @@ module.exports = app => {
                 '  (id, code, b_code, name, unit, source, remark, ledger_id, ledger_pid, level, `order`, full_path, is_leaf,' +
                 '     quantity, total_price, unit_price, drawing_code, memo, dgn_qty1, dgn_qty2, deal_qty, deal_tp,' +
                 '     sgfh_qty, sgfh_tp, sjcl_qty, sjcl_tp, qtcl_qty, qtcl_tp, node_type, crid, tender_id, is_tp,' +
-                '     sgfh_expr, sjcl_expr, qtcl_expr, gxby_status, dagl_status)' +
+                '     sgfh_expr, sjcl_expr, qtcl_expr, gxby_status, dagl_status, dagl_url, check_calc)' +
                 '  Select id, code, b_code, name, unit, source, remark, ledger_id, ledger_pid, level, `order`, full_path, is_leaf,' +
                 '      quantity, total_price, unit_price, drawing_code, memo, dgn_qty1, dgn_qty2, deal_qty, deal_tp,' +
                 '      sgfh_qty, sgfh_tp, sjcl_qty, sjcl_tp, qtcl_qty, qtcl_tp, node_type, crid, tender_id, is_tp,' +
-                '      sgfh_expr, sjcl_expr, qtcl_expr, gxby_status, dagl_status' +
+                '      sgfh_expr, sjcl_expr, qtcl_expr, gxby_status, dagl_status, dagl_url, 0' +
                 '  From ' + this.ctx.service.ledger.tableName +
                 '  Where `tender_id` = ?';
             const sqlParam = [tid];
@@ -114,10 +114,10 @@ module.exports = app => {
             const sql = 'Insert Into ' + this.ctx.service.revisePos.tableName +
                 '  (id, tid, lid, name, drawing_code, quantity, add_stage, add_times, add_user,' +
                 '     sgfh_qty, sjcl_qty, qtcl_qty, crid, in_time, porder, position,' +
-                '     sgfh_expr, sjcl_expr, qtcl_expr, real_qty, gxby_status, dagl_status)' +
+                '     sgfh_expr, sjcl_expr, qtcl_expr, real_qty, gxby_status, dagl_status, dagl_url)' +
                 '  Select id, tid, lid, name, drawing_code, quantity, add_stage, add_times, add_user,' +
                 '     sgfh_qty, sjcl_qty, qtcl_qty, crid, in_time, porder, position,' +
-                '     sgfh_expr, sjcl_expr, qtcl_expr, real_qty, gxby_status, dagl_status' +
+                '     sgfh_expr, sjcl_expr, qtcl_expr, real_qty, gxby_status, dagl_status, dagl_url' +
                 '  From ' + this.ctx.service.pos.tableName +
                 '  Where `tid` = ?';
             const sqlParam = [tid];
@@ -142,7 +142,7 @@ module.exports = app => {
                     status: audit.status.uncheck,
                 });
             }
-            await transaction.insert(this.ctx.service.reviseAudit.tableName, newAuditors);
+            if (auditors) await transaction.insert(this.ctx.service.reviseAudit.tableName, newAuditors);
         }
 
         /**

+ 3 - 62
app/service/login_logging.js

@@ -7,7 +7,7 @@
  * @date 2020/8/31
  * @version
  */
-const UAParser = require('ua-parser-js');
+// const UAParser = require('ua-parser-js');
 
 module.exports = app => {
     class LoginLogging extends app.BaseService {
@@ -39,75 +39,16 @@ module.exports = app => {
          */
         async addLoginLog(type, status) {
             const { ctx } = this;
-            const ip = ctx.request.ip ? ctx.request.ip : '';
-            const ipInfo = await this.getIpInfoFromApi(ip);
-            const parser = new UAParser(ctx.header['user-agent']);
-            const osInfo = parser.getOS();
-            const cpuInfo = parser.getCPU();
-            const browserInfo = parser.getBrowser();
+            const ipMsg = await this.ctx.helper.getUserIPMsg();
             const payload = {
-                os: `${osInfo.name} ${osInfo.version} ${cpuInfo.architecture}`,
-                browser: `${browserInfo.name} ${browserInfo.version}`,
-                ip,
-                address: ipInfo,
                 uid: ctx.session.sessionUser.accountId,
                 pid: ctx.session.sessionProject.id,
                 type,
                 show: status,
             };
+            this._.assign(payload, ipMsg);
             return await this.createLog(payload);
         }
-
-        /**
-         * 根据ip请求获取详细地址
-         * @param {String} a_ip - ip地址
-         * @return {String} 详细地址
-         */
-        async getIpInfoFromApi(a_ip = '') {
-            if (!a_ip) return '';
-            if (a_ip === '127.0.0.1' || a_ip === '::1' || a_ip.indexOf('192.168') !== -1) return '服务器本机访问';
-            const { ip = '', region = '', city = '', isp = '' } = await this.sendRequest(a_ip);
-            let address = '';
-            region && (address += region + '省');
-            city && (address += city + '市 ');
-            isp && (address += isp + ' ');
-            ip && (address += `(${ip})`);
-            return address;
-
-        }
-
-        /**
-         * 发送请求获取详细地址
-         * @param {String} ip - ip地址
-         * @return {Object} the result of request
-         * @private
-         */
-        async sendRequest(ip) {
-            return new Promise(resolve => {
-                this.ctx.curl(`https://api01.aliyun.venuscn.com/ip?ip=${ip}`, {
-                    dateType: 'json',
-                    encoding: 'utf8',
-                    timeout: 2000,
-                    headers: {
-                        Authorization: 'APPCODE 85c64bffe70445c4af9df7ae31c7bfcc',
-                    },
-                }).then(({ status, data }) => {
-                    if (status === 200) {
-                        const result = JSON.parse(data.toString()).data;
-                        if (!result.ip) {
-                            resolve({});
-                        } else {
-                            resolve(result);
-                        }
-                    } else {
-                        resolve({});
-                    }
-                }).catch(() => {
-                    resolve({});
-                });
-            });
-        }
-
         /**
          * 获取登录日志
          * @param {Number} pid - 项目id

+ 4 - 0
app/service/material.js

@@ -9,6 +9,7 @@
  */
 
 const auditConst = require('../const/audit').material;
+const projectLogConst = require('../const/project_log');
 module.exports = app => {
     class Material extends app.BaseService {
         /**
@@ -213,6 +214,9 @@ module.exports = app => {
                     await transaction.query(sql2, sqlParam2);
                 }
                 await transaction.delete(this.tableName, { id });
+
+                // 记录删除日志
+                await this.ctx.service.projectLog.addProjectLog(transaction, projectLogConst.type.material, projectLogConst.status.delete, '第' + materialInfo.order + '期');
                 await transaction.commit();
                 return true;
             } catch (err) {

+ 6 - 4
app/service/pos.js

@@ -19,15 +19,17 @@ module.exports = app => {
          */
         constructor(ctx) {
             super(ctx);
+            this.depart = 20;
             this.tableName = 'pos';
         }
 
         async getPosData(condition) {
-            return await this.db.select(this.tableName, {
+            if (!condition.tid) throw '查询计量单元缺少必要信息';
+            return await this.db.select(this.departTableName(condition.tid), {
                 where: condition,
                 columns: ['id', 'tid', 'lid', 'name', 'quantity', 'position', 'drawing_code', 'sgfh_qty', 'sjcl_qty',
                     'qtcl_qty', 'in_time', 'porder', 'add_stage', 'sgfh_expr', 'sjcl_expr', 'qtcl_expr', 'real_qty',
-                    'dagl_status', 'gxby_status'],
+                    'dagl_status', 'dagl_url', 'gxby_status'],
                 order: [['porder', 'ASC']],
             });
         }
@@ -35,7 +37,7 @@ module.exports = app => {
         async getPosDataWithAddStageOrder(condition) {
             const sql = 'SELECT p.id, p.tid, p.lid, p.name, p.quantity, p.position, p.drawing_code,' +
                 '    p.sgfh_qty, p.sjcl_qty, p.qtcl_qty, p.porder, p.add_stage, p.add_times, p.add_user, s.order As add_stage_order,' +
-                '    p.sgfh_expr, p.sjcl_expr, p.qtcl_expr, p.real_qty, p.gxby_status, p.dagl_status' +
+                '    p.sgfh_expr, p.sjcl_expr, p.qtcl_expr, p.real_qty, p.gxby_status, p.dagl_status, p.dagl_url' +
                 '  FROM ' + this.tableName + ' p ' +
                 '  LEFT JOIN ' + this.ctx.service.stage.tableName + ' s' +
                 '  ON p.add_stage = s.id'
@@ -47,7 +49,7 @@ module.exports = app => {
             if (ids instanceof Array && ids.length > 0) {
                 const sql = 'SELECT id, tid, lid, name, quantity, position, drawing_code,' +
                     '    sgfh_qty, sjcl_qty, qtcl_qty, add_stage, add_times, add_user, sgfh_expr, sjcl_expr, qtcl_expr, real_qty,' +
-                    '    dagl_status, gxby_status' +
+                    '    dagl_status, dagl_url, gxby_status' +
                     '  FROM ' + this.tableName +
                     '  WHERE id in (' + this.ctx.helper.getInArrStrSqlFilter(ids) + ')';
                 return await this.db.query(sql, []);

+ 53 - 0
app/service/project_log.js

@@ -0,0 +1,53 @@
+'use strict';
+
+/**
+ * 项目操作日志-数据模型
+ *
+ * @author ellisran
+ * @date 2021/01/12
+ * @version
+ */
+
+module.exports = app => {
+    class projectLog extends app.BaseService {
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'project_log';
+        }
+
+        /**
+         * 创建登录日志
+         * @return {Boolean} 日志是否创建成功
+         * @param {Number} type - 登录类型
+         * @param {Number} status - 是否显示记录
+         */
+        async addProjectLog(transaction, type, status, msg, tid = 0) {
+            const { ctx } = this;
+            const ipMsg = await this.ctx.helper.getUserIPMsg();
+            const payload = {
+                uid: ctx.session.sessionUser.accountId,
+                pid: ctx.session.sessionProject.id,
+                tid: ctx.tender && ctx.tender.id ? ctx.tender.id : tid,
+                type,
+                status,
+                msg,
+            };
+            this._.assign(payload, ipMsg);
+            return await transaction.insert(this.tableName, payload);
+        }
+
+        /**
+         * 获取操作日志
+         * @param {Number} pid - 项目id
+         * @param {Number} type - 类型
+         * @return {Promise<Array>} 日志数组
+         */
+        async getLogs(pid, type = 0) {
+            const typeSql = parseInt(type) !== 0 ? ' AND A.`type` = ' + type : '';
+            const sql = 'SELECT A.*, B.`name` as `username`, B.`mobile` FROM ?? as A LEFT JOIN ?? as B ON A.`uid` = B.`id` WHERE A.`pid` = ?' + typeSql + ' AND TO_DAYS(NOW()) - TO_DAYS(A.`create_time`) <= 30  ORDER BY A.`id` DESC';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, pid];
+            return await this.db.query(sql, sqlParam);
+        }
+    }
+    return projectLog;
+};

+ 6 - 3
app/service/report.js

@@ -32,6 +32,7 @@ module.exports = app => {
 
         async getReportData(params, filters, memFieldKeys, customDefine, customSelect) {
             const service = this.ctx.service;
+            await service.tender.checkTender(params.tender_id);
             const rst = {};
             const runnableRst = [];
             const runnableKey = []; // 这个配合runnableRst用,未来考虑并行查询优化
@@ -47,7 +48,7 @@ module.exports = app => {
                             runnableKey.push(filter);
                             break;
                         case 'advance_pay':
-                            runnableRst.push(service.advance.getAllDataByCondition({ where: {tid: params.tender_id}}));
+                            runnableRst.push(service.advance.getAllDataByCondition({ where: { tid: params.tender_id } }));
                             runnableKey.push(filter);
                             break;
                         case 'deal_bills' :
@@ -175,6 +176,10 @@ module.exports = app => {
                             runnableRst.push(service.reportMemory.getSignSelect(params.tender_id, params.stage_id, customSelect));
                             runnableKey.push(filter);
                             break;
+                        case 'mem_stage_change_bills':
+                            runnableRst.push(service.stageChangeFinal.getFinalData(params.tender_id));
+                            runnableKey.push(filter);
+                            break;
                         default:
                             break;
                     }
@@ -184,8 +189,6 @@ module.exports = app => {
             for (let idx = 0; idx < runnableKey.length; idx++) {
                 rst[runnableKey[idx]] = queryRst[idx];
             }
-            this.ctx.helper.saveBufferFile(JSON.stringify(queryRst, '', '\t'), this.ctx.app.baseDir + '/reportDataOrg.json');
-            this.ctx.helper.saveBufferFile(JSON.stringify(rst, '', '\t'), this.ctx.app.baseDir + '/reportData.json');
             for (const filter of filters) {
                 switch (filter) {
                     case 'mem_stage_im_tz':

+ 16 - 0
app/service/report_memory.js

@@ -718,6 +718,22 @@ module.exports = app => {
             }
         }
 
+        async getStageChangeBillsData(tid, sid, fields) {
+            try {
+                await this.ctx.service.tender.checkTender(tid);
+                await this.ctx.service.stage.checkStage(sid);
+
+                if (this.ctx.stage.readOnly) {
+                    return await this.ctx.service.stageChange.getReportAuditorAllStageData(tid, sid,
+                        this.ctx.stage.curTimes, this.ctx.stage.curOrder);
+                } else {
+                    return await this.ctx.service.stageChange.getReportLastestAllStageData(tid, sid);
+                }
+            } catch (err) {
+                return [];
+            }
+        }
+
         _getChangeConstName(define, value) {
             for (const prop in define) {
                 if (define[prop].value === value) {

+ 4 - 4
app/service/revise_audit.js

@@ -300,11 +300,11 @@ module.exports = app => {
                 '  (id, code, b_code, name, unit, source, remark, ledger_id, ledger_pid, level, `order`, full_path, is_leaf,' +
                 '     quantity, total_price, unit_price, drawing_code, memo, dgn_qty1, dgn_qty2, deal_qty, deal_tp,' +
                 '     sgfh_qty, sgfh_tp, sjcl_qty, sjcl_tp, qtcl_qty, qtcl_tp, node_type, crid, tender_id, is_tp,' +
-                '     sgfh_expr, sjcl_expr, qtcl_expr, gxby_status, dagl_status)' +
+                '     sgfh_expr, sjcl_expr, qtcl_expr, gxby_status, dagl_status, dagl_url, check_calc)' +
                 '  Select id, code, b_code, name, unit, source, remark, ledger_id, ledger_pid, level, `order`, full_path, is_leaf,' +
                 '      quantity, total_price, unit_price, drawing_code, memo, dgn_qty1, dgn_qty2, deal_qty, deal_tp,' +
                 '      sgfh_qty, sgfh_tp, sjcl_qty, sjcl_tp, qtcl_qty, qtcl_tp, node_type, crid, tender_id, is_tp, ' +
-                '      sgfh_expr, sjcl_expr, qtcl_expr, gxby_status, dagl_status' +
+                '      sgfh_expr, sjcl_expr, qtcl_expr, gxby_status, dagl_status, dagl_url, check_calc' +
                 '  From ' +
                 this.ctx.service.reviseBills.tableName +
                 '  Where `tender_id` = ?';
@@ -315,10 +315,10 @@ module.exports = app => {
                 this.ctx.service.pos.tableName +
                 '  (id, tid, lid, name, drawing_code, quantity, add_stage, add_times, add_user,' +
                 '     sgfh_qty, sjcl_qty, qtcl_qty, crid, porder, position, ' +
-                '     sgfh_expr, sjcl_expr, qtcl_expr, real_qty, gxby_status, dagl_status)' +
+                '     sgfh_expr, sjcl_expr, qtcl_expr, real_qty, gxby_status, dagl_status, dagl_url)' +
                 '  Select id, tid, lid, name, drawing_code, quantity, add_stage, add_times, add_user,' +
                 '     sgfh_qty, sjcl_qty, qtcl_qty, crid, porder, position,' +
-                '     sgfh_expr, sjcl_expr, qtcl_expr, real_qty, gxby_status, dagl_status' +
+                '     sgfh_expr, sjcl_expr, qtcl_expr, real_qty, gxby_status, dagl_status, dagl_url' +
                 '  From ' +
                 this.ctx.service.revisePos.tableName +
                 '  Where `tid` = ?';

+ 4 - 1
app/service/revise_bills.js

@@ -29,6 +29,7 @@ module.exports = app => {
                 keyPre: 'revise_bills_maxLid:',
                 uuid: true,
             }, 'revisePos');
+            this.depart = 10;
             this.tableName = 'revise_bills';
         }
 
@@ -45,7 +46,7 @@ module.exports = app => {
          */
         async addReviseNode(tid, rid, lid, count) {
             if (!rid) return null;
-            return await this.addNodeBatch(tid, lid, {crid: rid}, count);
+            return await this.addNodeBatch(tid, lid, {crid: rid, check_calc: 1}, count);
         }
 
         /**
@@ -85,6 +86,7 @@ module.exports = app => {
                     name: data[i].name,
                     unit: data[i].unit,
                     unit_price: data[i].price,
+                    check_calc: 1,
                 };
 
                 const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, bills.unit);
@@ -186,6 +188,7 @@ module.exports = app => {
                     name: data[i].name,
                     unit: data[i].unit,
                     unit_price: data[i].price,
+                    check_calc: 1,
                 };
 
                 const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, bills.unit);

+ 1 - 0
app/service/revise_pos.js

@@ -19,6 +19,7 @@ module.exports = app => {
          */
         constructor(ctx) {
             super(ctx);
+            this.depart = 20;
             this.tableName = 'revise_pos';
         }
 

+ 54 - 49
app/service/rpt_gather_memory.js

@@ -284,18 +284,33 @@ module.exports = app => {
                     const curStage = await this.ctx.service.stageBills.getAuditorStageData(tender.id,
                         stage.id, stage.curTimes, stage.curOrder);
                     this.ctx.helper.assignRelaData(billsData, [
-                        {data: curStage, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'], prefix: '', relaId: 'lid'}
+                        {
+                            data: curStage,
+                            fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'],
+                            prefix: '',
+                            relaId: 'lid'
+                        }
                     ]);
                 } else {
                     const curStage = await this.ctx.service.stageBills.getLastestStageData(tender.id, stage.id);
                     this.ctx.helper.assignRelaData(billsData, [
-                        {data: curStage, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'], prefix: '', relaId: 'lid'}
+                        {
+                            data: curStage,
+                            fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'],
+                            prefix: '',
+                            relaId: 'lid'
+                        }
                     ]);
                 }
                 if (hasPre) {
                     const preStage = stage.order > 1 ? await this.ctx.service.stageBillsFinal.getFinalData(tender, stage.order - 1) : [];
                     this.ctx.helper.assignRelaData(billsData, [
-                        {data: preStage, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'], prefix: 'pre_', relaId: 'lid'}
+                        {
+                            data: preStage,
+                            fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'],
+                            prefix: 'pre_',
+                            relaId: 'lid'
+                        }
                     ]);
                 }
             }
@@ -304,7 +319,6 @@ module.exports = app => {
             this.resultTree.loadGatherTree(billsTree, function (gatherNode, sourceNode) {
                 gatherUtils.gatherStage(tender, gatherNode, sourceNode, completeData.prefix, helper);
             });
-
         }
 
         async _gatherStagesData(completeData, tender, stages) {
@@ -368,14 +382,20 @@ module.exports = app => {
                 if (stage.readOnly) {
                     const curStage = await this.ctx.service.stageBills.getAuditorStageData(tender.id,
                         stage.id, stage.curTimes, stage.curOrder);
-                    sumAssignRelaData(billsIndexData, [
-                        {data: curStage, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'], prefix: '', relaId: 'lid'}
-                    ]);
+                    sumAssignRelaData(billsIndexData, [{
+                        data: curStage,
+                        fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'],
+                        prefix: '',
+                        relaId: 'lid'
+                    }]);
                 } else {
                     const curStage = await this.ctx.service.stageBills.getLastestStageData(tender.id, stage.id);
-                    sumAssignRelaData(billsIndexData, [
-                        {data: curStage, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'], prefix: '', relaId: 'lid'}
-                    ]);
+                    sumAssignRelaData(billsIndexData, [{
+                        data: curStage,
+                        fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'],
+                        prefix: '',
+                        relaId: 'lid'
+                    }]);
                 }
             }
 
@@ -386,45 +406,39 @@ module.exports = app => {
             });
         }
 
-        async _gatherMonthData(sTender, completeData, month, hasPre) {
-            const tender = await this.ctx.service.tender.getCheckTender(sTender.tid);
+        async _gatherMonthData(tender, completeData, month, hasPre) {
             const stages = await this._getValidStages(tender.id);
             const stage = this.ctx.helper._.find(stages, {s_time: month});
             await this._gatherStageData(completeData, tender, stage, hasPre);
         }
 
-        async _gatherIndexData(sTender, completeData, index, hasPre) {
-            const tender = await this.ctx.service.tender.getCheckTender(sTender.tid);
+        async _gatherIndexData(tender, completeData, index, hasPre) {
             const stages = await this._getValidStages(tender.id);
             const stage = this.ctx.helper._.find(stages, {order: index});
             await this._gatherStageData(completeData, tender, stage, hasPre);
         }
 
-        async _gatherZoneData(sTender, completeData, zone) {
-            const tender = await this.ctx.service.tender.getCheckTender(sTender.tid);
+        async _gatherZoneData(tender, completeData, zone) {
             const stages = await this._getTimeZoneStages(tender, zone);
             await this._gatherStagesData(completeData, tender, stages);
         }
 
-        async _gatherIndexZoneData(sTender, completeData, stageZone) {
-            const tender = await this.ctx.service.tender.getCheckTender(sTender.tid);
+        async _gatherIndexZoneData(tender, completeData, stageZone) {
             const stages = await this._getOrderZoneStages(tender, stageZone);
             await this._gatherStagesData(completeData, tender, stages);
         }
 
-        async _gatherFinalData(sTender, completeData, hasPre) {
-            const tender = await this.ctx.service.tender.getCheckTender(sTender.tid);
+        async _gatherFinalData(tender, completeData, hasPre) {
             const stages = await this._getValidStages(tender.id);
             await this._gatherStageData(completeData, tender, stages[0], hasPre);
         }
 
-        async _gatherCheckedFinalData(sTender, completeData, hasPre) {
-            const tender = await this.ctx.service.tender.getCheckTender(sTender.tid);
+        async _gatherCheckedFinalData(tender, completeData, hasPre) {
             const stages = await this._getCheckedStages(tender.id);
             await this._gatherStageData(completeData, tender, stages[0], hasPre);
         }
 
-        async _gatherLedgerData(sTender, completeData) {
+        async _gatherLedgerData(tender, completeData) {
             const helper = this.ctx.helper;
             const billsTree = new Ledger.billsTree(this.ctx, {
                 id: 'ledger_id',
@@ -436,16 +450,15 @@ module.exports = app => {
                 stageId: 'id',
                 calcFields: ['deal_tp', 'total_price'],
             });
-            const tender = await this.ctx.service.tender.getCheckTender(sTender.tid);
             const billsData = await this.ctx.service.ledger.getData(tender.id);
             billsTree.loadDatas(billsData);
             billsTree.calculateAll();
             this.resultTree.loadGatherTree(billsTree, function (gatherNode, sourceNode) {
                 gatherUtils.gatherLedger(tender, gatherNode, sourceNode, completeData.prefix, helper);
-            })
+            });
         }
 
-        async _gatherSpecialData(sTender, sKey) {
+        async _gatherSpecialData(tender, sKey) {
             const helper = this.ctx.helper;
             const billsTree = new Ledger.billsTree(this.ctx, {
                 id: 'ledger_id',
@@ -457,13 +470,12 @@ module.exports = app => {
                 stageId: 'id',
                 calcFields: ['deal_tp', 'total_price'],
             });
-            const tender = await this.ctx.service.tender.getCheckTender(sTender.tid);
             const billsData = await this.ctx.service.ledger.getData(tender.id);
             billsTree.loadDatas(billsData);
             billsTree.calculateAll();
             this.resultTree.loadGatherTree(billsTree, function (gatherNode, sourceNode) {
                 gatherUtils.gatherSpecial(gatherNode, sourceNode, 'ts_' + sKey + '_', helper);
-            })
+            });
         }
 
         async getGatherStageBills(memFieldKeys, gsDefine, gsCustom) {
@@ -473,8 +485,9 @@ module.exports = app => {
             const gsSetting = JSON.parse(gsDefine.setting);
             let commonIndex = 0;
             const completeDatas = [];
-            for (const tender of gsCustom.tenders) {
-                const specialKey = this._checkSpecialTender(tender, gsSetting.special);
+            for (const t of gsCustom.tenders) {
+                const specialKey = this._checkSpecialTender(t, gsSetting.special);
+                const tender = await this.ctx.service.tender.getCheckTender(t.tid);
                 if (specialKey === '') {
                     const completeData = {
                         prefix: 't_' + commonIndex + '_',
@@ -580,8 +593,7 @@ module.exports = app => {
             info.gather_tp = helper.add(info.contract_tp, info.qc_tp);
         }
 
-        async _gatherMonthTenderInfo(sTender, index, month, hasPre) {
-            const tender = await this.ctx.service.tender.getCheckTender(sTender.tid);
+        async _gatherMonthTenderInfo(tender, index, month, hasPre) {
             const info = await this._getBaseTenderInfo(tender);
             const stages = await this._getValidStages(tender.id);
             const stage = this.ctx.helper._.find(stages, {s_time: month});
@@ -589,8 +601,7 @@ module.exports = app => {
             this.resultTenderInfo.push(info);
         }
 
-        async _gatherOrderTenderInfo(sTender, index, order, hasPre) {
-            const tender = await this.ctx.service.tender.getCheckTender(sTender.tid);
+        async _gatherOrderTenderInfo(tender, index, order, hasPre) {
             const info = await this._getBaseTenderInfo(tender);
             const stages = await this._getValidStages(tender.id);
             const stage = this.ctx.helper._.find(stages, {order: order});
@@ -598,47 +609,40 @@ module.exports = app => {
             this.resultTenderInfo.push(info);
         }
 
-        async _gatherZoneTenderInfo(sTender, index, zone) {
-            const tender = await this.ctx.service.tender.getCheckTender(sTender.tid);
+        async _gatherZoneTenderInfo(tender, index, zone) {
             const info = await this._getBaseTenderInfo(tender);
             const stages = await this._getTimeZoneStages(tender, zone);
             await this._getStagesTenderInfo(stages, info);
             this.resultTenderInfo.push(info);
         }
 
-        async _gatherOrderZoneTenderInfo(sTender, index, stageZone) {
-            const tender = await this.ctx.service.tender.getCheckTender(sTender.tid);
+        async _gatherOrderZoneTenderInfo(tender, index, stageZone) {
             const info = await this._getBaseTenderInfo(tender);
             const stages = await this._getOrderZoneStages(tender, stageZone);
             await this._getStagesTenderInfo(stages, info);
             this.resultTenderInfo.push(info);
         }
 
-        async _gatherFinalTenderInfo(sTender, index, hasPre) {
-            const tender = await this.ctx.service.tender.getCheckTender(sTender.tid);
+        async _gatherFinalTenderInfo(tender, index, hasPre) {
             const info = await this._getBaseTenderInfo(tender);
             const stages = await this._getValidStages(tender.id);
             await this._getStageTenderInfo(stages[0], info);
             this.resultTenderInfo.push(info);
         }
 
-        async _gatherCheckedFinalTenderInfo(sTender, index, hasPre) {
-            const tender = await this.ctx.service.tender.getCheckTender(sTender.tid);
+        async _gatherCheckedFinalTenderInfo(tender, index, hasPre) {
             const info = await this._getBaseTenderInfo(tender);
             const stages = await this._getCheckedStages(tender.id);
             await this._getStageTenderInfo(stages[0], info);
             this.resultTenderInfo.push(info);
         }
 
-        async _gatherLedgerTenderInfo(sTender, index) {
-            const tender = await this.ctx.service.tender.getCheckTender(sTender.tid);
+        async _gatherLedgerTenderInfo(tender, index) {
             const info = await this._getBaseTenderInfo(tender);
             this.resultTenderInfo.push(info);
         }
 
-        async _gatherSpecialTenderInfo(sTender, sKey) {
-            const tender = await this.ctx.service.tender.getCheckTender(sTender.tid);
-
+        async _gatherSpecialTenderInfo(tender, sKey) {
             const info = await this._getBaseTenderInfo(tender);
             info.spec = sKey;
             this.resultTenderInfo.push(info);
@@ -651,8 +655,9 @@ module.exports = app => {
             this.resultTenderInfo = [];
             const gsSetting = JSON.parse(gsDefine.setting);
             let commonIndex = 0;
-            for (const tender of gsCustom.tenders) {
-                const specialKey = this._checkSpecialTender(tender, gsSetting.special);
+            for (const t of gsCustom.tenders) {
+                const specialKey = this._checkSpecialTender(t, gsSetting.special);
+                const tender = await this.ctx.service.tender.getCheckTender(t.tid);
                 if (specialKey === '') {
                     switch (gsSetting.type) {
                         case 'month':

+ 63 - 1
app/service/schedule_ledger_month.js

@@ -13,7 +13,7 @@ module.exports = app => {
             try {
                 const info = await this.getDataByCondition({ tid: this.ctx.tender.id, lid: data.lid, yearmonth: data.yearmonth });
                 if (info) {
-                    if (data.plan_gcl === null && data.plan_tp === null) {
+                    if (data.plan_gcl === null && data.plan_tp === null && info.sj_gcl === null && info.sj_tp === null) {
                         await transaction.delete(this.tableName, { id: info.id });
                     } else {
                         const updateData = {
@@ -44,6 +44,60 @@ module.exports = app => {
             }
         }
 
+        async saveSj(data) {
+            // 判断是添加,删除,还是修改
+            const transaction = await this.db.beginTransaction();
+            try {
+                const info = await this.getDataByCondition({ tid: this.ctx.tender.id, lid: data.lid, yearmonth: data.yearmonth });
+                if (info) {
+                    if (data.sj_gcl === null && data.sj_tp === null && info.plan_gcl === null && info.plan_tp === null) {
+                        await transaction.delete(this.tableName, { id: info.id });
+                    } else {
+                        const updateData = {
+                            id: info.id,
+                            sj_gcl: data.sj_gcl,
+                            sj_tp: data.sj_tp,
+                        };
+                        await transaction.update(this.tableName, updateData);
+                    }
+                } else {
+                    const insertData = {
+                        tid: this.ctx.tender.id,
+                        lid: data.lid,
+                        yearmonth: data.yearmonth,
+                        sj_gcl: data.sj_gcl,
+                        sj_tp: data.sj_tp,
+                    };
+                    await transaction.insert(this.tableName, insertData);
+                }
+                // 重新计算本月、总 计划金额和计划工程量
+                await this.calcMonthSj(transaction, this.ctx.tender.id, data.yearmonth);
+                await this.ctx.service.scheduleMonth.calcSj(transaction, this.ctx.tender.id);
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        async calcMonthSj(transaction, tid, yearmonth) {
+            const sql = 'SELECT SUM(`sj_gcl`) as total_sj_gcl, SUM(`sj_tp`) as total_sj_tp FROM ?? WHERE tid = ? and yearmonth = ?';
+            const sqlParam = [this.tableName, tid, yearmonth];
+            const result = await transaction.queryOne(sql, sqlParam);
+            const updateData = {
+                sj_gcl: result.total_sj_gcl,
+                sj_tp: result.total_sj_tp,
+            };
+            const option = {
+                where: {
+                    tid,
+                    yearmonth,
+                },
+            };
+            return await transaction.update(this.ctx.service.scheduleMonth.tableName, updateData, option);
+        }
+
         async calcMonthPlan(transaction, tid, yearmonth) {
             const sql = 'SELECT SUM(`plan_gcl`) as total_plan_gcl, SUM(`plan_tp`) as total_plan_tp FROM ?? WHERE tid = ? and yearmonth = ?';
             const sqlParam = [this.tableName, tid, yearmonth];
@@ -60,6 +114,14 @@ module.exports = app => {
             };
             return await transaction.update(this.ctx.service.scheduleMonth.tableName, updateData, option);
         }
+
+        async getConllectionList(tid, yearmonthArray) {
+            const sql = 'SELECT tid, lid, SUM(`plan_gcl`) as plan_gcl, SUM(`plan_tp`) as plan_tp' +
+                ' FROM ?? WHERE `tid` = ? AND `yearmonth` in (?) GROUP BY `lid`';
+            const sqlParam = [this.tableName, tid, yearmonthArray];
+            const result = await this.db.query(sql, sqlParam);
+            return result;
+        }
     }
     return ScheduleLedgerMonth;
 };

+ 45 - 0
app/service/schedule_month.js

@@ -62,6 +62,51 @@ module.exports = app => {
             };
             return await transaction.update(this.ctx.service.schedule.tableName, updateData, option);
         }
+
+        async calcSj(transaction, tid) {
+            const sql = 'SELECT SUM(`sj_gcl`) as total_sj_gcl, SUM(`sj_tp`) as total_sj_tp FROM ?? WHERE tid = ?';
+            const sqlParam = [this.tableName, tid];
+            const result = await transaction.queryOne(sql, sqlParam);
+            const updateData = {
+                sj_gcl: result.total_sj_gcl,
+                sj_tp: result.total_sj_tp,
+            };
+            const option = {
+                where: {
+                    tid,
+                },
+            };
+            return await transaction.update(this.ctx.service.schedule.tableName, updateData, option);
+        }
+
+        async addStageUsed(data) {
+            const updateData = {
+                id: data.id,
+                stage_gcl_used: 1,
+            };
+            return await this.db.update(this.tableName, updateData);
+        }
+
+        async delStageUsed(data) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                const updateDatas = [];
+                const updateLmDatas = [];
+                for (const m of data) {
+                    updateDatas.push({ row: { stage_gcl_used: 0, sj_gcl: null, sj_tp: null }, where: { yearmonth: m, tid: this.ctx.tender.id } });
+                    updateLmDatas.push({ row: { sj_gcl: null, sj_tp: null }, where: { yearmonth: m, tid: this.ctx.tender.id } });
+                }
+                if (updateDatas.length > 0) await transaction.updateRows(this.tableName, updateDatas);
+                if (updateLmDatas.length > 0) await transaction.updateRows(this.ctx.service.scheduleLedgerMonth.tableName, updateLmDatas);
+                // 重新计算总 计划金额和计划工程量
+                await this.calcSj(transaction, this.ctx.tender.id);
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
     }
     return ScheduleMonth;
 };

+ 85 - 0
app/service/schedule_stage.js

@@ -0,0 +1,85 @@
+'use strict';
+
+module.exports = app => {
+    class ScheduleStage extends app.BaseService {
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'schedule_stage';
+        }
+
+        async getLastPlanMonth() {
+            const sql = 'SELECT `yearmonth` FROM ?? WHERE `tid` = ? ORDER BY `yearmonth` DESC Limit 0,1';
+            const sqlParam = [this.tableName, this.ctx.tender.id];
+            return await this.db.query(sql, sqlParam);
+        }
+
+        async add(data) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                const insertData = {
+                    tid: this.ctx.tender.id,
+                    yearmonth: data.yearmonth,
+                    order: data.order,
+                };
+                // 更新schedule_month stage_tp_used为1
+                const updateData = {
+                    stage_tp_used: 1,
+                };
+                const option = {
+                    where: {
+                        tid: this.ctx.tender.id,
+                        yearmonth: data.yearmonth,
+                    },
+                };
+                await transaction.update(this.ctx.service.scheduleMonth.tableName, updateData, option);
+                await transaction.insert(this.tableName, insertData);
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        async del(data) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                const info = await this.getDataById(data.id);
+                await transaction.delete(this.tableName, { id: data.id });
+                // 更新schedule_month stage_tp_used为0
+                const updateData = {
+                    stage_tp_used: 0,
+                };
+                const option = {
+                    where: {
+                        tid: this.ctx.tender.id,
+                        yearmonth: info.yearmonth,
+                    },
+                };
+                await transaction.update(this.ctx.service.scheduleMonth.tableName, updateData, option);
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        async changeOrder(data) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                const updateData = {
+                    id: data.id,
+                    order: data.order,
+                };
+                await transaction.update(this.tableName, updateData);
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+    }
+    return ScheduleStage;
+};

+ 3 - 0
app/service/stage.js

@@ -14,6 +14,7 @@ const roleRelSvr = require('./role_rpt_rel');
 const fs = require('fs');
 const path = require('path');
 const _ = require('lodash');
+const projectLogConst = require('../const/project_log');
 
 module.exports = app => {
     class Stage extends app.BaseService {
@@ -546,6 +547,8 @@ module.exports = app => {
                 }
                 await transaction.delete(this.ctx.service.stageBonus.tableName, { sid: id });
                 await transaction.delete(this.ctx.service.stageOther.tableName, { sid: id });
+                // 记录删除日志
+                await this.ctx.service.projectLog.addProjectLog(transaction, projectLogConst.type.stage, projectLogConst.status.delete, '第' + stageInfo.order + '期');
                 await transaction.commit();
                 return true;
             } catch (err) {

+ 2 - 0
app/service/stage_audit.js

@@ -449,6 +449,7 @@ module.exports = app => {
                     // 生成截止本期数据 final数据
                     await this.ctx.service.stageBillsFinal.generateFinalData(transaction, this.ctx.tender, this.ctx.stage);
                     await this.ctx.service.stagePosFinal.generateFinalData(transaction, this.ctx.tender, this.ctx.stage);
+                    await this.ctx.service.stageChangeFinal.generateFinalData(transaction, this.ctx.tender, this.ctx.stage);
                     // 同步 期信息
                     await transaction.update(this.ctx.service.stage.tableName, {
                         id: stageId,
@@ -885,6 +886,7 @@ module.exports = app => {
                 // 生成截止本期数据 final数据
                 await this.ctx.service.stageBillsFinal.delGenerateFinalData(transaction, this.ctx.tender, this.ctx.stage);
                 await this.ctx.service.stagePosFinal.delGenerateFinalData(transaction, this.ctx.tender, this.ctx.stage);
+                await this.ctx.service.stageChangeFinal.delGenerateFinalData(transaction, this.ctx.tender, this.ctx.stage);
                 // 同步 期信息
                 await transaction.update(this.ctx.service.stage.tableName, {
                     id: stageId,

+ 10 - 9
app/service/stage_bills.js

@@ -21,6 +21,7 @@ module.exports = app => {
          */
         constructor(ctx) {
             super(ctx);
+            this.depart = 10;
             this.tableName = 'stage_bills';
         }
 
@@ -41,9 +42,9 @@ module.exports = app => {
                     lidSql = ' And lid in (' + this.db.escape(lid) + ')';
                 }
             }
-            const sql = 'SELECT Bills.* FROM ' + this.tableName + ' As Bills ' +
+            const sql = 'SELECT Bills.* FROM ' + this.departTableName(tid) + ' As Bills ' +
                         '  INNER JOIN ( ' +
-                        '    SELECT MAX(`times` * ' + timesLen + ' + `order`) As `progress`, `lid`, `sid` From ' + this.tableName +
+                        '    SELECT MAX(`times` * ' + timesLen + ' + `order`) As `progress`, `lid`, `sid` From ' + this.departTableName(tid) +
                         '      WHERE tid = ? And sid = ?' + lidSql +
                         '      GROUP BY `lid`' +
                         '  ) As MaxFilter ' +
@@ -69,9 +70,9 @@ module.exports = app => {
          */
         async getAuditorStageData(tid, sid, times, order, lid) {
             const lidSql = lid ? ' And Bills.lid in (?)' : '';
-            const sql = 'SELECT Bills.* FROM ' + this.tableName + ' As Bills ' +
+            const sql = 'SELECT Bills.* FROM ' + this.departTableName(tid) + ' As Bills ' +
                 '  INNER JOIN ( ' +
-                '    SELECT MAX(`times` * ' + timesLen + ' + `order`) As `progress`, `lid`, `tid`, `sid` From ' + this.tableName +
+                '    SELECT MAX(`times` * ' + timesLen + ' + `order`) As `progress`, `lid`, `tid`, `sid` From ' + this.departTableName(tid) +
                 '      WHERE (`times` < ? OR (`times` = ? AND `order` <= ?)) And tid = ? And sid = ?' + lidSql +
                 '      GROUP BY `lid`' +
                 '  ) As MaxFilter ' +
@@ -360,9 +361,9 @@ module.exports = app => {
         }
 
         async getSumTotalPrice(stage) {
-            const sql = 'SELECT Sum(`contract_tp`) As `contract_tp`, Sum(`qc_tp`) As `qc_tp` FROM ' + this.tableName + ' As Bills ' +
+            const sql = 'SELECT Sum(`contract_tp`) As `contract_tp`, Sum(`qc_tp`) As `qc_tp` FROM ' + this.departTableName(stage.tid) + ' As Bills ' +
                 '  INNER JOIN ( ' +
-                '    SELECT MAX(`times` * ' + timesLen + ' + `order`) As `flow`, `lid`, `sid` From ' + this.tableName +
+                '    SELECT MAX(`times` * ' + timesLen + ' + `order`) As `flow`, `lid`, `sid` From ' + this.departTableName(stage.tid) +
                 '      WHERE (`times` < ? OR (`times` = ? AND `order` <= ?)) AND `sid` = ?' +
                 '      GROUP BY `lid`' +
                 '  ) As MaxFilter ' +
@@ -374,14 +375,14 @@ module.exports = app => {
 
         async getSumTotalPriceFilter(stage, operate, filter) {
             const sql = 'SELECT Sum(`contract_tp`) As `contract_tp`, Sum(`qc_tp`) As `qc_tp`' +
-                '  FROM ' + this.tableName + ' As Bills ' +
+                '  FROM ' + this.departTableName(stage.tid) + ' As Bills ' +
                 '  INNER JOIN ( ' +
-                '    SELECT MAX(`times` * ' + timesLen + ' + `order`) As `flow`, `lid` From ' + this.tableName +
+                '    SELECT MAX(`times` * ' + timesLen + ' + `order`) As `flow`, `lid` From ' + this.departTableName(stage.tid) +
                 '      WHERE (`times` < ? OR (`times` = ? AND `order` <= ?)) AND `sid` = ?' +
                 '      GROUP BY `lid`' +
                 '  ) As MaxFilter ' +
                 '  ON (Bills.times * ' + timesLen + ' + `order`) = MaxFilter.flow And Bills.lid = MaxFilter.lid ' +
-                '  INNER JOIN ' + this.ctx.service.ledger.tableName + ' As Ledger ON Bills.lid = Ledger.id' +
+                '  INNER JOIN ' + this.ctx.service.ledger.departTableName(stage.tid) + ' As Ledger ON Bills.lid = Ledger.id' +
                 '  WHERE Bills.sid = ? And Ledger.b_code ' + operate + ' ?';
             const sqlParam = [stage.times, stage.curTimes, stage.curOrder, stage.id, stage.id, filter];
             const result = await this.db.queryOne(sql, sqlParam);

+ 3 - 11
app/service/stage_bills_final.js

@@ -20,6 +20,7 @@ module.exports = app => {
          */
         constructor(ctx) {
             super(ctx);
+            this.depart = 10;
             this.tableName = 'stage_bills_final';
         }
 
@@ -30,16 +31,7 @@ module.exports = app => {
          * @returns {Promise<void>}
          */
         async getFinalData(tender, stageOrder) {
-            // const sql = 'SELECT * FROM ' + this.tableName + ' As Bills' +
-            //     '  INNER JOIN ( ' +
-            //     '    SELECT MAX(`sorder`) As `sorder`, `lid` From ' + this.tableName +
-            //     '      WHERE tid = ? AND sorder < ?' +
-            //     '      GROUP BY `lid`' +
-            //     '  ) As MaxFilter ' +
-            //     '  ON Bills.sorder = MaxFilter.sorder And Bills.lid = MaxFilter.lid';
-            // const sqlParam = [tender.id, stage.order, tender.id];
-            // return await this.db.query(sql, sqlParam);
-            return await this.getAllDataByCondition({
+            return await this.db.select(this.departTableName(tender.id), {
                 //columns: ['lid', 'contract_qty', 'qc_qty', 'contract_tp', 'qc_tp'],
                 where: {tid: tender.id, sorder: stageOrder},
             });
@@ -147,7 +139,7 @@ module.exports = app => {
         }
 
         async getSumTotalPrice(tenderId, stageOrder) {
-            const sql = 'SELECT Sum(`contract_tp`) As `contract_tp`, Sum(`qc_tp`) As `qc_tp` FROM ' + this.tableName +
+            const sql = 'SELECT Sum(`contract_tp`) As `contract_tp`, Sum(`qc_tp`) As `qc_tp` FROM ' + this.departTableName(tenderId) +
                 '  WHERE sorder = ? and tid = ?';
             const sqlParam = [stageOrder, tenderId];
             const result = await this.db.queryOne(sql, sqlParam);

+ 38 - 31
app/service/stage_change.js

@@ -33,7 +33,7 @@ module.exports = app => {
          * @param {Number} sid - 期id
          * @param {Number} lid - 台账节点id
          * @param {Number} pid - 部位明细id
-         * @returns {Promise<*>}
+         * @return {Promise<*>}
          */
         async getLastestStageData(tid, sid, lid, pid) {
             const sql = 'SELECT c.*,' +
@@ -62,7 +62,7 @@ module.exports = app => {
          * @param {Number} order - 第几人
          * @param {Number} lid - 台账节点id
          * @param {Number} pid - 部位明细id
-         * @returns {Promise<*>}
+         * @return {Promise<*>}
          */
         async getAuditorStageData(tid, sid, times, order, lid, pid) {
             const sql = 'SELECT c.*,' +
@@ -71,7 +71,7 @@ module.exports = app => {
                 '  INNER JOIN ( ' +
                 '    SELECT MAX(`stimes` * ' + timesLen + ' + `sorder`) As `progress`, `lid`, `pid`, `sid`, `cid`, `cbid` From ' + this.tableName +
                 '      WHERE tid = ? And sid = ? And (`stimes` < ? OR (`stimes` = ? AND `sorder` <= ?)) And lid = ? And pid = ?' +
-                '      GROUP By `lid`, `pid`' +
+                '      GROUP By `lid`, `pid`, cid, cbid' +
                 '  ) As m ' +
                 '  ON (c.stimes * ' + timesLen + ' + c.sorder) = m.progress And c.lid = m.lid And c.pid = m.pid And c.`sid` = m.`sid` And c.`cid` = m.`cid` And c.`cbid` = m.`cbid`' +
                 '  LEFT JOIN ' + this.ctx.service.change.tableName + ' As oc' +
@@ -90,11 +90,11 @@ module.exports = app => {
                 '  INNER JOIN ( ' +
                 '    SELECT MAX(`stimes` * ' + timesLen + ' + `sorder`) As `progress`, `lid`, `pid`, `sid`, `cid`, `cbid` From ' + this.tableName +
                 '      WHERE tid = ? And sid = ?' +
-                '      GROUP By `lid`, `pid`' +
+                '      GROUP By `lid`, `pid`, cid, cbid' +
                 '  ) As m ' +
                 '  ON (c.stimes * ' + timesLen + ' + c.sorder) = m.progress And c.lid = m.lid And c.pid = m.pid And c.`sid` = m.`sid` And c.`cid` = m.`cid` And c.`cbid` = m.`cbid`' +
                 '  LEFT JOIN ' + this.ctx.service.change.tableName + ' As oc' +
-                '  ON c.cid = oc.cid'+
+                '  ON c.cid = oc.cid' +
                 '  LEFT JOIN ' + this.ctx.service.changeAuditList.tableName + ' As ocb' +
                 '  ON c.cbid = ocb.id' +
                 '  WHERE not ISNULL(ocb.id)';
@@ -109,11 +109,11 @@ module.exports = app => {
                 '  INNER JOIN ( ' +
                 '    SELECT MAX(`stimes` * ' + timesLen + ' + `sorder`) As `progress`, `lid`, `pid`, `sid`, `cid`, `cbid` From ' + this.tableName +
                 '      WHERE tid = ? And sid = ? And (`stimes` < ? OR (`stimes` = ? AND `sorder` <= ?))' +
-                '      GROUP By `lid`, `pid`' +
+                '      GROUP By `lid`, `pid`, cid, cbid' +
                 '  ) As m ' +
                 '  ON (c.stimes * ' + timesLen + ' + c.sorder) = m.progress And c.lid = m.lid And c.pid = m.pid And c.`sid` = m.`sid` And c.`cid` = m.`cid` And c.`cbid` = m.`cbid`' +
                 '  LEFT JOIN ' + this.ctx.service.change.tableName + ' As oc' +
-                '  ON c.cid = oc.cid'+
+                '  ON c.cid = oc.cid' +
                 '  LEFT JOIN ' + this.ctx.service.changeAuditList.tableName + ' As ocb' +
                 '  ON c.cbid = ocb.id' +
                 '  WHERE not ISNULL(ocb.id)';
@@ -126,7 +126,7 @@ module.exports = app => {
          *
          * @param {Object} bills - 台账节点数据
          * @param {Array} changes - 调用的变更令
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async billsChange(bills, changes) {
             const self = this;
@@ -136,12 +136,12 @@ module.exports = app => {
                     sid: self.ctx.stage.id,
                     lid: bills.id,
                     pid: -1,
-                    cid: cid,
-                    cbid: cbid,
+                    cid,
+                    cbid,
                     stimes: times,
                     sorder: order,
-                    qty: qty
-                }
+                    qty,
+                };
             }
             const ledgerBills = await this.ctx.service.ledger.getDataById(bills.id);
             if (!ledgerBills || ledgerBills.tender_id !== this.ctx.tender.id) {
@@ -151,10 +151,11 @@ module.exports = app => {
             // 获取原变更令
             const oldChanges = await this.getLastestStageData(this.ctx.tender.id, this.ctx.stage.id, bills.id, -1);
             // 获取更新数据
-            const updateChanges = [], newChanges = [];
+            const updateChanges = [],
+                newChanges = [];
             let billsQty = 0;
             for (const oc of oldChanges) {
-                const nc = this._.find(changes, {cid: oc.cid, cbid: oc.cbid});
+                const nc = this._.find(changes, { cid: oc.cid, cbid: oc.cbid });
                 if (!nc || nc.qty !== oc.qty) {
                     const qty = nc ? this.round(nc.qty, precision.value) : null;
                     const change = getNewChange(oc.cid, oc.cbid, this.ctx.stage.curTimes, this.ctx.stage.curOrder, qty);
@@ -170,7 +171,7 @@ module.exports = app => {
                 }
             }
             for (const c of changes) {
-                const nc = this._.find(oldChanges, {cid: c.cid, cbid: c.cbid});
+                const nc = this._.find(oldChanges, { cid: c.cid, cbid: c.cbid });
                 if (!nc) {
                     const change = getNewChange(c.cid, c.cbid, this.ctx.stage.curTimes, this.ctx.stage.curOrder, this.round(c.qty, precision.value));
                     billsQty = this.ctx.helper.add(billsQty, change.qty);
@@ -194,7 +195,7 @@ module.exports = app => {
                 throw err;
             }
             const result = await this.ctx.service.stageBills.getLastestStageData(this.ctx.tender.id, this.ctx.stage.id, [bills.id]);
-            return { bills: {curStageData: result} };
+            return { bills: { curStageData: result } };
         }
 
         /**
@@ -202,7 +203,7 @@ module.exports = app => {
          *
          * @param {Object} pos - 部位明细数据
          * @param {Array} changes - 调用的变更令
-         * @returns {Promise<{}>}
+         * @return {Promise<{}>}
          */
         async posChange(pos, changes) {
             const self = this;
@@ -212,12 +213,12 @@ module.exports = app => {
                     sid: self.ctx.stage.id,
                     lid: pos.lid,
                     pid: pos.id,
-                    cid: cid,
-                    cbid: cbid,
+                    cid,
+                    cbid,
                     stimes: times,
                     sorder: order,
-                    qty: qty
-                }
+                    qty,
+                };
             }
             const ledgerBills = await this.ctx.service.ledger.getDataById(pos.lid);
             if (!ledgerBills || ledgerBills.tender_id !== this.ctx.tender.id) {
@@ -226,10 +227,11 @@ module.exports = app => {
             const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, ledgerBills.unit);
             // 获取原变更令
             const oldChanges = await this.getLastestStageData(this.ctx.tender.id, this.ctx.stage.id, pos.lid, pos.id);
-            const updateChanges = [], newChanges = [];
+            const updateChanges = [],
+                newChanges = [];
             let posQty = 0;
             for (const oc of oldChanges) {
-                const nc = this._.find(changes, {cid: oc.cid, cbid: oc.cbid});
+                const nc = this._.find(changes, { cid: oc.cid, cbid: oc.cbid });
                 if (!nc || nc.qty !== oc.qty) {
                     const qty = nc ? this.round(nc.qty, precision.value) : null;
                     const change = getNewChange(oc.cid, oc.cbid, this.ctx.stage.curTimes, this.ctx.stage.curOrder, qty);
@@ -245,7 +247,7 @@ module.exports = app => {
                 }
             }
             for (const c of changes) {
-                const nc = this._.find(oldChanges, {cid: c.cid, cbid: c.cbid});
+                const nc = this._.find(oldChanges, { cid: c.cid, cbid: c.cbid });
                 if (!nc) {
                     const change = getNewChange(c.cid, c.cbid, this.ctx.stage.curTimes, this.ctx.stage.curOrder, this.round(c.qty, precision.value));
                     posQty = this.ctx.helper.add(posQty, change.qty);
@@ -272,9 +274,9 @@ module.exports = app => {
             try {
                 const data = { bills: {}, pos: {} };
                 data.bills.curStageData = await this.ctx.service.stageBills.getLastestStageData(this.ctx.tender.id, this.ctx.stage.id, [pos.lid]);
-                data.pos.curStageData = await this.ctx.service.stagePos.getLastestStageData2(this.ctx.tender.id, this.ctx.stage.id, {pid: pos.id});
+                data.pos.curStageData = await this.ctx.service.stagePos.getLastestStageData2(this.ctx.tender.id, this.ctx.stage.id, { pid: pos.id });
                 return data;
-            } catch(err) {
+            } catch (err) {
                 throw '获取数据错误,请刷新页面';
             }
         }
@@ -283,7 +285,7 @@ module.exports = app => {
          * 获取 变更令 - 变更清单 使用情况
          * @param {Number} sid - 查询期id
          * @param {uuid} cid - 变更令id
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async getUsedData(tid, cid) {
             const lastStage = await this.ctx.service.stage.getLastestStage(tid, true);
@@ -298,7 +300,7 @@ module.exports = app => {
                     filter = '';
                 } else if (lastStage.status === audit.stage.status.checkNo) {
                     filter = this.db.format(' And (s.`order` < ? || (s.`order` = ? And sChange.`stimes` <= ?))',
-                        [lastStage.order, lastStage.order, lastStage.times])
+                        [lastStage.order, lastStage.order, lastStage.times]);
                 } else {
                     const curAuditor = await this.ctx.service.stageAudit.getCurAuditor(lastStage.id, lastStage.times);
                     filter = this.db.format(' And (s.`order` < ? || (s.`order` = ? And sChange.`stimes` <= ? And sChange.`sorder` <= ?))',
@@ -347,7 +349,7 @@ module.exports = app => {
          * 获取 变更令 - 变更清单 当期使用情况
          * @param {Number} sid - 查询期id
          * @param {uuid} cid - 变更令id
-         * @returns {Promise<*>}
+         * @return {Promise<*>}
          */
         async getStageUsedData(sid, cid) {
             const sql = 'SELECT c.*, ' +
@@ -371,7 +373,7 @@ module.exports = app => {
         /**
          * 获取 本期 使用的变更令
          * @param sid
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async getStageUsedChangeId(sid) {
             const sql = 'SELECT c.`cid`, sum(qty) As qty FROM ' + this.tableName + ' As c' +
@@ -386,8 +388,13 @@ module.exports = app => {
             const result = await this.db.query(sql, sqlParam);
             return this._.map(this._.filter(result, 'qty'), 'cid');
         }
+
+        async getFinalStageData(tid, sid) {
+            const data = await this.getAllDataByCondition({ where: { tid, sid } });
+            return this.ctx.helper.filterLastestData(data, ['lid', 'pid', 'cid', 'cbid']);
+        }
     }
 
     return StageChange;
 
-};
+};

+ 84 - 0
app/service/stage_change_final.js

@@ -0,0 +1,84 @@
+'use strict';
+
+/**
+ * stage_change_final 期-变更令使用-最终数据
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const timesLen = require('../const/audit').stage.timesLen;
+
+module.exports = app => {
+
+    class StageChangeFinal extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'stage_change_final';
+        }
+
+        /**
+         * 获取截止本期数据
+         * @param {Object} tender - 标段
+         * @param {Object} stage - 本期
+         * @return {Promise<void>}
+         */
+        async getFinalData(tid) {
+            const sql = 'Select cf.*, c.code As c_code, c.name As c_name, c.new_code As c_new_code, c.new_name As c_new_name, c.content As c_content, c.basis As c_basis, c.cin_time As c_cin_time' +
+                '  FROM ' + this.tableName + ' cf Left Join ' + this.ctx.service.change.tableName + ' c' +
+                '  ON cf.cid = c.cid' +
+                '  Where cf.tid = ?';
+            return await this.db.query(sql, [tid]);
+            //return await this.getAllDataByCondition({ where: { tid: tid } });
+        }
+
+        /**
+         * 生成本期最终数据
+         * @param transaction - 所属事务
+         * @param {Number} tender - 标段
+         * @param {Number}stage - 本期
+         * @return {Promise<void>}
+         */
+        async generateFinalData(transaction, tender, stage) {
+            if (!transaction || !tender || !stage) {
+                throw '数据错误';
+            }
+
+            const cur = await this.ctx.service.stageChange.getFinalStageData(tender.id, stage.id);
+            const data = [];
+            for (const c of cur) {
+                if (!c.qty) continue;
+
+                data.push({
+                    tid: c.tid, sid: c.sid,
+                    lid: c.lid, pid: c.pid, cid: c.cid, cbid: c.cbid,
+                    qty: c.qty,
+                });
+            }
+            if (data.length > 0) await transaction.insert(this.tableName, data);
+        }
+
+        /**
+         * 删除生成本期最终数据
+         * @param transaction - 所属事务
+         * @param {Object} tender - 标段
+         * @param {Object} stage - 本期
+         * @return {Promise<void>}
+         */
+        async delGenerateFinalData(transaction, tender, stage) {
+            if (!transaction || !tender || !stage) {
+                throw '数据错误';
+            }
+            await transaction.delete(this.tableName, { tid: tender.id, sid: stage.id });
+        }
+    }
+
+    return StageChangeFinal;
+};

+ 1 - 0
app/service/stage_pos.js

@@ -21,6 +21,7 @@ module.exports = app => {
          */
         constructor(ctx) {
             super(ctx);
+            this.depart = 20;
             this.tableName = 'stage_pos';
             this.qtyFields = ['contract_qty', 'qc_qty']
         }

+ 1 - 0
app/service/stage_pos_final.js

@@ -21,6 +21,7 @@ module.exports = app => {
          */
         constructor(ctx) {
             super(ctx);
+            this.depart = 20;
             this.tableName = 'stage_pos_final';
         }
 

+ 13 - 8
app/service/tender.js

@@ -10,6 +10,7 @@
 
 const tenderConst = require('../const/tender');
 const auditConst = require('../const/audit');
+const projectLogConst = require('../const/project_log');
 const fs = require('fs');
 const path = require('path');
 const commonQueryColumns = ['id', 'project_id', 'name', 'status', 'category', 'ledger_times', 'ledger_status', 'measure_type', 'user_id', 'valuation', 'total_price', 'deal_tp', 'copy_id'];
@@ -270,6 +271,7 @@ module.exports = app => {
         async deleteTenderNoBackup(id) {
             const transaction = await this.db.beginTransaction();
             try {
+                const tenderMsg = await this.getDataById(id);
                 // 先删除附件文件
                 const attList = await this.ctx.service.changeAtt.getAllDataByCondition({ where: { tid: id } });
                 const newAttList = await this.ctx.service.materialFile.getAllMaterialFiles(id);
@@ -277,18 +279,18 @@ module.exports = app => {
                 await this.ctx.helper.delFiles(attList);
                 await transaction.delete(this.tableName, { id });
                 await transaction.delete(this.ctx.service.tenderInfo.tableName, { tid: id });
-                await transaction.delete(this.ctx.service.ledger.tableName, { tender_id: id });
+                await transaction.delete(this.ctx.service.ledger.departTableName(id), { tender_id: id });
                 await transaction.delete(this.ctx.service.ledgerAudit.tableName, { tender_id: id });
-                await transaction.delete(this.ctx.service.pos.tableName, { tid: id });
+                await transaction.delete(this.ctx.service.pos.departTableName(id), { tid: id });
                 await transaction.delete(this.ctx.service.pay.tableName, { tid: id });
 
                 await transaction.delete(this.ctx.service.stage.tableName, { tid: id });
                 await transaction.delete(this.ctx.service.stageAudit.tableName, { tid: id });
-                await transaction.delete(this.ctx.service.stageBills.tableName, { tid: id });
-                await transaction.delete(this.ctx.service.stagePos.tableName, { tid: id });
+                await transaction.delete(this.ctx.service.stageBills.departTableName(id), { tid: id });
+                await transaction.delete(this.ctx.service.stagePos.departTableName(id), { tid: id });
                 await transaction.delete(this.ctx.service.stageBillsDgn.tableName, { tid: id });
-                await transaction.delete(this.ctx.service.stageBillsFinal.tableName, { tid: id });
-                await transaction.delete(this.ctx.service.stagePosFinal.tableName, { tid: id });
+                await transaction.delete(this.ctx.service.stageBillsFinal.departTableName(id), { tid: id });
+                await transaction.delete(this.ctx.service.stagePosFinal.departTableName(id), { tid: id });
                 await transaction.delete(this.ctx.service.stageDetail.tableName, { tid: id });
                 await transaction.delete(this.ctx.service.stagePay.tableName, { tid: id });
                 await transaction.delete(this.ctx.service.stageChange.tableName, { tid: id });
@@ -304,8 +306,8 @@ module.exports = app => {
 
                 await transaction.delete(this.ctx.service.ledgerRevise.tableName, { tid: id });
                 await transaction.delete(this.ctx.service.reviseAudit.tableName, { tender_id: id });
-                await transaction.delete(this.ctx.service.reviseBills.tableName, { tender_id: id });
-                await transaction.delete(this.ctx.service.revisePos.tableName, { tid: id });
+                await transaction.delete(this.ctx.service.reviseBills.departTableName(id), { tender_id: id });
+                await transaction.delete(this.ctx.service.revisePos.departTableName(id), { tid: id });
 
                 await transaction.delete(this.ctx.service.material.tableName, { tid: id });
                 await transaction.delete(this.ctx.service.materialAudit.tableName, { tid: id });
@@ -325,6 +327,9 @@ module.exports = app => {
                 await transaction.delete(this.ctx.service.materialFile.tableName, { tid: id });
 
                 await transaction.delete(this.ctx.service.advanceFile.tableName, { tid: id });
+
+                // 记录删除日志
+                await this.ctx.service.projectLog.addProjectLog(transaction, projectLogConst.type.tender, projectLogConst.status.delete, tenderMsg.name, id);
                 await transaction.commit();
                 return true;
             } catch (err) {

+ 45 - 7
app/service/tender_info.js

@@ -70,7 +70,7 @@ module.exports = app => {
                 info = await this.addTenderInfo(tenderId, this.ctx.session.sessionProject.id);
             }
             for (const pi of parseInfo) {
-                info[pi] = !info[pi] || info[pi] === '' ? defaultInfo[pi] : JSON.parse(info[pi]);
+                info[pi] = !info[pi] || info[pi] === '' ? (pi === 'shenpi' ? JSON.parse(JSON.stringify(defaultInfo[pi])) : defaultInfo[pi]) : JSON.parse(info[pi]);
                 this.ctx.helper._.defaults(info[pi], defaultInfo[pi]);
             }
             for (const ai of arrayInfo) {
@@ -125,12 +125,9 @@ module.exports = app => {
         async _getLedgerService() {
             try {
                 if (this.ctx.tender.data.ledger_status === auditConst.ledger.status.checked) {
-                    const stageCount = await this.ctx.service.stage.count({tid: this.ctx.tender.id});
-                    if (stageCount === 0) {
-                        const revise = await this.ctx.service.ledgerRevise.getLastestRevise(this.ctx.tender.id);
-                        if (revise.status === auditConst.revise.status.uncheck || revise.status === auditConst.revise.status.checkNo) {
-                            return [this.ctx.service.reviseBills, this.ctx.service.revisePos];
-                        }
+                    const revise = await this.ctx.service.ledgerRevise.getLastestRevise(this.ctx.tender.id);
+                    if (revise.status === auditConst.revise.status.uncheck || revise.status === auditConst.revise.status.checkNo) {
+                        return [this.ctx.service.reviseBills, this.ctx.service.revisePos];
                     }
                 } else {
                     return [this.ctx.service.ledger, this.ctx.service.pos];
@@ -244,6 +241,43 @@ module.exports = app => {
             }
             return changeBills;
         }
+
+        async _reCalcStageBills(tenderId, newDecimal, oldDecimal) {
+            let updateStageBills = [], insertStageBills = [];
+            const stage = await this.ctx.service.stage.getLastestStage(tenderId, true);
+            if (!stage || stage.status === auditConst.stage.status.checking ||
+                stage.status === auditConst.stage.status.checked || newDecimal.tp === oldDecimal.tp)
+                return [updateStageBills, insertStageBills];
+
+            const stageBills = await this.ctx.service.stageBills.getLastestStageData2(stage.tid, stage.id);
+            const bills = await this.ctx.service.ledger.getAllDataByCondition({
+                columns: ['id', 'unit_price'],
+                where: { tender_id: tenderId, is_leaf: true },
+            });
+            for (const sb of stageBills) {
+                const b = bills.find(x => {return x.id === sb.lid});
+                const contract_tp = this.ctx.helper.mul(b.unit_price, sb.contract_qty, newDecimal.tp);
+                const qc_tp = this.ctx.helper.mul(b.unit_price, sb.qc_qty, newDecimal.tp);
+                if (contract_tp == sb.contract_tp && qc_tp === sb.qc_tp) continue;
+
+                //if (sb.)
+                if (sb.times === stage.times && sb.order === 0) {
+                    updateStageBills.push({
+                        id: sb.id, contract_tp, qc_tp
+                    });
+                } else {
+                    insertStageBills.push({
+                        tid: stage.tid, lid: sb.lid, sid: stage.id, said: this.ctx.session.sessionUser.accountId,
+                        times: stage.times, order: 0,
+                        contract_qty: sb.contract_qty, contract_expr: sb.contract_expr, contract_tp,
+                        qc_qty: sb.qc_qty, qc_tp,
+                        postil: sb.postil,
+                    });
+                }
+            }
+            return [updateStageBills, insertStageBills];
+        }
+
         async _reCalcStageExtra(tenderId, newDecimal, oldDecimal) {
             let changeSj = [], changeSb = [], changeSo = [];
             const stage = await this.ctx.service.stage.getLastestStage(tenderId, true);
@@ -309,15 +343,19 @@ module.exports = app => {
             const [billsService] = await this._getLedgerService();
 
             const changeBills = await this._reCalcLedger(tenderId, billsService, newDecimal, oldDecimal);
+            const [updateStageBills, insertStageBills] = await this._reCalcStageBills(tenderId, newDecimal, oldDecimal);
             const [changeSj, changeSb, changeSo] = await this._reCalcStageExtra(tenderId, newDecimal, oldDecimal);
             if (changeBills.length > 0 ||
                 changeAdvanceBills.length > 0 ||
+                updateStageBills.length > 0 || insertStageBills.length > 0 ||
                 changeSj.length > 0 || changeSb.length > 0 || changeSo.length > 0) {
                 const transaction = await this.db.beginTransaction();
                 try {
                     await transaction.update(this.tableName,
                         { decimal: JSON.stringify(newDecimal) }, { where: { tid: tenderId } });
                     if (changeBills.length > 0) await transaction.updateRows(billsService.tableName, changeBills);
+                    if (updateStageBills.length > 0) await transaction.updateRows(this.ctx.service.stageBills.tableName, updateStageBills);
+                    if (insertStageBills.length > 0) await transaction.insert(this.ctx.service.stageBills.tableName, insertStageBills);
                     if (changeSj.length > 0) await transaction.updateRows(this.ctx.service.stageJgcl.tableName, changeSj);
                     if (changeSb.length > 0) await transaction.updateRows(this.ctx.service.stageBonus.tableName, changeSb);
                     if (changeSo.length > 0) await transaction.updateRows(this.ctx.service.stageOther.tableName, changeSo);

+ 3 - 3
app/view/layout/menu.ejs

@@ -29,9 +29,9 @@
         <ul class="nav nav-pills nav-stacked bg-nav">
             <li <% if (ctx.controllerName === 'setting') { %>class="active"<% } %>><a href="/setting/info" data-toggle="tooltip" data-placement="right" title="" data-original-title="项目信息"><i class="fa fa-cogs"></i><span>项目信息</span></a></li>
         </ul>
-        <div class="dropup mb-1 ml-1">
-            <a href="" class="btn btn-sm btn-light" data-toggle="dropdown" aria-haspopup="false" aria-expanded="false">
-                <%- ctx.session.sessionUser.name.substr(ctx.session.sessionUser.name.length > 2 ? ctx.session.sessionUser.name.length - 2 : 0) %>
+        <div class="dropup mb-1 ml-1 mr-1">
+            <a href="" class="btn btn-sm btn-light p-1 w-100" data-toggle="dropdown" aria-haspopup="false" aria-expanded="false">
+                <%- ctx.session.sessionUser.name.substr(ctx.session.sessionUser.name.length > 3 ? ctx.session.sessionUser.name.length - 3 : 0) %>
             </a>
             <div class="dropdown-menu">
                 <a href="/profile/info" class="dropdown-item">账号资料</a>

+ 21 - 0
app/view/ledger/bwtz.ejs

@@ -44,6 +44,27 @@
                             <li class="nav-item">
                                 <a class="nav-link active" data-toggle="tab" href="#jldyjlqd" role="tab">计量单元/计量清单</a>
                             </li>
+                            <li class="nav-item">
+                                <div class="d-inline-block ml-2">
+                                    <a class="btn btn-sm btn-light">
+                                        <div class="custom-control custom-checkbox">
+                                            <input type="checkbox" class="custom-control-input" id="unit-show-1">
+                                            <label class="custom-control-label text-primary" for="unit-show-1">只显示第一层</label>
+                                        </div>
+                                    </a>
+                                </div>
+                                <div class="d-inline-block">
+                                    <div class="input-group input-group-sm ml-2">
+                                        <div class="input-group-prepend">
+                                            <span class="input-group-text" id="basic-addon1">数据筛选</span>
+                                        </div>
+                                        <input type="text" class="form-control form-control-sm m-0" id="unit-filter" placeholder="可根据 计量单元/清单编号/名称 筛选数据" style="width: 300px">
+                                    </div>
+                                </div>
+                                <div class="d-inline-block ml-2">
+                                    <div class="alert-warning p-1"><i class="fa Example of exclamation-circle fa-exclamation-circle "></i> 父项/子项任一符合,均显示</div>
+                                </div>
+                            </li>
                         </ul>
                     </div>
                     <div class="sp-wrap" id="unit-spread">

+ 2 - 1
app/view/ledger/explode_modal.ejs

@@ -6,7 +6,7 @@
                 <h5 class="modal-title">导入</h5>
             </div>
             <div class="modal-body">
-                <p>请上传符合格式的 <b>0号台账</b> 格式的 .xls和.xlsx 文件,<a id="downloadLedgerTemplate" href="/tender/<%- ctx.tender.id %>/ledger/download/导入分项清单EXCEL格式.xls">下载示例</a>。</p>
+                <p>请上传符合格式的 <b>0号台账</b> 格式的 .xls和.xlsx 文件,<a id="downloadLedgerTemplate" href="/tender/<%- ctx.tender.id %>/ledger/download/导入分项清单EXCEL格式.xlsx">下载示例</a>。</p>
                 <div class="form-group">
                     <label for="exampleFormControlFile1">选择文件</label><i class="fa fa-spinner fa-pulse fa-lg fa-fw text-primary" id="select-excel-loading" style="display: none;"></i>
                     <input type="file" class="form-control-file" id="upload-ledger-file" accept="*.xls">
@@ -403,3 +403,4 @@
 <% include ../shares/delete_hint_modal.ejs %>
 <% include ../shares/check_data_modal.ejs %>
 <% include ../shares/check_modal2.ejs %>
+<% include ../shares/new_tag_modal.ejs %>

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

@@ -68,5 +68,4 @@
 <script>
     const chapter = JSON.parse('<%- JSON.stringify(ctx.tender.info.chapter) %>');
     const thousandth = <%- ctx.tender.info.display.thousandth %>;
-    const filter = JSON.parse('<%- JSON.stringify(chapterFilter) %>');
 </script>

+ 7 - 1
app/view/report/index.ejs

@@ -235,7 +235,9 @@
         // rptCustomObj.initTenderTreeForCross(tenders, category);
     });
 </script>
-
+<script type="text/javascript">
+    let COMMON_WATER_MARK_PIC_DATA = null;
+</script>
 <script type="text/javascript">  autoFlashHeight();</script>
 <script type="text/javascript" src="/public/jspdf/jspdf.min.js"></script>
 <script src="/public/js/datepicker/datepicker.min.js"></script>
@@ -263,6 +265,10 @@
 <script type="text/javascript" src="/public/report/js/rpt_signature.js"></script>
 <script type="text/javascript" src="/public/report/js/rpt_jspdf.js"></script>
 
+<!--
+<script type="text/javascript" src="/public/report/js/rpt_custom.js"></script>
+-->
+
 
 <script type="text/javascript">
     let TOP_TREE_NODES = <%- rpt_tpl_data %>;

+ 31 - 34
app/view/report/rpt_all_popup.ejs

@@ -203,14 +203,16 @@
             <div class="modal-body" id="eSignatureBodyDiv">
             </div>
             <div class="modal-footer">
+                <% if ((pageShow !== null) && (parseInt(pageShow.openSign) === 1)) { %>
+                    <button type="button" id="btn_cross_tender" class="btn btn-sm btn-link float-left" data-dismiss="modal" data-toggle="modal" data-target="#batch-eSignature" id="batch-setupProjSignature" onclick="rptSignatureHelper.resetESignature(zTreeOprObj.currentRptPageRst, 'batch-eSignatureBodyDiv'); buildTendersTree();">批量设置其他标段</button>
+                <% } %>
                 <!--
-                <button type="button" id="btn_cross_tender" class="btn btn-sm btn-link float-left" data-dismiss="modal" data-toggle="modal" data-target="#batch-eSignature" id="batch-setupProjSignature" onclick="rptSignatureHelper.resetESignature(zTreeOprObj.currentRptPageRst, 'batch-eSignatureBodyDiv'); buildTendersTree();">批量设置其他标段</button>
 
                 <button type="button" class="btn btn-sm btn-link float-left" data-toggle="modal" data-target="#batch-eSignature" id="hidden_show_batch_eSignature" style="display:none"></button>
                 <button type="button" class="btn btn-sm btn-link float-left" data-dismiss="modal" onclick="setTimeout(function(){$('#hidden_show_batch_eSignature').trigger('click');}, 50);">批量设置其他标段</button>
                 -->
                 <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal" onclick="rptSignatureHelper.removeSelectSignature()">取消</button>
-                <a href="javascript:void(0);" onclick="rptSignatureHelper.setupAfterSelectSignature()" class="btn btn-primary btn-sm" data-dismiss="modal">确定</a>
+                <a href="javascript:void(0);" onclick="rptSignatureHelper.setupAfterSelectSignature('eSignatureBodyDiv')" class="btn btn-primary btn-sm" data-dismiss="modal">确定</a>
             </div>
         </div>
     </div>
@@ -238,7 +240,7 @@
             </div>
             <div class="modal-footer">
                 <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
-                <a href="javascript:void(0);" onclick="rptSignatureHelper.setupAfterSelectMultiTenders(SELECTED_TENDERS)" class="btn btn-sm btn-primary" data-dismiss="modal">确定</a>
+                <a href="javascript:void(0);" onclick="rptSignatureHelper.setupAfterSelectMultiTenders(SELECTED_TENDERS, 'batch-eSignatureBodyDiv')" class="btn btn-sm btn-primary" data-dismiss="modal">确定</a>
             </div>
         </div>
     </div>
@@ -534,12 +536,27 @@
         }
         //3. 把标段挂上去
         let tmpCacheArr = []; //这个临时用来储存相应标段节点挂接信息
+        let _createTenderItem = function (tender) {
+            let rst = {
+                name: tender.name,
+                id : -1,
+                cid : -1,
+                tender_id: tender.id,
+                last_stage: tender.lastStage.order,
+                last_stage_id: tender.lastStage.id,
+                report_id: zTreeOprObj.currentNode.refId,
+                signature: '',
+                items: [],
+                isParent: false
+            }
+            return rst;
+        }
         let _putTender = function (catLv, tender, parentItems) {
             if (tender.lastStage !== undefined && tender.lastStage !== null) {
                 if (hasCat) {
                     let isSorted = false;
                     for (let sCat of sortedCat) {
-                        if (sCat.id === tender.category[catLv].cid && sCat.level > 0) {
+                        if (sCat.level <= tender.category.length && sCat.level > 0 && sCat.id === tender.category[catLv].cid) {
                             isSorted = true;
                             break;
                         }
@@ -547,49 +564,30 @@
                     if (isSorted) {
                         for (const item of parentItems) {
                             if (item.cid === tender.category[catLv].cid && item.id === tender.category[catLv].value) {
-                                if (item.items.length > 0) {
+                                if (item.items.length > 0 && tender.category.length > (catLv + 1)) {
                                     //递归循环
                                     _putTender(catLv + 1, tender, item.items);
                                 } else {
                                     //就是这个了
-                                    let tdItem = {
-                                        name: tender.name,
-                                        id : -1,
-                                        cid : -1,
-                                        tender_id: tender.id,
-                                        last_stage: tender.lastStage.order,
-                                        last_stage_id: tender.lastStage.id,
-                                        report_id: zTreeOprObj.currentNode.refId,
-                                        signature: '临时',
-                                        items: [],
-                                        isParent: false
-                                    }; //标段节点
-                                    //
+                                    let tdItem = _createTenderItem(tender); //标段节点
                                     tmpCacheArr.push([item, tdItem]);
                                 }
                             }
                         }
+                    } else {
+                        //超界了
+                        //let tdItem = _createTenderItem(tender); //标段节点
+                        //tmpCacheArr.push([item, tdItem]);
                     }
                 } else {
                     // 没有设置category
-                    let tdItem = {
-                        name: tender.name,
-                        id : -1,
-                        cid : -1,
-                        tender_id: tender.id,
-                        last_stage: tender.lastStage.order,
-                        last_stage_id: tender.lastStage.id,
-                        report_id: zTreeOprObj.currentNode.refId,
-                        signature: '临时',
-                        items: [],
-                        isParent: false
-                    }; //标段节点
-                    //
+                    let tdItem = _createTenderItem(tender); //标段节点
                     tmpCacheArr.push([null, tdItem]);
                 }
             }
         }
         for (const tender of tenders) {
+            //console.log('tender.name: ' + tender.name);
             _putTender(0, tender, treeCache);
         }
         if (hasCat) {
@@ -660,7 +658,7 @@
                 }
             } else {
                 for (let idx = parentItem.items.length - 1; idx >= 0; idx--) {
-                    _getTenderRelatedInfos(parentItem.items[idx], relArr);
+                    _addHint(parentItem.items[idx], relArr);
                 }
             }
         };
@@ -723,7 +721,6 @@
                 } else {
                     lastStgStr = '第' + rowItem.last_stage + '期';
                 }
-                //let chgStr = 'changeCrossTender(this, SELECTED_TENDERS,' + rowItem.tender_id + ', ' + rowItem.last_stage_id + ', ' + rowItem.report_id + ')';
                 if (rowItem.signature !== undefined && rowItem.signature !== null && rowItem.signature !== '') {
                     let hintStr = '<i class="fa fa-exclamation-circle text-primary" data-container="body" data-toggle="tooltip" data-placement="bottom" data-original-title="' + rowItem.signature + '"></i>';
                     domStrs.push('<tr><td class="' + tdClassStr + '"><a href="' + hrefStr + '" target="_blank">' + rowItem.name + '</a></td><td>' + lastStgStr + '</td><td>' + hintStr + '</td><td><input type="checkbox" onclick="changeCrossTender(this, SELECTED_TENDERS, ' + rowItem.tender_id + ', ' + rowItem.last_stage_id + ', ' + rowItem.report_id + ')' + '"></td></tr>');
@@ -743,7 +740,7 @@
         }
         domStrs.push('</tbody>');
         tbDom.append(domStrs.join(''));
-
+        $('[data-toggle="tooltip"]').tooltip();
     }
 
     function buildCustRptCommon(tbDomId, topTreeNode, checkingArr, isCommonStr) {

+ 0 - 0
app/view/revise/gcl_compare.ejs


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