Przeglądaj źródła

Merge branch 'master' of http://192.168.1.41:3000/maixinrong/Calculation

Conflicts:
	app/router.js
	package-lock.json
TonyKang 5 lat temu
rodzic
commit
ce7ce996d4
100 zmienionych plików z 13379 dodań i 18455 usunięć
  1. 8 0
      app.js
  2. 8 0
      app/base/base_controller.js
  3. 3 2
      app/base/base_service.js
  4. 17 0
      app/const/audit.js
  5. 8 8
      app/const/deal_pay.js
  6. 228 61
      app/const/spread.js
  7. 1 0
      app/const/tender_info.js
  8. 40 28
      app/controller/change_controller.js
  9. 27 8
      app/controller/deal_bills_controller.js
  10. 3 2
      app/controller/ledger_audit_controller.js
  11. 25 35
      app/controller/ledger_controller.js
  12. 102 5
      app/controller/profile_controller.js
  13. 355 0
      app/controller/revise_controller.js
  14. 73 0
      app/controller/sign_controller.js
  15. 581 121
      app/controller/stage_controller.js
  16. 1 0
      app/controller/tender_controller.js
  17. 134 24
      app/extend/helper.js
  18. 91 29
      app/lib/analysis_excel.js
  19. 60 0
      app/lib/base_calc.js
  20. 54 21
      app/lib/pay_calc.js
  21. 2 1
      app/middleware/gzip.js
  22. 1 1
      app/middleware/stage_check.js
  23. 7 2
      app/middleware/tender_check.js
  24. BIN
      app/public/css/jquery-ui/images/ui-icons_444444_256x240.png
  25. BIN
      app/public/css/jquery-ui/images/ui-icons_555555_256x240.png
  26. BIN
      app/public/css/jquery-ui/images/ui-icons_777620_256x240.png
  27. BIN
      app/public/css/jquery-ui/images/ui-icons_777777_256x240.png
  28. BIN
      app/public/css/jquery-ui/images/ui-icons_cc0000_256x240.png
  29. BIN
      app/public/css/jquery-ui/images/ui-icons_ffffff_256x240.png
  30. 7 0
      app/public/css/jquery-ui/jquery-ui.min.css
  31. 47 13
      app/public/css/main.css
  32. 18 18
      app/public/css/spreadjs/sheets/gc.spread.sheets.excelsmartcost.css
  33. BIN
      app/public/images/baobiao3.png
  34. BIN
      app/public/images/user-sign.PNG
  35. 41 0
      app/public/js/bootstrap/bootstrap-paginator.js
  36. 33 0
      app/public/js/change.js
  37. 9 2
      app/public/js/change_approval.js
  38. 44 6
      app/public/js/change_calculation.js
  39. 32 28
      app/public/js/change_detail.js
  40. 171 106
      app/public/js/change_set.js
  41. 1 1
      app/public/js/change_show.js
  42. 4877 0
      app/public/js/decimal.js
  43. 3 0
      app/public/js/decimal.min.js
  44. 4 4
      app/public/js/div_resizer.js
  45. 241 0
      app/public/js/draw.js
  46. 32 27
      app/public/js/gcl_gather.js
  47. 19 16
      app/public/js/global.js
  48. 6 16615
      app/public/js/jquery/jquery-ui.js
  49. 355 105
      app/public/js/ledger.js
  50. 54 18
      app/public/js/ledger_audit.js
  51. 151 0
      app/public/js/number-precision.js
  52. 215 68
      app/public/js/path_tree.js
  53. 715 0
      app/public/js/revise.js
  54. 131 12
      app/public/js/spreadjs_rela/spreadjs_zh.js
  55. 996 139
      app/public/js/stage.js
  56. 7 1
      app/public/js/stage_audit.js
  57. 2 2
      app/public/js/stage_change.js
  58. 44 16
      app/public/js/stage_compare.js
  59. 236 113
      app/public/js/stage_detail.js
  60. 17 3
      app/public/js/stage_gather.js
  61. 60 28
      app/public/js/stage_im.js
  62. 270 43
      app/public/js/stage_pay.js
  63. 254 84
      app/public/js/tender.js
  64. 21 8
      app/public/js/tender_list.js
  65. 16 7
      app/public/js/tender_list_manage.js
  66. 13 6
      app/public/js/tender_list_progress.js
  67. 125 0
      app/public/js/zh_calc.js
  68. 1 0
      app/public/upload/pay/page.html
  69. 1 0
      app/public/upload/sign/page.html
  70. 1 0
      app/public/upload/stage/page.html
  71. 27 3
      app/router.js
  72. 17 15
      app/service/change.js
  73. 76 1
      app/service/deal_bills.js
  74. 158 68
      app/service/ledger.js
  75. 0 16
      app/service/ledger_change.js
  76. 142 0
      app/service/ledger_revise.js
  77. 36 0
      app/service/ledger_revise_bills.js
  78. 36 0
      app/service/ledger_revise_pos.js
  79. 51 12
      app/service/pay.js
  80. 94 30
      app/service/pos.js
  81. 14 13
      app/service/project_account.js
  82. 507 0
      app/service/revise_bills.js
  83. 63 0
      app/service/revise_pos.js
  84. 21 10
      app/service/stage.js
  85. 90 0
      app/service/stage_att.js
  86. 252 108
      app/service/stage_audit.js
  87. 75 26
      app/service/stage_bills.js
  88. 137 0
      app/service/stage_bills_dgn.js
  89. 22 19
      app/service/stage_bills_final.js
  90. 10 10
      app/service/stage_change.js
  91. 71 12
      app/service/stage_detail.js
  92. 81 40
      app/service/stage_pay.js
  93. 218 154
      app/service/stage_pos.js
  94. 26 19
      app/service/stage_pos_final.js
  95. 1 1
      app/service/standard_lib.js
  96. 3 3
      app/service/tender.js
  97. 0 20
      app/service/tree_sort.js
  98. 5 5
      app/view/change/index.ejs
  99. 47 33
      app/view/change/info.ejs
  100. 0 0
      app/view/change/info_modal.ejs

+ 8 - 0
app.js

@@ -33,6 +33,11 @@ module.exports = app => {
     // 控制器基类
     app.BaseController = BaseController;
 
+    // 检查日志路径
+    if (!fs.existsSync(app.baseDir + '/logs')) {
+        fs.mkdirSync(app.baseDir + '/logs');
+    }
+
     // 自定义手机校验规则
     app.validator.addRule('mobile', (rule, value) => {
         try {
@@ -67,6 +72,9 @@ 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] = {};

+ 8 - 0
app/base/base_controller.js

@@ -138,6 +138,14 @@ class BaseController extends Controller {
             throw '该模式下不可提交此数据';
         }
     }
+
+    ajaxErrorBody(error, msg) {
+        if (error.stack) {
+            return {err: 2, msg: msg, data: null};
+        } else {
+            return {err: 1, msg: error.toString(), data: null};
+        }
+    }
 }
 
 module.exports = BaseController;

+ 3 - 2
app/base/base_service.js

@@ -111,7 +111,8 @@ class BaseService extends Service {
     }
 
     async defaultUpdate(data, options) {
-        await this.db.update(this.tableName, data, options);
+        const result = await this.db.update(this.tableName, data, options);
+        return result;
     }
 
     /**
@@ -194,7 +195,7 @@ class BaseService extends Service {
     }
 
     round(value, decimal) {
-        return this.ctx.helper.round(value, decimal ? decimal : 8);
+        return this.ctx.helper.round(value, this._.isNumber(decimal) ? decimal : 8);
     }
 }
 module.exports = BaseService;

+ 17 - 0
app/const/audit.js

@@ -44,6 +44,22 @@ const ledger = (function () {
     return { status, statusString, statusClass, auditString, auditStringClass }
 })();
 
+// 台账修订 审批流程
+const revise = (function () {
+    const status = {
+        uncheck: 1, // 待上报
+        checking: 2, // 待审批|审批中
+        checked: 3, // 审批通过
+        checkNo: 4, // 审批退回
+    };
+    const statusString = [];
+    statusString[status.uncheck] = '草稿';
+    statusString[status.checking] = '审批中';
+    statusString[status.checked] = '完成';
+    statusString[status.checkNo] = '退回';
+    return { status, statusString }
+})();
+
 // 期审批流程
 const stage = (function () {
     // 流程状态
@@ -221,6 +237,7 @@ filter.statusString[filter.status.checked] = '已完成';
 module.exports = {
     ledger,
     stage,
+    revise,
     flow: {
         status,
         statusString,

+ 8 - 8
app/const/deal_pay.js

@@ -17,19 +17,19 @@ const payType = {
 const payTemplate = [
     {order: 1, name: '本期应付', ptype: payType.yf, minus: false, expr: null, sexpr: null, rexpr: null},
     {order: 2, name: '本期实付', ptype: payType.sf, minus: false},
-    {order: 3, name: '本期计量完成', ptype: payType.wc, minus: false, expr: 'bqwc'},
+    {order: 3, name: '本期完成计量', ptype: payType.wc, minus: false, expr: 'bqwc'},
     {order: 4, name: '质量保证金', ptype: payType.normal, minus: true},
     {order: 5, name: '扣回开工预付款', ptype: payType.normal, minus: true, expr: '(bqwc/htj)*2*kgyfk', sexpr: 'htj*30%', rexpr: 'kgyfk'},
 ];
 
 const calcBase = [
-    {name: '签约合同价', code: 'htj'},
-    {name: '暂列金额', code: 'zlje'},
-    {name: '签约合同价(不含暂列金)', code: 'htjszl'},
-    {name: '签约开工预付款', code: 'kgyfk'},
-    {name: '签约材料预付款', code: 'clyfk'},
-    {name: '本期完成计量', code: 'bqwc', limit: true},
-    {name: '100章本期完成计量', code: 'ybbqwc', limit: true},
+    {name: '签约合同价', code: 'htj', sort: 10},
+    {name: '暂列金额', code: 'zlje', sort: 2},
+    {name: '签约合同价(不含暂列金)', code: 'htjszl', sort: 1},
+    {name: '签约开工预付款', code: 'kgyfk', sort: 2},
+    {name: '签约材料预付款', code: 'clyfk', sort: 2},
+    {name: '本期完成计量', code: 'bqwc', limit: true, sort: 10},
+    {name: '100章本期完成计量', code: 'ybbqwc', limit: true, sort: 1},
 ];
 
 const chapterDetailType = {

+ 228 - 61
app/const/spread.js

@@ -10,44 +10,208 @@
 
 const tzWithoutCols = ['deal_qty', 'deal_tp'];
 const dgnCols = ['dgn_qty1', 'dgn_qty2', 'dgn_price'];
-const posTzWithoutCols = ['add_stage'];
+const clCols = ['sjcl_qty', 'sjcl_tp', 'qtcl_qty', 'qtcl_tp', 'quantity', 'total_price'];
+const stageDgnCols = ['deal_dgn_qty1', 'deal_dgn_qty2', 'c_dgn_qty1', 'c_dgn_qty2', 'final_dgn_price'];
 
-const ledgerSpread = {
-    cols: [
-        {title: '项目节编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 150, formatter: '@', cellType: 'tree'},
-        {title: '清单编号', colSpan: '1', rowSpan: '2', field: 'b_code', hAlign: 0, width: 80, formatter: '@'},
-        {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 230, formatter: '@'},
-        {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 50, formatter: '@', cellType: 'unit'},
-        {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, type: 'Number', readOnly: 'readOnly.unit_price'},
-        {title: '设计数量|数量1',  colSpan: '2|1', rowSpan: '1|1', field: 'dgn_qty1', hAlign: 2, width: 60, type: 'Number', readOnly: 'readOnly.dgnQty'},
-        {title: '|数量2',  colSpan: '|1', rowSpan: '|1', field: 'dgn_qty2', hAlign: 2, width: 60, type: 'Number', readOnly: 'readOnly.dgnQty'},
-        {title: '经济指标',  colSpan: '1', rowSpan: '2', field: 'dgn_price', hAlign: 2, width: 60, type: 'Number', readOnly: true},
-        {title: '签约|数量', colSpan: '2|1', rowSpan: '1|1', field: 'deal_qty', hAlign: 2, width: 60, type: 'Number', readOnly: 'readOnly.quantity'},
-        {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'deal_tp', hAlign: 2, width: 60, type: 'Number', readOnly: true},
-        {title: '施工图复核|数量', colSpan: '2|1', rowSpan: '1|1', field: 'quantity', hAlign: 2, width: 60, type: 'Number', readOnly: 'readOnly.quantity'},
-        {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'total_price', hAlign: 2, width: 60, type: 'Number', readOnly: true},
-        {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: '@'}
-    ],
-    emptyRows: 3,
-    headRows: 2,
-    headRowHeight: [40, 40],
-    defaultRowHeight: 21,
+const withCl = {
+    ledger: {
+        cols: [
+            {title: '项目节编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 150, formatter: '@', cellType: 'tree'},
+            {title: '清单编号', colSpan: '1', rowSpan: '2', field: 'b_code', hAlign: 0, width: 80, formatter: '@'},
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 230, formatter: '@'},
+            {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 50, formatter: '@', cellType: 'unit'},
+            {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, type: 'Number', readOnly: 'readOnly.unit_price'},
+            {title: '设计数量|数量1',  colSpan: '2|1', rowSpan: '1|1', field: 'dgn_qty1', hAlign: 2, width: 60, type: 'Number', readOnly: 'readOnly.dgnQty'},
+            {title: '|数量2',  colSpan: '|1', rowSpan: '|1', field: 'dgn_qty2', hAlign: 2, width: 60, type: 'Number', readOnly: 'readOnly.dgnQty'},
+            {title: '经济指标',  colSpan: '1', rowSpan: '2', field: 'dgn_price', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+            {title: '签约|数量', colSpan: '2|1', rowSpan: '1|1', field: 'deal_qty', hAlign: 2, width: 60, type: 'Number', readOnly: 'readOnly.quantity'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'deal_tp', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+            {title: '施工图复核|数量', colSpan: '2|1', rowSpan: '1|1', field: 'sgfh_qty', hAlign: 2, width: 60, type: 'Number', readOnly: 'readOnly.quantity'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'sgfh_tp', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+            {title: '设计错漏增减|数量', colSpan: '2|1', rowSpan: '1|1', field: 'sjcl_qty', hAlign: 2, width: 60, type: 'Number', readOnly: 'readOnly.quantity'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'sjcl_tp', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+            {title: '其他错漏增减|数量', colSpan: '2|1', rowSpan: '1|1', field: 'qtcl_qty', hAlign: 2, width: 60, type: 'Number', readOnly: 'readOnly.quantity'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'qtcl_tp', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+            {title: '小计|数量', colSpan: '2|1', rowSpan: '1|1', field: 'quantity', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'total_price', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+            {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'}
+        ],
+        emptyRows: 3,
+        headRows: 2,
+        headRowHeight: [35, 35],
+        defaultRowHeight: 21,
+    },
+    pos: {
+        cols: [
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 230, formatter: '@'},
+            {title: '数量|施工图复核', colSpan: '4|1', rowSpan: '1|1', field: 'sgfh_qty', hAlign: 2, width: 100, type: 'Number'},
+            {title: '数量|设计错漏增减', colSpan: '|1', rowSpan: '|1', field: 'sjcl_qty', hAlign: 2, width: 100, type: 'Number'},
+            {title: '数量|其他错漏增减', colSpan: '|1', rowSpan: '|1', field: 'qtcl_qty', hAlign: 2, width: 100, type: 'Number'},
+            {title: '|小计', colSpan: '|1', rowSpan: '|1', field: 'quantity', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+            {title: '图册号', colSpan: '1', rowSpan: '2', field: 'drawing_code', hAlign: 0, width: 80, formatter: '@'},
+        ],
+        emptyRows: 20,
+        headRows: 2,
+        headRowHeight: [35, 35],
+        defaultRowHeight: 21,
+    }
 };
-
-const ledgerPosSpread = {
-    cols: [
-        {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 230, formatter: '@'},
-        {title: '数量', colSpan: '1', rowSpan: '1', field: 'quantity', hAlign: 2, width: 60, type: 'Number'},
-        {title: '图册号', colSpan: '1', rowSpan: '1', field: 'drawing_code', hAlign: 0, width: 80, formatter: '@'},
-    ],
-    emptyRows: 3,
-    headRows: 1,
-    headRowHeight: [40],
-    defaultRowHeight: 21,
+const withoutCl = {
+    ledger: {
+        cols: [
+            {title: '项目节编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 150, formatter: '@', cellType: 'tree'},
+            {title: '清单编号', colSpan: '1', rowSpan: '2', field: 'b_code', hAlign: 0, width: 80, formatter: '@'},
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 230, formatter: '@'},
+            {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 50, formatter: '@', cellType: 'unit'},
+            {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, type: 'Number', readOnly: 'readOnly.unit_price'},
+            {title: '设计数量|数量1',  colSpan: '2|1', rowSpan: '1|1', field: 'dgn_qty1', hAlign: 2, width: 60, type: 'Number', readOnly: 'readOnly.dgnQty'},
+            {title: '|数量2',  colSpan: '|1', rowSpan: '|1', field: 'dgn_qty2', hAlign: 2, width: 60, type: 'Number', readOnly: 'readOnly.dgnQty'},
+            {title: '经济指标',  colSpan: '1', rowSpan: '2', field: 'dgn_price', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+            {title: '签约|数量', colSpan: '2|1', rowSpan: '1|1', field: 'deal_qty', hAlign: 2, width: 60, type: 'Number', readOnly: 'readOnly.quantity'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'deal_tp', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+            {title: '施工图复核|数量', colSpan: '2|1', rowSpan: '1|1', field: 'sgfh_qty', hAlign: 2, width: 60, type: 'Number', readOnly: 'readOnly.quantity'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'sgfh_tp', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+            {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'}
+        ],
+        emptyRows: 3,
+        headRows: 2,
+        headRowHeight: [35, 35],
+        defaultRowHeight: 21,
+    },
+    pos: {
+        cols: [
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 230, formatter: '@'},
+            {title: '施工图复核数量', colSpan: '1', rowSpan: '1', field: 'sgfh_qty', hAlign: 2, width: 120, type: 'Number'},
+            {title: '图册号', colSpan: '1', rowSpan: '2', field: 'drawing_code', hAlign: 0, width: 80, formatter: '@'},
+        ],
+        emptyRows: 20,
+        headRows: 1,
+        headRowHeight: [40],
+        defaultRowHeight: 21,
+    }
 };
-
-const stage = {
+// 期 -- 本期计量台账
+const stageTz = {
+    ledger: {
+        cols: [
+            {title: '项目节编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 150, formatter: '@', readOnly: true, cellType: 'tree'},
+            {title: '清单编号', colSpan: '1', rowSpan: '2', field: 'b_code', hAlign: 0, width: 80, formatter: '@', readOnly: true},
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 230, formatter: '@', readOnly: true},
+            {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 60, formatter: '@', readOnly: true, cellType: 'unit'},
+            {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '台账|数量', colSpan: '2|1', rowSpan: '1|1', field: 'quantity', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'total_price', hAlign: 2, width: 60, readOnly: true, 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', readOnly: true},
+            {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', readOnly: true},
+            {title: '本期完成计量|数量', colSpan: '2|1', rowSpan: '1|1', field: 'gather_qty', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'gather_tp', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '截止本期合同计量|数量', colSpan: '2|1', rowSpan: '1|1', field: 'end_contract_qty', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_contract_tp', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '截止本期数量变更|数量', colSpan: '2|1', rowSpan: '1|1', field: 'end_qc_qty', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_qc_tp', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '截止本期完成计量|数量', colSpan: '2|1', rowSpan: '1|1', field: 'end_gather_qty', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_gather_tp', hAlign: 2, width: 60, readOnly: true, 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, readOnly: true, 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: '@', readOnly: true},
+            {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', readOnly: true},
+        ],
+        emptyRows: 0,
+        headRows: 2,
+        headRowHeight: [32, 32],
+        defaultRowHeight: 21,
+    },
+    pos: {
+        cols: [
+            {title: '部位名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 180, formatter: '@', readOnly: true},
+            {title: '台账数量', colSpan: '1', rowSpan: '2', field: 'quantity', hAlign: 2, width: 60, formatter: '@', readOnly: true},
+            {title: '本期计量|合同', colSpan: '3|1', rowSpan: '1|1', field: 'contract_qty', hAlign: 2, width: 60, type: 'Number'},
+            {title: '|数量变更', colSpan: '|1', rowSpan: '|1', field: 'qc_qty', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|完成', colSpan: '|1', rowSpan: '|1', field: 'gather_qty', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+            {title: '截止本期计量|合同', colSpan: '3|1', rowSpan: '1|1', field: 'end_contract_qty', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+            {title: '|数量变更', colSpan: '|1', rowSpan: '|1', field: 'end_qc_qty', hAlign: 2, width: 80, type: 'Number', readOnly: true},
+            {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: '@'},
+        ],
+        emptyRows: 3,
+        headRows: 2,
+        headRowHeight: [32, 32],
+        defaultRowHeight: 21,
+    }
+};
+const stageCl = {
+    ledger: {
+        cols: [
+            {title: '项目节编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 150, formatter: '@', readOnly: true, cellType: 'tree'},
+            {title: '清单编号', colSpan: '1', rowSpan: '2', field: 'b_code', hAlign: 0, width: 80, formatter: '@', readOnly: true},
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 230, formatter: '@', readOnly: true},
+            {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 60, formatter: '@', readOnly: true, cellType: 'unit'},
+            {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '签约|数量', colSpan: '2|1', rowSpan: '1|1', field: 'deal_qty', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'deal_tp', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '台账|数量', colSpan: '2|1', rowSpan: '1|1', field: 'quantity', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'total_price', hAlign: 2, width: 60, readOnly: true, 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', readOnly: true},
+            {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', readOnly: true},
+            {title: '本期完成计量|数量', colSpan: '2|1', rowSpan: '1|1', field: 'gather_qty', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'gather_tp', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '截止本期合同计量|数量', colSpan: '2|1', rowSpan: '1|1', field: 'end_contract_qty', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_contract_tp', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '截止本期数量变更|数量', colSpan: '2|1', rowSpan: '1|1', field: 'end_qc_qty', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_qc_tp', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '截止本期完成计量|数量', colSpan: '2|1', rowSpan: '1|1', field: 'end_gather_qty', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_gather_tp', hAlign: 2, width: 60, readOnly: true, 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, readOnly: true, 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: '@', readOnly: true},
+            {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', readOnly: true},
+        ],
+        emptyRows: 0,
+        headRows: 2,
+        headRowHeight: [32, 32],
+        defaultRowHeight: 21,
+    },
+    pos: {
+        cols: [
+            {title: '部位名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 180, formatter: '@'},
+            {title: '复核数量|施工图复核', colSpan: '4|1', rowSpan: '1|1', field: 'sgfh_qty', hAlign: 2, width: 60, type: 'Number'},
+            {title: '|设计错漏增减', colSpan: '1', rowSpan: '|1', field: 'sjcl_qty', hAlign: 2, width: 60, type: 'Number'},
+            {title: '|其他错漏增减', colSpan: '1', rowSpan: '|1', field: 'qtcl_qty', hAlign: 2, width: 60, type: 'Number'},
+            {title: '|小计', colSpan: '1', rowSpan: '|1', field: 'quantity', hAlign: 2, width: 60, readOnly: true},
+            {title: '本期计量|合同', colSpan: '3|1', rowSpan: '1|1', field: 'contract_qty', hAlign: 2, width: 60, type: 'Number'},
+            {title: '|数量变更', colSpan: '|1', rowSpan: '|1', field: 'qc_qty', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|完成', colSpan: '|1', rowSpan: '|1', field: 'gather_qty', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+            {title: '截止本期计量|合同', colSpan: '3|1', rowSpan: '1|1', field: 'end_contract_qty', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+            {title: '|数量变更', colSpan: '|1', rowSpan: '|1', field: 'end_qc_qty', hAlign: 2, width: 80, type: 'Number', readOnly: true},
+            {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: 'add_stage_order', hAlign:1, width: 80, readOnly: true},
+        ],
+        emptyRows: 20,
+        headRows: 2,
+        headRowHeight: [32, 32],
+        defaultRowHeight: 21,
+    }
+};
+const stageNoCl = {
     ledger: {
         cols: [
             {title: '项目节编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 150, formatter: '@', readOnly: true, cellType: 'tree'},
@@ -60,9 +224,9 @@ const stage = {
             {title: '施工图复核|数量', colSpan: '2|1', rowSpan: '1|1', field: 'quantity', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'total_price', hAlign: 2, width: 60, readOnly: true, 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: '|1', rowSpan: '|1', field: 'contract_tp', hAlign: 2, width: 60, type: 'Number', readOnly: true},
             {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: '|1', rowSpan: '|1', field: 'qc_tp', hAlign: 2, width: 60, type: 'Number', readOnly: true},
             {title: '本期完成计量|数量', colSpan: '2|1', rowSpan: '1|1', field: 'gather_qty', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'gather_tp', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
             {title: '截止本期合同计量|数量', colSpan: '2|1', rowSpan: '1|1', field: 'end_contract_qty', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
@@ -71,14 +235,15 @@ const stage = {
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_qc_tp', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
             {title: '截止本期完成计量|数量', colSpan: '2|1', rowSpan: '1|1', field: 'end_gather_qty', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_gather_tp', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
-            {title: '合同|设计数量1',  colSpan: '2|1', rowSpan: '1|1', field: 'deal_dgn_qty1', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
-            {title: '|设计数量2',  colSpan: '|1', rowSpan: '|1', field: 'deal_dgn_qty2', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
-            {title: '变更|设计数量1',  colSpan: '2|1', rowSpan: '1|1', field: 'c_dgn_qty1', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
-            {title: '|数量2',  colSpan: '|1', rowSpan: '|1', field: 'c_dgn_qty2', hAlign: 2, width: 60, readOnly: true, 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, readOnly: true, 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: '@', readOnly: true},
             {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: '@', readOnly: true},
+            {title: '备注', colSpan: '1', rowSpan: '2', field: 'memo', hAlign: 0, width: 100, formatter: '@', cellType: 'ellipsisAutoTip', readOnly: true},
         ],
         emptyRows: 0,
         headRows: 2,
@@ -87,24 +252,25 @@ const stage = {
     },
     pos: {
         cols: [
-            {title: '部位名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 180, formatter: '@', readOnly: true},
-            {title: '复核数量', colSpan: '1', rowSpan: '2', field: 'quantity', hAlign: 2, width: 60, formatter: '@'},
+            {title: '部位名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 180, formatter: '@'},
+            {title: '复核数量', colSpan: '1', rowSpan: '2', field: 'sgfh_qty', hAlign: 2, width: 60, type: 'Number'},
             {title: '本期计量|合同', colSpan: '3|1', rowSpan: '1|1', field: 'contract_qty', hAlign: 2, width: 60, type: 'Number'},
             {title: '|数量变更', colSpan: '|1', rowSpan: '|1', field: 'qc_qty', hAlign: 2, width: 80, type: 'Number'},
             {title: '|完成', colSpan: '|1', rowSpan: '|1', field: 'gather_qty', hAlign: 2, width: 60, type: 'Number', readOnly: true},
             {title: '截止本期计量|合同', colSpan: '3|1', rowSpan: '1|1', field: 'end_contract_qty', hAlign: 2, width: 60, type: 'Number', readOnly: true},
             {title: '|数量变更', colSpan: '|1', rowSpan: '|1', field: 'end_qc_qty', hAlign: 2, width: 80, type: 'Number', readOnly: true},
             {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: '@'},
-            {title: '图册号', colSpan: '1', rowSpan: '2', field: 'drawing_code', hAlign: 0, width: 80, formatter: '@', readOnly: true},
-            {title: '添加期数', colSpan: '1', rowSpan: '2', field: 'add_stage', hAlign:1, width: 80, 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: 'add_stage_order', hAlign:1, width: 80, readOnly: true},
         ],
-        emptyRows: 3,
+        emptyRows: 20,
         headRows: 2,
         headRowHeight: [32, 32],
         defaultRowHeight: 21,
     }
 };
+// 期 -- 清单汇总
 const stageGather = {
     gcl: {
         cols: [
@@ -112,9 +278,11 @@ const stageGather = {
             {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 230, 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: 'deal_qty', hAlign: 2, width: 60, type: 'Number'},
-            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'deal_tp', hAlign: 2, width: 60, type: 'Number'},
-            {title: '施工图复核|数量', colSpan: '2|1', rowSpan: '1|1', field: 'quantity', hAlign: 2, width: 60, type: 'Number'},
+            // {title: '签约|数量', colSpan: '2|1', rowSpan: '1|1', field: 'deal_qty', hAlign: 2, width: 60, type: 'Number'},
+            // {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'deal_tp', hAlign: 2, width: 60, type: 'Number'},
+            {title: '签约清单|数量', colSpan: '2|1', rowSpan: '1|1', field: 'deal_bills_qty', hAlign: 2, width: 60, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'deal_bills_tp', 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'},
@@ -144,7 +312,7 @@ const stageGather = {
     leafXmj: {
         cols: [
             {title: '项目节编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 100, formatter: '@'},
-            {title: '施工图复核数量', colSpan: '1', rowSpan: '2', field: 'quantity', hAlign: 2, width: 60, type: 'Number'},
+            {title: '台账数量', colSpan: '1', rowSpan: '2', field: 'quantity', hAlign: 2, width: 60, type: 'Number'},
             {title: '本期计量数量|合同', colSpan: '3|1', rowSpan: '1|1', field: 'contract_qty', hAlign: 2, width: 60, type: 'Number'},
             {title: '|数量变更', colSpan: '|1', rowSpan: '|1', field: 'qc_qty', hAlign: 2, width: 60, type: 'Number'},
             {title: '|完成', colSpan: '|1', rowSpan: '1|1', field: 'gather_qty', hAlign: 2, width: 60, type: 'Number'},
@@ -168,6 +336,7 @@ const stageGather = {
         font: '10pt 微软雅黑',
     }
 };
+// 期 -- 审核比较
 const stageCompare = {
     ledger: {
         baseCols: [
@@ -192,7 +361,7 @@ const stageCompare = {
             {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 230, formatter: '@'},
         ],
         extraCols: [
-            {title: '%s数量', colSpan: '1', rowSpan: '1', field: 'quantity%s', hAlign: 2, width: 60, formatter: '@'},
+            {title: '%s数量', colSpan: '1', rowSpan: '1', field: 'gather_qty%s', hAlign: 2, width: 60, formatter: '@'},
         ],
         emptyRows: 3,
         headRows: 1,
@@ -244,15 +413,13 @@ measure.compare.pos = {
 };
 
 module.exports = {
-    ledgerSpread,
-    ledgerPosSpread,
-    stage,
+    withCl,
+    withoutCl,
+    stageTz,
+    stageCl,
+    stageNoCl,
     stageGather,
     stageCompare,
-    filterCols: {
-        tzWithoutCols,
-        dgnCols,
-        posTzWithoutCols,
-    },
+    filterCols: { tzWithoutCols, dgnCols, clCols, stageDgnCols},
     measure,
 };

+ 1 - 0
app/const/tender_info.js

@@ -89,6 +89,7 @@ const defaultInfo = {
     display: {
         ledger: {
             dgnQty: false,
+            clQty: false,
         },
     },
     chapter: [

+ 40 - 28
app/controller/change_controller.js

@@ -214,7 +214,7 @@ module.exports = app => {
                 }
                 const data = JSON.parse(ctx.request.body.data);
                 if (!data.code || data.code === '' || !data.name || data.name === '') {
-                    throw '变更令名称不能为空';
+                    throw '变更令不能为空';
                 }
 
                 const change = await ctx.service.change.add(tenderId, ctx.session.sessionUser.accountId, data.code, data.name);
@@ -327,6 +327,7 @@ module.exports = app => {
                         const cLArray = [
                             cl.code,
                             cl.name,
+                            cl.bwmx,
                             cl.unit,
                             cl.unit_price,
                             cl.oamount,
@@ -545,34 +546,45 @@ module.exports = app => {
             };
             let stream;
             try {
-                stream = await ctx.getFileStream();
-                const create_time = Date.parse(new Date()) / 1000;
-                const fileInfo = path.parse(stream.filename);
-                const dirName = 'app/public/upload/changes/' + moment().format('YYYYMMDD');
-                const fileName = 'changes' + create_time + fileInfo.ext;
-
-                // 判断文件夹是否存在,不存在则直接创建文件夹
-                if (!fs.existsSync(path.join(this.app.baseDir, dirName))) {
-                    await fs.mkdirSync(path.join(this.app.baseDir, dirName));
-                }
-                // 保存文件
-                await ctx.helper.saveStreamFile(stream, path.join(this.app.baseDir, dirName, fileName));
-                // 保存数据到att表
-                const fileData = {
-                    in_time: create_time,
-                    filename: fileInfo.name,
-                    fileext: fileInfo.ext,
-                    filesize: stream.fields.size,
-                    filepath: path.join(dirName, fileName),
-                };
-                const result = await ctx.service.changeAtt.save(stream.fields, fileData, ctx.session.sessionUser.accountId);
-                if (!result) {
-                    throw '导入数据库保存失败';
+                const parts = ctx.multipart({ autoFields: true });
+                const files = [];
+                let index = 0;
+                while ((stream = await parts()) !== undefined) {
+                    // 判断用户是否选择上传文件
+                    if (!stream.filename) {
+                        throw '请选择上传的文件!';
+                    }
+                    const create_time = Date.parse(new Date()) / 1000;
+                    const fileInfo = path.parse(stream.filename);
+                    const dirName = 'app/public/upload/changes/' + moment().format('YYYYMMDD');
+                    const fileName = 'changes' + create_time + '_' + index + fileInfo.ext;
+                    // 判断文件夹是否存在,不存在则直接创建文件夹
+                    if (!fs.existsSync(path.join(this.app.baseDir, dirName))) {
+                        await fs.mkdirSync(path.join(this.app.baseDir, dirName));
+                    }
+                    // 保存文件
+                    await ctx.helper.saveStreamFile(stream, path.join(this.app.baseDir, dirName, fileName));
+                    await sendToWormhole(stream);
+                    // 保存数据到att表
+                    const fileData = {
+                        in_time: create_time,
+                        filename: fileInfo.name,
+                        fileext: fileInfo.ext,
+                        filesize: Array.isArray(parts.field.size) ? parts.field.size[index] : parts.field.size,
+                        filepath: path.join(dirName, fileName),
+                    };
+                    const result = await ctx.service.changeAtt.save(parts.field, fileData, ctx.session.sessionUser.accountId);
+                    if (!result) {
+                        throw '导入数据库保存失败';
+                    }
+                    fileData.in_time = moment(create_time * 1000).format('YYYY-MM-DD');
+                    fileData.filesize = await ctx.helper.bytesToSize(fileData.filesize);
+                    fileData.id = result.insertId;
+                    delete fileData.filepath;
+                    files.push(fileData);
+                    ++index;
                 }
-                fileData.in_time = moment(create_time * 1000).format('YYYY-MM-DD');
-                fileData.filesize = await ctx.helper.bytesToSize(stream.fields.size);
-                fileData.id = result.insertId;
-                responseData.data = fileData;
+                responseData.data = files;
             } catch (err) {
                 this.log(err);
                 // 失败需要消耗掉stream 以防卡死

+ 27 - 8
app/controller/deal_bills_controller.js

@@ -11,7 +11,13 @@
 const fs = require('fs');
 const path = require('path');
 const excel = require('node-xlsx');
+const xlsx = require('js-xlsx');
 const sendToWormhole = require('stream-wormhole');
+const loadExcelType = {
+    display: 1,
+    actual: 2,
+};
+const loadType = loadExcelType.display;
 
 module.exports = app => {
     class DealBillsController extends app.BaseController {
@@ -52,14 +58,27 @@ module.exports = app => {
                 const fileName = this.app.baseDir + '/app/public/deal_bills/uploads/' + 'deal_bills' + create_time + fileInfo.ext;
                 // 保存文件
                 await ctx.helper.saveStreamFile(stream, fileName);
-                // 读取文件
-                const sheet = excel.parse(fileName);
-                if (!sheet || sheet.length === 0 || sheet[0] === undefined || sheet[0].data === undefined) {
-                    throw 'excel没有对应数据';
-                }
-                const result = await ctx.service.dealBills.importData(sheet[0], ctx.tender.id);
-                if (!result) {
-                    throw '导入数据失败';
+                if (loadType === loadExcelType.display) {
+                    const wb = xlsx.readFile(fileName);
+                    const name = wb.SheetNames[0];
+                    const sheetData = {
+                        rows: xlsx.utils.sheet_to_json(wb.Sheets[name], {header: 1}),
+                        merge: wb.Sheets[name]["!merges"],
+                    };
+                    const result = await ctx.service.dealBills.importDataJsXlsx(sheetData, ctx.tender.id);
+                    if (!result) {
+                        throw '导入数据失败';
+                    }
+                } else {
+                    // 读取文件
+                    const sheet = excel.parse(fileName);
+                    if (!sheet || sheet.length === 0 || sheet[0] === undefined || sheet[0].data === undefined) {
+                        throw 'excel没有对应数据';
+                    }
+                    const result = await ctx.service.dealBills.importData(sheet[0], ctx.tender.id);
+                    if (!result) {
+                        throw '导入数据失败';
+                    }
                 }
 
                 const dealBills = await this.ctx.service.dealBills.getAllDataByCondition({ where: { tender_id: ctx.tender.id } });

+ 3 - 2
app/controller/ledger_audit_controller.js

@@ -35,9 +35,10 @@ module.exports = app => {
                     return cols.indexOf(c.field) > -1;
                 });
             }
-            const ledger = JSON.parse(JSON.stringify(spreadConst.ledgerSpread));
-            const pos = JSON.parse(JSON.stringify(spreadConst.ledgerPosSpread));
             const tender = this.ctx.tender;
+            const setting = tender.info.display.ledger.clQty ? spreadConst.withCl : spreadConst.withoutCl;
+            const ledger = JSON.parse(JSON.stringify(setting.ledger));
+            const pos = JSON.parse(JSON.stringify(setting.pos));
             ledger.readOnly = true;
             pos.readOnly = true;
             if (tender.data.measure_type === measureType.tz.value) {

+ 25 - 35
app/controller/ledger_controller.js

@@ -13,7 +13,8 @@ const stdDataAddType = {
     child: 2,
     next: 3,
 };
-const auditConst = require('../const/audit').ledger;
+const audit = require('../const/audit');
+const auditConst = audit.ledger;
 const tenderMenu = require('../../config/menu').tenderMenu;
 const measureType = require('../const/tender').measureType;
 const spreadConst = require('../const/spread');
@@ -60,22 +61,30 @@ module.exports = app => {
                     return cols.indexOf(c.field) > -1;
                 });
             }
+            function hideFieldCols(setting, cols) {
+                for (const c of setting.cols) {
+                    if (cols.indexOf(c.field) > -1) {
+                        c.visible = false;
+                    }
+                }
+            }
             function setColFormat(cols, field, formatter) {
                 const col = _.find(cols, function (c) {
                     return c.field === field;
                 });
                 col.formatter = formatter;
             }
-            const tpFormatter = this.ctx.helper.getNumberFormatter(this.ctx.tender.info.decimal.tp);
-            const upFormatter = this.ctx.helper.getNumberFormatter(this.ctx.tender.info.decimal.up);
-            const ledger = JSON.parse(JSON.stringify(spreadConst.ledgerSpread));
-            setColFormat(ledger.cols, 'unit_price', upFormatter);
-            setColFormat(ledger.cols, 'dgn_price', upFormatter);
-            setColFormat(ledger.cols, 'total_price', tpFormatter);
-            setColFormat(ledger.cols, 'deal_tp', tpFormatter);
-            const pos = JSON.parse(JSON.stringify(spreadConst.ledgerPosSpread));
-
+            // const tpFormatter = this.ctx.helper.getNumberFormatter(this.ctx.tender.info.decimal.tp);
+            // const upFormatter = this.ctx.helper.getNumberFormatter(this.ctx.tender.info.decimal.up);
             const tender = this.ctx.tender;
+            const setting = tender.info.display.ledger.clQty ? spreadConst.withCl : spreadConst.withoutCl;
+            const ledger = JSON.parse(JSON.stringify(setting.ledger));
+            // setColFormat(ledger.cols, 'unit_price', upFormatter);
+            // setColFormat(ledger.cols, 'dgn_price', upFormatter);
+            // setColFormat(ledger.cols, 'total_price', tpFormatter);
+            // setColFormat(ledger.cols, 'deal_tp', tpFormatter);
+            const pos = JSON.parse(JSON.stringify(setting.pos));
+
             if (this._ledgerReadOnly(tender.data)) {
                 ledger.readOnly = true;
                 pos.readOnly = true;
@@ -265,7 +274,7 @@ module.exports = app => {
                     throw '当前未打开标段';
                 }
                 const tenderData = await ctx.service.tender.getDataById(tenderId);
-                if (!tenderData || tenderData.user_id !== ctx.session.sessionUser.accountId || this._ledgerReadOnly(tenderData)) {
+                if (!tenderData || tenderData.user_id !== ctx.session.sessionUser.accountId || this._ledgerReadOnly()) {
                     throw '标段数据错误';
                 }
                 const data = JSON.parse(ctx.request.body.data);
@@ -295,12 +304,13 @@ module.exports = app => {
                 if (!ctx.tender.data || ctx.tender.data.user_id !== ctx.session.sessionUser.accountId || this._ledgerReadOnly()) {
                     throw '标段数据错误';
                 }
+
                 const data = JSON.parse(ctx.request.body.data);
-                if ((isNaN(data.id) || data.id <= 0) || (!data.block || data.block.length <= 0)) {
+                if ((isNaN(data.id) || data.id <= 0) || (!data.tid && data.tid <= 0) || (!data.block || data.block.length <= 0)) {
                     throw '参数错误';
                 }
 
-                responseData.data = await ctx.service.ledger.pasteBlock(ctx.tender.id, data.id, data.block);
+                responseData.data = await ctx.service.ledger.pasteBlock(data.tid, data.id, data.block);
             } catch (err) {
                 responseData.err = 1;
                 responseData.msg = err;
@@ -378,7 +388,7 @@ module.exports = app => {
         async addFromDealBills(ctx) {
             try {
                 if (ctx.tender.ledgerReadOnly) {
-                    throw '数据错误';
+                    throw '标段数据错误';
                 }
                 const data = JSON.parse(ctx.request.body.data);
                 if ((isNaN(data.id) || data.id <= 0) || !data.type || !data.dealBills) {
@@ -612,7 +622,7 @@ module.exports = app => {
                 const responseData = { err: 0, msg: '', data: {}, };
                 await ctx.service.ledger.importExcel(data);
                 responseData.data.bills = await ctx.service.ledger.getDataByTenderId(ctx.tender.id, -1);
-                responseData.data.pos = await ctx.service.pos.getPosData(null);
+                responseData.data.pos = await ctx.service.pos.getPosData({tid: this.ctx.tender.id});
                 ctx.body = responseData;
             } catch (err) {
                 this.log(err);
@@ -647,26 +657,6 @@ module.exports = app => {
         }
 
         /**
-         * 台账变更页面 (Get)
-         *
-         * @param {object} ctx - egg全局变量
-         * @return {void}
-         */
-        async change(ctx) {
-            try {
-                const renderData = {
-                    tender: ctx.tender.data,
-                    tenderMenu: this.menu.tenderMenu,
-                    preUrl: '/tender/' + ctx.tender.id,
-                };
-                await this.layout('ledger/change.ejs', renderData, 'ledger/change_modal.ejs');
-            } catch (err) {
-                this.log(err);
-                ctx.redirect(ctx.request.header.referer);
-            }
-        }
-
-        /**
          * 计量台账页面 (Get)
          *
          * @param {object} ctx - egg全局变量

+ 102 - 5
app/controller/profile_controller.js

@@ -8,11 +8,24 @@
  * @version
  */
 
+const profileMenu = require('../../config/menu').profileMenu;
+const qr = require('qr-image');
+
 module.exports = app => {
 
     class ProfileController extends app.BaseController {
 
         /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局context
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            ctx.subMenu = profileMenu;
+        }
+        /**
          * 账号资料页面
          *
          * @param {Object} ctx - egg全局变量
@@ -29,14 +42,9 @@ module.exports = app => {
             const baseRule = ctx.service.projectAccount.rule('profileBase');
             const baseJsValidator = await this.jsValidator.convert(baseRule).setSelector('#base-form').build();
 
-            // 获取修改密码的字段规则
-            const passwordRule = ctx.service.projectAccount.rule('modifyPassword');
-            const passwordJsValidator = await this.jsValidator.convert(passwordRule).setSelector('#password-form').build();
-
             const renderData = {
                 accountData,
                 baseJsValidator,
-                passwordJsValidator,
             };
             await this.layout('profile/info.ejs', renderData);
         }
@@ -156,6 +164,95 @@ module.exports = app => {
             }
             ctx.redirect(ctx.request.header.referer);
         }
+
+        /**
+         * 短信通知
+         *
+         * @param {object} ctx - egg全局变量
+         * @return {void}
+         */
+        async sms(ctx) {
+            // 获取当前用户数据
+            const sessionUser = ctx.session.sessionUser;
+
+            // 获取账号数据
+            const accountData = await ctx.service.projectAccount.getDataByCondition({ id: sessionUser.accountId });
+
+            const renderData = {
+                accountData,
+            };
+            await this.layout('profile/sms.ejs', renderData);
+        }
+
+        /**
+         * 电子签名
+         *
+         * @param {object} ctx - egg全局变量
+         * @return {void}
+         */
+        async sign(ctx) {
+            // 获取当前用户数据
+            const sessionUser = ctx.session.sessionUser;
+
+            // 获取账号数据
+            const accountData = await ctx.service.projectAccount.getDataByCondition({ id: sessionUser.accountId });
+
+            const renderData = {
+                accountData,
+            };
+            await this.layout('profile/sign.ejs', renderData);
+        }
+
+        /**
+         * 生成二维码
+         *
+         * @param {object} ctx - egg全局变量
+         * @return {void}
+         */
+        async qrCode(ctx) {
+            const size = 5;
+            const margin = 1;
+            try {
+                // 获取当前用户数据
+                const sessionUser = ctx.session.sessionUser;
+
+                const text = ctx.request.header.host + '/sign?user_id=' + sessionUser.accountId + '&app_token=' + sessionUser.sessionToken;
+
+                // 大小默认5,二维码周围间距默认1
+                const img = qr.image(text || '', { type: 'png', size: size || 5, margin: margin || 1 });
+                ctx.status = 200;
+                ctx.type = 'image/png';
+                ctx.body = img;
+            } catch (e) {
+                ctx.status = 414;
+                ctx.set('Content-Type', 'text/html');
+                ctx.body = '<h1>414 Request-URI Too Large</h1>';
+            }
+        }
+
+        /**
+         * 账号安全
+         *
+         * @param {object} ctx - egg全局变量
+         * @return {void}
+         */
+        async safe(ctx) {
+            // 获取当前用户数据
+            const sessionUser = ctx.session.sessionUser;
+
+            // 获取账号数据
+            const accountData = await ctx.service.projectAccount.getDataByCondition({ id: sessionUser.accountId });
+
+            // 获取修改密码的字段规则
+            const passwordRule = ctx.service.projectAccount.rule('modifyPassword');
+            const passwordJsValidator = await this.jsValidator.convert(passwordRule).setSelector('#password-form').build();
+
+            const renderData = {
+                accountData,
+                passwordJsValidator,
+            };
+            await this.layout('profile/safe.ejs', renderData);
+        }
     }
 
     return ProfileController;

+ 355 - 0
app/controller/revise_controller.js

@@ -0,0 +1,355 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const stdDataAddType = {
+    withParent: 1,
+    child: 2,
+    next: 3,
+};
+const audit = require('../const/audit');
+const tenderMenu = require('../../config/menu').tenderMenu;
+const measureType = require('../const/tender').measureType;
+const spreadConst = require('../const/spread');
+const fs = require('fs');
+const LzString = require('lz-string');
+const accountGroup = require('../const/account_group').group;
+
+module.exports = app => {
+
+    class ReviseController extends app.BaseController {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+        }
+
+        /**
+         * 是否可以新增修订
+         * @param ctx
+         * @returns {Promise<boolean>}
+         * @private
+         */
+        async _getAddReviseValid(ctx) {
+            const stage  = await ctx.service.stage.getLastestStage(ctx.tender.id, true);
+            const revise = await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id);
+            return (ctx.tender.data.user_id === ctx.session.sessionUser.accountId) &&
+                (ctx.tender.data.ledger_status === audit.ledger.status.checked) &&
+                (!stage || stage.status === audit.stage.status.checked) &&
+                (!revise || !revise.valid || revise.status === audit.revise.status.checked);
+        }
+
+        /**
+         * 台账修订页面 (Get)
+         *
+         * @param {object} ctx - egg全局变量
+         * @return {void}
+         */
+        async index(ctx) {
+            try {
+                // 分页相关
+                const count = await ctx.service.ledgerRevise.count({tid: ctx.tender.id});
+                const ledgerRevise = await ctx.service.ledgerRevise.getReviseList(ctx.tender.id);
+                const addValid = await this._getAddReviseValid(ctx);
+                const renderData = {
+                    tender: ctx.tender.data,
+                    tenderMenu: this.menu.tenderMenu,
+                    preUrl: '/tender/' + ctx.tender.id,
+                    pageInfo: {
+                        page: ctx.page,
+                        total: Math.ceil(count/app.config.pageSize),
+                        queryData: JSON.stringify(ctx.urlInfo.query),
+                    },
+                    ledgerRevise,
+                    addValid,
+                    auditConst: audit.revise,
+                };
+                await this.layout('revise/index.ejs', renderData, 'revise/modal.ejs');
+            } catch (err) {
+                this.log(err);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 新增 修订 (Post)
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async add(ctx) {
+            try {
+                const addValid = await this._getAddReviseValid(ctx);
+                if (!addValid) {
+                    throw '无法新增修订';
+                }
+                const revise = await ctx.service.ledgerRevise.add(ctx.tender.id, ctx.session.sessionUser.accountId);
+
+                ctx.redirect('/tender/' + ctx.tender.id + '/revise/info');
+            } catch(err) {
+                this.log(err);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 作废 修订 (Post)
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async cancel(ctx) {
+            try {
+                const revise = await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id);
+                if (revise.uid !== ctx.session.sessionUser.accountId) {
+                    throw '您无权作废该修订';
+                }
+                if (revise.status === audit.revise.checking) {
+                    throw '修订审批中,不可作废';
+                } else if (revise.status === audit.revise.checked) {
+                    throw '修订已审批通过,不可作废';
+                }
+                if (revise.valid) {
+                    const result = await ctx.service.ledgerRevise.cancelRevise(revise.id);
+                }
+
+                ctx.redirect('/tender/' + ctx.tender.id + '/revise/');
+            } catch(err) {
+                this.log(err);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 保存 修订详情 (Ajax-post)
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async save(ctx) {
+            try {
+                const revise = await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id);
+                if (!revise.valid) {
+                    throw '该修订已作废';
+                } else if (revise.status === audit.revise.status.uncheck || revise.status === audit.revise.status.checkNo) {
+                    if (ctx.session.sessionUser.accountId !== revise.uid) {
+                        throw '您无权修改';
+                    }
+                } else if (revise.status === audit.revise.status.checking) {
+                    throw '修订在审批中,不可修改修订数据';
+                } else if (revise.staut === audit.revise.status.checked) {
+                    throw '修订已经审批通过,不可修改修订数据,请新增下一修订';
+                }
+                const data = JSON.parse(ctx.request.body.data);
+                if (data.content === undefined) {
+                    throw '保存数据有误'
+                }
+                const result = await ctx.service.ledgerRevise.defaultUpdate({id: revise.id, content: data.content});
+                if (result.affectedRows !== 1) {
+                    throw '保存数据失败,请重试';
+                }
+                ctx.body = {err: 0, msg: '', data: null};
+            } catch(err) {
+                this.log(err);
+                ctx.body = this.ajaxErrorBody(err, '保存数据失败,请重试');
+            }
+        }
+
+        /**
+         * 获取SpreadSetting
+         * @private
+         */
+        _getSpreadSetting(revise) {
+            const _ = this.app._;
+            function removeFieldCols(setting, cols) {
+                _.remove(setting.cols, function(c) {
+                    return cols.indexOf(c.field) > -1;
+                });
+            }
+            const tender = this.ctx.tender;
+            const setting = tender.info.display.ledger.clQty ? spreadConst.withCl : spreadConst.withoutCl;
+            const ledger = JSON.parse(JSON.stringify(setting.ledger));
+            const pos = JSON.parse(JSON.stringify(setting.pos));
+
+            if (revise.status === audit.revise.status.checking || revise.status === audit.revise.status.checked) {
+                ledger.readOnly = true;
+                pos.readOnly = true;
+            }
+            if (tender.data.measure_type === measureType.tz.value) {
+                removeFieldCols(ledger, spreadConst.filterCols.tzWithoutCols);
+            }
+            if (!tender.info.display.ledger.dgnQty) {
+                removeFieldCols(ledger, spreadConst.filterCols.dgnCols);
+            }
+            return [ledger, pos];
+        }
+
+        async _getDefaultReviseInfoData(ctx, revise) {
+            const reviseBills = await ctx.service.reviseBills.getData(ctx.tender.id, revise.id);
+            const revisePos = await ctx.service.revisePos.getData(ctx.tender.id, revise.id);
+            const [ledgerSpread, posSpread] = this._getSpreadSetting(revise);
+            return {
+                revise: revise, tender: ctx.tender.data,
+                reviseBills, revisePos, ledgerSpread, posSpread, tenderMenu, measureType,
+                preUrl: '/tender/' + ctx.tender.id,
+                audit: audit.revise,
+                jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.ledger.revise),
+            };
+        }
+
+        /**
+         * 历史修订页面(修订完成后,用于查看)
+         * @param ctx
+         * @param revise
+         * @returns {Promise<void>}
+         * @private
+         */
+        async _getHistoryReviseInfo(ctx, revise) {
+            const renderData = await this._getDefaultReviseInfoData(ctx, revise);
+            renderData.ledgerSpread.readOnly = true;
+            renderData.posSpread.readOnly = true;
+            renderData.readOnly = true;
+            renderData.history = true;
+            renderData.historyRevise = await ctx.service.ledgerRevise.getReviseList(ctx.tender.id);
+            await this.layout('revise/info.ejs', renderData, 'revise/info_modal.ejs');
+        }
+
+        /**
+         * 修订页面(草稿,审批中)
+         * @param ctx
+         * @param revise
+         * @returns {Promise<void>}
+         * @private
+         */
+        async _getReviseInfo(ctx, revise) {
+            const renderData = await this._getDefaultReviseInfoData(ctx, revise);
+            const readOnly = (revise.status === audit.revise.status.checking || revise.status === audit.revise.status.checked || revise.uid !== ctx.session.sessionUser.accountId);
+            if (readOnly) {
+                renderData.ledgerSpread.readOnly = true;
+                renderData.posSpread.readOnly = true;
+            }
+            renderData.readOnly = readOnly;
+            renderData.history = false;
+            renderData.historyRevise = [];
+            await this.layout('revise/info.ejs', renderData, 'revise/info_modal.ejs');
+        }
+
+        /**
+         * 修订 详细页面(Get)
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async info(ctx) {
+            try {
+                const revise = await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id);
+                if (revise.status === audit.revise.status.checked) {
+                    await this._getHistoryReviseInfo(ctx);
+                } else {
+                    await this._getReviseInfo(ctx, revise);
+                }
+            } catch (err) {
+                this.log(err);
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        async _checkRevise() {
+            const revise = await this.ctx.service.ledgerRevise.getLastestRevise(this.ctx.tender.id);
+            if (revise.uid !== this.ctx.session.sessionUser.accountId) {
+                throw '修订数据错误';
+            } else if (revise.status === audit.revise.status.checking) {
+                throw '修订审批中,不可修改';
+            } else if (revise.status === audit.revise.status.checked) {
+                throw '修订已完成,如需修订台账,请新增修订';
+            }
+            this.ctx.revise = revise;
+        }
+
+        /**
+         * 增、删、上下移、升降级
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async baseOpr(ctx) {
+            try {
+                await this._checkRevise();
+                const data = JSON.parse(ctx.request.body.data);
+                let result;
+
+                switch (data.postType) {
+                    case 'add':
+                        result = await ctx.service.reviseBills.addNode(ctx.tender.id, ctx.revise.id, data.id);
+                        break;
+                    // case 'delete':
+                    //     result = await ctx.service.reviseBills.deleteNode(ctx.revise.id, data.id);
+                    //     break;
+                    case 'up-move':
+                        result = await ctx.service.reviseBills.upMoveNode(ctx.tender.id, data.id);
+                        break;
+                    case 'down-move':
+                        result = await ctx.service.reviseBills.downMoveNode(ctx.tender.id, data.id);
+                        break;
+                    // case 'up-level':
+                    //     result = await ctx.service.reviseBills.upLevelNode(ctx.revise.id, data.id);
+                    //     break;
+                    // case 'down-level':
+                    //     result = await ctx.service.reviseBills.downLevelNode(ctx.revise.id, data.id);
+                    //     break;
+                    default:
+                        throw '未知操作';
+                }
+                ctx.body = {err: 0, msg: '', data: result};
+            } catch (err) {
+                this.log(err);
+                ctx.body = this.ajaxErrorBody(err, '数据错误');
+            }
+        }
+
+        /**
+         * 批量插入数据 (Ajax)
+         *
+         * data = {id, batchData, batchType}
+         * data.batchType = 'batchInsertChild'/'batchInsertNext'
+         * data.batchData = [{name, children}] -- 项目节列表
+         * data.batchData.children = [{code, name, unit, unit_price, quantity}] -- 工程量清单列表
+         *
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async batchInsert(ctx) {
+            try {
+                await this._checkRevise();
+                const data = JSON.parse(ctx.request.body.data);
+                if ((isNaN(data.id) || data.id <= 0) || !data.batchType) {
+                    throw '参数错误';
+                }
+
+                let result;
+                switch (data.batchType) {
+                    case 'child':
+                        result = await ctx.service.reviseBills.batchInsertChild(ctx.tender.id, ctx.revise.id, data.id, data.batchData);
+                        break;
+                    case 'next':
+                        result = await ctx.service.reviseBills.batchInsertNext(ctx.tender.id, ctx.revise.id, data.id, data.batchData);
+                        break;
+                    default:
+                        throw '参数错误';
+                }
+                ctx.body = {err: 0, msg: '', data: result};
+            } catch (err) {
+                this.log(err);
+                ctx.body = this.ajaxErrorBody(err, '数据错误');
+            }
+        }
+    }
+
+    return ReviseController;
+};

+ 73 - 0
app/controller/sign_controller.js

@@ -0,0 +1,73 @@
+'use strict';
+
+/**
+ * 签名相关控制器
+ *
+ * @author EllisRan
+ * @date 2019/8/14
+ * @version
+ */
+const moment = require('moment');
+const path = require('path');
+const sendToWormhole = require('stream-wormhole');
+const fs = require('fs');
+
+module.exports = app => {
+
+    class SignController extends app.BaseController {
+        /**
+         * 电子签名页面
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async index(ctx) {
+            const renderData = {
+                error: false,
+            };
+            try {
+                const userinfo = await ctx.service.projectAccount.getDataById(ctx.query.user_id);
+                if (userinfo && userinfo.session_token && userinfo.session_token === ctx.query.app_token) {
+                    renderData.id = userinfo.id;
+                    renderData.name = userinfo.name;
+                    renderData.role = userinfo.role;
+                } else {
+                    throw '参数有误, 无法访问本页.';
+                }
+            } catch (error) {
+                console.log(error);
+                renderData.error = true;
+            }
+            await ctx.render('sign/info.ejs', renderData);
+        }
+
+        /**
+         * 保存签名
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async save(ctx) {
+            try {
+                const stream = await ctx.getFileStream({ requireFile: false });
+                const create_time = Date.parse(new Date()) / 1000;
+                const dirName = 'app/public/upload/sign/';
+                const fileName = moment().format('YYYYMMDD') + '_sign_' + create_time + '.png';
+                await ctx.helper.saveStreamFile(stream, path.join(this.app.baseDir, dirName, fileName));
+                await sendToWormhole(stream);
+
+                const result = await ctx.service.projectAccount.update({ sign_path: fileName }, { id: stream.fields.id });
+                if (result) {
+                    ctx.body = { err: 0, msg: '' };
+                } else {
+                    throw '添加数据库失败';
+                }
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString() };
+            }
+        }
+    }
+
+    return SignController;
+};

+ 581 - 121
app/controller/stage_controller.js

@@ -19,6 +19,8 @@ const measureType = tenderConst.measureType;
 const path = require('path');
 const PayCalculator = require('../lib/pay_calc');
 const accountGroup = require('../const/account_group').group;
+const sendToWormhole = require('stream-wormhole');
+const fs = require('fs');
 
 module.exports = app => {
     class StageController extends app.BaseController {
@@ -86,25 +88,19 @@ module.exports = app => {
                     col.formatter = formatter;
                 }
             }
-            const tpFormatter = this.ctx.helper.getNumberFormatter(this.ctx.tender.info.decimal.tp);
-            const upFormatter = this.ctx.helper.getNumberFormatter(2);
-            const ledger = JSON.parse(JSON.stringify(spreadConst.stage.ledger));
-            setColFormat(ledger.cols, 'unit_price', upFormatter);
-            setColFormat(ledger.cols, 'total_price', tpFormatter);
-            setColFormat(ledger.cols, 'tp', tpFormatter);
-            const pos = JSON.parse(JSON.stringify(spreadConst.stage.pos));
+            // const tpFormatter = this.ctx.helper.getNumberFormatter(this.ctx.tender.info.decimal.tp);
+            // const upFormatter = this.ctx.helper.getNumberFormatter(2);
             const tender = this.ctx.tender, stage = this.ctx.stage;
-            if (this._stageReadOnly(stage)) {
-                ledger.readOnly = true;
-                pos.readOnly = true;
-            }
-            if (tender.data.measure_type === measureType.tz.value) {
-                removeFieldCols(ledger, spreadConst.filterCols.tzWithoutCols);
-                removeFieldCols(pos, spreadConst.filterCols.posTzWithoutCols);
-            }
+            const stageSetting = tender.data.measure_type === measureType.tz.value ? spreadConst.stageTz :
+                (tender.info.display.ledger.clQty ? spreadConst.stageCl : spreadConst.stageNoCl);
+            const ledger = JSON.parse(JSON.stringify(stageSetting.ledger));
+            // setColFormat(ledger.cols, 'unit_price', upFormatter);
+            // setColFormat(ledger.cols, 'total_price', tpFormatter);
+            // setColFormat(ledger.cols, 'tp', tpFormatter);
             if (!tender.info.display.ledger.dgnQty) {
-                removeFieldCols(ledger, spreadConst.filterCols.dgnCols);
+                removeFieldCols(ledger, spreadConst.filterCols.stageDgnCols);
             }
+            const pos = JSON.parse(JSON.stringify(stageSetting.pos));
             if (this.ctx.stage.readOnly) {
                 ledger.readOnly = true;
                 pos.readOnly = true;
@@ -146,19 +142,37 @@ module.exports = app => {
                 [renderData.ledgerSpread, renderData.posSpread] = this._getSpreadSetting();
                 renderData.changeConst = changeConst;
                 renderData.ledgerData = await ctx.service.ledger.getDataByTenderId(ctx.tender.id, -1);
+                const dgnData = await ctx.service.stageBillsDgn.getDgnData(ctx.tender.id);
+                for (const d of dgnData) {
+                    const l = ctx.app._.find(renderData.ledgerData, {id: d.id});
+                    ctx.app._.assignIn(l, d);
+                }
+                let curStageData, preStageData;
                 // 当前操作人查看最新数据,其他人查看历史数据
                 if (ctx.stage.readOnly) {
-                    renderData.curStageData = await ctx.service.stageBills.getAuditorStageData(ctx.tender.id, ctx.stage.id, ctx.stage.curTimes, ctx.stage.curOrder);
+                    curStageData = await ctx.service.stageBills.getAuditorStageData(ctx.tender.id, ctx.stage.id, ctx.stage.curTimes, ctx.stage.curOrder);
                 } else {
-                    renderData.curStageData = await ctx.service.stageBills.getLastestStageData(ctx.tender.id, ctx.stage.id);
+                    curStageData = await ctx.service.stageBills.getLastestStageData(ctx.tender.id, ctx.stage.id);
                 }
-                renderData.jsFiles = this.app.jsFiles.common.concat(this.app.jsFiles.stage.index);
                 // 查询截止上期数据
                 if (ctx.stage.order > 1) {
-                    renderData.preStageData = await ctx.service.stageBillsFinal.getFinalData(ctx.tender.data, ctx.stage);
+                    preStageData = await ctx.service.stageBillsFinal.getFinalData(ctx.tender.data, ctx.stage);
+                    //renderData.preStageData = await ctx.service.stageBills.getEndStageData(ctx.tender.id, ctx.stage.order - 1);
                 } else {
-                    renderData.preStageData = [];
+                    preStageData = [];
                 }
+                this.ctx.helper.assignRelaData(renderData.ledgerData, [
+                    {data: curStageData, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'postil'], prefix: '', relaId: 'lid',},
+                    {data: preStageData, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'], prefix: 'pre_', relaId: 'lid',}
+                ]);
+                renderData.jsFiles = this.app.jsFiles.common.concat(this.app.jsFiles.stage.index);
+                renderData.whiteList = this.ctx.app.config.multipart.whitelist;
+                // 获取附件列表
+                const attData = await ctx.service.stageAtt.getDataByTenderIdAndStageId(ctx.tender.id, ctx.params.order);
+                for (const index in attData) {
+                    attData[index].in_time = moment(attData[index].in_time * 1000).format('YYYY-MM-DD');
+                }
+                renderData.attData = attData;
                 await this.layout('stage/index.ejs', renderData, 'stage/modal.ejs');
             } catch (err) {
                 this.log(err);
@@ -176,21 +190,34 @@ module.exports = app => {
                 const condition = JSON.parse(ctx.request.body.data) || {};
                 condition.tid = ctx.tender.id;
                 const responseData = {err: 0, msg: '', data: {}};
-                responseData.data.pos = await ctx.service.pos.getPosData(condition);
+
+                let curStageData, preStageData;
+                responseData.data = await ctx.service.pos.getPosData(condition);
                 // 根据当前人,或指定对象查询数据
+                console.time('cur');
+                const curWhere = JSON.parse(ctx.request.body.data);
                 if (ctx.stage.readOnly) {
-                    responseData.data.curStageData = await ctx.service.stagePos.getAuditorStageData(ctx.tender.id,
-                        ctx.stage.id, ctx.stage.curTimes, ctx.stage.curOrder, this.app._.map(responseData.data.pos, 'id'));
+                    curStageData = await ctx.service.stagePos.getAuditorStageData(ctx.tender.id,
+                        ctx.stage.id, ctx.stage.curTimes, ctx.stage.curOrder, curWhere);
                 } else {
-                    responseData.data.curStageData = await ctx.service.stagePos.getLastestStageData(ctx.tender.id,
-                        ctx.stage.id, this.app._.map(responseData.data.pos, 'id'));
+                    curStageData = await ctx.service.stagePos.getLastestStageData(ctx.tender.id, ctx.stage.id, curWhere);
                 }
+                console.timeEnd('cur');
                 // 查询截止上期数据
+                console.time('pre');
                 if (ctx.stage.order > 1) {
-                    responseData.data.preStageData = await ctx.service.stagePosFinal.getFinalData(ctx.tender.data, ctx.stage);
+                    preStageData = await ctx.service.stagePosFinal.getFinalData(ctx.tender.data, ctx.stage);
+                    //responseData.data.preStageData = await ctx.service.stagePos.getEndStageData(ctx.tender.id, ctx.stage.order - 1);
                 } else {
-                    responseData.data.preStageData = [];
+                    preStageData = [];
                 }
+                console.timeEnd('pre');
+                console.time('assign');
+                this.ctx.helper.assignRelaData(responseData.data, [
+                    {data: curStageData, fields: ['contract_qty', 'qc_qty', 'postil'], prefix: '', relaId: 'pid'},
+                    {data: preStageData, fields: ['contract_qty', 'qc_qty'], prefix: 'pre_', relaId: 'pid'}
+                ]);
+                console.timeEnd('assign');
                 ctx.body = responseData;
             } catch (err) {
                 this.log(err);
@@ -215,14 +242,91 @@ module.exports = app => {
                 if (data.pos) {
                     responseData.data = await ctx.service.stagePos.updateStageData(data.pos);
                 } else if (data.bills) {
-                    responseData.data.bills = await ctx.service.stageBills.updateStageData(data.bills);
+                    if (data.bills.dgn) {
+                        responseData.data.dgn = await ctx.service.stageBillsDgn.saveDgnData(data.bills.dgn);
+                    }
+                    if (data.bills.stage) {
+                        responseData.data.curStageData = await ctx.service.stageBills.updateStageData(data.bills.stage);
+                    }
                 }
+                await this.ctx.service.stage.updateCheckDetailFlag(ctx.stage.id, true);
                 ctx.body = responseData;
             } catch (err) {
                 this.log(err);
                 ctx.body = {err: 1, msg: err.toString(), data: null};
             }
         }
+        /**
+         * 查询可用变更令 (Ajax-Post)
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async searchValidChange(ctx) {
+            try {
+                // if (ctx.stage.readOnly) {
+                //     throw '您无权调用变更令';
+                // }
+
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.bills && !data.pos) {
+                    throw '数据错误';
+                }
+                const bills = data.bills ? data.bills : await ctx.service.ledger.getDataById(data.pos.lid);
+                const pos = data.pos;
+                const changes = await ctx.service.change.getValidChanges(ctx.tender.id, bills);
+                const useChanges = await ctx.service.stageChange.getLastestStageData(ctx.tender.id, ctx.stage.id, bills.id, pos ? pos.id : -1);
+                ctx.body = {err: 0, msg: '', data: {changes, useChanges}};
+            } catch(err) {
+                this.log(err);
+                ctx.body = {err: 1, msg: err.toString(), data: null};
+            }
+        }
+        /**
+         * 调用变更令 (Ajax-Post)
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async useChange(ctx) {
+            try {
+                if (ctx.stage.readOnly) {
+                    throw '您无权使用变更令';
+                }
+
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.target || (!data.target.bills && !data.target.pos) || !data.change) {
+                    throw '调用变更令数据错误'
+                }
+                let result;
+                if (data.target.pos) {
+                    result = await ctx.service.stageChange.posChange(data.target.pos, data.change);
+                } else {
+                    result = await ctx.service.stageChange.billsChange(data.target.bills, data.change);
+                }
+                await this.ctx.service.stage.updateCheckDetailFlag(ctx.stage.id, true);
+                ctx.body = {err: 0, msg: '', data: result};
+            } catch(err) {
+                this.log(err);
+                ctx.body = {err: 1, msg: err.toString(), data: null};
+            }
+        }
+        /**
+         * 查询变更令 明细数据(包括附件、变更清单、累计使用情况、本期使用情况) (Ajax-Post)
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async changeDetail(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.cid) {
+                    throw '查询数据错误';
+                }
+                const detailData = await this._getChangeDetailData(ctx.tender.id, ctx.stage.id, data.cid);
+                ctx.body = {err: 0, msg: '', data: detailData};
+            } catch(err) {
+                this.log(err);
+                ctx.body = {err: 1, msg: err.toString(), data: null};
+            }
+        }
 
         /**
          * 中间计量 (Get)
@@ -281,14 +385,27 @@ module.exports = app => {
                     result.ledger = await ctx.service.ledger.getDataByTenderId(ctx.tender.id, -1);
                     result.pos = await ctx.service.pos.getPosData({tid: ctx.tender.id});
                     if (ctx.stage.readOnly) {
-                        result.curStage = await ctx.service.stageBills.getAuditorStageData(ctx.tender.id, ctx.stage.id, ctx.stage.curTimes, ctx.stage.curOrder);
-                        result.curPosStage = await ctx.service.stagePos.getAuditorStageData(ctx.tender.id, ctx.stage.id, ctx.stage.curTimes, ctx.stage.curOrder);
+                        const curStage = await ctx.service.stageBills.getAuditorStageData(ctx.tender.id, ctx.stage.id, ctx.stage.curTimes, ctx.stage.curOrder);
+                        this.ctx.helper.assignRelaData(result.ledger, [
+                            {data: curStage, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'], prefix: '', relaId: 'lid'}
+                        ]);
+                        const curPosStage = await ctx.service.stagePos.getAuditorStageData(ctx.tender.id, ctx.stage.id, ctx.stage.curTimes, ctx.stage.curOrder);
+                        this.ctx.helper.assignRelaData(result.pos, [
+                            {data: curPosStage, fields: ['contract_qty', 'qc_qty'], prefix: '', relaId: 'pid'}
+                        ]);
                         result.stageDetail = await ctx.service.stageDetail.getAuditorStageData(ctx.tender.id, ctx.stage.id, ctx.stage.curTimes, ctx.stage.curOrder);
                     } else {
-                        result.curStage = await ctx.service.stageBills.getLastestStageData(ctx.tender.id, ctx.stage.id);
-                        result.curPosStage = await ctx.service.stagePos.getLastestStageData(ctx.tender.id, ctx.stage.id);
+                        const curStage = await ctx.service.stageBills.getLastestStageData(ctx.tender.id, ctx.stage.id);
+                        this.ctx.helper.assignRelaData(result.ledger, [
+                            {data: curStage, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'], prefix: '', relaId: 'lid'}
+                        ]);
+                        const curPosStage = await ctx.service.stagePos.getLastestStageData(ctx.tender.id, ctx.stage.id);
+                        this.ctx.helper.assignRelaData(result.pos, [
+                            {data: curPosStage, fields: ['contract_qty', 'qc_qty'], prefix: '', relaId: 'pid'}
+                        ]);
                         result.stageDetail = await ctx.service.stageDetail.getLastestStageData(ctx.tender.id, ctx.stage.id);
                     }
+                    console.log(result.length);
                     ctx.body = { err: 0, msg: '', data: result };
                 }
             } catch (err) {
@@ -333,7 +450,11 @@ module.exports = app => {
 
                 const data = JSON.parse(ctx.request.body.data);
                 const responseData = { err: 0, msg: '', data: {}, };
-                responseData.data = await ctx.service.stageDetail.saveDetailData(data);
+                if (data instanceof Array) {
+                    responseData.data = await ctx.service.stageDetail.saveDetailDatas(data);
+                } else {
+                    responseData.data = await ctx.service.stageDetail.saveDetailData(data);
+                }
                 ctx.body = responseData;
             } catch (err) {
                 this.log(err);
@@ -403,6 +524,36 @@ module.exports = app => {
         }
 
         /**
+         * 中间计量 -- 完成中间计量(Post)
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async doneDetail(ctx) {
+            try {
+                await ctx.service.stage.updateCheckDetailFlag(ctx.stage.id, false);
+                ctx.stage.check_detail = false;
+                ctx.redirect(ctx.request.header.referer);
+            } catch (err) {
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
+         * 中间计量 -- 解锁编辑(Post)
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async unlockDetail(ctx) {
+            try {
+                await ctx.service.stage.updateCheckDetailFlag(ctx.stage.id, true);
+                ctx.stage.check_detail = true;
+                ctx.redirect(ctx.request.header.referer);
+            } catch (err) {
+                ctx.redirect(ctx.request.header.referer);
+            }
+        }
+
+        /**
          * 合同支付 (Get)
          * @param ctx
          * @returns {Promise<void>}
@@ -411,7 +562,19 @@ module.exports = app => {
             try {
                 await this._getStageAuditViewData(ctx);
                 const renderData = await this._getDefaultRenderData(ctx);
-                renderData.dealPay = await ctx.service.stagePay.getStagePays(ctx.stage);
+                const dealPay = await ctx.service.stagePay.getStagePays(ctx.stage);
+                // 附件不取下载地址
+                for (const index in dealPay) {
+                    if (dealPay[index].attachment !== null) {
+                        const attachments = JSON.parse(dealPay[index].attachment);
+                        for (const att_index in attachments) {
+                            delete attachments[att_index].filepath;
+                            attachments[att_index].username = (await ctx.service.projectAccount.getAccountInfoById(attachments[att_index].uid)).name;
+                        }
+                        dealPay[index].attachment = attachments;
+                    }
+                }
+                renderData.dealPay = dealPay;
                 // if (dealPay && dealPay.length > 0) {
                 //     renderData.dealPay = dealPay;
                 // } else {
@@ -421,12 +584,19 @@ module.exports = app => {
                 // }
                 renderData.calcBase = await ctx.service.stage.getStagePayCalcBase();
                 renderData.jsFiles = this.app.jsFiles.common.concat(this.app.jsFiles.stage.pay);
+                renderData.whiteList = this.ctx.app.config.multipart.whitelist;
+
+                // 用户有无权限上传和删除附件
+                renderData.uploadPermission = ((ctx.stage.status === auditConst.status.uncheck || ctx.stage.status === auditConst.status.checkNo) && ctx.session.sessionUser.accountId === ctx.stage.user_id) ||
+                    (ctx.stage.status === auditConst.status.checkNoPre && ctx.session.sessionUser.accountId === ctx.stage.curAuditor.aid) ||
+                    (ctx.stage.status === auditConst.status.checking && ctx.stage.curAuditor && ctx.stage.curAuditor.aid === ctx.session.sessionUser.accountId);
 
                 // 计算 本期金额
                 const payCalculator = new PayCalculator(this.ctx, this.ctx.tender.info.decimal);
                 await payCalculator.calculateAll(renderData.dealPay);
                 await this.layout('stage/pay.ejs', renderData, 'stage/pay_modal.ejs');
             } catch (err) {
+                console.log(err);
                 this.log(err);
                 ctx.redirect('/tender/' + ctx.tender.id + '/measure/stage');
             }
@@ -438,6 +608,11 @@ module.exports = app => {
          * @returns {Promise<void>}
          */
         async savePayData(ctx) {
+            function checkCalcField(data) {
+                return data.sexpr !== undefined || data.sprice !== undefined
+                || data.rexpr !== undefined || data.rprice !== undefined
+                || data.minus !== undefined || data.is_yf !== undefined || data.dl_type !== undefined
+            }
             try {
                 // 检查登录用户,是否可操作
                 if (ctx.stage.readOnly) {
@@ -462,15 +637,16 @@ module.exports = app => {
                         break;
                     case 'info':
                         responseData.data = await ctx.service.pay.save(data.updateData);
-                        if (data.updateData.sexpr !== undefined || data.updateData.sprice !== undefined
-                            || data.updateData.rexpr !== undefined || data.updateData.rprice !== undefined
-                            || data.updateData.minus !== undefined || data.updateData.is_yf !== undefined
-                            || data.updateData.dl_type !== undefined) {
+                        const bReCalc = data.updateData instanceof Array ? checkCalcField(data.updateData[0]) : checkCalcField(data.updateData);
+                        if (bReCalc) {
                             responseData.data = await ctx.service.stagePay.getStagePays(ctx.stage);
                             await payCalculator.calculateAll(responseData.data);
-                            //console.log(responseData.data[responseData.data.length - 1]);
                         } else {
-                            responseData.data = await ctx.service.stagePay.getStagePay(ctx.stage, responseData.data.id);
+                            if (data.updateData instanceof Array) {
+                                responseData.data = await ctx.service.stagePay.getStagePay(ctx.stage, this.app._.map(responseData.data, 'id'));
+                            } else {
+                                responseData.data = await ctx.service.stagePay.getStagePay(ctx.stage, responseData.data.id);
+                            }
                         }
                         break;
                     case 'stage':
@@ -498,14 +674,14 @@ module.exports = app => {
             function assignStageData(chapter, curStage, preStage) {
                 chapter.contract_tp = curStage.contract_tp;
                 chapter.qc_tp = curStage.qc_tp;
-                chapter.gather_tp = _.add(curStage.contract_tp, curStage.qc_tp);
+                chapter.gather_tp = ctx.helper.add(curStage.contract_tp, curStage.qc_tp);
                 if (preStage) {
                     chapter.pre_contract_tp = preStage.contract_tp;
                     chapter.pre_qc_tp = preStage.qc_tp;
-                    chapter.pre_gather_tp = _.add(preStage.contract_tp, preStage.qc_tp);
-                    chapter.end_contract_tp = _.add(curStage.contract_tp, preStage.contract_tp);
-                    chapter.end_qc_tp = _.add(curStage.qc_tp, preStage.qc_tp);
-                    chapter.end_gather_tp = _.add(chapter.gather_tp, chapter.pre_gather_tp);
+                    chapter.pre_gather_tp = ctx.helper.add(preStage.contract_tp, preStage.qc_tp);
+                    chapter.end_contract_tp = ctx.helper.add(curStage.contract_tp, preStage.contract_tp);
+                    chapter.end_qc_tp = ctx.helper.add(curStage.qc_tp, preStage.qc_tp);
+                    chapter.end_gather_tp = ctx.helper.add(chapter.gather_tp, chapter.pre_gather_tp);
                 } else {
                     chapter.end_contract_tp = curStage.contract_tp;
                     chapter.end_qc_tp = curStage.qc_tp;
@@ -529,7 +705,7 @@ module.exports = app => {
                             const sum = _.find(calcDetail, {cType: 11});
                             const chapters = _.filter(calcDetail, {cType: 1});
                             for (const f of fields) {
-                                cd[f] = _.subtract(sum[f], _.sumBy(chapters, f));
+                                cd[f] = ctx.helper.sub(sum[f], this.ctx.helper.sum(_.map(chapters, f)));//_.sumBy(chapters, f));
                             }
                             break;
                         case 31:
@@ -539,7 +715,7 @@ module.exports = app => {
                             const sum1 = _.find(calcDetail, {cType: 11});
                             const sum2 = _.find(calcDetail, {cType: 31});
                             for (const f of fields) {
-                                cd[f] = _.add(sum1.value, sum2.value);
+                                cd[f] = ctx.helper.add(sum1.value, sum2.value);
                             }
                             break;
                     }
@@ -574,6 +750,7 @@ module.exports = app => {
          */
         async change(ctx) {
             try {
+                await this._getStageAuditViewData(ctx);
                 const renderData = await this._getDefaultRenderData(ctx);
                 renderData.ledger = await ctx.service.ledger.getDataByTenderId(ctx.tender.id, -1);
                 renderData.changes = await ctx.service.change.getChangeAndUsedInfo(ctx.tender.id);
@@ -590,77 +767,6 @@ module.exports = app => {
                 ctx.redirect('/tender/' + ctx.tender.id + '/measure/stage');
             }
         }
-        /**
-         * 查询可用变更令 (Ajax-Post)
-         * @param ctx
-         * @returns {Promise<void>}
-         */
-        async searchValidChange(ctx) {
-            try {
-                // if (ctx.stage.readOnly) {
-                //     throw '您无权调用变更令';
-                // }
-
-                const data = JSON.parse(ctx.request.body.data);
-                if (!data.bills && !data.pos) {
-                    throw '数据错误';
-                }
-                const bills = data.bills ? data.bills : await ctx.service.ledger.getDataById(data.pos.lid);
-                const pos = data.pos;
-                const changes = await ctx.service.change.getValidChanges(ctx.tender.id, bills, pos);
-                const useChanges = await ctx.service.stageChange.getLastestStageData(ctx.tender.id, ctx.stage.id, bills.id, pos ? pos.id : -1);
-                ctx.body = {err: 0, msg: '', data: {changes, useChanges}};
-            } catch(err) {
-                this.log(err);
-                ctx.body = {err: 1, msg: err.toString(), data: null};
-            }
-        }
-        /**
-         * 调用变更令 (Ajax-Post)
-         * @param ctx
-         * @returns {Promise<void>}
-         */
-        async useChange(ctx) {
-            try {
-                if (ctx.stage.readOnly) {
-                    throw '您无权使用变更令';
-                }
-
-                const data = JSON.parse(ctx.request.body.data);
-                if (!data.target || (!data.target.bills && !data.target.pos) || !data.change) {
-                    throw '调用变更令数据错误'
-                }
-                let result;
-                if (data.target.bills) {
-                    result = await ctx.service.stageChange.billsChange(data.target.bills, data.change);
-                } else {
-                    result = await ctx.service.stageChange.posChange(data.target.pos, data.change);
-                }
-                ctx.body = {err: 0, msg: '', data: result};
-            } catch(err) {
-                this.log(err);
-                ctx.body = {err: 1, msg: err.toString(), data: null};
-            }
-        }
-        /**
-         * 查询变更令 明细数据(包括附件、变更清单、累计使用情况、本期使用情况) (Ajax-Post)
-         * @param ctx
-         * @returns {Promise<void>}
-         */
-        async changeDetail(ctx) {
-            try {
-                const data = JSON.parse(ctx.request.body.data);
-                if (!data.cid) {
-                    throw '查询数据错误';
-                }
-                const detailData = await this._getChangeDetailData(ctx.tender.id, ctx.stage.id, data.cid);
-                ctx.body = {err: 0, msg: '', data: detailData};
-            } catch(err) {
-                this.log(err);
-                ctx.body = {err: 1, msg: err.toString(), data: null};
-            }
-        }
-
 
         // 审批相关
         /**
@@ -741,6 +847,9 @@ module.exports = app => {
                 if (ctx.stage.status === auditConst.status.checking || ctx.stage.status === auditConst.status.checked) {
                     throw '该期数据当前无法上报';
                 }
+                if (ctx.stage.check_detail) {
+                    throw '上报前,需要完成中间计量。';
+                }
 
                 await ctx.service.stageAudit.start(ctx.stage.id, ctx.stage.times);
 
@@ -771,6 +880,9 @@ module.exports = app => {
                 if (!data.checkType || isNaN(data.checkType)) {
                     throw '提交数据错误';
                 }
+                if (data.checkType === auditConst.status.checked && ctx.stage.check_detail) {
+                    throw '审批通过前,需要中间计量。';
+                }
                 if (data.checkType === auditConst.status.checkNo) {
                     if (!data.checkType || isNaN(data.checkType)) {
                         throw '提交数据错误';
@@ -826,6 +938,7 @@ module.exports = app => {
          */
         async gather(ctx) {
             try {
+                await this._getStageAuditViewData(ctx);
                 const renderData = await this._getDefaultRenderData(ctx);
                 [renderData.gclSpread, renderData.leafXmjSpread] = this._getGatherSpreadSetting();
                 renderData.ledger = await ctx.service.ledger.getDataByTenderId(ctx.tender.id, -1);
@@ -836,9 +949,10 @@ module.exports = app => {
                 renderData.curPosData = await ctx.service.stagePos.getAuditorStageData(ctx.tender.id,
                     ctx.stage.id, ctx.stage.times, ctx.stage.curAuditor ? ctx.stage.curAuditor.order : 0);
                 //renderData.gcl = await this.ctx.service.ledger.getAllLeafGclBills(this.ctx.tender.id);
+                renderData.dealBills = await ctx.service.dealBills.getAllDataByCondition({ where: {tender_id: this.ctx.tender.id} });
 
                 renderData.jsFiles = this.app.jsFiles.common.concat(this.app.jsFiles.stage.gather);
-                await this.layout('stage/gather.ejs', renderData);
+                await this.layout('stage/gather.ejs', renderData, 'stage/gather_modal.ejs');
             } catch (err) {
                 this.log(err);
                 ctx.redirect('/tender/' + ctx.tender.id + '/measure/stage/' + ctx.params.order);
@@ -869,8 +983,9 @@ module.exports = app => {
 
                 const pos = JSON.parse(JSON.stringify(spreadConst.stageCompare.pos));
                 return [ledger, pos];
-            };
+            }
             try {
+                await this._getStageAuditViewData(ctx);
                 const renderData = await this._getDefaultRenderData(ctx);
                 [renderData.ledgerSpread, renderData.posSpread] = getCompareSpreadSetting();
                 renderData.ledger = await ctx.service.ledger.getDataByTenderId(ctx.tender.id, -1);
@@ -917,6 +1032,351 @@ module.exports = app => {
                 ctx.redirect('/tender/' + ctx.tender.id + '/measure/stage');
             }
         }
+
+        /**
+         * 上传附件
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async uploadFile(ctx) {
+            const responseData = {
+                err: 0,
+                msg: '',
+                data: [],
+            };
+            let stream;
+            try {
+                const parts = ctx.multipart({ autoFields: true });
+                const files = [];
+                let index = 0;
+                while ((stream = await parts()) !== undefined) {
+                    // 判断用户是否选择上传文件
+                    if (!stream.filename) {
+                        throw '请选择上传的文件!';
+                    }
+                    const dirName = 'app/public/upload/stage/' + moment().format('YYYYMMDD');
+                    // 判断文件夹是否存在,不存在则直接创建文件夹
+                    if (!fs.existsSync(path.join(this.app.baseDir, dirName))) {
+                        await fs.mkdirSync(path.join(this.app.baseDir, dirName));
+                    }
+                    const create_time = Date.parse(new Date()) / 1000;
+                    const fileInfo = path.parse(stream.filename);
+                    const fileName = 'stage' + create_time + '_' + index + fileInfo.ext;
+
+                    // 保存文件
+                    await ctx.helper.saveStreamFile(stream, path.join(this.app.baseDir, dirName, fileName));
+
+                    if (stream) {
+                        await sendToWormhole(stream);
+                    }
+
+                    // 保存数据到att表
+                    const fileData = {
+                        tid: ctx.params.id,
+                        sid: ctx.params.order,
+                        in_time: create_time,
+                        filename: fileInfo.name,
+                        fileext: fileInfo.ext,
+                        filesize: Array.isArray(parts.field.size) ? parts.field.size[index] : parts.field.size,
+                        filepath: path.join(dirName, fileName),
+                    };
+                    const result = await ctx.service.stageAtt.save(parts.field, fileData, ctx.session.sessionUser.accountId);
+                    if (!result) {
+                        throw '导入数据库保存失败';
+                    }
+                    const attData = await ctx.service.stageAtt.getDataByFid(result.insertId);
+                    attData.in_time = moment(create_time * 1000).format('YYYY-MM-DD');
+                    files.length !== 0 ? files.unshift(attData) : files.push(attData);
+                    ++index;
+                }
+                responseData.data = files;
+            } catch (err) {
+                this.log(err);
+                // 失败需要消耗掉stream 以防卡死
+                if (stream) {
+                    await sendToWormhole(stream);
+                }
+                this.setMessage(err.toString(), this.messageType.ERROR);
+                responseData.err = 1;
+                responseData.msg = err.toString();
+            }
+            ctx.body = responseData;
+        }
+
+        /**
+         * 下载附件
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async downloadFile(ctx) {
+            const id = ctx.params.fid;
+            if (id) {
+                try {
+                    const fileInfo = await ctx.service.stageAtt.getDataById(id);
+                    if (fileInfo !== undefined && fileInfo !== '') {
+                        const fileName = path.join(this.app.baseDir, fileInfo.filepath);
+                        // 解决中文无法下载问题
+                        const userAgent = (ctx.request.header['user-agent'] || '').toLowerCase();
+                        let disposition = '';
+                        if (userAgent.indexOf('msie') >= 0 || userAgent.indexOf('chrome') >= 0) {
+                            disposition = 'attachment; filename=' + encodeURIComponent(fileInfo.filename + fileInfo.fileext);
+                        } else if (userAgent.indexOf('firefox') >= 0) {
+                            disposition = 'attachment; filename*="utf8\'\'' + encodeURIComponent(fileInfo.filename + fileInfo.fileext) + '"';
+                        } else {
+                            /* safari等其他非主流浏览器只能自求多福了 */
+                            disposition = 'attachment; filename=' + new Buffer(fileInfo.filename + fileInfo.fileext).toString('binary');
+                        }
+                        ctx.response.set({
+                            'Content-Type': 'application/octet-stream',
+                            'Content-Disposition': disposition,
+                            'Content-Length': fileInfo.filesize,
+                        });
+                        ctx.body = await fs.createReadStream(fileName);
+                    } else {
+                        throw '不存在该文件';
+                    }
+                } catch (err) {
+                    this.log(err);
+                    this.setMessage(err.toString(), this.messageType.ERROR);
+                }
+            }
+        }
+
+        /**
+         * 删除附件
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async deleteFile(ctx) {
+            const responseData = {
+                err: 0,
+                msg: '',
+                data: '',
+            };
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const fileInfo = await ctx.service.stageAtt.getDataById(data.id);
+                if (fileInfo !== undefined && fileInfo !== '') {
+                    // 先删除文件
+                    await fs.unlinkSync(path.join(this.app.baseDir, fileInfo.filepath));
+                    // 再删除数据库
+                    await ctx.service.stageAtt.deleteById(data.id);
+                    responseData.data = '';
+                } else {
+                    throw '不存在该文件';
+                }
+
+                // if (data.tid === undefined || data.uci === undefined || data.uc === undefined || data.ac === undefined) {
+                //     throw '参数有误';
+                // }
+                // const [addCompany, selectCompany] = await ctx.service.changeCompany.setCompanyList(data);
+                // responseData.data = { add: addCompany, select: selectCompany };
+            } catch (err) {
+                responseData.err = 1;
+                responseData.msg = err;
+            }
+
+            ctx.body = responseData;
+        }
+
+        /**
+         * 保存附件(或替换)
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async saveFile(ctx) {
+            const responseData = {
+                err: 0,
+                msg: '',
+                data: [],
+            };
+            let stream;
+            try {
+                stream = await ctx.getFileStream({ requireFile: false });
+                let fileData = {};
+                if (stream.filename !== undefined) {
+                    const create_time = Date.parse(new Date()) / 1000;
+                    const fileInfo = path.parse(stream.filename);
+                    const dirName = 'app/public/upload/stage/' + moment().format('YYYYMMDD');
+                    const fileName = 'stage' + create_time + fileInfo.ext;
+
+                    // 判断文件夹是否存在,不存在则直接创建文件夹
+                    if (!fs.existsSync(path.join(this.app.baseDir, dirName))) {
+                        await fs.mkdirSync(path.join(this.app.baseDir, dirName));
+                    }
+                    // 保存文件
+                    await ctx.helper.saveStreamFile(stream, path.join(this.app.baseDir, dirName, fileName));
+                    // 保存数据到att表
+                    fileData = {
+                        filesize: stream.fields.size,
+                        filepath: path.join(dirName, fileName),
+                    };
+                }
+                const result = await ctx.service.stageAtt.updateByID(stream.fields, fileData);
+                if (!result) {
+                    throw '导入数据库保存失败';
+                }
+                const attData = await ctx.service.stageAtt.getDataByFid(stream.fields.id);
+                attData.in_time = moment(attData.in_time * 1000).format('YYYY-MM-DD');
+                responseData.data = attData;
+            } catch (err) {
+                this.log(err);
+                // 失败需要消耗掉stream 以防卡死
+                if (stream) {
+                    await sendToWormhole(stream);
+                }
+                this.setMessage(err.toString(), this.messageType.ERROR);
+                responseData.err = 1;
+                responseData.msg = err.toString();
+            }
+            ctx.body = responseData;
+        }
+
+        /**
+         * 合同支付上传附件
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async payUploadFile(ctx) {
+            const responseData = {
+                err: 0,
+                msg: '',
+                data: [],
+            };
+            let stream;
+            try {
+                const parts = ctx.multipart({ autoFields: true });
+                const files = [];
+                let index = 0;
+                const create_time = Date.parse(new Date()) / 1000;
+                while ((stream = await parts()) !== undefined) {
+                    // 判断用户是否选择上传文件
+                    if (!stream.filename) {
+                        throw '请选择上传的文件!';
+                    }
+                    const fileInfo = path.parse(stream.filename);
+                    const dirName = 'app/public/upload/pay/' + moment().format('YYYYMMDD');
+                    const fileName = 'pay' + create_time + '_' + index + fileInfo.ext;
+
+                    // 判断文件夹是否存在,不存在则直接创建文件夹
+                    if (!fs.existsSync(path.join(this.app.baseDir, dirName))) {
+                        await fs.mkdirSync(path.join(this.app.baseDir, dirName));
+                    }
+                    // 保存文件
+                    await ctx.helper.saveStreamFile(stream, path.join(this.app.baseDir, dirName, fileName));
+                    await sendToWormhole(stream);
+                    // 插入到stage_pay对应的附件列表中
+                    const attData = {
+                        filename: fileInfo.name,
+                        fileext: fileInfo.ext,
+                        filesize: Array.isArray(parts.field.size) ? parts.field.size[index] : parts.field.size,
+                        filepath: path.join(dirName, fileName),
+                        uid: ctx.session.sessionUser.accountId,
+                        in_time: moment(create_time * 1000).format('YYYY-MM-DD'),
+                    };
+                    const result = await ctx.service.stagePay.saveAtt(parts.field.pay_id, attData);
+                    if (!result) {
+                        throw '导入数据库保存失败';
+                    }
+                    delete attData.filepath;
+                    attData.username = ctx.session.sessionUser.name;
+                    files.length !== 0 ? files.unshift(attData) : files.push(attData);
+                    ++index;
+                }
+                responseData.data = files;
+            } catch (err) {
+                this.log(err);
+                // 失败需要消耗掉stream 以防卡死
+                if (stream) {
+                    await sendToWormhole(stream);
+                }
+                this.setMessage(err.toString(), this.messageType.ERROR);
+                responseData.err = 1;
+                responseData.msg = err.toString();
+            }
+            ctx.body = responseData;
+        }
+
+        /**
+         * 合同支付下载附件
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async payDownloadFile(ctx) {
+            const id = ctx.params.pid;
+            const index = ctx.params.index;
+            if (id && index) {
+                try {
+                    const payInfo = await ctx.service.stagePay.getDataById(id);
+                    if (payInfo !== undefined && payInfo.attachment !== null) {
+                        const fileInfo = JSON.parse(payInfo.attachment)[index];
+                        const fileName = path.join(this.app.baseDir, fileInfo.filepath);
+                        // 解决中文无法下载问题
+                        const userAgent = (ctx.request.header['user-agent'] || '').toLowerCase();
+                        let disposition = '';
+                        if (userAgent.indexOf('msie') >= 0 || userAgent.indexOf('chrome') >= 0) {
+                            disposition = 'attachment; filename=' + encodeURIComponent(fileInfo.filename + fileInfo.fileext);
+                        } else if (userAgent.indexOf('firefox') >= 0) {
+                            disposition = 'attachment; filename*="utf8\'\'' + encodeURIComponent(fileInfo.filename + fileInfo.fileext) + '"';
+                        } else {
+                            /* safari等其他非主流浏览器只能自求多福了 */
+                            disposition = 'attachment; filename=' + new Buffer(fileInfo.filename + fileInfo.fileext).toString('binary');
+                        }
+                        ctx.response.set({
+                            'Content-Type': 'application/octet-stream',
+                            'Content-Disposition': disposition,
+                            'Content-Length': fileInfo.filesize,
+                        });
+                        ctx.body = await fs.createReadStream(fileName);
+                    } else {
+                        throw '不存在该文件';
+                    }
+                } catch (err) {
+                    this.log(err);
+                    this.setMessage(err.toString(), this.messageType.ERROR);
+                }
+            }
+        }
+
+        /**
+         * 合同支付删除附件
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async payDeleteFile(ctx) {
+            const responseData = {
+                err: 0,
+                msg: '',
+                data: '',
+            };
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const payInfo = await ctx.service.stagePay.getDataById(data.id);
+                if (payInfo !== undefined) {
+                    const fileInfo = JSON.parse(payInfo.attachment)[data.index];
+                    // 先删除文件
+                    await fs.unlinkSync(path.join(this.app.baseDir, fileInfo.filepath));
+                    // 再删除数据库
+                    const attachment = JSON.parse(payInfo.attachment);
+                    attachment.splice(data.index, 1);
+                    const result = await ctx.service.stagePay.deleteAtt(data.id, attachment);
+                    responseData.data = '';
+                } else {
+                    throw '不存在该文件';
+                }
+
+                // if (data.tid === undefined || data.uci === undefined || data.uc === undefined || data.ac === undefined) {
+                //     throw '参数有误';
+                // }
+                // const [addCompany, selectCompany] = await ctx.service.changeCompany.setCompanyList(data);
+                // responseData.data = { add: addCompany, select: selectCompany };
+            } catch (err) {
+                responseData.err = 1;
+                responseData.msg = err;
+            }
+
+            ctx.body = responseData;
+        }
     }
 
     return StageController;

+ 1 - 0
app/controller/tender_controller.js

@@ -119,6 +119,7 @@ module.exports = app => {
                 if (userPermission === null || userPermission.tender === undefined || userPermission.tender.indexOf('1') === -1) {
                     throw '当前用户没有创建标段的权限';
                 }
+                console.log(ctx.request.body.data);
                 const data = JSON.parse(ctx.request.body.data);
                 if (!data.name || data.name === '') {
                     throw '标段信息不完整';

+ 134 - 24
app/extend/helper.js

@@ -12,8 +12,9 @@ const fs = require('fs');
 const path = require('path');
 const streamToArray = require('stream-to-array');
 const _ = require('lodash');
-const np = require('number-precision');
-const math = require('mathjs');
+const bc = require('../lib/base_calc.js');
+const Decimal = require('decimal.js');
+Decimal.set({precision: 50, defaults: true});
 
 module.exports = {
     _: _,
@@ -230,16 +231,19 @@ module.exports = {
      */
     async sendRequest(url, data, type = 'POST', dataType = 'json') {
         // 发起请求
-        const response = await this.ctx.curl(url, {
-            method: type,
-            data,
-            dataType,
-        });
-        if (response.status !== 200) {
+        try {
+            const response = await this.ctx.curl(url, {
+                method: type,
+                data,
+                dataType,
+            });
+            if (response.status !== 200) {
+                throw '请求失败';
+            }
+            return response.data;
+        } catch(err) {
             throw '请求失败';
         }
-
-        return response.data;
     },
 
     /**
@@ -347,7 +351,7 @@ module.exports = {
      * @return {boolean}
      */
     checkZero(value) {
-        return value && Math.abs(value) > zeroRange;
+        return !(value && Math.abs(value) > zeroRange);
     },
     /**
      * 检查数字是否相等
@@ -379,10 +383,17 @@ module.exports = {
 
         const path1 = str1.split(symbol);
         const path2 = str2.split(symbol);
+        const reg = /^[0-9]*$/;
         for (let i = 0, iLen = Math.min(path1.length, path2.length); i < iLen; i++) {
-            if (path1 < path2) {
+            if (reg.test(path1[i]) && reg.test(path2[i])) {
+                const num1 = parseInt(path1[i]);
+                const num2 = parseInt(path2[i]);
+                if (num1 !== num2)  {
+                    return num1 - num2;
+                }
+            } else if (path1[i] < path2[i]) {
                 return -1;
-            } else if (path1 > path2) {
+            } else if (path1[i] > path2[i]) {
                 return 1;
             }
         }
@@ -412,7 +423,7 @@ module.exports = {
             });
             children.sort(function (a, b) {
                 return a.order - b.order;
-            })
+            });
             return children;
         };
         const getChildren = function (nodes, node) {
@@ -592,16 +603,16 @@ module.exports = {
         }
     },
 
-    // 以下方法均调用number-precision处理
     // 加减乘除方法,为方便调用,兼容num为空的情况
+    // 加减法使用base_calc,乘除法使用Decimal(原因详见demo/calc_test)
     /**
      * 加法 num1 + num2
      * @param num1
      * @param num2
      * @returns {number}
      */
-    plus(num1, num2) {
-        return np.plus(num1 ? num1 : 0, num2 ? num2: 0);
+    add(num1, num2) {
+        return bc.add(num1 ? num1 : 0, num2 ? num2: 0);
     },
     /**
      * 减法 num1 - num2
@@ -609,8 +620,8 @@ module.exports = {
      * @param num2
      * @returns {number}
      */
-    minus(num1, num2) {
-        return np.minus(num1 ? num1 : 0, num2 ? num2 : 0);
+    sub(num1, num2) {
+        return bc.sub(num1 ? num1 : 0, num2 ? num2 : 0);
     },
     /**
      * 乘法 num1 * num2
@@ -618,8 +629,8 @@ module.exports = {
      * @param num2
      * @returns {*}
      */
-    times(num1, num2) {
-        return np.times(num1 ? num1 : 0, num2 ? num2 : 0);
+    mul(num1, num2, digit = 6) {
+        return Decimal.mul(num1 ? num1 : 0, num2 ? num2 : 0).toDecimalPlaces(digit).toNumber();
     },
     /**
      * 除法 num1 / num2
@@ -627,9 +638,9 @@ module.exports = {
      * @param num2 - 除数
      * @returns {*}
      */
-    divide(num1, num2) {
+    div(num1, num2, digit = 6) {
         if (num2 && !this.checkZero(num2)) {
-            return np.divide(num1 ? num1: 0, num2);
+            return Decimal.div(num1 ? num1: 0, num2).toDecimalPlaces(digit).toNumber();
         } else {
             return null;
         }
@@ -641,6 +652,105 @@ module.exports = {
      * @returns {*}
      */
     round(value, decimal) {
-        return value ? np.round(value, decimal) : null;
+        //return value ? bc.round(value, decimal) : null;
+        return value ? new Decimal(value).toDecimalPlaces(decimal).toNumber() : null;
+    },
+    /**
+     * 汇总
+     * @param array
+     * @returns {number}
+     */
+    sum(array) {
+        let result = 0;
+        for (const a of array) {
+            result = this.add(result, a);
+        }
+        return result;
+    },
+
+    /**
+     * 使用正则替换字符
+     * @param str
+     * @param reg
+     * @param subStr
+     * @returns {*}
+     */
+    replaceStr(str, reg, subStr) {
+        return str ? str.replace(reg, subStr) : str;
+    },
+    /**
+     * 替换字符串中的 换行符回车符
+     * @param str
+     * @returns {*}
+     */
+    replaceReturn(str) {
+        return (str && typeof str === 'string') ? str.replace(/[\r\n]/g, '') : str;
+    },
+
+    /**
+     * 获取 字符串 数组的 mysql 筛选条件
+     *
+     * @param arr
+     * @returns {*}
+     */
+    getInArrStrSqlFilter(arr) {
+        let result = '';
+        for (const a of arr) {
+            if (result !== '') {
+                result = result + ','
+            }
+            result = result + this.ctx.app.mysql.escape(a);
+        }
+        return result;
+    },
+
+    /**
+     * 合并相关数据
+     * @param {Array} main - 主数据
+     * @param {Array[]}rela - 相关数据 {data, fields, prefix, relaId}
+     */
+    assignRelaData(main, rela) {
+        const index = {}, indexPre = 'id_';
+        const loadFields = function (datas, fields, prefix, relaId) {
+            for (const d of datas) {
+                const key = indexPre + d[relaId];
+                const m = index[key];
+                if (m) {
+                    for (const f of fields) {
+                        if (d[f] !== undefined) {
+                            m[prefix + f] = d[f];
+                        }
+                    }
+                }
+            }
+        };
+        for (const m of main) {
+            index[indexPre + m.id] = m;
+        }
+        for (const r of rela) {
+            loadFields(r.data, r.fields, r.prefix, r.relaId);
+        }
+    },
+    whereSql (where, as) {
+        if (!where) {
+            return '';
+        }
+
+        const wheres = [];
+        const values = [];
+        for (const key in where) {
+            const value = where[key];
+            if (Array.isArray(value)) {
+                wheres.push('?? IN (?)');
+            } else {
+                wheres.push('?? = ?');
+            }
+            values.push((as && as !== '') ? as + '.' + key : key);
+            values.push(value);
+        }
+        if (wheres.length > 0) {
+            return this.ctx.app.mysql.format(' WHERE ' + wheres.join(' AND '), values);
+        }
+        return '';
     },
 };

+ 91 - 29
app/lib/analysis_excel.js

@@ -14,7 +14,8 @@ class ImportBaseTree {
      * 构造函数
      * @param {Array} tempData - 清单模板数据
      */
-    constructor (tempData) {
+    constructor (tempData, ctx) {
+        this.ctx = ctx;
         // 常量
         this.splitChar = '-';
         // 索引
@@ -27,6 +28,7 @@ class ImportBaseTree {
 
         // 缓存
         this.finalNode = null;
+        this.finalPrecision = null;
         this.finalXmjNode = null;
         this.keyNodeId = 1;
 
@@ -57,6 +59,7 @@ class ImportBaseTree {
             this.tempData.push(node);
         }
         for (const node of this.items) {
+            node.tender_id = this.ctx.tender.id;
             node.children = this.items.filter(function (i) {
                 return i.ledger_pid === node.ledger_id;
             });
@@ -117,6 +120,7 @@ class ImportBaseTree {
      */
     defineCacheData(node) {
         this.finalNode = node;
+        this.finalPrecision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, node.unit);
         if (node.code) {
             this.finalXmjNode = node;
         }
@@ -128,6 +132,8 @@ class ImportBaseTree {
      * @returns {*}
      */
     addXmjNode(node) {
+        node.id = this.ctx.app.uuid.v4();
+        node.tender_id = this.ctx.tender.id;
         node.children = [];
         if (node.code.split(this.splitChar).length > 1) {
             const temp = this.findTempData(node);
@@ -154,6 +160,8 @@ class ImportBaseTree {
      * @param {Object} node - 工程量清单
      */
     addGclNode(node) {
+        node.id = this.ctx.app.uuid.v4();
+        node.tender_id = this.ctx.tender.id;
         node.pos = [];
         if (this.finalXmjNode) {
             return this.addNodeWithParent(node, this.finalXmjNode);
@@ -167,8 +175,17 @@ class ImportBaseTree {
      */
     addPos (pos){
         if (this.finalNode && this.finalNode.pos) {
+            pos.id = this.ctx.app.uuid.v4();
+            pos.tid = this.ctx.tender.id;
+            pos.add_stage = 0;
+            pos.add_times = 0;
+            pos.in_time = new Date();
+            pos.porder = this.finalNode.pos.length + 1;
+            pos.add_user = this.ctx.session.sessionUser.accountId;
             this.finalNode.pos.push(pos);
             this.pos.push(pos);
+            pos.sgfh_qty = this.ctx.helper.round(pos.sgfh_qty, this.finalPrecision.value);
+            pos.quantity = pos.sgfh_qty;
             return pos;
         }
     }
@@ -197,12 +214,28 @@ class ImportBaseTree {
         for (const node of this.items) {
             if (node.children && node.children.length > 0) { continue; }
             if (!node.pos || node.pos.length === 0) { continue; }
-            node.quantity = _.round(_.sum(_.map(node.pos, 'quantity')), 6);
-            if (node.quantity && node.unit_price) {
-                node.total_price = _.round(node.quantity * node.unit_price, 6);
+            // node.sgfh_qty = this.ctx.helper.sum(_.map(node.pos, 'sgfh_qty'));
+            // if (node.sgfh_qty && node.unit_price) {
+            //     node.sgfh_tp = this.ctx.helper.round(this.ctx.helper.mul(node.sgfh_qty, node.unit_price),
+            //         this.ctx.tender.info.decimal.tp);
+            // } else {
+            //     node.sgfh_tp = null;
+            // }
+            // node.quantity = this.ctx.helper.sum(_.map(node.pos, 'quantity'));
+            // if (node.quantity && node.unit_price) {
+            //     node.total_price = this.ctx.helper.round(this.ctx.helper.mul(node.quantity, node.unit_price),
+            //         this.ctx.tender.info.decimal.tp);
+            // } else {
+            //     node.total_price = null;
+            // }
+            node.sgfh_qty = this.ctx.helper.sum(_.map(node.pos, 'sgfh_qty'));
+            if (node.sgfh_qty && node.unit_price) {
+                node.sgfh_tp = this.ctx.helper.mul(node.sgfh_qty, node.unit_price, this.ctx.tender.info.decimal.tp);
             } else {
-                node.total_price = null;
+                node.sgfh_tp = null;
             }
+            node.quantity = node.sgfh_qty;
+            node.total_price = node.sgfh_tp;
         }
     }
 }
@@ -211,7 +244,8 @@ class AnalysisExcelTree {
     /**
      * 构造函数
      */
-    constructor() {
+    constructor(ctx) {
+        this.ctx = ctx;
         this.colsDef = null;
         this.colHeaderMatch = {
             code: ['项目节编号', '预算项目节'],
@@ -219,7 +253,7 @@ class AnalysisExcelTree {
             pos: ['部位明细'],
             name: ['名称'],
             unit: ['单位'],
-            quantity: ['清单数量'], // 施工图复核数量
+            sgfh_qty: ['清单数量'], // 施工图复核数量
             dgn_qty1: ['设计数量1'],
             dgn_qty2: ['设计数量2'],
             unit_price: ['单价'],
@@ -241,20 +275,34 @@ class AnalysisExcelTree {
      */
     _loadXmjNode(row) {
         const node = {};
-        node.code = row[this.colsDef.code];
-        node.name = row[this.colsDef.name];
-        node.unit = row[this.colsDef.unit];
-        node.quantity = this.toNumber(row[this.colsDef.quantity]);
+        node.code = this.ctx.helper.replaceReturn(row[this.colsDef.code]);
+        node.name = this.ctx.helper.replaceReturn(row[this.colsDef.name]);
+        node.unit = this.ctx.helper.replaceReturn(row[this.colsDef.unit]);
+        const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, node.unit);
+        node.sgfh_qty = this.ctx.helper.round(this.toNumber(row[this.colsDef.sgfh_qty]), precision.value);
         node.dgn_qty1 = this.toNumber(row[this.colsDef.dgn_qty1]);
         node.dgn_qty2 = this.toNumber(row[this.colsDef.dgn_qty2]);
         node.unit_price = this.toNumber(row[this.colsDef.unit_price]);
-        node.drawing_code = row[this.colsDef.drawing_code];
-        node.memo = row[this.colsDef.memo];
-        if (node.quantity && node.unit_price) {
-            node.total_price = _.round(node.quantity * node.unit_price, 6);
+        node.drawing_code = this.ctx.helper.replaceReturn(row[this.colsDef.drawing_code]);
+        node.memo = this.ctx.helper.replaceReturn(row[this.colsDef.memo]);
+        // if (node.sgfh_qty && node.unit_price) {
+        //     node.sgfh_tp = this.ctx.helper.round(this.ctx.helper.mul(node.sgfh_qty, node.unit_price), this.ctx.tender.info.decimal.tp);
+        // } else {
+        //     node.sgfh_tp = null;
+        // }
+        // node.quantity = node.sgfh_qty;
+        // if (node.quantity && node.unit_price) {
+        //     node.total_price = this.ctx.helper.round(this.ctx.helper.mul(node.quantity, node.unit_price), this.ctx.tender.info.decimal.tp);
+        // } else {
+        //     node.total_price = null;
+        // }
+        if (node.sgfh_qty && node.unit_price) {
+            node.sgfh_tp = this.ctx.helper.mul(node.sgfh_qty, node.unit_price, this.ctx.tender.info.decimal.tp);
         } else {
-            node.total_price = null;
+            node.sgfh_tp = null;
         }
+        node.quantity = node.sgfh_qty;
+        node.total_price = node.sgfh_tp;
         return this.cacheTree.addXmjNode(node);
     }
     /**
@@ -265,18 +313,32 @@ class AnalysisExcelTree {
      */
     _loadGclNode(row) {
         const node = {};
-        node.b_code = row[this.colsDef.b_code];
-        node.name = row[this.colsDef.name];
-        node.unit = row[this.colsDef.unit];
-        node.quantity = this.toNumber(row[this.colsDef.quantity]);
+        node.b_code = this.ctx.helper.replaceReturn(row[this.colsDef.b_code]);
+        node.name = this.ctx.helper.replaceReturn(row[this.colsDef.name]);
+        node.unit = this.ctx.helper.replaceReturn(row[this.colsDef.unit]);
+        const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, node.unit);
+        node.sgfh_qty = this.ctx.helper.round(this.toNumber(row[this.colsDef.sgfh_qty]), precision.value);
         node.unit_price = this.toNumber(row[this.colsDef.unit_price]);
-        node.drawing_code = row[this.colsDef.drawing_code];
-        node.memo = row[this.colsDef.memo];
-        if (node.quantity && node.unit_price) {
-            node.total_price = _.round(node.quantity * node.unit_price, 6);
+        node.drawing_code = this.ctx.helper.replaceReturn(row[this.colsDef.drawing_code]);
+        node.memo = this.ctx.helper.replaceReturn(row[this.colsDef.memo]);
+        // if (node.sgfh_qty && node.unit_price) {
+        //     node.sgfh_tp = this.ctx.helper.round(this.ctx.helper.mul(node.sgfh_qty, node.unit_price), this.ctx.tender.info.decimal.tp);
+        // } else {
+        //     node.sgfh_tp = null;
+        // }
+        // node.quantity = node.sgfh_qty;
+        // if (node.quantity && node.unit_price) {
+        //     node.total_price = this.ctx.helper.round(this.ctx.helper.mul(node.quantity, node.unit_price), this.ctx.tender.info.decimal.tp);
+        // } else {
+        //     node.total_price = null;
+        // }
+        if (node.sgfh_qty && node.unit_price) {
+            node.sgfh_tp = this.ctx.helper.mul(node.sgfh_qty, node.unit_price, this.ctx.tender.info.decimal.tp);
         } else {
-            node.total_price = null;
+            node.sgfh_tp = null;
         }
+        node.quantity = node.sgfh_qty;
+        node.total_price = node.sgfh_tp;
         return this.cacheTree.addGclNode(node);
     }
     /**
@@ -287,9 +349,9 @@ class AnalysisExcelTree {
      */
     _loadPos(row) {
         const pos = {};
-        pos.name = row[this.colsDef.name];
-        pos.quantity = this.toNumber(row[this.colsDef.quantity]);
-        pos.drawing_code = row[this.colsDef.drawing_code];
+        pos.name = this.ctx.helper.replaceReturn(row[this.colsDef.name]);
+        pos.sgfh_qty = this.toNumber(row[this.colsDef.sgfh_qty]);
+        pos.drawing_code = this.ctx.helper.replaceReturn(row[this.colsDef.drawing_code]);
         return this.cacheTree.addPos(pos);
     }
 
@@ -350,7 +412,7 @@ class AnalysisExcelTree {
      */
     analysisData(sheet, tempData) {
         this.colsDef = null;
-        this.cacheTree = new ImportBaseTree(tempData);
+        this.cacheTree = new ImportBaseTree(tempData, this.ctx);
         this.errorData = [];
         this.loadEnd = false;
 

+ 60 - 0
app/lib/base_calc.js

@@ -0,0 +1,60 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+const mulPrecision = 12, divPrecision = 12;
+
+function digitLength (num) {
+    // 兼容科学计数
+    var eSplit = num.toString().split(/[eE]/);
+    var len = (eSplit[0].split('.')[1] || '').length - (+(eSplit[1] || 0));
+    return len > 0 ? len : 0;
+}
+
+function powLength (num) {
+    var rs = num.toString();
+    if (rs.indexOf('+') > 0) {
+        return rs.match(/0*$/g).length();
+    } else {
+        const eSplit = rs.split(/[eE]/);
+        const len = Number(eSplit[1]) - this.digitLength(eSplit[0]);
+        return len > 0 ? len : 0;
+    }
+}
+
+function round (num, digit) {
+    return Math.round(num * Math.pow(10, digit)) / Math.pow(10, digit);
+}
+
+function add(num1, num2) {
+    var d1 = this.digitLength(num1), d2 = this.digitLength(num2);
+    return this.round(num1 + num2, Math.max(d1, d2));
+}
+
+function sub(num1, num2) {
+    var d1 = this.digitLength(num1), d2 = this.digitLength(num2);
+    return this.round(num1 - num2, Math.max(d1, d2));
+}
+
+function mul(num1, num2) {
+    return this.round(num1 * num2, mulPrecision);
+}
+
+function div(num1, num2) {
+    return this.round(num1 / num2, divPrecision);
+}
+
+module.exports = {
+    digitLength: digitLength,
+    powLength: powLength,
+    round: round,
+    add: add,
+    sub: sub,
+    mul: mul,
+    div: div,
+};

+ 54 - 21
app/lib/pay_calc.js

@@ -28,17 +28,42 @@ class PayCalculate {
         if (this.bases) { return; }
         const bases = await this.ctx.service.stage.getStagePayCalcBase();
         this.bases = bases.sort(function (a, b) {
-            if (a && b) {
-                return b.code.indexOf(a.code) === -1 ? 1 : -1;
-            } else {
-                return 0;
-            }
+            return a.sort - b.sort;
+            // if (a && b) {
+            //     return b.code.indexOf(a.code) >= 0 ? 1 : 0;
+            // } else {
+            //     return 0;
+            // }
         });
         for (const b of this.bases) {
             b.reg = new RegExp(b.code, 'igm');
         }
     }
 
+    calculateTpExpr(pay, first, addRela) {
+        let formula = pay.expr;
+        for (const b of this.bases) {
+            if ((b.code === 'bqwc') && (first && pay.sprice)) {
+                formula = formula.replace(b.reg, this.ctx.helper.sub(addRela.gather_tp, pay.sprice));
+            } else {
+                formula = formula.replace(b.reg, b.value);
+            }
+        }
+        const percent = formula.match(this.percentReg);
+        if (percent) {
+            for (const p of percent) {
+                const v = math.eval(p.replace('%', '/100'));
+                formula = formula.replace(p, v);
+            }
+        }
+        try {
+            const value = math.eval(formula);
+            return value;
+        } catch(err) {
+            return 0;
+        }
+    }
+
     calculateExpr(expr) {
         let formula = expr;
         for (const b of this.bases) {
@@ -51,7 +76,12 @@ class PayCalculate {
                 formula = formula.replace(p, v);
             }
         }
-        return math.eval(formula);
+        try {
+            const value = math.eval(formula);
+            return value;
+        } catch(err) {
+            return 0;
+        }
     }
 
     /**
@@ -86,13 +116,13 @@ class PayCalculate {
         const cur = await this.ctx.service.stageBills.getSumTotalPrice(this.ctx.stage);
         const add = {};
         if (pre) {
-            add.contract_tp = this.ctx.helper.plus(pre.contract_tp, cur.contract_tp);
-            add.qc_tp = this.ctx.helper.plus(pre.qc_tp, cur.qc_tp);
+            add.contract_tp = this.ctx.helper.add(pre.contract_tp, cur.contract_tp);
+            add.qc_tp = this.ctx.helper.add(pre.qc_tp, cur.qc_tp);
         } else {
             add.contract_tp = cur.contract_tp;
             add.qc_tp = cur.qc_tp;
         }
-        add.gather_tp = this.ctx.helper.plus(add.contract_tp, add.qc_tp);
+        add.gather_tp = this.ctx.helper.add(add.contract_tp, add.qc_tp);
         return add;
     }
 
@@ -129,34 +159,37 @@ class PayCalculate {
         yfPay.tp = 0;
         for (const p of pays) {
             if (p.ptype === payType.normal || p.ptype === payType.wc) {
-                if (p.expr && p.expr !== '') {
-                    const value = this.ctx.helper.round(this.calculateExpr(p.expr), this.decimal);
-                    if (!p.pause && (!p.sprice || addRela.gather_tp > p.sprice)) {
+                if (!p.pause && (!p.sprice || addRela.gather_tp > p.sprice)) {
+                    if (p.expr && p.expr !== '') {
+                        const first = !(p.pre_tp && !this.ctx.helper.checkZero(p.pre_tp));
+                        const value = this.ctx.helper.round(this.calculateTpExpr(p, first, addRela), this.decimal);
                         if (p.rprice) {
-                            if (this.checkDeadline(p)) {
-                                p.tp = this.ctx.helper.minus(p.rprice, p.pre_total_price);
+                            if (this.checkDeadline(p, addRela)) {
+                                p.tp = this.ctx.helper.sub(p.rprice, p.pre_total_price);
                             } else {
-                                p.tp = Math.min(this.ctx.helper.minus(p.rprice, p.pre_total_price), value);
+                                p.tp = Math.min(this.ctx.helper.sub(p.rprice, p.pre_total_price), value);
                             }
                         } else {
                             p.tp = value;
                         }
-                    } else {
-                        p.tp = null;
+                    } else if (p.tp && !this.ctx.helper.checkZero(p.tp)) {
+                        p.tp = this.ctx.helper.round(p.tp, this.decimal);
                     }
+                } else {
+                    p.tp = 0;
                 }
                 // 累加 至 应付
                 if (p.is_yf) {
                     if (p.minus) {
-                        yfPay.tp = this.ctx.helper.minus(yfPay.tp, p.tp);
+                        yfPay.tp = this.ctx.helper.sub(yfPay.tp, p.tp);
                     } else {
-                        yfPay.tp = this.ctx.helper.plus(yfPay.tp, p.tp);
+                        yfPay.tp = this.ctx.helper.add(yfPay.tp, p.tp);
                     }
                 }
             }
-            p.end_tp = this.ctx.helper.round(this.ctx.helper.plus(p.tp, p.pre_tp), this.decimal);
+            p.end_tp = this.ctx.helper.round(this.ctx.helper.add(p.tp, p.pre_tp), this.decimal);
         }
-        yfPay.end_tp = this.ctx.helper.plus(yfPay.tp, yfPay.pre_tp);
+        yfPay.end_tp = this.ctx.helper.add(yfPay.tp, yfPay.pre_tp);
     }
 
     async calculateAll(pays) {

+ 2 - 1
app/middleware/gzip.js

@@ -18,11 +18,12 @@ module.exports = options => {
         // 后续中间件执行完成后将响应体转换成 gzip
         let body = ctx.body;
         if (!body) return;
+        if (isJSON(body)) return;
 
         // 支持 options.threshold
         if (options.threshold && ctx.length < options.threshold) return;
 
-        if (isJSON(body)) body = JSON.stringify(body);
+        //if (isJSON(body)) body = JSON.stringify(body);
 
         // 设置 gzip body,修正响应头
         const stream = zlib.createGzip();

+ 1 - 1
app/middleware/stage_check.js

@@ -65,7 +65,7 @@ module.exports = options => {
                 if (stage.status === status.uncheck) {
                     throw '您无权查看该数据';
                 }
-                stage.readOnly = (stage.status === status.checking || stage.status === status.checkNoPre) && accountId !== stage.curAuditor.aid;
+                stage.readOnly = (stage.status !== status.checking && stage.status !== status.checkNoPre) || accountId !== stage.curAuditor.aid;
                 stage.curTimes = stage.status === status.checkNo ? stage.times - 1 : stage.times;
                 if (stage.status === status.checked) {
                     stage.curOrder = _.max(_.map(stage.auditors, 'order'));

+ 7 - 2
app/middleware/tender_check.js

@@ -9,6 +9,7 @@
  */
 
 const auditConst = require('../const/audit').ledger;
+const messageType = require('../const/message_type');
 
 module.exports = options => {
     /**
@@ -45,7 +46,7 @@ module.exports = options => {
                 throw '您无权查看该项目';
             }
             tender.ledgerReadOnly = this.session.sessionUser.accountId !== tender.data.user_id ||
-                tender.ledger_status === auditConst.status.checking || tender.ledger_status === auditConst.status.checked;
+                tender.data.ledger_status === auditConst.status.checking || tender.data.ledger_status === auditConst.status.checked;
             this.tender = tender;
             yield next;
         } catch (err) {
@@ -53,7 +54,11 @@ module.exports = options => {
             if (err.stack) {
                 this.logger.error(err);
             } else {
-                this.setMessage(err, measureType.ERROR);
+                this.session.message = {
+                    type: messageType.ERROR,
+                    icon: 'exclamation-circle',
+                    message: err,
+                };
                 this.getLogger('fail').info(JSON.stringify({
                     error: err,
                     project: this.session.sessionProject,

BIN
app/public/css/jquery-ui/images/ui-icons_444444_256x240.png


BIN
app/public/css/jquery-ui/images/ui-icons_555555_256x240.png


BIN
app/public/css/jquery-ui/images/ui-icons_777620_256x240.png


BIN
app/public/css/jquery-ui/images/ui-icons_777777_256x240.png


BIN
app/public/css/jquery-ui/images/ui-icons_cc0000_256x240.png


BIN
app/public/css/jquery-ui/images/ui-icons_ffffff_256x240.png


Plik diff jest za duży
+ 7 - 0
app/public/css/jquery-ui/jquery-ui.min.css


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

@@ -35,6 +35,19 @@ font-size: .875rem;
   height: calc(1.5em + .5rem + 2px);
 font-size: .875rem;
 }
+.table th {
+  background: #e9ecef;
+  font-weight: normal;
+}
+.form-check,.form-check-label{
+  cursor: pointer;
+}
+.input-group-text .group-checkbox[type="checkbox"],.input-group-text .group-checkbox[type="radio"]{
+  margin-top: .3rem;
+}
+.custom-control {
+  min-height: 1.2rem
+}
 /*自定义css*/
 .in-1{padding-left:5px!important}
 .in-2{padding-left:21px!important}
@@ -46,29 +59,29 @@ font-size: .875rem;
 /* 滚动条 */
 ::-webkit-scrollbar-thumb:horizontal { /*水平滚动条的样式*/
 	width: 5px;
-	background-color: #ddd;
-	-webkit-border-radius: 6px;
+	background-color: #e9ecef;
+	-webkit-border-radius: 0;
 }
 ::-webkit-scrollbar-track-piece {
-	background-color: #fff; /*滚动条的背景颜色*/
+	background-color: #efefef; /*滚动条的背景颜色*/
 	-webkit-border-radius: 0; /*滚动条的圆角宽度*/
 }
 ::-webkit-scrollbar {
-	width: 10px; /*滚动条的宽度*/
-	height: 8px; /*滚动条的高度*/
+	width: 14px; /*滚动条的宽度*/
+	height: 14px; /*滚动条的高度*/
 }
 ::-webkit-scrollbar-thumb:vertical { /*垂直滚动条的样式*/
 	height: 50px;
-	background-color: #ddd;
-	-webkit-border-radius: 6px;
+	background-color: #e9ecef;
+	-webkit-border-radius: 0;
 	outline: 1px solid #fff;
 	outline-offset: -1px;
-	border: 1px solid #fff;
+	border: 1px solid #ced4da;
 }
 ::-webkit-scrollbar-thumb:hover { /*滚动条的hover样式*/
 	height: 50px;
-	background-color: #999;
-	-webkit-border-radius: 6px;
+	background-color: #ced4da;
+	-webkit-border-radius: 0;
 }
 .sjs-height-1,.sjs-height-2,.sjs-sh-1,.sjs-sh-2,.sjs-sh-3,.sjs-sh-4,.sjs-sh-5{
   overflow: hidden;
@@ -205,6 +218,7 @@ font-size: .875rem;
 .panel-content .content-wrap{
   margin:0 15px 15px;
   position: relative;
+  background-color: #d2d5da
 }
 .panel-sidebar+.panel-content{
   padding: 65px 0 0 200px;
@@ -261,6 +275,10 @@ font-size: .875rem;
 .panel-title>.title-main{
   padding-left: 15px
 }
+.panel-title .alert {
+  line-height: normal;
+  z-index: 999
+}
 .side-menu{
   position: fixed;
   right:15px;
@@ -268,7 +286,12 @@ font-size: .875rem;
 }
 .side-menu .nav-link{
   line-height: 16px;
-  font-size: 14px
+  font-size: 14px;
+  color:#666;
+}
+.side-menu .nav-link:hover{
+  background:#dcdee3;
+  color:#333;
 }
 .sub-content{
   margin:0;
@@ -285,7 +308,7 @@ font-size: .875rem;
   }
 }
 .modal-xl {
-  max-width: 1000px
+  max-width: 1200px
 }
 /*滚动*/
 .scrollbar-auto {
@@ -518,7 +541,7 @@ font-size: .875rem;
   padding:0 0 5px
 }
 .c-body{
-  padding:15px;
+  padding:6px 6px 4px 6px;
   background:#fff;
 }
 .right-nav{
@@ -637,4 +660,15 @@ font-size: .875rem;
 }
 .m-close-side:hover{
   background:#eae9e9;
+}
+.flowtoolsbar{
+  display:none;
+  position: absolute;
+  bottom: 0;
+  left:50%;
+  margin-left:-110px;
+  background-color: #33425b
+}
+.showtoolsbar:hover .flowtoolsbar{
+  display: block
 }

+ 18 - 18
app/public/css/spreadjs/sheets/gc.spread.sheets.excelsmartcost.css

@@ -57,7 +57,7 @@
 .gc-columnHeader-normal {
     color: #444444;
     background-image: none;
-    background-color: white;
+    background-color:#e9ecef;
     border-style:solid;
     border-left-color: #dee2e6 !important;
     border-right-color: #dee2e6 !important;
@@ -66,7 +66,7 @@
 .gc-columnHeader-hover {
     color: #444444;
     background-image: none;
-    background-color: #f5f5f5;
+    background-color: #dddfe1;
     border-style:solid;
     border-left-color: #efefef !important;
     border-right-color: #d5ded5 !important;
@@ -75,7 +75,7 @@
 .gc-columnHeader-selected {
     color: #73a2e3;
     background-image: none;
-    background-color: #f5f5f5;
+    background-color: #dddfe1;
     border-style:solid;
     border-left-color: #efefef !important;
     border-right-color: #d5ded5 !important;
@@ -84,7 +84,7 @@
 .gc-columnHeader-highlight {
     color: #73a2e3;
     background-image: none;
-    background-color: #f5f5f5;
+    background-color: #dddfe1;
     border-style:solid;
     border-left-color: #efefef !important;
     border-right-color: #d5ded5 !important;
@@ -92,7 +92,7 @@
 }
 .gc-rowHeader-normal {
     color: #444444;
-    background-color: white;
+    background-color: #e9ecef;
     background-image: none;
     border-style:solid;
     border-top-color: #efefef !important;
@@ -101,7 +101,7 @@
 }
 .gc-rowHeader-hover {
     color: #73a2e3;
-    background-color: #f5f5f5;
+    background-color: #dddfe1;
     background-image: none;
     border-style:solid;
     border-top-color: #efefef !important;
@@ -110,7 +110,7 @@
 }
 .gc-rowHeader-selected {
     color: #73a2e3;
-    background-color: #f5f5f5;
+    background-color: #dddfe1;
     background-image: none;
     border-style:solid;
     border-top-color: #dee2e6 !important;
@@ -119,7 +119,7 @@
 }
 .gc-rowHeader-highlight {
     color: #73a2e3;
-    background-color: #f5f5f5;
+    background-color: #dddfe1;
     background-image: none;
     border-style:solid;
     border-top-color: #efefef !important;
@@ -128,11 +128,11 @@
 }
 .gc-horizontal-scrollbar {
     background-color: #fff;
-    border-top-color: #efefef;
+    border-top-color: #fff
 }
 .gc-vertical-scrollbar {
     background-color: #fff;
-    border-left-color: #efefef;
+    border-left-color: #fff
 }
 .gc-footer-corner {
     background-color: #fff;
@@ -819,28 +819,28 @@ a.gc-filter-check:hover {
 
 .gc-scroll-bar {
     border-style:solid;
-    border-color:#bfbfbf;
-    background:  #bfbfbf;
+    border-color:#ced4da;
+    background:  #e9ecef;
     -moz-border-radius: 0px;
     -webkit-border-radius: 0px;
     border-radius: 0px;
 }
 .gc-scroll-arrow-hover {
     border-style:solid;
-    border-color:#a7a7a7;
-    background: #a7a7a7;
+    border-color:#ced4da;
+    background: #ced4da;
 }
 .gc-scrollbar-stateHover {
     border-style:solid;
-    border-color:#a7a7a7;
-    background: #a7a7a7;
+    border-color:#ced4da;
+    background: #ced4da;
 }
 
 .gc-scroll-arrow:active,
 .gc-scrollbar-stateActive {
     border-style:solid;
-    border-color:#a7a7a7;
-    background: #a7a7a7;
+    border-color:#ced4da;
+    background: #ced4da;
     -webkit-box-shadow: none;
           box-shadow: none;
 }

BIN
app/public/images/baobiao3.png


BIN
app/public/images/user-sign.PNG


+ 41 - 0
app/public/js/bootstrap/bootstrap-paginator.js

@@ -205,6 +205,14 @@
 
         },
 
+        showMore: function () {
+            var pages = this.getPages();
+
+            if (pages.more) {
+                this.show(pages.more);
+            }
+        },
+
         /**
          * Internal on page item click handler, when the page item is clicked, change the current page to the corresponding page and
          * trigger the pageclick event for the listeners.
@@ -242,6 +250,9 @@
             case "page":
                 currentTarget.bootstrapPaginator("show", page);
                 break;
+            case "page":
+                currentTarget.bootstrapPaginator("showMore");
+                break;
             }
 
         },
@@ -264,6 +275,7 @@
                 prev = null,
                 next = null,
                 last = null,
+                more = null,
                 p = null,
                 i = 0;
 
@@ -339,6 +351,17 @@
                 }
             }
 
+            // modified by mai 显示...类型
+            if (pages.more) {
+
+                more = this.buildPageItem("more", pages.more);
+
+                if (more) {
+                    listContainer.append(more);
+                }
+            }
+
+
             if (pages.next) {//if there is next page
 
                 next = this.buildPageItem("next", pages.next);
@@ -405,6 +428,11 @@
                 text = this.options.itemTexts(type, page, this.currentPage);
                 title = this.options.tooltipTitles(type, page, this.currentPage);
                 break;
+            case "more":
+                if (!this.getValueFromOption(this.options.shouldShowPage, type, page, this.currentPage)) { return; }
+                text = this.options.itemTexts(type, page, this.currentPage);
+                title = this.options.tooltipTitles(type, page, this.currentPage);
+                break;
             }
 
             itemContainer.addClass(itemContainerClass).append(itemContent);
@@ -467,6 +495,12 @@
                 output.prev = 1;
             }
 
+            if (output[output.length - 1] === totalPages) {
+                output.more = totalPages
+            } else {
+                output.more = output[output.length - 1] + 1;
+            }
+
             if (this.currentPage < totalPages) {// add the next page when the current page doesn't reach the last page
                 output.next = this.currentPage + 1;
             } else {
@@ -606,6 +640,9 @@
             case "page":
                 result = true;
                 break;
+            case "more":
+                result = (page !== this.totalPages);
+                break;
             }
 
             return result;
@@ -623,6 +660,8 @@
                 return "&gt;&gt;";
             case "page":
                 return page;
+            case "more":
+                return "...";
             }
         },
         tooltipTitles: function (type, page, current) {
@@ -638,6 +677,8 @@
                 return "Go to last page";
             case "page":
                 return (page === current) ? "Current page is " + page : "Go to page " + page;
+            case "more":
+                return "more pages";
             }
         },
         bootstrapTooltipOptions: {

+ 33 - 0
app/public/js/change.js

@@ -72,6 +72,10 @@ class codeRuleSet {
                 }
                 case ruleConst.ruleType.text: {
                     rule.text = $('#text>input', obj).val();
+                    if (rule.text === '') {
+                        toast('文本内容不允许为空。', 'error');
+                        return false;
+                    }
                     preview = rule.text;
                     break;
                 }
@@ -82,6 +86,10 @@ class codeRuleSet {
                 case ruleConst.ruleType.addNo: {
                     rule.format = parseInt($('#format>input', obj).val());
                     rule.start = parseInt($('#text>input', obj).val());
+                    if ($('#text>input', obj).val().length !== rule.format) {
+                        toast('起始编号位数和自动编号位数不一致。', 'error');
+                        return false;
+                    }
                     const s = '0000000000';
                     preview = s.substr(s.length - rule.format);
                     break;
@@ -179,6 +187,29 @@ $(document).ready(() => {
     $('#autoCode').click(getNewCode);
     // 新增变更令 确认
     $('#addOk').click(function () {
+        $(this).attr('disabled', true);
+        if ($('#bj-name').val().length === 0) {
+            $('#bj-name').addClass('is-invalid');
+            $('#name_error_msg').show();
+            $('#name_error_msg').text('工程名称不能为空。');
+            $(this).attr('disabled', false);
+            setTimeout(function () {
+                $('#bj-name').removeClass('is-invalid');
+                $('#name_error_msg').hide();
+            }, 2000);
+            return;
+        }
+        if ($('#bj-name').val().length > 100) {
+            $('#bj-name').addClass('is-invalid');
+            $('#name_error_msg').show();
+            $('#name_error_msg').text('名称超过100个字,请缩减名称。');
+            $(this).attr('disabled', false);
+            setTimeout(function () {
+                $('#bj-name').removeClass('is-invalid');
+                $('#name_error_msg').hide();
+            }, 2000);
+            return;
+        }
         const data = {
             code: $('#bj-code').val(),
             name: $('#bj-name').val(),
@@ -187,10 +218,12 @@ $(document).ready(() => {
             postData('/tender/'+ $('#tenderId').val() +'/change/add', data, function (rst) {
                 $('#bj-code').removeClass('is-invalid');
                 $('#mj-add').modal('hide');
+                $(this).attr('disabled', false);
                 window.location.href = '/tender/'+ $('#tenderId').val() +'/change/' + rst.cid + '/info';
             }, function () {
                 $('#mj-code').addClass('is-invalid');
                 $('#mj-Hint').show();
+                $(this).attr('disabled', false);
             });
         }
     });

+ 9 - 2
app/public/js/change_approval.js

@@ -27,7 +27,7 @@ $(document).ready(() => {
         const amount = $(this).val();
         const lid = $(this).parents('tr').data('lid');
         const tr = $('#list tr[data-lid="' + lid + '"]').eq(0);
-        const unitprice = tr.children('td[data-site="4"]').text();
+        const unitprice = tr.children('td[data-site="5"]').text();
         tr.children('.amount_cost').text(amount != '' ?
             roundnum(parseFloat(unitprice).mul(parseFloat(amount)),totalPriceUnit) : '');
 
@@ -40,6 +40,11 @@ $(document).ready(() => {
         $('.amount_totalcost').eq(1).text(totalcost !== 0 ? roundnum(totalcost, totalPriceUnit) : '');
     });
 
+    // 选中input所有值
+    $('body').on('focus', ".clist input", function() {
+        $(this).select();
+    });
+
     // 审批提交与判断
     $('.approval-btn').on('click', function () {
         // 判断审批状态
@@ -51,7 +56,7 @@ $(document).ready(() => {
                 returnflag = false;
             }
             if ($('input[name="p_code"]').val() === '') {
-                toastr.error('批复文号不能为空!');
+                toastr.error('变更令号(批复编号)不能为空!');
                 returnflag = false;
             }
             // 判断并提交变更清单表格数据到表单中
@@ -68,6 +73,7 @@ $(document).ready(() => {
             $('#change-list-approval').val(clist.join(','));
 
             if(returnflag) {
+                $('input[name="w_code"]').val($('#w_code').val());
                 $('#success-approval').submit();
             }
         } else {
@@ -82,6 +88,7 @@ $(document).ready(() => {
                 returnflag = false;
             }
             if(returnflag) {
+                $('input[name="w_code"]').val($('#w_code').val());
                 $('#fail-approval').submit();
             }
         }

+ 44 - 6
app/public/js/change_calculation.js

@@ -81,6 +81,44 @@ Number.prototype.sub = function (arg){
 };
 
 //四舍五入或末尾加零,实现类似php的 sprintf("%.".decimal."f", val);
+// function roundnum(val,decimals){
+//     if(val !== ''){
+//         val = parseFloat(val);
+//         if(decimals < 1){
+//             val = (Math.round(val)).toString();
+//         }else{
+//             let num = val.toString();
+//             if(num.lastIndexOf('.') == -1){
+//                 num += '.';
+//                 num += makezero(decimals);
+//                 val = num;
+//             }else{
+//                 let valdecimals = num.split('.')[1].length;
+//                 if(parseInt(valdecimals) < parseInt(decimals)){
+//                     num += makezero(parseInt(decimals)-parseInt(valdecimals));
+//                     val = num;
+//                 }else if(parseInt(valdecimals) > parseInt(decimals)){
+//                     val = parseFloat(val) != 0 ? Math.round(val.mul(makemultiple(decimals))).div(makemultiple(decimals)) : makedecimalzero(decimals);
+//                     let num = val.toString();
+//                     if(num.lastIndexOf('.') == -1){
+//                         num += '.';
+//                         num += makezero(decimals);
+//                         val = num;
+//                     }else {
+//                         let valdecimals = num.split('.')[1].length;
+//                         if (parseInt(valdecimals) < parseInt(decimals)) {
+//                             num += makezero(parseInt(decimals) - parseInt(valdecimals));
+//                             val = num;
+//                         }
+//                     }
+//                 }
+//             }
+//         }
+//     }
+//     return val;
+// }
+
+//四舍五入
 function roundnum(val,decimals){
     if(val !== ''){
         val = parseFloat(val);
@@ -89,25 +127,25 @@ function roundnum(val,decimals){
         }else{
             let num = val.toString();
             if(num.lastIndexOf('.') == -1){
-                num += '.';
-                num += makezero(decimals);
+                // num += '.';
+                // num += makezero(decimals);
                 val = num;
             }else{
                 let valdecimals = num.split('.')[1].length;
                 if(parseInt(valdecimals) < parseInt(decimals)){
-                    num += makezero(parseInt(decimals)-parseInt(valdecimals));
+                    // num += makezero(parseInt(decimals)-parseInt(valdecimals));
                     val = num;
                 }else if(parseInt(valdecimals) > parseInt(decimals)){
                     val = parseFloat(val) != 0 ? Math.round(val.mul(makemultiple(decimals))).div(makemultiple(decimals)) : makedecimalzero(decimals);
                     let num = val.toString();
                     if(num.lastIndexOf('.') == -1){
-                        num += '.';
-                        num += makezero(decimals);
+                        // num += '.';
+                        // num += makezero(decimals);
                         val = num;
                     }else {
                         let valdecimals = num.split('.')[1].length;
                         if (parseInt(valdecimals) < parseInt(decimals)) {
-                            num += makezero(parseInt(decimals) - parseInt(valdecimals));
+                            // num += makezero(parseInt(decimals) - parseInt(valdecimals));
                             val = num;
                         }
                     }

+ 32 - 28
app/public/js/change_detail.js

@@ -45,38 +45,42 @@ $(document).ready(() => {
 
     // 上传附件
     $('#upload-file-btn').click(function () {
-        const file = $('#upload-file')[0];
-        if (file.files[0] === undefined) {
-            toastr.error('未选择上传文件!');
-            return false;
-        }
-        const filesize = file.files[0].size;
-        if (filesize > 30 * 1024 * 1024) {
-            toastr.error('文件大小过大!');
-            return false;
-        }
-        const fileext = '.' + file.files[0].name.toLowerCase().split('.').splice(-1)[0];
-        if (whiteList.indexOf(fileext) === -1) {
-            toastr.error('只能上传指定格式的附件!');
-            return false;
-        }
+        const files = $('#upload-file')[0].files;
         const formData = new FormData();
         formData.append('cid', $('#changeId').val());
         formData.append('tid', $('#tenderId').val());
-        formData.append('size', filesize);
-        formData.append('file', file.files[0]);
+        for (const file of files) {
+            if (file === undefined) {
+                toastr.error('未选择上传文件!');
+                return false;
+            }
+            const filesize = file.size;
+            if (filesize > 30 * 1024 * 1024) {
+                toastr.error('文件大小过大!');
+                return false;
+            }
+            const fileext = '.' + file.name.toLowerCase().split('.').splice(-1)[0];
+            if (whiteList.indexOf(fileext) === -1) {
+                toastr.error('只能上传指定格式的附件!');
+                return false;
+            }
+            formData.append('size', filesize);
+            formData.append('file[]', file);
+        }
         postDataWithFile('/change/upload/file', formData, function (data) {
             $('#addfujian').modal('hide');
-            console.log(data);
-            const fileInfo = data;
-            let index = $('#attList tr').length;
-            let html = '<tr> ' +
-                '<td>' + (index+1) + '</td> ' +
-                '<td><a href="/change/download/file/' + fileInfo.id + '">' + fileInfo.filename + fileInfo.fileext + '</a></td> ' +
-                '<td>' + fileInfo.filesize + '</td> ' +
-                '<td>' + fileInfo.in_time + '</td> ' +
-                '<td> <a class="btn btn-light btn-sm delete-file" data-attid="' + fileInfo.id + '"  title="删除附件"><span class="fa fa-trash text-danger"></span></a> </td> ' +
-                '</tr>';
+            let html = '';
+            let index = $('#attList tr').length + 1;
+            for (const fileInfo of data) {
+                html += '<tr> ' +
+                    '<td>' + index + '</td> ' +
+                    '<td><a href="/change/download/file/' + fileInfo.id + '">' + fileInfo.filename + fileInfo.fileext + '</a></td> ' +
+                    '<td>' + fileInfo.filesize + '</td> ' +
+                    '<td>' + fileInfo.in_time + '</td> ' +
+                    '<td> <a class="btn btn-light btn-sm delete-file" data-attid="' + fileInfo.id + '"  title="删除附件"><span class="fa fa-trash text-danger"></span></a> </td> ' +
+                    '</tr>';
+                ++index;
+            }
             $('#attList').append(html);
         }, function () {
 
@@ -107,7 +111,7 @@ $(document).ready(() => {
         if($(e.target).is('label')){
             return;
         }
-        let column = table.column(2);
+        let column = table.column(3);
         column.visible(!column.visible());
     })
 });

+ 171 - 106
app/public/js/change_set.js

@@ -9,21 +9,67 @@
  */
 
 $(document).ready(() => {
+    // 编号排序,多重判断
+    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;
+                }
+            }
+        }
+    }
 
     gclGatherModel.loadLedgerData(ledger);
     gclGatherModel.loadPosData(pos);
     const gclGatherData = gclGatherModel.gatherGclData();
+    for (const ggd in gclGatherData) {
+        gclGatherData[ggd].code = gclGatherData[ggd].b_code;
+    }
     console.log(gclGatherData);
+    // 数组去重
+    for (const db of gclGatherData) {
+        const exist_index = dealBillList.findIndex(function (item) {
+            return item.code === db.code && item.name === db.name && item.unit === db.unit && item.unit_price === db.unit_price;
+        });
+        if (exist_index !== -1) {
+            dealBillList.splice(exist_index, 1);
+        }
+    }
+    const changeListData = gclGatherData.concat(dealBillList).sort(sortByCode);
     // 先加载台账数据
     let listHtml = '';
     let list_index = 1;
-    for (const gcl of gclGatherData) {
+    let gcl_index = 0;
+    for (const gcl of changeListData) {
         const unit = gcl.unit !== undefined && gcl.unit !== null ? gcl.unit : '';
         const quantity = gcl.quantity !== null && gcl.quantity !== undefined ? (unit !== '' ? roundnum(gcl.quantity, findDecimal(gcl.unit)) : gcl.quantity) : 0;
         const unit_price = gcl.unit_price !== null && gcl.unit_price !== undefined ? gcl.unit_price : 0;
-        listHtml += '<tr data-lid="' + list_index + '" data-gcl="' + (list_index-1) + '" data-index="' + list_index + '" data-detail="">' +
+        let gclhtml = gcl.leafXmjs !== undefined && gcl.leafXmjs !== null ? ' data-gcl="' + gcl_index + '"': '';
+        gcl_index = gclhtml !== '' ? ++gcl_index : gcl_index;
+        listHtml += '<tr data-lid="' + list_index + '"'+ gclhtml +' data-index="' + list_index + '" data-bwmx="">' +
             '<td>' + list_index + '</td>' +
-            '<td>' + gcl.b_code + '</td>' +
+            '<td>' + gcl.code + '</td>' +
             '<td>' + gcl.name + '</td>' +
             '<td>' + unit + '</td>' +
             '<td>' + roundnum(unit_price, unitPriceUnit) + '</td>' +
@@ -32,22 +78,22 @@ $(document).ready(() => {
             '</tr>';
         list_index++;
     }
-    // 再加载签约清单
-    for (const db of dealBillList) {
-        const unit = db.unit !== undefined && db.unit !== null ? db.unit : '';
-        const quantity = db.quantity !== null && db.quantity !== undefined ? (unit !== '' ? roundnum(db.quantity, findDecimal(db.unit)) : db.quantity) : 0;
-        const unit_price = db.unit_price !== null && db.unit_price !== undefined ? db.unit_price : 0;
-        listHtml += '<tr data-lid="' + db.id + '" data-index="' + list_index + '" data-detail="">' +
-            '<td>' + list_index + '</td>' +
-            '<td>' + db.code + '</td>' +
-            '<td>' + db.name + '</td>' +
-            '<td>' + unit + '</td>' +
-            '<td>' + roundnum(unit_price, unitPriceUnit) + '</td>' +
-            '<td>' + quantity + '</td>' +
-            // '<td>' + roundnum(parseFloat(db.unit_price).mul(parseFloat(db.quantity)), totalPriceUnit) + '</td>' +
-            '</tr>';
-        list_index++;
-    }
+    // // 再加载签约清单
+    // for (const db of dealBillList) {
+    //     const unit = db.unit !== undefined && db.unit !== null ? db.unit : '';
+    //     const quantity = db.quantity !== null && db.quantity !== undefined ? (unit !== '' ? roundnum(db.quantity, findDecimal(db.unit)) : db.quantity) : 0;
+    //     const unit_price = db.unit_price !== null && db.unit_price !== undefined ? db.unit_price : 0;
+    //     listHtml += '<tr data-lid="' + db.id + '" data-index="' + list_index + '" data-bwmx="">' +
+    //         '<td>' + list_index + '</td>' +
+    //         '<td>' + db.code + '</td>' +
+    //         '<td>' + db.name + '</td>' +
+    //         '<td>' + unit + '</td>' +
+    //         '<td>' + roundnum(unit_price, unitPriceUnit) + '</td>' +
+    //         '<td>' + quantity + '</td>' +
+    //         // '<td>' + roundnum(parseFloat(db.unit_price).mul(parseFloat(db.quantity)), totalPriceUnit) + '</td>' +
+    //         '</tr>';
+    //     list_index++;
+    // }
     $('#table-list-select').html(listHtml);
 
     // 上报时按钮点击
@@ -131,7 +177,7 @@ $(document).ready(() => {
                 for (const [index, cl] of changeList.entries()) {
                     const clInfo = cl.split(';');
                     if (clInfo[0] === '' || clInfo[1] === '' || clInfo[3] === '' || clInfo[4] === '' || clInfo[5] === '') {
-                        toastr.error('变更清单第' + (index+1) + '行未完整填写数据(变更详情可空)');
+                        toastr.error('变更清单第' + (index+1) + '行未完整填写数据(变更部位、变更详情可空)');
                         returnFlag = true;
                     }
                 }
@@ -164,6 +210,7 @@ $(document).ready(() => {
         }
         $('#change_form').ajaxSubmit(function (result) {
             if ( result.err === 0) {
+                $('.reduction-code').attr('data-code', $('input[name="code"]').val());
                 toastr.success(result.msg);
             } else {
                 toastr.error(result.msg);
@@ -251,26 +298,25 @@ $(document).ready(() => {
     // 打开签约清单modal并删除之前的操作
     $('#open-list-modal').click(function () {
        $('#table-list-select tr').removeClass('table-success');
-       $('#table-list-select tr').attr('data-detail', '');
+       $('#table-list-select tr').attr('data-bwmx', '');
        $('#code-list').html('');
     });
 
     // 清单选中和移除
     $('body').on('click', '#table-list-select tr', function () {
+        $('#table-list-select tr').removeClass('table-warning');
+        $(this).addClass('table-warning');
         const isCheck = $(this).hasClass('table-success') ? true : false;
-        const data_detail = $(this).attr('data-detail').split('$#$');
+        const data_bwmx = $(this).attr('data-bwmx').split('$#$');
         const isDeal = $(this).data('gcl') !== undefined ? true : false;
-        let codeHtml = '<tr quantity="0"><td colspan="4">自行编辑变更详情</td><td><input type="checkbox"></td></tr>';
+        let codeHtml = '<tr quantity="0"><td colspan="4" class="colspan_1">&nbsp;</td><td class="colspan_2"><input type="checkbox"></td></tr>';
         if (isDeal) {
             const gcl = gclGatherData[$(this).data('gcl')];
-            codeHtml = '<tr quantity="0"><td colspan="4">自行编辑变更详情</td><td><input type="checkbox" ';
-            // 判断自行编辑变更详情是否已选中
-            codeHtml += data_detail.indexOf('0;0') !== -1 && isCheck ? 'checked' : '';
-            codeHtml += '></td></tr>';
+            codeHtml = '';
             for (const leaf of gcl.leafXmjs) {
-                const quantity = leaf.quantity !== undefined ? leaf.quantity : 0;
+                const quantity = leaf.quantity !== undefined && leaf.quantity !== null ? leaf.quantity : 0;
                 const bwmx = leaf.bwmx !== undefined ? leaf.bwmx : '';
-                const isChecked = data_detail.indexOf(leaf.code + '_' + bwmx + ';' + quantity) !== -1 && isCheck ? 'checked' : '';
+                const isChecked = data_bwmx.indexOf(leaf.code + '_' + bwmx + ';' + quantity) !== -1 && isCheck ? 'checked' : '';
                 codeHtml += '<tr quantity="' + quantity + '"><td>' + leaf.code + '</td>' +
                     '<td>' + leaf.jldy + '</td>' +
                     '<td>' + bwmx + '</td>' +
@@ -279,7 +325,7 @@ $(document).ready(() => {
                     '></td></tr>';
             }
         } else if (!isDeal && isCheck) {
-            codeHtml = '<tr quantity="0"><td colspan="4">自行编辑变更详情</td><td><input type="checkbox" checked></td></tr>';
+            codeHtml = '<tr quantity="0"><td colspan="4" class="colspan_1">&nbsp;</td><td class="colspan_2"><input type="checkbox" checked></td></tr>';
         }
         $('#code-list').attr('data-index', $(this).children('td').eq(0).text());
         $('#code-list').html(codeHtml);
@@ -295,35 +341,35 @@ $(document).ready(() => {
             // 左边表单传值并添加class
             $('#table-list-select tr[data-index="' + index + '"]').addClass('table-success');
             // 去除部分data-detail值
-            let data_detail = [];
+            let data_bwmx = [];
             $('#code-list input:checked').each(function () {
                 const tr = $(this).parents('tr');
                 const length = tr.children('td').length;
-                const detail = length === 5 ? tr.children('td').eq(0).text() + '_' + tr.children('td').eq(2).text() : '0';
+                const bwmx = length === 5 ? tr.children('td').eq(0).text() + '_' + tr.children('td').eq(2).text() : '0';
                 const quantity = tr.attr('quantity');
-                const de_qu = detail + ';' + quantity;
-                data_detail.push(de_qu);
+                const de_qu = bwmx + ';' + quantity;
+                data_bwmx.push(de_qu);
             });
-            data_detail = data_detail.join('$#$');
-            $('#table-list-select tr[data-index="' + index + '"]').attr('data-detail', data_detail);
+            data_bwmx = data_bwmx.join('$#$');
+            $('#table-list-select tr[data-index="' + index + '"]').attr('data-bwmx', data_bwmx);
         } else {
             // 判断还有无选中项目节编号
             if ($('#code-list input').is(':checked')) {
                 // 去除部分data-detail值
-                let data_detail = [];
+                let data_bwmx = [];
                 $('#code-list input:checked').each(function () {
                     const tr = $(this).parents('tr');
                     const length = tr.children('td').length;
-                    const detail = length === 5 ? tr.children('td').eq(0).text() + '_' + tr.children('td').eq(2).text() : '0';
+                    const bwmx = length === 5 ? tr.children('td').eq(0).text() + '_' + tr.children('td').eq(2).text() : '0';
                     const quantity = tr.attr('quantity');
-                    const de_qu = detail + ';' + quantity;
-                    data_detail.push(de_qu);
+                    const de_qu = bwmx + ';' + quantity;
+                    data_bwmx.push(de_qu);
                 });
-                data_detail = data_detail.join('$#$');
-                $('#table-list-select tr[data-index="' + index + '"]').attr('data-detail', data_detail);
+                data_bwmx = data_bwmx.join('$#$');
+                $('#table-list-select tr[data-index="' + index + '"]').attr('data-bwmx', data_bwmx);
             } else {
                 $('#table-list-select tr[data-index="' + index + '"]').removeClass('table-success');
-                $('#table-list-select tr[data-index="' + index + '"]').attr('data-detail', '');
+                $('#table-list-select tr[data-index="' + index + '"]').attr('data-bwmx', '');
             }
         }
     });
@@ -339,11 +385,23 @@ $(document).ready(() => {
         $('#addlist').modal('hide');
     });
 
+    // 添加空白清单
+    $('#add-white-btn').on('click', function () {
+        maketablelist('addwhite');
+    });
+
     // 选中input所有值
     $('body').on('focus', ".clist input", function() {
         $(this).select();
     });
 
+    // 取消选中清单
+    $('#cancel-list-btn').click(function () {
+        $('#table-list-select tr').removeClass('table-success');
+        $('#table-list-select tr').attr('data-bwmx', '');
+        $('#code-list').html('');
+    });
+
     // 移除已选清单并重新编号
     $('body').on('click', '#list td a', function () {
         const index = $(this).parents('tr').data('index');
@@ -374,22 +432,24 @@ $(document).ready(() => {
             case 0:
             case 1:
             case 2:
-            case 4:
+            case 3:
+            case 5:
                 tr.children('td[data-site="' + site + '"]').children('input').val($(this).val());
                 break;
-            case 5:
-            case 7:
+            case 6:
+            case 8:
             default:
                 break;
         }
         const code = $.trim(tr.children('td[data-site="0"]').children('input').val()) || $.trim(tr.children('td[data-site="0"]').text());
         const name = $.trim(tr.children('td[data-site="1"]').children('input').val()) || $.trim(tr.children('td[data-site="1"]').text());
-        const unit = $.trim(tr.children('td[data-site="3"]').children('select').val()) || $.trim(tr.children('td[data-site="3"]').text());
-        const price = (tr.children('td[data-site="4"]').children('input').val() != '-' ? tr.children('td[data-site="4"]').children('input').val() : '') || tr.children('td[data-site="4"]').text();
-        const oamount = (tr.children('td[data-site="5"]').children('input').val() != '-' ? tr.children('td[data-site="5"]').children('input').val() : '') || tr.children('td[data-site="5"]').text();
-        const scnum = tr.children('td[data-site="7"]').children('input').val() != '-' ? tr.children('td[data-site="7"]').children('input').val() : '';
-        const detail = $.trim(tr.children('td[data-site="2"]').children('input').val());
-        const trlist = [code,name,unit,price,oamount,scnum,detail];
+        const bwmx = $.trim(tr.children('td[data-site="2"]').children('input').val()) || $.trim(tr.children('td[data-site="2"]').text());
+        const unit = $.trim(tr.children('td[data-site="4"]').children('select').val()) || $.trim(tr.children('td[data-site="4"]').text());
+        const price = (tr.children('td[data-site="5"]').children('input').val() != '-' ? tr.children('td[data-site="5"]').children('input').val() : '') || tr.children('td[data-site="5"]').text();
+        const oamount = (tr.children('td[data-site="6"]').children('input').val() != '-' ? tr.children('td[data-site="6"]').children('input').val() : '') || tr.children('td[data-site="6"]').text();
+        const scnum = tr.children('td[data-site="8"]').children('input').val() != '-' ? tr.children('td[data-site="8"]').children('input').val() : '';
+        const detail = $.trim(tr.children('td[data-site="3"]').children('input').val());
+        const trlist = [code,name,bwmx,unit,price,oamount,scnum,detail];
         if (isWhite) {
             let changelist = $('#change-whitelist').val().split('^_^');
             trlist.push(0);
@@ -401,8 +461,8 @@ $(document).ready(() => {
             changelist.splice(index, 1, trlist.join(';'));
             $('#change-list').val(changelist.join('^_^'));
         }
-        tr.children('td[data-site="6"]').text(price != '' && oamount != '' ? roundnum(parseFloat(price).mul(parseFloat(oamount)),totalPriceUnit) : '');
-        tr.children('td[data-site="8"]').text(price != '' && scnum != '' ? roundnum(parseFloat(price).mul(parseFloat(scnum)),totalPriceUnit) : '');
+        tr.children('td[data-site="7"]').text(price != '' && oamount != '' ? roundnum(parseFloat(price).mul(parseFloat(oamount)),totalPriceUnit) : '');
+        tr.children('td[data-site="9"]').text(price != '' && scnum != '' ? roundnum(parseFloat(price).mul(parseFloat(scnum)),totalPriceUnit) : '');
         totalamount(totalPriceUnit);
     });
 
@@ -412,32 +472,33 @@ $(document).ready(() => {
         const tr = $('#list tr[data-lid="' + index + '"]');
         const code = $.trim(tr.children('td[data-site="0"]').children('input').val());
         const name = $.trim(tr.children('td[data-site="1"]').children('input').val());
+        const bwmx = $.trim(tr.children('td[data-site="2"]').children('input').val());
         const unit = $(this).val();
-        const price = tr.children('td[data-site="4"]').children('input').val() != '-' ? tr.children('td[data-site="4"]').children('input').val() : '';
-        let oamount = tr.children('td[data-site="5"]').children('input').val() != '-' ? tr.children('td[data-site="5"]').children('input').val() : '';
-        let scnum = tr.children('td[data-site="7"]').children('input').val() != '-' ? tr.children('td[data-site="7"]').children('input').val() : '';
-        const detail = $.trim(tr.children('td[data-site="2"]').children('input').val());
+        const price = tr.children('td[data-site="5"]').children('input').val() != '-' ? tr.children('td[data-site="5"]').children('input').val() : '';
+        let oamount = tr.children('td[data-site="6"]').children('input').val() != '-' ? tr.children('td[data-site="6"]').children('input').val() : '';
+        let scnum = tr.children('td[data-site="8"]').children('input').val() != '-' ? tr.children('td[data-site="8"]').children('input').val() : '';
+        const detail = $.trim(tr.children('td[data-site="3"]').children('input').val());
 
         // 根据单位更新数量位数和合计
         const numdecimal = findDecimal(unit);
         oamount = roundnum(oamount, numdecimal);
         scnum = roundnum(scnum, numdecimal);
-        tr.children('td[data-site="5"]').children('input').val(oamount);
-        tr.children('td[data-site="5"]').children('input').attr('onkeyup','RegNum(this,event,'+ numdecimal +')');
-        tr.children('td[data-site="7"]').children('input').val(scnum);
-        tr.children('td[data-site="7"]').children('input').attr('onkeyup','RegNum(this,event,'+ numdecimal +')');
-        const trlist = [code,name,unit,price,oamount,scnum,detail,0];
+        tr.children('td[data-site="6"]').children('input').val(oamount);
+        tr.children('td[data-site="6"]').children('input').attr('onkeyup','RegNum(this,event,'+ numdecimal +')');
+        tr.children('td[data-site="8"]').children('input').val(scnum);
+        tr.children('td[data-site="8"]').children('input').attr('onkeyup','RegNum(this,event,'+ numdecimal +')');
+        const trlist = [code,name,bwmx,unit,price,oamount,scnum,detail,0];
         let changelist = $('#change-whitelist').val().split('^_^');
         changelist.splice(index, 1, trlist.join(';'));
         $('#change-whitelist').val(changelist.join('^_^'));
-        tr.children('td[data-site="6"]').text(price != '' && oamount != '' ? roundnum(parseFloat(price).mul(parseFloat(oamount)),totalPriceUnit) : '');
-        tr.children('td[data-site="8"]').text(price != '' && scnum != '' ? roundnum(parseFloat(price).mul(parseFloat(scnum)),totalPriceUnit) : '');
+        tr.children('td[data-site="7"]').text(price != '' && oamount != '' ? roundnum(parseFloat(price).mul(parseFloat(oamount)),totalPriceUnit) : '');
+        tr.children('td[data-site="9"]').text(price != '' && scnum != '' ? roundnum(parseFloat(price).mul(parseFloat(scnum)),totalPriceUnit) : '');
         totalamount(totalPriceUnit);
     });
 
     // 自动编号
     $('.reduction-code').click(function () {
-       const code = $(this).data('code');
+       const code = $(this).attr('data-code');
        $('input[name="code"]').val(code);
     });
 
@@ -494,11 +555,11 @@ function maketablelist(status){
         // let detail = $(this).attr('data-detail') != 0 ? $(this).attr('data-detail').split('_')[1] : '';
         let lid = $(this).data('lid');
         // 原清单和数量改变
-        let data_detail = $(this).attr('data-detail').split('$#$');
-        for (const d of data_detail) {
-            const oamount = d.split(';')[1];
-            let detail = d.split(';')[0] != 0 ? d.split(';')[0].split('_')[1] : '';
-            let trlist = [code, name, unit, price, oamount, scnum, detail, lid];
+        let data_bwmx = $(this).attr('data-bwmx').split('$#$');
+        for (const b of data_bwmx) {
+            const oamount = b.split(';')[1];
+            let bwmx = b.split(';')[0] != 0 ? b.split(';')[0].split('_')[1] : '';
+            let trlist = [code, name, bwmx, unit, price, oamount, scnum, '', lid];
             radionList.push(trlist.join(';'));
         }
     });
@@ -511,12 +572,13 @@ function maketablelist(status){
         const radionArray = radion.split(';');
         let code = radionArray[0];
         let name = radionArray[1];
-        let unit = radionArray[2];
-        let price = radionArray[3];
-        let oamount = radionArray[4];
-        let scnum = radionArray[5];
-        let detail = radionArray[6];
-        let lid = radionArray[7];
+        let bwmx = radionArray[2];
+        let unit = radionArray[3];
+        let price = radionArray[4];
+        let oamount = radionArray[5];
+        let scnum = radionArray[6];
+        let detail = radionArray[7];
+        let lid = radionArray[8];
         let sctotal = scnum !== '' && scnum !== '-' ? roundnum(parseFloat(price).mul(parseFloat(scnum)),decimal) : '';
 
         // 根据单位获取数量的位数,并得出
@@ -525,13 +587,14 @@ function maketablelist(status){
         html += '<tr class="clist clid" data-lid="' + lid + '_' + index + '" data-index="' + index + '">' +
             '<td data-site="0">'+ code +'</td>' +
             '<td data-site="1">'+ name +'</td>' +
-            '<td data-site="2"><input class="form-control input-sm" type="text" placeholder="变更详情" value="' + detail + '"></td>' +
-            '<td data-site="3">'+ unit +'</td>' +
-            '<td data-site="4">'+ roundnum(price, updecimal) +'</td>' +
-            '<td data-site="5">'+ roundnum(oamount, numdecimal) +'</td>' +
-            '<td data-site="6">'+ roundnum(parseFloat(price).mul(parseFloat(oamount)),decimal) +'</td>' +
-            '<td data-site="7"><input class="form-control input-sm" type="text" onkeyup="RegNum(this,event,'+ numdecimal +')" placeholder="请输入变更数量" value="'+ (scnum != '-' ? roundnum(scnum, numdecimal) : '') +'"></td>' +
-            '<td data-site="8">'+ sctotal +'</td>'+
+            '<td data-site="2">'+ bwmx +'</td>' +
+            '<td data-site="3"><input class="form-control input-sm" type="text" placeholder="变更详情" value="' + detail + '"></td>' +
+            '<td data-site="4">'+ unit +'</td>' +
+            '<td data-site="5">'+ roundnum(price, updecimal) +'</td>' +
+            '<td data-site="6">'+ roundnum(oamount, numdecimal) +'</td>' +
+            '<td data-site="7">'+ roundnum(parseFloat(price).mul(parseFloat(oamount)),decimal) +'</td>' +
+            '<td data-site="8"><input class="form-control input-sm" type="text" onkeyup="RegNum(this,event,'+ numdecimal +')" placeholder="请输入变更数量" value="'+ (scnum != '-' ? roundnum(scnum, numdecimal) : '') +'"></td>' +
+            '<td data-site="9">'+ sctotal +'</td>'+
             deteletr +'</tr>';
         index ++;
     }
@@ -541,7 +604,7 @@ function maketablelist(status){
     let radionWhiteList = $('#change-whitelist').val() !== '' ? $('#change-whitelist').val().split('^_^') : [];
     //判断是否添加空白清单
     if(status == 'addwhite'){
-        let trlist = ['','',changeUnits.m.unit,makedecimalzero(decimal),makedecimalzero(findDecimal(changeUnits.m.unit)),makedecimalzero(findDecimal(changeUnits.m.unit)),'',0];
+        let trlist = ['','','',changeUnits.m.unit,makedecimalzero(decimal),makedecimalzero(findDecimal(changeUnits.m.unit)),makedecimalzero(findDecimal(changeUnits.m.unit)),'',0];
         radionWhiteList.push(trlist.join(';'));
     }
 
@@ -549,11 +612,12 @@ function maketablelist(status){
         const radionArray = rw.split(';');
         let code = radionArray[0];
         let name = radionArray[1];
-        let unit = radionArray[2];
-        let price = radionArray[3];
-        let oamount = radionArray[4];
-        let scnum = radionArray[5];
-        let detail = radionArray[6];
+        let bwmx = radionArray[2];
+        let unit = radionArray[3];
+        let price = radionArray[4];
+        let oamount = radionArray[5];
+        let scnum = radionArray[6];
+        let detail = radionArray[7];
         let ototal = price != '' && oamount != '' ? roundnum(parseFloat(price).mul(parseFloat(oamount)),decimal) : '';
         let sctotal = price != '' && scnum != '' ? roundnum(parseFloat(price).mul(parseFloat(scnum)),decimal) : '';
 
@@ -570,15 +634,16 @@ function maketablelist(status){
         let numdecimal = findDecimal(unit);
 
         html += '<tr class="clist" data-lid="' + whiteIndex + '" data-index="' + whiteIndex + '">' +
-            '<td data-site="0"><input class="form-control input-sm" type="text" value="'+ code +'" placeholder="请输入清单编号"></td>' +
-            '<td data-site="1"><input class="form-control input-sm" type="text" value="'+ name +'" placeholder="请输入名称"></td>' +
-            '<td data-site="2"><input class="form-control input-sm" type="text" value="'+ detail +'" placeholder="变更详情"></td>' +
-            '<td data-site="3"><select class="form-control input-sm">'+ optionlist +'</select></td>' +
-            '<td data-site="4"><input class="form-control input-sm" type="text" onkeyup="RegNum(this,event,'+ updecimal +')" value="'+ roundnum(price, updecimal) +'" placeholder="请输入单价"></td>' +
-            '<td data-site="5"><input class="form-control input-sm" type="text" onkeyup="RegNum(this,event,'+ numdecimal +')" value="'+ roundnum(oamount, numdecimal) +'" placeholder="请输入数量"></td>' +
-            '<td data-site="6">'+ ototal +'</td>' +
-            '<td data-site="7"><input class="form-control input-sm" type="text" onkeyup="RegNum(this,event,'+ numdecimal +')" value="'+ roundnum(scnum, numdecimal) +'" placeholder="请输入变更数量"></td>' +
-            '<td data-site="8">'+ sctotal +'</td>'+
+            '<td data-site="0"><input class="form-control input-sm" type="text" value="'+ code +'" placeholder="清单编号"></td>' +
+            '<td data-site="1"><input class="form-control input-sm" type="text" value="'+ name +'" placeholder="名称"></td>' +
+            '<td data-site="2"><input class="form-control input-sm" type="text" value="'+ bwmx +'" placeholder="变更部位"></td>' +
+            '<td data-site="3"><input class="form-control input-sm" type="text" value="'+ detail +'" placeholder="变更详情"></td>' +
+            '<td data-site="4"><select class="form-control input-sm">'+ optionlist +'</select></td>' +
+            '<td data-site="5"><input class="form-control input-sm" type="text" onkeyup="RegNum(this,event,'+ updecimal +')" value="'+ roundnum(price, updecimal) +'" placeholder="请输入单价"></td>' +
+            '<td data-site="6"><input class="form-control input-sm" type="text" onkeyup="RegNum(this,event,'+ numdecimal +')" value="'+ roundnum(oamount, numdecimal) +'" placeholder="请输入数量"></td>' +
+            '<td data-site="7">'+ ototal +'</td>' +
+            '<td data-site="8"><input class="form-control input-sm" type="text" onkeyup="RegNum(this,event,'+ numdecimal +')" value="'+ roundnum(scnum, numdecimal) +'" placeholder="请输入变更数量"></td>' +
+            '<td data-site="9">'+ sctotal +'</td>'+
             deteletr +'</tr>';
         whiteIndex ++;
     }
@@ -590,18 +655,18 @@ function maketablelist(status){
     table = $('#tablelist').removeAttr('width').DataTable({
         columnDefs: [
             { className: 'allwidth1', width: 100, targets: 0 },
-            { className: 'allwidth2', width: 150, targets: [1,2] },
-            { className: 'allwidth4', width: 40, targets: 9 },
-            { className: 'allwidth5', width: 60, targets: 3 },
+            { className: 'allwidth2', width: 150, targets: [1,2,3] },
+            { className: 'allwidth4', width: 40, targets: 10 },
+            { className: 'allwidth5', width: 60, targets: 4 },
             { className: 'allwidth3',width: 80, targets: '_all' }
         ],
         fixedColumns: {
-            leftColumns: 5
+            leftColumns: 6
         }
     });
     // 判断是否显示变更详情
     if (!$('.change-detail-checkbox').is(':checked')) {
-        const column = table.column(2);
+        const column = table.column(3);
         column.visible(!column.visible());
     }
     totalamount(decimal);
@@ -614,9 +679,9 @@ function totalamount(decimal){
     let ctotalnum = 0;
     let ototalnum = 0;
     $('.clist').each(function(){
-        let ctotal = $(this).children('td[data-site="8"]').text();
+        let ctotal = $(this).children('td[data-site="9"]').text();
         ctotalnum = ctotal != '' ? parseFloat(ctotalnum).add(parseFloat(ctotal)) : parseFloat(ctotalnum);
-        let ototal = $(this).children('td[data-site="6"]').text();
+        let ototal = $(this).children('td[data-site="7"]').text();
         ototalnum = ototal != '' ? parseFloat(ototalnum).add(parseFloat(ototal)) : parseFloat(ototalnum);
     });
     $('.ctatalamount').eq(1).text(ctotalnum != 0 ? roundnum(ctotalnum,decimal) : zero);

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

@@ -25,7 +25,7 @@ $(document).ready(() => {
         }
         // 判断是否显示变更详情
         if (!$('.change-detail-checkbox').is(':checked')) {
-            const column = table.column(2);
+            const column = table.column(3);
             column.visible(!column.visible());
         }
     })

Plik diff jest za duży
+ 4877 - 0
app/public/js/decimal.js


Plik diff jest za duży
+ 3 - 0
app/public/js/decimal.min.js


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

@@ -6,7 +6,7 @@
  *
  * 示例结构:
  * <div id="top-div"></div>
- * <div r-Type="height|width" div1="#top-div" div2="#bottom-div" store-id="XXX" min="100">
+ * <div r-Type="height|width" div1="#top-div" div2="#bottom-div" store-id="XXX" store-version="1.0.0" min="100">
  * <div id="bottom-div"></div>
  *
  * 其中:
@@ -37,12 +37,12 @@
         // }
         // 根据localStorage初始化
         if (obj.attr('store-id')) {
-            const rType = obj.attr('r-type');
-            const objSize1 = getLocalCache('v-resize-1-' + obj.attr('store-id'));
+            const rType = obj.attr('r-type'), version = obj.attr('store-version') ? ('-'+obj.attr('store-version')) : '' ;
+            const objSize1 = getLocalCache('v-resize-1-' + obj.attr('store-id') + version);
             if (objSize1) {
                 $(obj.attr('div1')).css(rType, objSize1);
             }
-            const objSize2 = getLocalCache('v-resize-2-' + obj.attr('store-id'));
+            const objSize2 = getLocalCache('v-resize-2-' + obj.attr('store-id') + version);
             if (objSize2) {
                 $(obj.attr('div2')).css(rType, objSize2);
             }

+ 241 - 0
app/public/js/draw.js

@@ -0,0 +1,241 @@
+/**
+ * Created by louizhai on 17/6/30.
+ * description: Use canvas to draw.
+ */
+function Draw(canvas, degree, config = {}) {
+  if (!(this instanceof Draw)) {
+    return new Draw(canvas, config);
+  }
+  if (!canvas) {
+    return;
+  }
+  let { width, height } = window.getComputedStyle(canvas, null);
+  // width = width.replace('px', '');
+  height = height.replace('px', '');
+  width = height.replace('px', '')*2;
+
+  this.canvas = canvas;
+  this.context = canvas.getContext('2d');
+  this.width = width;
+  this.height = height;
+  const context = this.context;
+
+  // 根据设备像素比优化canvas绘图
+  const devicePixelRatio = window.devicePixelRatio;
+  if (devicePixelRatio) {
+    canvas.style.width = `${width}px`;
+    canvas.style.height = `${height}px`;
+    canvas.height = height * devicePixelRatio;
+    canvas.width = width * devicePixelRatio;
+    context.scale(devicePixelRatio, devicePixelRatio);
+  } else {
+    canvas.width = width;
+    canvas.height = height;
+  }
+
+  context.lineWidth = 6;
+  context.strokeStyle = 'black';
+  context.lineCap = 'round';
+  context.lineJoin = 'round';
+  Object.assign(context, config);
+
+  const { left, top } = canvas.getBoundingClientRect();
+  const point = {};
+  const isMobile = /phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone/i.test(navigator.userAgent);
+  // 移动端性能太弱, 去掉模糊以提高手写渲染速度
+  if (!isMobile) {
+    context.shadowBlur = 1;
+    context.shadowColor = 'black';
+  }
+  let pressed = false;
+
+  const paint = (signal) => {
+    switch (signal) {
+      case 1:
+        context.beginPath();
+        context.moveTo(point.x, point.y);
+      case 2:
+        context.lineTo(point.x, point.y);
+        context.stroke();
+        break;
+      default:
+    }
+  };
+  const create = signal => (e) => {
+    e.preventDefault();
+    if (signal === 1) {
+      pressed = true;
+    }
+    if (signal === 1 || pressed) {
+      e = isMobile ? e.touches[0] : e;
+      point.x = e.clientX - left;
+      point.y = e.clientY - top;
+      paint(signal);
+    }
+  };
+  const start = create(1);
+  const move = create(2);
+  const requestAnimationFrame = window.requestAnimationFrame;
+  const optimizedMove = requestAnimationFrame ? (e) => {
+    requestAnimationFrame(() => {
+      move(e);
+    });
+  } : move;
+
+  if (isMobile) {
+    canvas.addEventListener('touchstart', start);
+    canvas.addEventListener('touchmove', optimizedMove);
+  } else {
+    canvas.addEventListener('mousedown', start);
+    canvas.addEventListener('mousemove', optimizedMove);
+    ['mouseup', 'mouseleave'].forEach((event) => {
+      canvas.addEventListener(event, () => {
+        pressed = false;
+      });
+    });
+  }
+
+  // 重置画布坐标系
+  if (typeof degree === 'number') {
+    this.degree = degree;
+    context.rotate((degree * Math.PI) / 180);
+    switch (degree) {
+      case -90:
+        context.translate(-height, 0);
+        break;
+      case 90:
+        context.translate(0, -width);
+        break;
+      case -180:
+      case 180:
+        context.translate(-width, -height);
+        break;
+      default:
+    }
+  }
+}
+Draw.prototype = {
+  scale(width, height, canvas = this.canvas) {
+    const w = canvas.width;
+    const h = canvas.height;
+    width = width || w;
+    height = height || h;
+    if (width !== w || height !== h) {
+      const tmpCanvas = document.createElement('canvas');
+      const tmpContext = tmpCanvas.getContext('2d');
+      tmpCanvas.width = width;
+      tmpCanvas.height = height;
+      tmpContext.drawImage(canvas, 0, 0, w, h, 0, 0, width, height);
+      canvas = tmpCanvas;
+    }
+    return canvas;
+  },
+  rotate(degree, image = this.canvas) {
+    degree = ~~degree;
+    if (degree !== 0) {
+      const maxDegree = 180;
+      const minDegree = -90;
+      if (degree > maxDegree) {
+        degree = maxDegree;
+      } else if (degree < minDegree) {
+        degree = minDegree;
+      }
+
+      const canvas = document.createElement('canvas');
+      const context = canvas.getContext('2d');
+      const height = image.height;
+      const width = image.width;
+      const degreePI = (degree * Math.PI) / 180;
+
+      switch (degree) {
+        // 逆时针旋转90°
+        case -90:
+          canvas.width = height;
+          canvas.height = width;
+          context.rotate(degreePI);
+          context.drawImage(image, -width, 0);
+          break;
+        // 顺时针旋转90°
+        case 90:
+          canvas.width = height;
+          canvas.height = width;
+          context.rotate(degreePI);
+          context.drawImage(image, 0, -height);
+          break;
+        // 顺时针旋转180°
+        case 180:
+          canvas.width = width;
+          canvas.height = height;
+          context.rotate(degreePI);
+          context.drawImage(image, -width, -height);
+          break;
+        default:
+      }
+      image = canvas;
+    }
+    return image;
+  },
+  getPNGImage(canvas = this.canvas) {
+    console.log(canvas.width, canvas.height);
+    return canvas.toDataURL('image/png');
+  },
+  getJPGImage(canvas = this.canvas) {
+    return canvas.toDataURL('image/jpeg', 0.5);
+  },
+  downloadPNGImage(image) {
+    const url = image.replace('image/png', 'image/octet-stream;Content-Disposition:attachment;filename=test.png');
+    window.location.href = url;
+  },
+  dataURLtoBlob(dataURL) {
+    const arr = dataURL.split(',');
+    const mime = arr[0].match(/:(.*?);/)[1];
+    const bStr = atob(arr[1]);
+    let n = bStr.length;
+    const u8arr = new Uint8Array(n);
+    while (n--) {
+      u8arr[n] = bStr.charCodeAt(n);
+    }
+    return new Blob([u8arr], { type: mime });
+  },
+  clear() {
+    let width;
+    let height;
+    switch (this.degree) {
+      case -90:
+      case 90:
+        width = this.height;
+        height = this.width;
+        break;
+      default:
+        width = this.width;
+        height = this.height;
+    }
+    this.context.clearRect(0, 0, width, height);
+  },
+  upload(blob, url, success, failure) {
+    const formData = new FormData();
+    const xhr = new XMLHttpRequest();
+    xhr.withCredentials = true;
+    formData.append('id', id);
+    formData.append('image', blob, 'sign');
+
+    xhr.open('POST', url, true);
+    xhr.setRequestHeader("x-csrf-token", csrf);
+    xhr.onload = () => {
+      if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
+        success(xhr.responseText);
+      } else {
+        failure();
+      }
+    };
+    xhr.onerror = (e) => {
+      if (typeof failure === 'function') {
+        failure(e);
+      } else {
+        console.log(`upload img error: ${e}`);
+      }
+    };
+    xhr.send(formData);
+  },
+};
+// export default Draw;

+ 32 - 27
app/public/js/gcl_gather.js

@@ -1,7 +1,8 @@
 'use strict';
 
 /**
- * 清单汇总(需使用path_tree.js, lodash.js)
+ *
+ * 清单汇总(需使用 decimal.min.js, zh_calc.js, path_tree.js, lodash.js)
  *
  * @author Mai
  * @date
@@ -28,20 +29,16 @@ const gclGatherModel = (function () {
     gsTreeSetting.calcFields = ['deal_tp', 'total_price', 'contract_tp', 'qc_tp', 'gather_tp', 'end_contract_tp', 'end_qc_tp', 'end_gather_tp'];
     gsTreeSetting.calcFun = function (node) {
         if (node.children && node.children.length === 0) {
-            node.gather_qty = _.add(node.contract_qty, node.qc_qty);
-            node.end_contract_qty = _.add(node.pre_contract_qty, node.contract_qty);
-            node.end_qc_qty = _.add(node.pre_qc_qty, node.qc_qty);
-            node.end_gather_qty = _.add(node.pre_gather_qty, node.gather_qty);
-        }
-        node.gather_tp = _.add(node.contract_tp, node.qc_tp);
-        node.end_contract_tp = _.add(node.pre_contract_tp, node.contract_tp);
-        node.end_qc_tp = _.add(node.pre_qc_tp, node.qc_tp);
-        node.end_gather_tp = _.add(node.pre_gather_tp, node.gather_tp);
-        if (checkZero(node.dgn_qty1)) {
-            node.dgn_price = _.round(node.total_price/node.dgn_qty1, 2);
-        } else {
-            node.dgn_price = null;
+            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.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.dgn_price = ZhCalc.round(ZhCalc.div(node.total_price, node.dgn_qty1), 2);
     };
     const gsTree = createNewPathTree('stage', gsTreeSetting);
     // 初始化 部位明细
@@ -50,12 +47,15 @@ const gclGatherModel = (function () {
         updateFields: ['contract_qty', 'qc_qty'],
     };
     posSetting.calcFun = function (pos) {
-        pos.gather_qty = _.add(pos.contract_qty, pos.qc_qty);
+        pos.gather_qty = ZhCalc.add(pos.contract_qty, pos.qc_qty);
     };
     const gsPos = new StagePosData(posSetting);
+    let deal = [];
 
     const gclList = [], leafXmjs = [];
 
+
+
     /**
      * 将所有数据加载至树结构
      *
@@ -83,22 +83,14 @@ const gclGatherModel = (function () {
         gsPos.loadPreStageData(prePos);
     }
 
-    function addNumber(a, b) {
-        if (b) {
-            if (a) {
-                return a + b;
-            } else {
-                return b;
-            }
-        } else {
-            return a;
-        }
+    function loadDealBillsData(dealBills) {
+        deal = dealBills;
     }
 
     function gatherfields(obj, src, fields) {
         if (obj && src) {
             for (const f of fields) {
-                obj[f] = addNumber(obj[f], src[f]);
+                obj[f] = ZhCalc.add(obj[f], src[f]);
             }
         }
     }
@@ -326,15 +318,27 @@ const gclGatherModel = (function () {
         }
     }
 
+    function gatherDealBillsData() {
+        if (deal && deal.length > 0) {
+            for (const node of deal) {
+                node.b_code = node.code;
+                const gcl = getGclNode(node);
+                gcl.deal_bills_qty = node.quantity;
+                gcl.deal_bills_tp = node.total_price;
+            }
+        }
+    }
+
     /**
      * 根据树结构 清单汇总
      */
     function gatherGclData() {
         // 清空旧数据
         if (gclList.length > 0) {
-            gclList.splice(0, gclList.length - 1);
+            gclList.length = 0; //splice(0, gclList.length);
         }
         recursiveGatherGclData(gsTree.children, null);
+        gatherDealBillsData();
         gclList.sort(function (a, b) {
             function compareCode(code1, code2) {
                 if (numReg.test(code1)) {
@@ -366,6 +370,7 @@ const gclGatherModel = (function () {
     return {
         loadLedgerData,
         loadPosData,
+        loadDealBillsData,
         gatherGclData,
     };
 })();

+ 19 - 16
app/public/js/global.js

@@ -18,21 +18,21 @@ function autoFlashHeight(){
     var pBarz = getObjHeight($(".toolsbar-f"));
     var bdtopc = getObjHeight($(".body-height-top"));
     var bcontent = getObjHeight($(".bcontent-wrap"));
-    $(".sjs-height-0").height($(window).height()-cHeader-110);
-    $(".sjs-height-1").height($(window).height()-cHeader-bcontent-110);
+    $(".sjs-height-0").height($(window).height()-cHeader-90);
+    $(".sjs-height-1").height($(window).height()-cHeader-bcontent-90);
     $(".sjs-height-2").height($(window).height()-cHeader-sBarz-110);
-    $(".sjs-height-3").height($(window).height()-cHeader-sBar-510);
+    $(".sjs-height-3").height($(window).height()-cHeader-sBar-492);
     $(".sjs-height-4").height($(window).height()-cHeader-pBarz-110);
     $(".sjs-height-5").height($(window).height()-cHeader-sBart-555);
     $(".sjs-height-6").height($(window).height()-cHeader-bdtopc-156);
     $(".sjs-height-7").height($(window).height()-cHeader-sBar-110);
     $(".sp-wrap").height(bcontent-40);
     /*侧栏高度*/
-    $(".sjs-sh-1").height($(window).height()-cHeader-sBar1-110);
-    $(".sjs-sh-2").height($(window).height()-cHeader-sBar2-110);
-    $(".sjs-sh-3").height($(window).height()-cHeader-sBar3-110);
-    $(".sjs-sh-4").height($(window).height()-cHeader-sBar4-110);
-    $(".sjs-sh-5").height($(window).height()-cHeader-sBar5-110);
+    $(".sjs-sh-1").height($(window).height()-cHeader-sBar1-92);
+    $(".sjs-sh-2").height($(window).height()-cHeader-sBar2-92);
+    $(".sjs-sh-3").height($(window).height()-cHeader-sBar3-92);
+    $(".sjs-sh-4").height($(window).height()-cHeader-sBar4-92);
+    $(".sjs-sh-5").height($(window).height()-cHeader-sBar5-92);
 };
 $(window).resize(autoFlashHeight);
 /*全局自适应高度结束*/
@@ -83,12 +83,12 @@ $(function(){
     });
 
     // modal弹窗拖动
-    $(document).on("show.bs.modal", ".modal", function() {
-        $(this).draggable({
-            handle: ".modal-header"   // 只能点击头部拖动
-        });
-        $(this).css("overflow", "hidden");
-    });
+    // $(document).on("show.bs.modal", ".modal", function() {
+    //     $(this).draggable({
+    //         handle: ".modal-header"   // 只能点击头部拖动
+    //     });
+    //     $(this).css("overflow", "hidden");
+    // });
 });
 
 /**
@@ -128,7 +128,7 @@ const postData = function (url, data, successCallback, errorCallBack) {
         data: {'data': JSON.stringify(data)},
         dataType: 'json',
         cache: false,
-        timeout: 5000,
+        timeout: 40000,
         beforeSend: function(xhr) {
             let csrfToken = Cookies.get('csrfToken');
             xhr.setRequestHeader('x-csrf-token', csrfToken);
@@ -257,6 +257,9 @@ const zeroRange = 0.00000001;
 function checkZero(value) {
     return _.isNumber(value) && Math.abs(value) < zeroRange;
 }
+function checkFieldChange(o, n) {
+    return o == n || ((!o || o === '') && (n === ''));
+}
 
 /**
  * 设置本地缓存
@@ -300,4 +303,4 @@ function removeLocalCache(key) {
         return null;
     }
     return storage.removeItem(key);
-}
+}

Plik diff jest za duży
+ 6 - 16615
app/public/js/jquery/jquery-ui.js


+ 355 - 105
app/public/js/ledger.js

@@ -5,6 +5,7 @@
  * @date 2018/02/05
  * @version
  */
+const ckBillsSpread = window.location.pathname + '-billsSelect';
 function checkTzMeasureType () {
     return tender.measure_type === measureType.tz.value;
 }
@@ -13,6 +14,8 @@ function getTenderId() {
     return window.location.pathname.split('/')[2];
 }
 
+const copyBlockTag = 'zh.calc.copyBlock';
+
 $(document).ready(function() {
     autoFlashHeight();
     // 初始化台账
@@ -28,16 +31,12 @@ $(document).ready(function() {
         preUrl: '/tender/' + getTenderId() + '/ledger',
     };
     if (checkTzMeasureType()) {
-        treeSetting.calcFields = ['total_price'];
+        treeSetting.calcFields = ['sgfh_tp', 'sjcl_tp', 'qtcl_tp', 'total_price'];
     } else {
-        treeSetting.calcFields = ['deal_tp', 'total_price'];
+        treeSetting.calcFields = ['deal_tp', 'sgfh_tp', 'sjcl_tp', 'qtcl_tp', 'total_price'];
     }
     treeSetting.calcFun = function (node) {
-        if (!checkZero(node.dgn_qty1)) {
-            node.dgn_price = _.round(node.total_price/node.dgn_qty1, 6);
-        } else {
-            node.dgn_price = null;
-        }
+        node.dgn_price = ZhCalc.round(ZhCalc.div(node.total_price, node.dgn_qty1), 2);
     };
     const ledgerTree = createNewPathTree('ledger', treeSetting);
     ledgerTree.loadDatas(ledger);
@@ -46,7 +45,7 @@ $(document).ready(function() {
     SpreadJsObj.initSheet(ledgerSpread.getActiveSheet(), ledgerSpreadSetting);
     // 加载台账数据到界面
     SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), 'tree', ledgerTree);
-
+    SpreadJsObj.loadTopAndSelect(ledgerSpread.getActiveSheet(), ckBillsSpread);
     // 初始化 部位明细
     const pos = new PosData({
         id: 'id', ledgerId: 'lid',
@@ -85,18 +84,21 @@ $(document).ready(function() {
             const preNode = tree.getPreSiblingNode(node);
             const valid = !sheet.zh_setting.readOnly;
 
-            setObjEnable($('#delete'), valid && node);
-            setObjEnable($('#up-move'), valid && node && preNode);
-            setObjEnable($('#down-move'), valid && node && !tree.isLastSibling(node));
+            setObjEnable($('#insert'), valid && node && node.level > 1);
+            setObjEnable($('#delete'), valid && node && node.level > 1);
+            setObjEnable($('#up-move'), valid && node && node.level > 1 && preNode);
+            setObjEnable($('#down-move'), valid && node && node.level > 1 && !tree.isLastSibling(node));
             if (checkTzMeasureType()) {
                 const posRange = node ? pos.getLedgerPos(node.id) : [];
                 setObjEnable($('#up-level'), valid && node && tree.getParent(node) && node.level > 2 && (!posRange || posRange.length === 0));
                 const preNodePosRange = preNode ? pos.getLedgerPos(preNode.id) : [];
-                setObjEnable($('#down-level'), valid && node && preNode && (!preNodePosRange || preNodePosRange.length === 0));
+                setObjEnable($('#down-level'), valid && node && node.level > 1 && preNode && (!preNodePosRange || preNodePosRange.length === 0));
             } else {
-                setObjEnable($('#up-level'), valid && node && tree.getParent(node));
-                setObjEnable($('#down-level'), valid && node && preNode);
+                setObjEnable($('#up-level'), valid && node && node.level > 2 && tree.getParent(node));
+                setObjEnable($('#down-level'), valid && node && node.level > 1 && preNode);
             }
+            setObjEnable($('#cut'), valid);
+            setObjEnable($('#paste'), valid);
         },
         refreshTree: function (sheet, data) {
             SpreadJsObj.massOperationSheet(sheet, function () {
@@ -108,28 +110,19 @@ $(document).ready(function() {
                     }
                 }
                 // 处理新增
-                let cTime = new Date();
                 if (data.create) {
                     const newNodes = data.create;
                     if (newNodes) {
-                        let sTime = new Date();
                         newNodes.sort(function (a, b) {
                             return a.index - b.index;
                         });
-                        sTime = new Date() - sTime;
-                        console.log('sort: ' + sTime);
 
-                        let lTime = new Date();
                         for (const node of newNodes) {
                             sheet.addRows(node.index, 1);
                             SpreadJsObj.reLoadRowData(sheet, tree.nodes.indexOf(node), 1);
                         }
-                        lTime = new Date() - lTime;
-                        console.log('load: '+ lTime);
                     }
                 }
-                cTime = new Date() - cTime;
-                console.log('refreshNewNode: ' + cTime);
                 // 处理更新
                 if (data.update) {
                     const rows = [];
@@ -192,6 +185,7 @@ $(document).ready(function() {
 
             const count = ledgerTree.getPosterity(node).length;
             tree.baseOperation('/tender/' + getTenderId() + '/ledger/base-operation', node, 'delete', function (result) {
+                //treeOperationObj.refreshTree(sheet, result);
                 sheet.deleteRows(row, count + 1);
                 for (const data of result.update) {
                     SpreadJsObj.reLoadRowData(sheet, tree.nodes.indexOf(data), tree.getPosterity(data).length + 1);
@@ -312,16 +306,26 @@ $(document).ready(function() {
                 }
                 // 台账模式,检查部位明细相关
                 if (checkTzMeasureType()) {
-                    if (col.field === 'quantity' || col.field === 'total_price') {
+                    if (col.field === 'sgfh_qty' || col.field === 'sgfh_tp' ||
+                        col.field === 'sjcl_qty' || col.field === 'sjcl_tp' ||
+                        col.field === 'qtcl_qty' || col.field === 'qtcl_tp') {
                         if (!node.children || node.children.length ===0) {
                             const lPos = pos.getLedgerPos(node.id);
                             if (lPos && lPos.length > 0) {
-                                toast('清单含有部位明细,不可修改施工图复核数量', 'error');
+                                toast('清单含有部位明细,请在部位明细输入数量', 'error');
                                 SpreadJsObj.reLoadRowData(info.sheet, info.row);
                                 return;
                             }
                         }
                     }
+                    if (col.field === 'b_code' && (info.editingText === '' || !info.editingText)) {
+                        const lPos = pos.getLedgerPos(node.id);
+                        if (lPos && lPos.length > 0) {
+                            toast('清单含有部位明细,请先删除部位明细,再删除清单编号', 'error');
+                            SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                            return;
+                        }
+                    }
                 }
                 // 获取更新数据
                 if (info.editingText) {
@@ -346,8 +350,11 @@ $(document).ready(function() {
                 if (!tree) { return; }
 
                 const sortData = info.sheet.zh_tree.nodes;
-                const datas = [], nodes = [];
+                const datas = [], filterNodes = [];
+                let bHint = false, bPaste;
+
                 for (let iRow = 0; iRow < info.cellRange.rowCount; iRow ++) {
+                    bPaste = false;
                     const curRow = info.cellRange.row + iRow;
                     const node = sortData[curRow];
                     if (node) {
@@ -355,16 +362,48 @@ $(document).ready(function() {
                         for (let iCol = 0; iCol < info.cellRange.colCount; iCol++) {
                             const curCol = info.cellRange.col + iCol;
                             const colSetting = info.sheet.zh_setting.cols[curCol];
-                            data[colSetting.field] = info.sheet.getText(curRow, curCol).replace('\n', '');
+                            const value = info.sheet.getText(curRow, curCol).replace('\n', '');
+                            const lPos = pos.getLedgerPos(node.id);
+                            if (lPos && lPos.length > 0) {
+                                if (value === '' && colSetting.field === 'b_code') {
+                                    if (!bHint) {
+                                        toast('清单含有部位明细,请先删除部位明细,再删除清单编号', 'warning');
+                                        bHint = true;
+                                    }
+                                    continue;
+                                }
+                                if (colSetting.field === 'sgfh_qty' || colSetting.field === 'sgfh_tp' ||
+                                    colSetting.field === 'sjcl_qty' || colSetting.field === 'sjcl_tp' ||
+                                    colSetting.field === 'qtcl_qty' || colSetting.field === 'qtcl_tp') {
+                                    if (!bHint) {
+                                        toast('清单含有部位明细,数量金额根据部位明细汇总计算所得,不可编辑', 'warning');
+                                        bHint = true;
+                                    }
+                                    continue;
+                                }
+                            }
+                            data[colSetting.field] = value;
+                            bPaste = true;
+                        }
+                        if (bPaste) {
+                            datas.push(data);
+                        } else {
+                            filterNodes.push(node);
                         }
-                        datas.push(data);
-                        nodes.push(node);
                     }
                 }
-                console.log(JSON.stringify(datas));
-                info.sheet.zh_tree.update('/tender/' + getTenderId() + '/ledger/update', datas, function (result) {
-                    treeOperationObj.refreshTree(info.sheet, result);
-                });
+                if (datas.length > 0) {
+                    info.sheet.zh_tree.update('/tender/' + getTenderId() + '/ledger/update', datas, function (result) {
+                        if (result.update) {
+                            result.update = result.update.concat(filterNodes);
+                        }
+                        treeOperationObj.refreshTree(info.sheet, result);
+                    }, function () {
+                        SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
+                    });
+                } else {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
+                }
             }
         },
         /**
@@ -379,12 +418,30 @@ $(document).ready(function() {
                 const sortData = sheet.zh_tree.nodes;
                 const datas = [], nodes = [];
                 const sel = sheet.getSelections()[0];
+                let bHint = false;
                 for (let iRow = sel.row; iRow < sel.row + sel.rowCount; iRow++) {
                     const node = sortData[iRow];
                     if (node) {
                         let bDel = false;
                         const data = sheet.zh_tree.getNodeKeyData(node);
                         for (let iCol = sel.col; iCol < sel.col + sel.colCount; iCol++) {
+                            const col = sheet.zh_setting.cols[iCol];
+                            if (col.field === 'b_code' || col.field === 'sgfh_qty' || col.field === 'sgfh_tp' ||
+                                col.field === 'sjcl_qty' || col.field === 'sjcl_tp' ||
+                                col.field === 'qtcl_qty' || col.field === 'qtcl_tp') {
+                                const lPos = pos.getLedgerPos(node.id);
+                                if (lPos && lPos.length > 0) {
+                                    if (!bHint) {
+                                        if (col.field === 'code') {
+                                            toast('清单含有部位明细,请先删除部位明细,再删除清单编号', 'warning');
+                                        } else {
+                                            toast('清单含有部位明细,数量金额根据部位明细汇总计算所得,不可删除', 'warning');
+                                        }
+                                        bHint = true;
+                                    }
+                                    continue;
+                                }
+                            }
                             const style = sheet.getStyle(iRow, iCol);
                             if (!style.locked) {
                                 const colSetting = sheet.zh_setting.cols[iCol];
@@ -410,7 +467,7 @@ $(document).ready(function() {
          * @param spread
          * @param block
          */
-        pasteBlock: function (spread, block) {
+        pasteBlock: function (spread, copyInfo) {
             const self = this;
             const sheet = spread.getActiveSheet();
             const row = sheet.getSelections()[0].row;
@@ -423,22 +480,31 @@ $(document).ready(function() {
 
             postData('/tender/' + getTenderId() + '/ledger/paste-block', {
                 id: node[tree.setting.id],
-                block: block,
+                tid: copyInfo.tid,
+                block: copyInfo.block,
             }, function (data) {
                 pos.updateDatas(data.pos);
                 const result = tree.loadPostData(data.ledger);
                 self.refreshTree(sheet, result);
                 self.refreshOperationValid(sheet);
+                removeLocalCache(copyBlockTag);
             });
         },
         selectionChanged: function (e, info) {
-            if (info.newSelections[0].row !== info.oldSelections[0].row) {
+            if (!info.oldSelections[0] || info.newSelections[0].row !== info.oldSelections[0].row) {
                 posOperationObj.loadCurPosData();
+                SpreadJsObj.resetTopAndSelect(posSpread.getActiveSheet());
+                posSearch.search($('#pos-keyword').val());
             }
+            SpreadJsObj.saveTopAndSelect(info.sheet, ckBillsSpread);
         },
+        topRowChanged(e, info) {
+            SpreadJsObj.saveTopAndSelect(info.sheet, ckBillsSpread);
+        }
     };
     // 绑定事件
     ledgerSpread.bind(GC.Spread.Sheets.Events.SelectionChanged, treeOperationObj.selectionChanged);
+    ledgerSpread.bind(spreadNS.Events.TopRowChanged, treeOperationObj.topRowChanged);
     if (!ledgerSpreadSetting.readOnly) {
         ledgerSpread.bind(GC.Spread.Sheets.Events.SelectionChanged, function (e, info) {
             treeOperationObj.refreshOperationValid(info.sheet, info.newSelections);
@@ -447,6 +513,7 @@ $(document).ready(function() {
         ledgerSpread.bind(GC.Spread.Sheets.Events.ClipboardPasted, treeOperationObj.clipboardPasted);
         SpreadJsObj.addDeleteBind(ledgerSpread, treeOperationObj.deletePress);
         ledgerSpread.bind(GC.Spread.Sheets.Events.ClipboardChanging, function (e, info) {
+            console.log(info);
             const copyText = SpreadJsObj.getFilterCopyText(info.sheet);
         });
         ledgerSpread.bind(GC.Spread.Sheets.Events.ClipboardChanged, function (e, info) {
@@ -456,6 +523,9 @@ $(document).ready(function() {
         });
 
         // 绑定 删除等 顶部按钮
+        $('#insert').click(() => {
+            treeOperationObj.addNode(ledgerSpread);
+        });
         $('#delete').click(function () {
             treeOperationObj.deleteNode(ledgerSpread);
         });
@@ -532,7 +602,9 @@ $(document).ready(function() {
                             copyBlockList.push(node.ledger_id);
                             iRow += sheet.zh_tree.getPosterity(node).length + 1;
                         }
-                        treeOperationObj.block = copyBlockList;
+                        const tenderId = _.toInteger(getTenderId());
+                        setLocalCache(copyBlockTag, JSON.stringify({tid: tenderId, block: copyBlockList}));
+                        //treeOperationObj.block = copyBlockList;
                     },
                     visible: function (key, opt) {
                         const sheet = ledgerSpread.getActiveSheet();
@@ -546,13 +618,15 @@ $(document).ready(function() {
                     name: '粘贴整块',
                     icon: 'fa-clipboard',
                     disabled: function (key, opt) {
-                        const block = treeOperationObj.block || [];
-                        return block.length <= 0 && false;
+                        //const block = treeOperationObj.block || [];
+                        const copyInfo = JSON.parse(getLocalCache(copyBlockTag));
+                        return !(copyInfo && copyInfo.tid && copyInfo.tid > 0 && copyInfo.block && copyInfo.block.length > 0);
                     },
                     callback: function (key, opt) {
-                        const block = treeOperationObj.block || [];
-                        if (block.length > 0) {
-                            treeOperationObj.pasteBlock(ledgerSpread, block);
+                        //const block = treeOperationObj.block || [];
+                        const copyInfo = JSON.parse(getLocalCache(copyBlockTag));
+                        if (copyInfo.block.length > 0) {
+                            treeOperationObj.pasteBlock(ledgerSpread, copyInfo);
                         } else {
                             document.execCommand('paste');
                         }
@@ -562,6 +636,7 @@ $(document).ready(function() {
                     name: '批量插入清单-部位',
                     icon: 'fa-sign-in',
                     disabled: function (key, opt) {
+                        if (!checkTzMeasureType()) return true;
                         const sheet = ledgerSpread.getActiveSheet();
                         const selection = sheet.getSelections();
                         const row = selection[0].row;
@@ -598,9 +673,64 @@ $(document).ready(function() {
                 }
             }
         });
+    } else {
+        SpreadJsObj.forbiddenSpreadContextMenu('#ledger-spread', ledgerSpread);
     }
+
     treeOperationObj.refreshOperationValid(ledgerSpread.getActiveSheet());
 
+    const posSearch = (function () {
+        let resultArr = [];
+        const search = function (keyword) {
+            if (keyword && keyword !== '') {
+                resultArr = [];
+                const sortData = posSpread.getActiveSheet().zh_data;
+                if (sortData) {
+                    for (let i = 0, iLength = sortData.length; i < iLength; i++) {
+                        const sd = sortData[i];
+                        if (sd.name && sd.name.indexOf(keyword) > -1) {
+                            resultArr.push({index: i, data: sd});
+                        }
+                    }
+                }
+                $('#pos-search-hint').html(' ' + resultArr.length + '个匹配').show();
+            } else {
+                resultArr = [];
+                $('#pos-search-hint').hide();
+            }
+        };
+        const locateNext = function () {
+            if (resultArr.length > 0) {
+                const sheet = posSpread.getActiveSheet();
+                const sel = sheet.getSelections()[0];
+                const curRow = sel ? sel.row : 0;
+                let next = _.find(resultArr, function (d) {
+                    return d.index > curRow;
+                });
+                if (!next) next = resultArr[0];
+                if (next.index !== curRow) {
+                    sheet.setSelection(next.index, sel ? sel.col : 0, 1, 1);
+                    sheet.showRow(next.index, spreadNS.VerticalPosition.center);
+                }
+            }
+        };
+        const locatePre = function () {
+            if (resultArr.length > 0) {
+                const sheet = posSpread.getActiveSheet();
+                const sel = sheet.getSelections()[0];
+                const curRow = sel ? sel.row : 0;
+                let next = _.findLast(resultArr, function (d) {
+                    return d.index < curRow;
+                });
+                if (!next) next = resultArr[resultArr.length - 1];
+                if (next.index !== curRow) {
+                    sheet.setSelection(next.index, sel ? sel.col : 0, 1, 1);
+                    sheet.showRow(next.index, spreadNS.VerticalPosition.center);
+                }
+            }
+        };
+        return {search, locateNext, locatePre};
+    })();
     // 台账模式加载部位明细数据
     if (checkTzMeasureType()) {
         $.divResizer({
@@ -615,10 +745,24 @@ $(document).ready(function() {
         SpreadJsObj.initSheet(posSpread.getActiveSheet(), posSpreadSetting);
         postData('/tender/' + getTenderId() + '/pos', null, function (data) {
             pos.loadDatas(data);
+            posOperationObj.loadCurPosData();
+            SpreadJsObj.resetTopAndSelect(posSpread.getActiveSheet());
+        });
+        $('#pos-keyword').bind('input propertychange', function () {
+            posSearch.search(this.value);
+        });
+        $('#search-pre-pos').click(function () {
+            posSearch.locatePre();
+        });
+        $('#search-next-pos').click(function () {
+            posSearch.locateNext();
         });
     }
     // 绑定部位明细编辑事件
     const posOperationObj = {
+        editStarting: function (e, info) {
+            posOperationObj.ledgerTreeNode = SpreadJsObj.getSelectObject(ledgerSpread.getActiveSheet());
+        },
         /**
          * 加载部位明细 根据当前台账选择节点
          */
@@ -646,7 +790,8 @@ $(document).ready(function() {
                     return;
                 }
 
-                const node = treeOperationObj.getSelectNode(ledgerSpread.getActiveSheet());
+                //const node = treeOperationObj.getSelectNode(ledgerSpread.getActiveSheet());
+                const node = posOperationObj.ledgerTreeNode;
                 if (!node) {
                     toast('数据错误,请选择台账节点后再试', 'error');
                     SpreadJsObj.reLoadRowData(info.sheet, info.row);
@@ -670,7 +815,9 @@ $(document).ready(function() {
                     } else if (!posData) {
                         if (newText && newText !== '') {
                             data.updateType = 'add';
-                            data.updateData = { name: newText, lid: node.id, tid: tender.id};
+                            const sortData = info.sheet.zh_data;
+                            const order = (!sortData || sortData.length === 0) ? 1 : Math.max(sortData[sortData.length - 1].porder + 1, sortData.length + 1);
+                            data.updateData = { name: newText, lid: node.id, tid: tender.id, porder: order};
                         } else {
                             return;
                         }
@@ -719,6 +866,7 @@ $(document).ready(function() {
                 const datas = [], posSelects = [];
                 const sel = sheet.getSelections()[0];
                 for (let iRow = sel.row; iRow < sel.row + sel.rowCount; iRow++) {
+                    let bDel = false;
                     const node = sortData[iRow];
                     if (node) {
                         const data = {id: node.id};
@@ -728,21 +876,30 @@ $(document).ready(function() {
                                 toast('部位名称不能为空', 'error');
                                 return;
                             }
-                            data[colSetting.field] = null;
+                            const style = sheet.getStyle(iRow, iCol);
+                            if (!style.locked) {
+                                const colSetting = sheet.zh_setting.cols[iCol];
+                                data[colSetting.field] = null;
+                                bDel = true;
+                            }
+                        }
+                        if (bDel) {
+                            datas.push(data);
+                            posSelects.push(node);
                         }
-                        datas.push(data);
-                        posSelects.push(node);
                     }
                 }
-                postData('/tender/' + getTenderId() + '/pos/update', {updateType: 'update', updateData: datas}, function (result) {
-                    pos.updateDatas(result.pos);
-                    posOperationObj.loadCurPosData();
-                    const loadResult = ledgerTree.loadPostData(result.ledger);
-                    treeOperationObj.refreshTree(ledgerSpread.getActiveSheet(), loadResult);
-                    treeOperationObj.refreshOperationValid(ledgerSpread.getActiveSheet());
-                }, function () {
-                    posOperationObj.loadCurPosData();
-                });
+                if (datas.length > 0) {
+                    postData('/tender/' + getTenderId() + '/pos/update', {updateType: 'update', updateData: datas}, function (result) {
+                        pos.updateDatas(result.pos);
+                        posOperationObj.loadCurPosData();
+                        const loadResult = ledgerTree.loadPostData(result.ledger);
+                        treeOperationObj.refreshTree(ledgerSpread.getActiveSheet(), loadResult);
+                        treeOperationObj.refreshOperationValid(ledgerSpread.getActiveSheet());
+                    }, function () {
+                        posOperationObj.loadCurPosData();
+                    });
+                }
             }
         },
         /**
@@ -758,15 +915,19 @@ $(document).ready(function() {
             const row = selection[0].row, count = selection[0].rowCount;
             const sortData = sheet.zh_data;
             for (let iRow = 0; iRow < count; iRow++) {
-                data.updateData.push(sortData[iRow + row].id);
+                if (sortData[iRow + row]) {
+                    data.updateData.push(sortData[iRow + row].id);
+                }
+            }
+            if (data.updateData.length > 0) {
+                postData('/tender/' + getTenderId() + '/pos/update', data, function (result) {
+                    pos.removeDatas(result.pos);
+                    sheet.deleteRows(row, count);
+                    const loadResult = ledgerTree.loadPostData(result.ledger);
+                    treeOperationObj.refreshTree(ledgerSpread.getActiveSheet(), loadResult);
+                    treeOperationObj.refreshOperationValid(ledgerSpread.getActiveSheet());
+                });
             }
-            postData('/tender/' + getTenderId() + '/pos/update', data, function (result) {
-                pos.removeDatas(result.pos);
-                sheet.deleteRows(row, count);
-                const loadResult = ledgerTree.loadPostData(result.ledger);
-                treeOperationObj.refreshTree(ledgerSpread.getActiveSheet(), loadResult);
-                treeOperationObj.refreshOperationValid(ledgerSpread.getActiveSheet());
-            });
         },
         /**
          * 粘贴单元格响应事件
@@ -796,9 +957,10 @@ $(document).ready(function() {
                         return;
                     }
                 }
+                const lastOrder = sortData.length > 0 ? sortData[sortData.length - 1].porder + 1 : 1;
                 for (let iRow = 0; iRow < info.cellRange.rowCount; iRow++) {
                     const curRow = info.cellRange.row + iRow;
-                    const posData = curRow >= sortData.length ? {lid: node.id} : {id: sortData[curRow].id, lid: node.id};
+                    const posData = curRow >= sortData.length ? {lid: node.id, porder: lastOrder + curRow - sortData.length} : {id: sortData[curRow].id, lid: node.id};
                     for (let iCol = 0; iCol < info.cellRange.colCount; iCol++) {
                         const curCol = info.cellRange.col + iCol;
                         const colSetting = info.sheet.zh_setting.cols[curCol];
@@ -816,15 +978,17 @@ $(document).ready(function() {
                     treeOperationObj.refreshTree(ledgerSpread.getActiveSheet(), loadResult);
                     posOperationObj.loadCurPosData();
                     treeOperationObj.refreshOperationValid(ledgerSpread.getActiveSheet());
+                }, function () {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
                 });
             }
         },
     };
-    posOperationObj.loadCurPosData();
     if (!posSpreadSetting.readOnly) {
         SpreadJsObj.addDeleteBind(posSpread, posOperationObj.deletePress);
-        posSpread.bind(GC.Spread.Sheets.Events.EditEnded, posOperationObj.editEnded);
-        posSpread.bind(GC.Spread.Sheets.Events.ClipboardPasted, posOperationObj.clipboardPasted);
+        posSpread.bind(spreadNS.Events.EditStarting, posOperationObj.editStarting);
+        posSpread.bind(spreadNS.Events.EditEnded, posOperationObj.editEnded);
+        posSpread.bind(spreadNS.Events.ClipboardPasted, posOperationObj.clipboardPasted);
         // 右键菜单
         $.contextMenu({
             selector: '#pos-spread',
@@ -851,6 +1015,8 @@ $(document).ready(function() {
                 },
             }
         });
+    } else {
+        SpreadJsObj.forbiddenSpreadContextMenu('#pos-spread', posSpread);
     }
 
     let stdChapter, stdBills, dealBills, searchLedger;
@@ -913,7 +1079,7 @@ $(document).ready(function() {
             showSideTools(tab.hasClass('active'));
             if (tab.attr('content') === '#std-chapter') {
                 if (!stdChapter) {
-                    stdChapter = new stdLib($('#std-chapter-spread')[0], 'chapter', {
+                    stdChapter = new stdLib('#std-chapter-spread', 'chapter', {
                         id: 'chapter_id',
                         pid: 'pid',
                         order: 'order',
@@ -937,7 +1103,7 @@ $(document).ready(function() {
                 stdChapter.spread.refresh();
             } else if (tab.attr('content') === '#std-bills') {
                 if (!stdBills) {
-                    stdBills = new stdLib($('#std-bills-spread')[0], 'bills', {
+                    stdBills = new stdLib('#std-bills-spread', 'bills', {
                         id: 'bill_id',
                         pid: 'pid',
                         order: 'order',
@@ -961,7 +1127,7 @@ $(document).ready(function() {
                 stdBills.spread.refresh();
             } else if (tab.attr('content') === '#deal-bills') {
                 if (!dealBills) {
-                    dealBills = new DealBills($('#deal-bills-spread')[0], {
+                    dealBills = new DealBills('#deal-bills-spread', {
                         cols: [
                             {title: '清单编号', field: 'code', hAlign: 0, width: 120, formatter: '@', readOnly: true},
                             {title: '名称', field: 'name', hAlign: 0, width: 230, formatter: '@', readOnly: true},
@@ -989,7 +1155,7 @@ $(document).ready(function() {
                             {title: '单价', field: 'unit_price', hAlign: 2, width: 50, readOnly: true},
                             {title: '数量', field: 'quantity', hAlign: 2, width: 50, readOnly: true},
                         ],
-                        emptyRows: 3,
+                        emptyRows: 0,
                         headRows: 1,
                         headRowHeight: [40],
                         defaultRowHeight: 21,
@@ -1009,14 +1175,15 @@ $(document).ready(function() {
     });
 
     class stdLib {
-        constructor(obj, stdType, treeSetting, spreadSetting) {
-            this.obj = obj;
+        constructor(selector, stdType, treeSetting, spreadSetting) {
+            this.obj = $(selector)[0];
             this.url = '/std/' + stdType;
             this.treeSetting = treeSetting;
             treeSetting.preUrl = this.url;
             this.spreadSetting = spreadSetting;
             this.spread = SpreadJsObj.createNewSpread(this.obj);
             SpreadJsObj.initSheet(this.spread.getActiveSheet(), this.spreadSetting);
+            SpreadJsObj.forbiddenSpreadContextMenu(selector, this.spread);
             this.spread.getActiveSheet().bind(GC.Spread.Sheets.Events.CellDoubleClick, function (e, info) {
                 const stdSheet = info.sheet;
                 const mainSheet = ledgerSpread.getActiveSheet();
@@ -1024,10 +1191,17 @@ $(document).ready(function() {
 
                 const stdTree = stdSheet.zh_tree;
                 const stdNode = stdTree.nodes[info.row];
+
                 const mainTree = mainSheet.zh_tree;
                 const sel = mainSheet.getSelections()[0];
                 const mainNode = mainTree.nodes[sel.row];
                 if (!stdNode) { return; }
+                if (stdType === 'bills') {
+                    if (!(mainNode.b_code && mainNode.b_code !== '') && !mainTree.isLeafXmj(mainNode)) {
+                        toast('非最底层项目下,不应添加清单', 'warning');
+                        return;
+                    }
+                }
 
                 mainTree.postData('/tender/' + getTenderId() + '/ledger/add-by-std', mainNode, {
                     tender_id: mainNode.tender_id,
@@ -1050,14 +1224,15 @@ $(document).ready(function() {
         }
     }
     class DealBills {
-        constructor (obj, spreadSetting) {
+        constructor (selector, spreadSetting) {
             const self = this;
-            this.obj = obj;
+            this.obj = $(selector)[0];
             this.url = '/tender/' + getTenderId() + '/deal';
             this.spreadSetting = spreadSetting;
             this.spread = SpreadJsObj.createNewSpread(this.obj);
             SpreadJsObj.initSheet(this.spread.getActiveSheet(), this.spreadSetting);
-            this.spread.bind(spreadNS.Events.CellDoubleClick, function (e, info) {
+            if (!readOnly) {
+                this.spread.bind(spreadNS.Events.CellDoubleClick, function (e, info) {
                 const dealSheet = info.sheet;
                 const mainSheet = ledgerSpread.getActiveSheet();
 
@@ -1085,25 +1260,27 @@ $(document).ready(function() {
                     treeOperationObj.refreshOperationValid(mainSheet);
                 });
             });
+            }
+            SpreadJsObj.forbiddenSpreadContextMenu(selector, this.spread);
             $('#upload-deal-bills').click(function () {
-                const file = $('#deal-bills-file')[0];
-                const formData = new FormData();
-                formData.append('file', file.files[0]);
-                postDataWithFile(self.url+'/upload-excel', formData, function (data) {
-                    self.data = data;
-                    self.calculateData();
-                    SpreadJsObj.loadSheetData(self.spread.getActiveSheet(), 'data', data);
-                    $('#upload-deal').modal('hide');
-                }, function () {
-                    $('#upload-deal').modal('hide');
+                    const file = $('#deal-bills-file')[0];
+                    const formData = new FormData();
+                    formData.append('file', file.files[0]);
+                    postDataWithFile(self.url+'/upload-excel', formData, function (data) {
+                        self.data = data;
+                        //self.calculateData();
+                        SpreadJsObj.loadSheetData(self.spread.getActiveSheet(), 'data', data);
+                        $('#upload-deal').modal('hide');
+                    }, function () {
+                        $('#upload-deal').modal('hide');
+                    });
                 });
-            });
         }
         loadData () {
             const self = this;
             postData(this.url+'/get-data', {}, function (data) {
                 self.data = data;
-                self.calculateData();
+                //self.calculateData();
                 SpreadJsObj.loadSheetData(self.spread.getActiveSheet(), 'data', data);
             });
         }
@@ -1168,6 +1345,46 @@ $(document).ready(function() {
             this.initView();
             SpreadJsObj.initSheet(this.dealSpread.getActiveSheet(), this.dealSpreadSetting);
             SpreadJsObj.refreshColumnAlign(this.dealSpread.getActiveSheet());
+            $.contextMenu({
+                selector: '.batch-l-t',
+                build: function ($trigger, e) {
+                    const target = SpreadJsObj.safeRightClickSelection($trigger, e, self.qdSpread);
+                    return target.hitTestType === spreadNS.SheetArea.viewport || target.hitTestType === spreadNS.SheetArea.rowHeader;
+                },
+                items: {
+                    'create': {
+                        name: '新增行',
+                        icon: 'fa-sign-in',
+                        callback: function (key, opt) {
+                            const qdSheet = self.qdSpread.getActiveSheet();
+                            const posSheet = self.posSpread.getActiveSheet();
+                            qdSheet.addRows(qdSheet.getRowCount(), 1);
+                            const index = qdSheet.getRowCount();
+                            qdSheet.getCell(qdSheet.getRowCount() - 1, 0, spreadNS.SheetArea.rowHeader).text('清单' + index);
+                            posSheet.addColumns(posSheet.getColumnCount(), 1);
+                            posSheet.getCell(0, index + 2, spreadNS.SheetArea.colHeader).text('清单' + index);
+                        },
+                    },
+                    'delete': {
+                        name: '删除行',
+                        icon: 'fa-remove',
+                        callback: function (key, opt) {
+                            const qdSheet = self.qdSpread.getActiveSheet();
+                            const posSheet = self.posSpread.getActiveSheet();
+                            const sel = qdSheet.getSelections()[0];
+                            qdSheet.deleteRows(sel.row, sel.rowCount);
+                            posSheet.deleteColumns(sel.row + 2, sel.rowCount);
+                            for (let iRow = 0, iLen = qdSheet.getRowCount(); iRow < iLen; iRow++) {
+                                qdSheet.getCell(iRow, 0, spreadNS.SheetArea.rowHeader).text('清单' + (iRow+1));
+                            }
+                            for (let iCol = 0, iLen = posSheet.getColumnCount() - 2; iCol < iLen; iCol++) {
+                                posSheet.getCell(0, iCol + 2, spreadNS.SheetArea.colHeader).text('清单' + (iCol+1));
+                            }
+                            qdSheet.setSelection(sel.row, sel.col, 1, 1);
+                        },
+                    },
+                }
+            });
             // 拉取签约清单数据
             if (dealBills) {
                 SpreadJsObj.loadSheetData(this.dealSpread.getActiveSheet(), 'data', dealBills.data);
@@ -1213,7 +1430,6 @@ $(document).ready(function() {
                     insertData.batchType = (select.code && select.code !== '') ? 'child' : 'next';
                     insertData.id = select[ledgerTree.setting.id];
                     insertData.batchData = self.getBatchData();
-                    console.log(insertData.batchData);
                     postData('/tender/' + getTenderId() + '/ledger/batch-insert', insertData, function (data) {
                         pos.updateDatas(data.pos);
                         const result = ledgerTree.loadPostData(data.ledger);
@@ -1274,7 +1490,7 @@ $(document).ready(function() {
                         qd.pos.push({
                             name: posSheet.getText(iPosRow, 0),
                             drawing_code: posSheet.getText(iPosRow, 1),
-                            quantity: value,
+                            quantity: value, porder: qd.pos.length + 1,
                         });
                     }
                 }
@@ -1286,17 +1502,16 @@ $(document).ready(function() {
         constructor(obj, spreadSetting) {
             const self = this;
             this.obj = obj;
-            this.url = '/ledger/search';
             this.spreadSetting = spreadSetting;
             this.spread = SpreadJsObj.createNewSpread($('#search-result', this.obj)[0]);
             SpreadJsObj.initSheet(this.spread.getActiveSheet(), this.spreadSetting);
-
-            $('#searchLedger', this.obj).bind('click', function () {
-                const data = { keyword: $('input', self.obj).val() };
-                postData(preUrl + '/ledger/search', data, function (result) {
-                    SpreadJsObj.loadSheetData(self.spread.getActiveSheet(), 'data', result);
-                });
+            SpreadJsObj.forbiddenSpreadContextMenu('#search-result', this.spread);
+            $('input', this.obj).bind('keydown', function (e) {
+                if (e.keyCode == 13) {
+                    self.search();
+                }
             });
+            $('#searchLedger', this.obj).bind('click', () => {self.search()});
             this.spread.getActiveSheet().bind(GC.Spread.Sheets.Events.CellDoubleClick, function (e, info) {
                 const sheet = info.sheet;
                 const data = sheet.zh_data;
@@ -1305,15 +1520,24 @@ $(document).ready(function() {
                 const curBills = data[info.row];
                 if (!curBills) { return }
 
-                const mainSheet = ledgerSpread.getActiveSheet();
-                const mainTree = mainSheet.zh_tree;
-                mainTree.postData('locate', null, {id: curBills.ledger_id}, function (result) {
-                    treeOperationObj.refreshTree(mainSheet, result);
-                    treeOperationObj.refreshOperationValid(mainSheet);
-                    SpreadJsObj.locateTreeNode(mainSheet, curBills.ledger_id);
-                });
+                SpreadJsObj.locateTreeNode(ledgerSpread.getActiveSheet(), curBills.ledger_id, true);
             });
         }
+        search () {
+            const keyword = $('input', this.obj).val();
+            this.searchResult = [];
+            const sortData = SpreadJsObj.getSortData(ledgerSpread.getActiveSheet());
+            for (const node of sortData) {
+                if ((node.code && node.code.indexOf(keyword) > -1) ||
+                    node.b_code && node.b_code.indexOf(keyword) > -1 ||
+                    node.name && node.name.indexOf(keyword) > -1) {
+                    const data = JSON.parse(JSON.stringify(node));
+                    data.visible = true;
+                    this.searchResult.push(data);
+                }
+            }
+            SpreadJsObj.loadSheetData(this.spread.getActiveSheet(), 'data', this.searchResult);
+        }
     }
 
     $('#searchAccount').click(() => {
@@ -1498,6 +1722,32 @@ $(document).ready(function() {
     $('#hideSp').click(function () {
         $('#sp-list2').modal('hide');
     });
+    // 显示层次
+    (function (select, sheet) {
+        if (!sheet.zh_tree) return;
+        $(select).click(function () {
+            const tag = $(this).attr('tag');
+            const tree = sheet.zh_tree;
+            switch (tag) {
+                case "1":
+                case "2":
+                case "3":
+                case "4":
+                case "5":
+                    tree.expandByLevel(parseInt(tag));
+                    SpreadJsObj.refreshTreeRowVisible(sheet);
+                    break;
+                case "last":
+                    tree.expandByCustom(() => { return true; });
+                    SpreadJsObj.refreshTreeRowVisible(sheet);
+                    break;
+                case "leafXmj":
+                    tree.expandToLeafXmj();
+                    SpreadJsObj.refreshTreeRowVisible(sheet);
+                    break;
+            }
+        });
+    })('a[name=showLevel]', ledgerSpread.getActiveSheet());
 });
 
 // 检查上报情况

+ 54 - 18
app/public/js/ledger_audit.js

@@ -8,6 +8,7 @@
  * @version
  */
 
+const ckBillsSpread = window.location.pathname + '-billsSelect';
 function getTenderId() {
     return window.location.pathname.split('/')[2];
 }
@@ -19,18 +20,26 @@ function checkTzMeasureType () {
 $(document).ready(() => {
     autoFlashHeight();
     const ledgerSpread = SpreadJsObj.createNewSpread($('#ledger-spread')[0]);
-    const ledgerTree = createNewPathTree('base', {
+    const treeSetting = {
         id: 'ledger_id',
         pid: 'ledger_pid',
         order: 'order',
         level: 'level',
         rootId: -1,
         keys: ['id', 'tender_id', 'ledger_id'],
-        preUrl: '/ledger'
-    });
+        preUrl: '/ledger',
+    };
+    if (checkTzMeasureType()) {
+        treeSetting.calcFields = ['sgfh_tp', 'sjcl_tp', 'qtcl_tp', 'total_price'];
+    } else {
+        treeSetting.calcFields = ['deal_tp', 'sgfh_tp', 'sjcl_tp', 'qtcl_tp', 'total_price'];
+    }
+    const ledgerTree = createNewPathTree('fx', treeSetting);
     ledgerTree.loadDatas(ledger);
+    treeCalc.calculateAll(ledgerTree);
     SpreadJsObj.initSheet(ledgerSpread.getActiveSheet(), ledgerSpreadSetting);
     SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), 'tree', ledgerTree);
+    SpreadJsObj.loadTopAndSelect(ledgerSpread.getActiveSheet(), ckBillsSpread);
 
     // 初始化 部位明细
     const pos = new PosData({
@@ -38,6 +47,15 @@ $(document).ready(() => {
     });
     const posSpread = SpreadJsObj.createNewSpread($('#pos-spread')[0]);
 
+    const loadCurPosData = function () {
+        const node = SpreadJsObj.getSelectObject(ledgerSpread.getActiveSheet());
+        if (node) {
+            const posData = pos.ledgerPos[itemsPre + node.id] || [];
+            SpreadJsObj.loadSheetData(posSpread.getActiveSheet(), 'data', posData);
+        } else {
+            SpreadJsObj.loadSheetData(posSpread.getActiveSheet(), 'data', []);
+        }
+    };
     // 台账模式加载部位明细数据
     if (checkTzMeasureType()) {
         $.divResizer({
@@ -52,25 +70,17 @@ $(document).ready(() => {
         SpreadJsObj.initSheet(posSpread.getActiveSheet(), posSpreadSetting);
         postData('/tender/' + getTenderId() + '/pos', null, function (data) {
             pos.loadDatas(data);
+            loadCurPosData();
+            SpreadJsObj.resetTopAndSelect(posSpread.getActiveSheet());
         });
     }
 
     ledgerSpread.bind(GC.Spread.Sheets.Events.SelectionChanged, function (e, info) {
-        const sheet = ledgerSpread.getActiveSheet();
-        let node;
-        if (!sheet || !sheet.zh_tree) {
-            node = null;
-        } else {
-            const sel = sheet.getSelections()[0];
-            node = sheet.zh_tree.nodes[sel.row];
-        }
-
-        if (node) {
-            const posData = pos.ledgerPos[itemsPre + node.id] || [];
-            SpreadJsObj.loadSheetData(posSpread.getActiveSheet(), 'data', posData);
-        } else {
-            SpreadJsObj.loadSheetData(posSpread.getActiveSheet(), 'data', []);
-        }
+        loadCurPosData();
+        SpreadJsObj.saveTopAndSelect(info.sheet, ckBillsSpread);
+    });
+    ledgerSpread.bind(spreadNS.Events.TopRowChanged, function (e, info) {
+        SpreadJsObj.saveTopAndSelect(info.sheet, ckBillsSpread);
     });
 
     let dealBills;
@@ -159,4 +169,30 @@ $(document).ready(() => {
             });
         }
     }
+    // 显示层次
+    (function (select, sheet) {
+        if (!sheet.zh_tree) return;
+        $(select).click(function () {
+            const tag = $(this).attr('tag');
+            const tree = sheet.zh_tree;
+            switch (tag) {
+                case "1":
+                case "2":
+                case "3":
+                case "4":
+                case "5":
+                    tree.expandByLevel(parseInt(tag));
+                    SpreadJsObj.refreshTreeRowVisible(sheet);
+                    break;
+                case "last":
+                    tree.expandByCustom(() => { return true; });
+                    SpreadJsObj.refreshTreeRowVisible(sheet);
+                    break;
+                case "leafXmj":
+                    tree.expandToLeafXmj();
+                    SpreadJsObj.refreshTreeRowVisible(sheet);
+                    break;
+            }
+        });
+    })('a[name=showLevel]', ledgerSpread.getActiveSheet());
 });

+ 151 - 0
app/public/js/number-precision.js

@@ -0,0 +1,151 @@
+var NP = (function (exports) {
+    'use strict';
+
+    /**
+     * @desc 解决浮动运算问题,避免小数点后产生多位数和计算精度损失。
+     * 问题示例:2.3 + 2.4 = 4.699999999999999,1.0 - 0.9 = 0.09999999999999998
+     */
+    /**
+     * 把错误的数据转正
+     * strip(0.09999999999999998)=0.1
+     */
+    function strip(num, precision) {
+        if (precision === void 0) { precision = 12; }
+        return +parseFloat(num.toPrecision(precision));
+    }
+    /**
+     * Return digits length of a number
+     * @param {*number} num Input number
+     */
+    function digitLength(num) {
+        // Get digit length of e
+        var eSplit = num.toString().split(/[eE]/);
+        var len = (eSplit[0].split('.')[1] || '').length - (+(eSplit[1] || 0));
+        return len > 0 ? len : 0;
+    }
+    function powLength(num) {
+        var eSplit = num.toString().split(/[eE]/);
+        var rs = num.toString().reverse();
+        var rs1 = rs.replace('/^0+/g', '');
+        return rs.length - rs1.length;
+    }
+    /**
+     * 把小数转成整数,支持科学计数法。如果是小数则放大成整数
+     * @param {*number} num 输入数
+     */
+    function float2Fixed(num) {
+        if (num.toString().indexOf('e') === -1) {
+            return Number(num.toString().replace('.', ''));
+        }
+        var dLen = digitLength(num);
+        return dLen > 0 ? strip(num * Math.pow(10, dLen)) : num;
+    }
+    /**
+     * 检测数字是否越界,如果越界给出提示
+     * @param {*number} num 输入数
+     */
+    function checkBoundary(num) {
+        if (_boundaryCheckingState) {
+            if (num > Number.MAX_SAFE_INTEGER || num < Number.MIN_SAFE_INTEGER) {
+                console.warn(num + " is beyond boundary when transfer to integer, the results may not be accurate");
+            }
+        }
+    }
+    /**
+     * 精确乘法
+     */
+    function times(num1, num2) {
+        var others = [];
+        for (var _i = 2; _i < arguments.length; _i++) {
+            others[_i - 2] = arguments[_i];
+        }
+        if (others.length > 0) {
+            return times.apply(void 0, [times(num1, num2), others[0]].concat(others.slice(1)));
+        }
+        var num1Changed = float2Fixed(num1);
+        var num2Changed = float2Fixed(num2);
+        var baseNum = digitLength(num1) + digitLength(num2);
+        var leftValue = num1Changed * num2Changed;
+        checkBoundary(leftValue);
+        return leftValue / Math.pow(10, baseNum);
+    }
+    /**
+     * 精确加法
+     */
+    function plus(num1, num2) {
+        var others = [];
+        for (var _i = 2; _i < arguments.length; _i++) {
+            others[_i - 2] = arguments[_i];
+        }
+        if (others.length > 0) {
+            return plus.apply(void 0, [plus(num1, num2), others[0]].concat(others.slice(1)));
+        }
+        var baseNum = Math.pow(10, Math.max(digitLength(num1), digitLength(num2)));
+        return (times(num1, baseNum) + times(num2, baseNum)) / baseNum;
+        //return (num1 + num2) / baseNum;
+    }
+    /**
+     * 精确减法
+     */
+    function minus(num1, num2) {
+        var others = [];
+        for (var _i = 2; _i < arguments.length; _i++) {
+            others[_i - 2] = arguments[_i];
+        }
+        if (others.length > 0) {
+            return minus.apply(void 0, [minus(num1, num2), others[0]].concat(others.slice(1)));
+        }
+        var digit1 = digitLength(num1);
+        var digit2 = digitLength(num2);
+        var baseNum = Math.pow(10, Math.max(digit1, digit2));
+        return round((times(num1, baseNum) - times(num2, baseNum)) / baseNum, digit1>=digit2 ? digit1 : digit2);
+    }
+    /**
+     * 精确除法
+     */
+    function divide(num1, num2) {
+        var others = [];
+        for (var _i = 2; _i < arguments.length; _i++) {
+            others[_i - 2] = arguments[_i];
+        }
+        if (others.length > 0) {
+            return divide.apply(void 0, [divide(num1, num2), others[0]].concat(others.slice(1)));
+        }
+        var num1Changed = float2Fixed(num1);
+        var num2Changed = float2Fixed(num2);
+        checkBoundary(num1Changed);
+        checkBoundary(num2Changed);
+        return times((num1Changed / num2Changed), Math.pow(10, digitLength(num2) - digitLength(num1)));
+    }
+    /**
+     * 四舍五入
+     */
+    function round(num, ratio) {
+        var base = Math.pow(10, ratio);
+        return divide(Math.round(times(num, base)), base);
+    }
+    var _boundaryCheckingState = true;
+    /**
+     * 是否进行边界检查,默认开启
+     * @param flag 标记开关,true 为开启,false 为关闭,默认为 true
+     */
+    function enableBoundaryChecking(flag) {
+        if (flag === void 0) { flag = true; }
+        _boundaryCheckingState = flag;
+    }
+    var index = { strip: strip, plus: plus, minus: minus, times: times, divide: divide, round: round, digitLength: digitLength, float2Fixed: float2Fixed, enableBoundaryChecking: enableBoundaryChecking };
+
+    exports.strip = strip;
+    exports.plus = plus;
+    exports.minus = minus;
+    exports.times = times;
+    exports.divide = divide;
+    exports.round = round;
+    exports.digitLength = digitLength;
+    exports.float2Fixed = float2Fixed;
+    exports.enableBoundaryChecking = enableBoundaryChecking;
+    exports['default'] = index;
+
+    return exports;
+
+}({}));

+ 215 - 68
app/public/js/path_tree.js

@@ -1,4 +1,6 @@
 /**
+ * (需使用 decimal.min.js, zh_calc.js)
+ *
  * 构建pathTree
  * 可动态加载子节点,要求子节点获取接口按/xxx/get-children定义
  * @param {Object} setting - 设置
@@ -29,6 +31,8 @@ class PosData {
      */
     loadDatas(datas) {
         this.datas = datas;
+        this.items = {};
+        this.ledgerPos = {};
         for (const data of this.datas) {
             const key = itemsPre + data[this.setting.id];
             this.items[key] = data;
@@ -39,6 +43,9 @@ class PosData {
             }
             this.ledgerPos[masterKey].push(data);
         }
+        for (const prop in this.ledgerPos) {
+            this.resortLedgerPos(this.ledgerPos[prop]);
+        }
     }
 
     /**
@@ -47,7 +54,7 @@ class PosData {
      */
     updateDatas(data) {
         const datas = data instanceof Array ? data : [data];
-        const result = { create: [], update: [] };
+        const result = { create: [], update: [] }, resort = [];
         for (const d of datas) {
             const key = itemsPre + d[this.setting.id];
             if (!this.items[key]) {
@@ -67,8 +74,16 @@ class PosData {
                 }
                 result.update.push(pos);
             }
+            const masterKey = itemsPre + d[this.setting.ledgerId];
+            if (resort.indexOf(masterKey) === -1) {
+                resort.push(masterKey);
+            }
+        }
+        for (const s of resort) {
+            this.resortLedgerPos(this.ledgerPos[s]);
         }
         return result;
+
     }
 
     /**
@@ -118,6 +133,14 @@ class PosData {
         return this.ledgerPos[itemsPre + mid];
     }
 
+    resortLedgerPos(ledgerPos) {
+        if (ledgerPos instanceof Array) {
+            ledgerPos.sort(function (a, b) {
+                return a.porder - b.porder;
+            })
+        }
+    }
+
     /**
      * 计算全部
      */
@@ -203,7 +226,7 @@ class MasterPosData extends PosData {
      * @returns {Array}
      */
     loadMinorData(datas, fieldSuf, fields) {
-        if (!datas) { return; }
+        if (!datas) return;
         datas = datas instanceof Array ? datas : [datas];
         this.minorData[fieldSuf] = datas;
         const loadedData = [];
@@ -261,9 +284,8 @@ const createNewPathTree = function (type, setting) {
                     addSortNodes(nodes[i].children);
                 }
             };
-            self.nodes = [];
-            this.children = this.getChildren(null);
-            addSortNodes(this.getChildren(null));
+            this.nodes = [];
+            addSortNodes(this.children);
         }
         /**
          * 加载数据(初始化), 并给数据添加部分树结构必须数据
@@ -276,19 +298,32 @@ const createNewPathTree = function (type, setting) {
             this.datas = [];
             this.children = [];
             // 加载全部数据
+            datas.sort(function (a, b) {
+                return a.level - b.level;
+            });
             for (const data of datas) {
                 const keyName = itemsPre + data[this.setting.id];
                 if (!this.items[keyName]) {
-                    this.items[keyName] = JSON.parse(JSON.stringify(data));
-                    this.datas.push(this.items[keyName]);
+                    const item = JSON.parse(JSON.stringify(data));
+                    item.children = [];
+                    item.expanded = true;
+                    item.visible = true;
+                    this.items[keyName] = item;
+                    this.datas.push(item);
+                    if (item[setting.pid] === setting.rootId) {
+                        this.children.push(item);
+                    } else {
+                        const parent = this.getParent(item);
+                        if (parent) {
+                            parent.children.push(item);
+                        }
+                    }
                 }
             }
-            this.sortTreeNode();
-            for (const node of this.nodes) {
-                //node.expanded = node.children.length > 0;
-                node.expanded = true;
-                node.visible = true;
-            }
+            this.children.sort(function (a, b) {
+                return a.order - b.order;
+            });
+            this.sortTreeNode(true);
         }
 
         getItemsByIndex(index) {
@@ -310,6 +345,24 @@ const createNewPathTree = function (type, setting) {
         getParent (node) {
             return this.getItems(node[this.setting.pid]);
         };
+        getAllParents (node) {
+            const parents = [];
+            if (node.full_path && node.full_path !== '') {
+                const parentIds = node.full_path.split('.');
+                for (const id of parentIds) {
+                    if (id !== node[this.setting.id]) {
+                        parents.push(this.getItems(id));
+                    }
+                }
+            } else {
+                let vP = this.getParent(node);
+                while (vP) {
+                    parents.push(vP);
+                    vP = this.getParent(vP);
+                }
+            }
+            return parents;
+        }
 
         /**
          * 查找node的前兄弟节点
@@ -317,14 +370,11 @@ const createNewPathTree = function (type, setting) {
          * @returns {*}
          */
         getPreSiblingNode(node) {
+            if (!node) return null;
             const parent = this.getParent(node);
             const siblings = parent ? parent.children : this.children;
             const index = siblings.indexOf(node);
-            if (index > 0) {
-                return siblings[index - 1];
-            } else {
-                return null;
-            }
+            return (index > 0) ? siblings[index - 1] : null;
         }
         /**
          * 查找node的后兄弟节点
@@ -495,6 +545,23 @@ const createNewPathTree = function (type, setting) {
                 return n.level < level;
             });
         }
+
+        /**
+         * 自动展开节点node
+         * @param node
+         * @returns {*}
+         */
+        autoExpandNode(node) {
+            const parents = this.getAllParents(node);
+            const reload = [];
+            for (const p of parents) {
+                if (!p.expanded) {
+                    reload.push(p);
+                    this.setExpanded(p, true);
+                }
+            }
+            return reload;
+        }
     }
 
     class MeasureTree extends BaseTree {
@@ -614,6 +681,21 @@ const createNewPathTree = function (type, setting) {
                 }
             })
         }
+
+        /**
+         * 展开至计算项
+         */
+        expandByCalcFields() {
+            const self = this;
+            this.expandByCustom(function (node) {
+                for (const field of self.setting.calcFields) {
+                    if (node[field]) {
+                        return true;
+                    }
+                }
+                return false;
+            })
+        }
     }
 
     class LedgerTree extends FxTree {
@@ -631,14 +713,14 @@ const createNewPathTree = function (type, setting) {
                 let node = this.getItems(data[this.setting.id]);
                 if (node) {
                     for (const prop in node) {
-                        if (prop === this.setting.pid && data[prop] !== node[prop]) {
-
-                        }
                         if (data[prop] !== undefined && data[prop] !== node[prop]) {
                             if (prop === this.setting.pid) {
                                 loadedData.push(this.getItems(node[this.setting.pid]));
                                 loadedData.push(this.getItems(data[this.setting.pid]));
                             }
+                            if (prop === this.setting.order) {
+                                loadedData = loadedData.concat(this.getPosterity(node));
+                            }
                             node[prop] = data[prop];
                         }
                     }
@@ -946,22 +1028,24 @@ const createNewPathTree = function (type, setting) {
                 data.id = node[self.setting.id];
             }
             postData(url, data, function (datas) {
-                const result = {};
-                if (datas.update) {
-                    result.update = self._updateData(datas.update);
-                }
-                if (datas.create) {
-                    result.create = self._loadData(datas.create);
-                }
-                if (datas.delete) {
-                    result.delete = self._freeData(datas.delete);
-                }
-                if (datas.expand) {
-                    const [create, update] = self._loadExpandData(datas.expand);
-                    result.create = result.create ? result.create.concat(create) : create;
-                    result.expand = update;
-                }
-                callback(result);
+                const refreshData = self.loadPostData(datas);
+                callback(refreshData);
+                // const result = {};
+                // if (datas.update) {
+                //     result.update = self._updateData(datas.update);
+                // }
+                // if (datas.create) {
+                //     result.create = self._loadData(datas.create);
+                // }
+                // if (datas.delete) {
+                //     result.delete = self._freeData(datas.delete);
+                // }
+                // if (datas.expand) {
+                //     const [create, update] = self._loadExpandData(datas.expand);
+                //     result.create = result.create ? result.create.concat(create) : create;
+                //     result.expand = update;
+                // }
+                // callback(result);
             });
         };
     }
@@ -1023,6 +1107,42 @@ const createNewPathTree = function (type, setting) {
          * @return {Array} 加载到树的数据
          * @privateA
          */
+        _updateData (datas) {
+            datas = datas instanceof Array ? datas : [datas];
+            let loadedData = [];
+            for (const data of datas) {
+                let node = this.getItems(data[this.setting.id]);
+                if (node) {
+                    for (const prop in node) {
+                        if (prop === this.setting.pid && data[prop] !== node[prop]) {
+
+                        }
+                        if (data[prop] !== undefined && data[prop] !== node[prop]) {
+                            if (prop === this.setting.pid) {
+                                loadedData.push(this.getItems(node[this.setting.pid]));
+                                loadedData.push(this.getItems(data[this.setting.pid]));
+                            }
+                            node[prop] = data[prop];
+                        }
+                    }
+                    loadedData.push(node);
+                }
+            }
+            loadedData = _.uniq(loadedData);
+            for (const node of loadedData) {
+                node.children = this.getChildren(node);
+                node.expanded = node.children.length === 0 ? true : node.children[0].visible;
+            }
+            this.sortTreeNode(true);
+            return loadedData;
+        };
+
+        /**
+         * 加载数据(动态),只加载不同部分
+         * @param {Array} datas
+         * @return {Array} 加载到树的数据
+         * @privateA
+         */
         _updateStageData (datas) {
             datas = datas instanceof Array ? datas : [datas];
             const loadedData = [];
@@ -1062,6 +1182,23 @@ const createNewPathTree = function (type, setting) {
             }
         }
 
+        _updateDgnData(datas) {
+            datas = datas instanceof Array ? datas : [datas];
+            let loadedData = [];
+            for (const data of datas) {
+                let node = this.getStageItems(data.id);
+                if (node) {
+                    for (const prop in data) {
+                        if (data[prop] !== undefined && data[prop] !== node[prop]) {
+                            node[prop] = data[prop];
+                        }
+                    }
+                    loadedData.push(node);
+                }
+            }
+            return loadedData;
+        }
+
         /**
          * 提交数据至后端,返回的前端树结构应刷新的部分
          * StageTree仅有更新CurStage部分,不需要增删
@@ -1071,10 +1208,18 @@ const createNewPathTree = function (type, setting) {
          */
         loadPostStageData(data) {
             let result, parents = [];
-            if (data) {
-                result = this._updateStageData(data);
+            if (data.bills) {
+                result = this._updateData(data.bills);
+                this._getNodesParents(parents, result);
+            }
+            if (data.curStageData) {
+                result = this._updateStageData(data.curStageData);
                 this._getNodesParents(parents, result);
             }
+            if (data.dgn) {
+                const dgnResult = this._updateDgnData(data.dgn);
+                result = result ? result.concat(dgnResult) : dgnResult;
+            }
             result = result ? result.concat(parents) : parents;
             result.sort((a, b) => {
                 return b.level - a.level;
@@ -1128,8 +1273,11 @@ const createNewPathTree = function (type, setting) {
          * @param {Array} fields - 关联字段
          * @returns {Array}
          */
-        loadMinorData(datas, fieldSuf, fields) {
-            if (!datas) { return; }
+        loadMinorData(datas, fieldSuf, fields, calcFields) {
+            for (const cf of calcFields) {
+                this.setting.calcFields.push(cf+fieldSuf);
+            }
+            if (!datas) return;
             datas = datas instanceof Array ? datas : [datas];
             this.minorData[fieldSuf] = datas;
             const loadedData = [];
@@ -1146,25 +1294,12 @@ const createNewPathTree = function (type, setting) {
             }
             return loadedData;
         }
-
-        /**
-         * 展开至最底层项目节
-         */
-        expandByCalcFields() {
-            const self = this;
-            this.expandByCustom(function (node) {
-                for (const field of self.setting.calcFields) {
-                    if (node[field]) {
-                        return true;
-                    }
-                }
-                return false;
-            })
-        }
     }
 
     if (type === 'base') {
         return new BaseTree(setting);
+    } else if (type === 'fx') {
+        return new FxTree(setting);
     } else if (type === 'stage') {
         return new StageTree(setting);
     } else if (type === 'ledger') {
@@ -1177,6 +1312,21 @@ const createNewPathTree = function (type, setting) {
 };
 
 const treeCalc = {
+    mapTreeNode: function (tree) {
+        let map = {}, maxLevel = 0;
+        for (const node of tree.nodes) {
+            let levelArr = map[node.level];
+            if (!levelArr) {
+                levelArr = [];
+                map[node.level] = levelArr;
+            }
+            if (node.level > maxLevel) {
+                maxLevel = node.level;
+            }
+            levelArr.push(node);
+        }
+        return [maxLevel, map];
+    },
     getMaxLevel: function (tree) {
         return Math.max.apply(Math, tree.datas.map(function(o) {return o.level}));
     },
@@ -1184,22 +1334,13 @@ const treeCalc = {
         if (node.children && node.children.length > 0) {
             const gather = node.children.reduce(function (rst, x) {
                 const result = {};
-                const fieldCalc = function (field) {
-                    if (rst[field]) {
-                        result[field] = x[field] ? _.round(rst[field] + x[field], 6) : rst[field];
-                    } else {
-                        result[field] = x[field] ? x[field] : undefined;
-                    }
-                };
                 for (const cf of tree.setting.calcFields) {
-                    result[cf] = _.round(_.add(rst[cf], x[cf]), 8);
-                    //fieldCalc(cf);
+                    result[cf] = ZhCalc.add(rst[cf], x[cf]);
                 }
                 return result;
             });
             // 汇总子项
             for (const cf of tree.setting.calcFields) {
-                //node[cf] = _.sumBy(node.children, cf);
                 if (gather[cf]) {
                     node[cf] = gather[cf];
                 } else {
@@ -1219,8 +1360,14 @@ const treeCalc = {
         }
     },
     calculateAll: function (tree) {
-        for (let i = this.getMaxLevel(tree); i >= 0; i--) {
-            this.calculateLevelNode(tree, i);
+        const [maxLevel, levelMap] = this.mapTreeNode(tree);
+        for (let i = maxLevel; i >= 0; i--) {
+            const levelNodes = levelMap[i];
+            if (levelNodes && levelNodes.length > 0) {
+                for (const node of levelNodes) {
+                    this.calculateNode(tree, node);
+                }
+            }
         }
     },
     calculateParent: function (tree, node) {

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

@@ -0,0 +1,715 @@
+'use strict';
+
+/**
+ * 台账修订页面js
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const ckBillsSpread = window.location.pathname + '-billsSelect';
+
+$(document).ready(() => {
+    autoFlashHeight();
+    // 初始化spread
+    const billsSpread = SpreadJsObj.createNewSpread($('#bills-spread')[0]);
+    const billsSheet = billsSpread.getActiveSheet();
+    SpreadJsObj.initSheet(billsSheet, billsSpreadSetting);
+    const posSpread = SpreadJsObj.createNewSpread($('#pos-spread')[0]);
+    const posSheet = posSpread.getActiveSheet();
+    SpreadJsObj.initSheet(posSheet, posSpreadSetting);
+    // 初始化 清单树结构
+    const treeSetting = {
+        id: 'ledger_id',
+        pid: 'ledger_pid',
+        order: 'order',
+        level: 'level',
+        rootId: -1,
+        keys: ['id', 'tender_id', 'ledger_id'],
+        calcFields: ['sgfh_tp', 'sjcl_tp', 'qtcl_tp', 'total_price'],
+    };
+    if (!isTz) {
+        treeSetting.calcFields.push('deal_tp');
+    }
+    treeSetting.calcFun = function (node) {
+        node.dgn_price = ZhCalc.round(ZhCalc.div(node.total_price, node.dgn_qty1), 2);
+    };
+    const billsTree = createNewPathTree('ledger', treeSetting);
+    billsTree.loadDatas(billsData);
+    treeCalc.calculateAll(billsTree);
+    // 加载至spread
+    SpreadJsObj.loadSheetData(billsSheet, SpreadJsObj.DataType.Tree, billsTree);
+    SpreadJsObj.loadTopAndSelect(billsSheet, ckBillsSpread);
+    // 初始化 部位明细
+    const pos = new PosData({ id: 'id', ledgerId: 'lid' });
+    pos.loadDatas(posData);
+
+    const billsTreeSpreadObj = {
+        /**
+         * 刷新顶部按钮是否可用
+         * @param sheet
+         * @param selections
+         */
+        refreshOperationValid: function (sheet) {
+            const setObjEnable = function (obj, enable) {
+                if (enable) {
+                    obj.removeClass('disabled');
+                } else {
+                    obj.addClass('disabled');
+                }
+            };
+            const tree = sheet.zh_tree;
+            const node = SpreadJsObj.getSelectObject(sheet);
+            const preNode = tree.getPreSiblingNode(node);
+            const valid = !sheet.zh_setting.readOnly;
+
+            setObjEnable($('a[type="add"]'), valid);
+            setObjEnable($('a[type="delete"]'), valid && node);
+            setObjEnable($('a[type="up-move"]'), valid && node && preNode);
+            setObjEnable($('a[type="down-move"]'), valid && node && !tree.isLastSibling(node));
+            if (isTz) {
+                const posRange = node ? pos.getLedgerPos(node.id) : [];
+                setObjEnable($('a[type="up-level"]'), valid && node && tree.getParent(node) && node.level > 2 && (!posRange || posRange.length === 0));
+                const preNodePosRange = preNode ? pos.getLedgerPos(preNode.id) : [];
+                setObjEnable($('a[type="down-level"]'), valid && node && preNode && (!preNodePosRange || preNodePosRange.length === 0));
+            } else {
+                setObjEnable($('#up-level'), valid && node && tree.getParent(node));
+                setObjEnable($('#down-level'), valid && node && preNode);
+            }
+            setObjEnable($('#cut'), valid);
+            setObjEnable($('#paste'), valid);
+        },
+        /**
+         *
+         * @param sheet
+         * @param data
+         */
+        refreshTree: function (sheet, data) {
+            SpreadJsObj.massOperationSheet(sheet, function () {
+                const tree = sheet.zh_tree;
+                // 处理删除
+                if (data.delete) {
+                    for (const d of data.delete) {
+                        sheet.deleteRows(tree.nodes.indexOf(d), 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);
+                            }
+                        }
+                    }
+                }
+            });
+        },
+        selectionChanged: function (e, info) {
+            if (info.newSelections[0].row !== info.oldSelections[0].row) {
+                billsTreeSpreadObj.refreshOperationValid(info.sheet);
+                posSpreadObj.loadCurPosData();
+                SpreadJsObj.saveTopAndSelect(posSheet, ckBillsSpread);
+                SpreadJsObj.resetTopAndSelect(billsSheet);
+                //posSearch.search($('#pos-keyword').val());
+            }
+        },
+        /**
+         * 新增节点
+         * @param spread
+         */
+        baseOpr: function (sheet, type) {
+            const self = this;
+            const tree = sheet.zh_tree;
+            const node = SpreadJsObj.getSelectObject(sheet);
+            if (!tree || !node) return;
+
+            postData(window.location.pathname + '/base-opr', {id: node.ledger_id, postType: type}, function (result) {
+                const refreshData = tree.loadPostData(result);
+                self.refreshTree(sheet, refreshData);
+                if (['up-move', 'down-move', 'up-level', 'down-level'].indexOf(type) > -1) {
+                    const sel = sheet.getSelections()[0];
+                    if (sel) {
+                        sheet.setSelection(tree.nodes.indexOf(node), sel.col, sel.rowCount, sel.colCount);
+                    }
+                }
+                self.refreshOperationValid(sheet);
+            });
+        },
+        /**
+         * 编辑单元格响应事件
+         * @param {Object} e
+         * @param {Object} info
+         */
+        editEnded: function (e, info) {
+            if (info.sheet.zh_setting) {
+                const col = info.sheet.zh_setting.cols[info.col];
+                const sortData = info.sheet.zh_dataType === 'tree' ? info.sheet.zh_tree.nodes : info.sheet.zh_data;
+                const node = sortData[info.row];
+                const data = {
+                    id: node.id,
+                    tender_id: node.tender_id,
+                    ledger_id: node.ledger_id
+                };
+                // 未改变值则不提交
+                const orgValue = node[col.field];
+                if (orgValue == info.editingText || ((!orgValue || orgValue === '') && (info.editingText === ''))) {
+                    return;
+                }
+                // 台账模式,检查部位明细相关
+                if (checkTzMeasureType()) {
+                    if (col.field === 'sgfh_qty' || col.field === 'sgfh_tp' ||
+                        col.field === 'sjcl_qty' || col.field === 'sjcl_tp' ||
+                        col.field === 'qtcl_qty' || col.field === 'qtcl_tp') {
+                        if (!node.children || node.children.length ===0) {
+                            const lPos = pos.getLedgerPos(node.id);
+                            if (lPos && lPos.length > 0) {
+                                toast('清单含有部位明细,不可修改施工图复核数量', 'error');
+                                SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                                return;
+                            }
+                        }
+                    }
+                    if (col.field === 'b_code' && (info.editingText === '' || !info.editingText)) {
+                        const lPos = pos.getLedgerPos(node.id);
+                        if (lPos && lPos.length > 0) {
+                            toast('清单含有部位明细,请先删除部位明细,再删除清单编号', 'error');
+                            SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                            return;
+                        }
+                    }
+                }
+                // 获取更新数据
+                if (info.editingText) {
+                    data[col.field] = col.type === 'Number' ? parseFloat(info.editingText) : info.editingText.replace('\n', '');
+                } else {
+                    data[col.field] = null;
+                }
+                // 更新至服务器
+                info.sheet.zh_tree.update('/tender/' + getTenderId() + '/ledger/update', data, function (result) {
+                    treeOperationObj.refreshTree(info.sheet, result);
+                });
+            }
+        },
+        topRowChanged: function (e, info) {
+            SpreadJsObj.saveTopAndSelect(info.sheet, ckBillsSpread);
+        },
+    };
+    billsTreeSpreadObj.refreshOperationValid(billsSheet);
+    billsSpread.bind(spreadNS.Events.SelectionChanged, billsTreeSpreadObj.selectionChanged);
+    billsSpread.bind(spreadNS.Events.topRowChanged, billsTreeSpreadObj.topRowChanged);
+    if (!readOnly) {
+        // 增删上下移升降级
+        $('a[name="base-opr"]').click(function () {
+            billsTreeSpreadObj.baseOpr(billsSheet, this.getAttribute('type'));
+        });
+        let batchInsertObj;
+        // 右键菜单
+        $.contextMenu({
+            selector: '#bills-spread',
+            build: function ($trigger, e) {
+                const target = SpreadJsObj.safeRightClickSelection($trigger, e, billsSpread);
+                return target.hitTestType === spreadNS.SheetArea.viewport || target.hitTestType === spreadNS.SheetArea.rowHeader;
+            },
+            items: {
+                'batchInsertBillsPos': {
+                    name: '批量插入清单-部位',
+                    icon: 'fa-sign-in',
+                    disabled: function (key, opt) {
+                        const select = SpreadJsObj.getSelectObject(billsSheet);
+                        if (select) {
+                            if (select.code && select.code !== '') {
+                                return !billsTree.isLeafXmj(select);
+                            } else {
+                                const parent = billsTree.getParent(select);
+                                return !(parent && billsTree.isLeafXmj(parent));
+                            }
+                        } else {
+                            return false;
+                        }
+                    },
+                    callback: function (key, opt) {
+                        if (!batchInsertObj) {
+                            batchInsertObj = new BatchInsertBillsPosObj($('#batch'));
+                        } else {
+                            batchInsertObj.initView();
+                        }
+                        $('#batch').modal('show');
+                    }
+                },
+            }
+        });
+    }
+
+    const posSpreadObj = {
+        /**
+         * 加载部位明细 根据当前台账选择节点
+         */
+        loadCurPosData: function () {
+            const node = SpreadJsObj.getSelectObject(billsSheet);
+            if (node) {
+                const posData = pos.getLedgerPos(node.id) || [];
+                SpreadJsObj.loadSheetData(posSheet, 'data', posData);
+            } else {
+                SpreadJsObj.loadSheetData(posSheet, 'data', []);
+            }
+            SpreadJsObj.resetFieldReadOnly(posSheet);
+        },
+        editStarting: function (e, info) {
+            posSpreadObj.ledgerTreeNode = SpreadJsObj.getSelectObject(billsSheet);
+        },
+    };
+    posSpreadObj.loadCurPosData();
+    SpreadJsObj.resetTopAndSelect(posSheet);
+
+    $.divResizer({
+        select: '#revise-resize',
+        callback: function () {
+            billsSpread.refresh();
+            let bcontent = $(".bcontent-wrap") ? $(".bcontent-wrap").height() : 0;
+            $(".sp-wrap").height(bcontent-40);
+            posSpread.refresh();
+        }
+    });
+
+    class stdLib {
+        constructor(obj, stdType, treeSetting, spreadSetting) {
+            this.obj = obj;
+            this.url = '/std/' + stdType;
+            this.treeSetting = treeSetting;
+            treeSetting.preUrl = this.url;
+            this.spreadSetting = spreadSetting;
+            this.spread = SpreadJsObj.createNewSpread(this.obj);
+            SpreadJsObj.initSheet(this.spread.getActiveSheet(), this.spreadSetting);
+            this.spread.getActiveSheet().bind(GC.Spread.Sheets.Events.CellDoubleClick, function (e, info) {
+                const stdSheet = info.sheet;
+                const mainSheet = ledgerSpread.getActiveSheet();
+                if (!stdSheet.zh_setting || !stdSheet.zh_tree || !mainSheet.zh_tree) { return; }
+
+                const stdTree = stdSheet.zh_tree;
+                const stdNode = stdTree.nodes[info.row];
+                const mainTree = mainSheet.zh_tree;
+                const sel = mainSheet.getSelections()[0];
+                const mainNode = mainTree.nodes[sel.row];
+                if (!stdNode) { return; }
+
+                mainTree.postData('/tender/' + getTenderId() + '/ledger/add-by-std', mainNode, {
+                    tender_id: mainNode.tender_id,
+                    stdType: stdType,
+                    stdLibId: stdNode.list_id,
+                    stdNode: stdTree.getNodeKey(stdNode)
+                }, function (result) {
+                    treeOperationObj.refreshTree(mainSheet, result);
+                    treeOperationObj.refreshOperationValid(mainSheet);
+                });
+            });
+            this.pathTree = createNewPathTree('base', this.treeSetting);
+        }
+        loadLib (listId) {
+            const self = this;
+            postData(this.url+'/get-data', {list_id: listId}, function (data) {
+                self.pathTree.loadDatas(data);
+                SpreadJsObj.loadSheetData(self.spread.getActiveSheet(), 'tree', self.pathTree);
+            });
+        }
+    }
+    class DealBills {
+        constructor (selector, spreadSetting) {
+            const self = this;
+            this.loaded = false;
+            this.obj = $(selector)[0];
+            this.url = '/tender/' + window.location.pathname.split('/')[2] + '/deal';
+            this.spreadSetting = spreadSetting;
+            this.spread = SpreadJsObj.createNewSpread(this.obj);
+            SpreadJsObj.initSheet(this.spread.getActiveSheet(), this.spreadSetting);
+            this.spread.bind(spreadNS.Events.CellDoubleClick, function (e, info) {
+                const dealSheet = info.sheet;
+                const mainSheet = ledgerSpread.getActiveSheet();
+
+                const dealBills = SpreadJsObj.getSelectObject(dealSheet);
+                if (!dealBills) { return; }
+                const mainTree = mainSheet.zh_tree;
+                const mainNode = SpreadJsObj.getSelectObject(mainSheet);
+                if (!mainNode || !mainTree) { return; }
+
+                if (mainNode.code && mainNode.code !== '' && !mainTree.isLeafXmj(mainNode)) {
+                    toast('非最底层项目下,不应添加清单', 'error');
+                    return;
+                }
+
+                postData('/tender/' + getTenderId() + '/ledger/add-by-deal', {
+                    id: mainNode.ledger_id,
+                    type: mainNode.code ? 'child' : 'next',
+                    dealBills: {
+                        b_code: dealBills.code, name: dealBills.name, unit: dealBills.unit,
+                        unit_price: dealBills.unit_price,
+                    },
+                }, function (result) {
+                    const refreshData = mainTree.loadPostData(result);
+                    treeOperationObj.refreshTree(mainSheet, refreshData);
+                    treeOperationObj.refreshOperationValid(mainSheet);
+                });
+            });
+            SpreadJsObj.forbiddenSpreadContextMenu(selector, this.spread);
+        }
+        loadData () {
+            if (this.loaded) return;
+            const self = this;
+            postData(this.url+'/get-data', {}, function (data) {
+                self.data = data;
+                //self.calculateData();
+                SpreadJsObj.loadSheetData(self.spread.getActiveSheet(), 'data', data);
+                self.loaded = true;
+            });
+        }
+        calculateData () {
+            if (this.data) {
+                for (const d of this.data) {
+                    d.total_price = _.multiply(d.quantity, d.unit_price);
+                }
+            }
+        }
+    }
+    class BatchInsertBillsPosObj {
+        constructor (obj) {
+            const self = this;
+            this.obj = obj;
+            this.billsCount = 6;
+            this.posCount = 1000;
+            // 初始化 清单编号窗口 参数
+            this.qdSpreadSetting = {
+                cols: [
+                    {title: '编号', field: 'code', hAlign: 0, width: 80, formatter: '@'},
+                    {title: '名称', field: 'name', hAlign: 0, width: 120, formatter: '@'},
+                    {title: '单位', field: 'unit', hAlign: 1, width: 50, formatter: '@'},
+                    {title: '单价', field: 'unit_price', hAlign: 2, width: 50},
+                    {title: '图册号', field: 'name', hAlign: 0, width: 60, formatter: '@'},
+                ],
+                emptyRows: this.billsCount,
+                headRows: 1,
+                headRowHeight: [40],
+            };
+            this.qdSpread = SpreadJsObj.createNewSpread($('.batch-l-t', this.obj)[0]);
+            // 初始化 部位数量复核表 参数
+            this.posSpreadSetting = {
+                cols: [
+                    {title: '部位', field: 'bw', hAlign: 0, width: 80, formatter: '@'},
+                    {title: '图册号', field: 'drawingCode', hAlign: 0, formatter: '@', width: 60},
+                ],
+                emptyRows: this.posCount,
+                headRows: 1,
+                headRowHeight: [40],
+            };
+            for (let iNum = 1; iNum <= this.billsCount; iNum++) {
+                this.posSpreadSetting.cols.push(
+                    {title: '清单' + iNum, field: 'bills' + iNum, hAlign: 2, width: 50}
+                )
+            }
+            this.posSpread = SpreadJsObj.createNewSpread($('.batch-l-b', this.obj)[0]);
+            // 初始化 签约清单 参数
+            this.dealSpreadSetting = {
+                cols: [
+                    {title: '清单编号', field: 'code', width: 80, formatter: '@', readOnly: true},
+                    {title: '名称', field: 'name', width: 120, formatter: '@', readOnly: true},
+                    {title: '单位', field: 'unit', width: 50, formatter: '@', readOnly: true},
+                    {title: '单价', field: 'unit_price', width: 50, readOnly: true},
+                ],
+                emptyRows: 0,
+                headRows: 1,
+                headRowHeight: [40],
+            };
+            this.dealSpread = SpreadJsObj.createNewSpread($('.batch-r', this.obj)[0]);
+            // 初始化 清单编号、部位数量复核表 表格
+            this.initView();
+            SpreadJsObj.initSheet(this.dealSpread.getActiveSheet(), this.dealSpreadSetting);
+            SpreadJsObj.refreshColumnAlign(this.dealSpread.getActiveSheet());
+            // 拉取签约清单数据
+            dealBills.loadData();
+            SpreadJsObj.loadSheetData(this.dealSpread.getActiveSheet(), 'data', dealBills.data);
+            // 双击签约清单,自动添加到清单编号窗口
+            this.dealSpread.bind(GC.Spread.Sheets.Events.CellDoubleClick, function (e, info) {
+                const deal = info.sheet.zh_data[info.row];
+                const qdSheet = self.qdSpread.getActiveSheet(), posSheet = self.posSpread.getActiveSheet();
+                const sel = qdSheet.getSelections()[0];
+                qdSheet.getCell(sel.row, 0).value(deal.code);
+                qdSheet.getCell(sel.row, 1).value(deal.name);
+                qdSheet.getCell(sel.row, 2).value(deal.unit);
+                qdSheet.getCell(sel.row, 3).value(deal.unit_price);
+                if (sel.row + 1 === qdSheet.getRowCount()) {
+                    const count = sel.row + 2;
+                    qdSheet.setRowCount(count);
+                    qdSheet.getCell(sel.row + 1, 0, GC.Spread.Sheets.SheetArea.rowHeader).text('清单' + count);
+
+                    const colCount = posSheet.getColumnCount() + 1
+                    posSheet.setColumnCount(colCount);
+                    posSheet.getCell(0, colCount - 1, GC.Spread.Sheets.SheetArea.colHeader).text('数量' + count);
+                }
+                qdSheet.setSelection(sel.row + 1, sel.col, 1, 1);
+            });
+
+            this.obj.bind('shown.bs.modal', function () {
+                self.qdSpread.refresh();
+                self.posSpread.refresh();
+                self.dealSpread.refresh();
+            });
+
+            $('#batch-ok').click(function () {
+                const selection = billsSheet.getSelections();
+                const row = selection[0].row;
+                const select = billsTree.nodes[row];
+                if (select) {
+                    const insertData = {};
+                    insertData.batchType = (select.code && select.code !== '') ? 'child' : 'next';
+                    insertData.id = select[billsTree.setting.id];
+                    insertData.batchData = self.getBatchData();
+                    postData(window.location.pathname + '/batch-insert', insertData, function (data) {
+                        pos.updateDatas(data.pos);
+                        const result = billsTree.loadPostData(data.ledger);
+                        billsTreeSpreadObj.refreshTree(billsSheet, result);
+                        billsTreeSpreadObj.refreshOperationValid(billsSheet, selection);
+                        self.obj.modal('hide');
+                    });
+                }
+            });
+        }
+        // 初始化左侧表格
+        initView () {
+            // 初始化 清单编号
+            const qdSheet = this.qdSpread.getActiveSheet();
+            SpreadJsObj.initSheet(qdSheet, this.qdSpreadSetting);
+            SpreadJsObj.refreshColumnAlign(qdSheet);
+            // 清理原有数据
+            SpreadJsObj.beginMassOperation(qdSheet);
+            qdSheet.clear(0, 0, qdSheet.getRowCount(), qdSheet.getColumnCount(), GC.Spread.Sheets.SheetArea.viewport, GC.Spread.Sheets.StorageType.data);
+            for (let iRow = 1; iRow <= this.billsCount; iRow++) {
+                qdSheet.getCell(iRow - 1, 0, GC.Spread.Sheets.SheetArea.rowHeader).text('清单' + iRow);
+            }
+            qdSheet.setSelection(0, 0, 1 ,1);
+            SpreadJsObj.endMassOperation(qdSheet);
+            // 初始化 部位数量复核表
+            const posSheet = this.posSpread.getActiveSheet();
+            SpreadJsObj.initSheet(posSheet, this.posSpreadSetting);
+            SpreadJsObj.refreshColumnAlign(posSheet);
+            // 清理原有数据
+            SpreadJsObj.beginMassOperation(posSheet);
+            posSheet.setColumnWidth(0, 45, GC.Spread.Sheets.SheetArea.rowHeader);
+            posSheet.clear(0, 0, posSheet.getRowCount(), posSheet.getColumnCount(), GC.Spread.Sheets.SheetArea.viewport, GC.Spread.Sheets.StorageType.data);
+            posSheet.setSelection(0, 0, 1 ,1);
+            SpreadJsObj.endMassOperation(posSheet);
+            // 检查签约清单数据,以工具栏数据为准
+            if (dealBills) {
+                SpreadJsObj.loadSheetData(this.dealSpread.getActiveSheet(), 'data', dealBills.data);
+            }
+            this.dealSpread.getActiveSheet().setSelection(0, 0, 1, 1);
+        }
+        // 获取界面数据
+        getBatchData () {
+            const result = [];
+            const qdSheet = this.qdSpread.getActiveSheet(), posSheet = this.posSpread.getActiveSheet();
+            for (let iRow = 0; iRow < qdSheet.getRowCount(); iRow++) {
+                if (qdSheet.getText(iRow, 0) === '') { continue; }
+                const qd = {
+                    b_code: qdSheet.getText(iRow, 0),
+                    name: qdSheet.getText(iRow, 1),
+                    unit: qdSheet.getText(iRow, 2),
+                    price: _.toNumber(qdSheet.getText(iRow, 3)),
+                    pos: [],
+                };
+                result.push(qd);
+                for (let iPosRow = 0; iPosRow < posSheet.getRowCount(); iPosRow++) {
+                    const value = _.toNumber(posSheet.getText(iPosRow, iRow + 2));
+                    if (value !== 0 && !isNaN(value)) {
+                        qd.pos.push({
+                            name: posSheet.getText(iPosRow, 0),
+                            drawing_code: posSheet.getText(iPosRow, 1),
+                            quantity: value,
+                        });
+                    }
+                }
+            }
+            return result;
+        }
+    }
+    let stdChapter, stdBills;
+    const dealBills = new DealBills('#deal-bills-spread', {
+        cols: [
+            {title: '清单编号', field: 'code', hAlign: 0, width: 120, formatter: '@', readOnly: true},
+            {title: '名称', field: 'name', hAlign: 0, width: 230, formatter: '@', readOnly: true},
+            {title: '单位', field: 'unit', hAlign: 1, width: 50, formatter: '@', readOnly: true},
+            {title: '单价', field: 'unit_price', hAlign: 2, width: 50, readOnly: true},
+            {title: '数量', field: 'quantity', hAlign: 2, width: 50, readOnly: true},
+            {title: '金额', field: 'total_price', hAlign: 2, width: 50, readOnly: true},
+        ],
+        emptyRows: 0,
+        headRows: 1,
+        headRowHeight: [40],
+        defaultRowHeight: 21,
+    });
+    $.divResizer({
+        select: '#revise-right-spr',
+        callback: function () {
+            billsSpread.refresh();
+            if (posSpread) {
+                posSpread.refresh();
+            }
+            if (stdChapter) {
+                stdChapter.spread.refresh();
+            }
+            if (stdBills) {
+                stdBills.spread.refresh();
+            }
+        }
+    });
+    // 展开收起标准清单
+    $('a', '#side-menu').bind('click', function (e) {
+        e.preventDefault();
+        const tab = $(this), tabPanel = $(tab.attr('content'));
+        const showSideTools = function (show) {
+            const left = $('#left-view'), right = $('#right-view'), parent = left.parent();
+            if (show) {
+                right.show();
+                autoFlashHeight();
+                /**
+                 * right.show()后, parent被撑开成2倍left.height, 导致parent.width减少了10px
+                 * 第一次left.width调整后,parent的缩回left.height, 此时parent.width又增加了10px
+                 * 故需要通过最终的parent.width再计算一次left.width
+                 *
+                 * Q: 为什么不通过先计算left.width的宽度,以避免计算两次left.width?
+                 * A: 右侧工具栏不一定显示,当右侧工具栏显示过一次后,就必须使用parent和right来计算left.width
+                 *
+                 */
+                    //left.css('width', parent.width() - right.outerWidth());
+                    //left.css('width', parent.width() - right.outerWidth());
+                const percent = 100 - right.outerWidth() /parent.width() * 100;
+                left.css('width', percent + '%');
+            } else {
+                left.width(parent.width());
+                right.hide();
+            }
+        };
+        // 展开工具栏、切换标签
+        if (!tab.hasClass('active')) {
+            const close = $('.active', '#side-menu').length === 0;
+            $('a', '#side-menu').removeClass('active');
+            tab.addClass('active');
+            $('.tab-content .tab-pane').removeClass('active');
+            tabPanel.addClass('active');
+            showSideTools(tab.hasClass('active'));
+            if (tab.attr('content') === '#std-chapter') {
+                if (!stdChapter) {
+                    stdChapter = new stdLib($('#std-chapter-spread')[0], 'chapter', {
+                        id: 'chapter_id',
+                        pid: 'pid',
+                        order: 'order',
+                        level: 'level',
+                        rootId: -1,
+                        keys: ['id', 'list_id', 'chapter_id'],
+                    }, {
+                        cols: [
+                            {title: '项目节编号', field: 'code', hAlign: 0, width: 120, formatter: '@', readOnly: true, cellType: 'tree'},
+                            {title: '名称', field: 'name', hAlign: 0, width: 230, formatter: '@', readOnly: true},
+                            {title: '单位', field: 'unit', hAlign: 1, width: 50, formatter: '@', readOnly: true}
+                        ],
+                        treeCol: 0,
+                        emptyRows: 0,
+                        headRows: 1,
+                        headRowHeight: [40],
+                        defaultRowHeight: 21,
+                    });
+                    stdChapter.loadLib(1);
+                }
+                stdChapter.spread.refresh();
+            } else if (tab.attr('content') === '#std-bills') {
+                if (!stdBills) {
+                    stdBills = new stdLib($('#std-bills-spread')[0], 'bills', {
+                        id: 'bill_id',
+                        pid: 'pid',
+                        order: 'order',
+                        level: 'level',
+                        rootId: -1,
+                        keys: ['id', 'list_id', 'bill_id']
+                    }, {
+                        cols: [
+                            {title: '清单编号', field: 'code', hAlign: 0, width: 120, formatter: '@', readOnly: true, cellType: 'tree'},
+                            {title: '名称', field: 'name', hAlign: 0, width: 230, formatter: '@', readOnly: true},
+                            {title: '单位', field: 'unit', hAlign: 1, width: 50, formatter: '@', readOnly: true}
+                        ],
+                        treeCol: 0,
+                        emptyRows: 0,
+                        headRows: 1,
+                        headRowHeight: [40],
+                        defaultRowHeight: 21,
+                    });
+                    stdBills.loadLib(1);
+                }
+                stdBills.spread.refresh();
+            } else if (tab.attr('content') === '#deal-bills') {
+                dealBills.loadData();
+                dealBills.spread.refresh();
+            }
+        }
+        billsSpread.refresh();
+        if (posSpread) {
+            posSpread.refresh();
+        }
+    });
+
+    // 修订详情 保存
+    $('#save').click(function () {
+        const content = $('textarea').val();
+        postData('save', { content: content });
+    });
+
+    // 显示层次
+    (function (select, sheet) {
+        if (!sheet.zh_tree) return;
+        $(select).click(function () {
+            const tag = $(this).attr('tag');
+            const tree = sheet.zh_tree;
+            switch (tag) {
+                case "1":
+                case "2":
+                case "3":
+                case "4":
+                case "5":
+                    tree.expandByLevel(parseInt(tag));
+                    SpreadJsObj.refreshTreeRowVisible(sheet);
+                    break;
+                case "last":
+                    tree.expandByCustom(() => { return true; });
+                    SpreadJsObj.refreshTreeRowVisible(sheet);
+                    break;
+                case "leafXmj":
+                    tree.expandToLeafXmj();
+                    SpreadJsObj.refreshTreeRowVisible(sheet);
+                    break;
+            }
+        });
+    })('a[name=showLevel]', billsSheet);
+});

+ 131 - 12
app/public/js/spreadjs_rela/spreadjs_zh.js

@@ -79,6 +79,7 @@ const SpreadJsObj = {
         spread.options.allowCopyPasteExcelStyle = false;
         spread.options.allowUserDragDrop = false;
         spread.options.allowUserEditFormula = false;
+        spread.options.allowExtendPasteRange = true;
         spread.getActiveSheet().options.clipBoardOptions = GC.Spread.Sheets.ClipboardPasteOptions.values;//设置粘贴时只粘贴值
         spread.getActiveSheet().setRowCount(3);
         return spread;
@@ -123,6 +124,7 @@ const SpreadJsObj = {
      * @returns {{x: number, y: number}}
      */
     getObjPos: function (obj) {
+        if (!obj) return null;
         let target = obj;
         let pos = {x: obj.offsetLeft, y: obj.offsetTop};
 
@@ -196,6 +198,20 @@ const SpreadJsObj = {
         return target;
     },
     /**
+     * 禁用右键菜单
+     * @param selector
+     * @param spread
+     */
+    forbiddenSpreadContextMenu: function (selector, spread) {
+        $.contextMenu({
+            selector: selector,
+            build: function ($trigger, e) {
+                const target = SpreadJsObj.safeRightClickSelection($trigger, e, spread);
+                return target.hitTestType === spreadNS.SheetArea.viewport || target.hitTestType === spreadNS.SheetArea.rowHeader;
+            }
+        });
+    },
+    /**
      * 获取写入sheet的数据序列
      * data:sheet.zh_data, tree: sheet.zh_tree.nodes
      * @param sheet
@@ -279,6 +295,7 @@ const SpreadJsObj = {
      */
     initSheet: function (sheet, setting) {
         this.beginMassOperation(sheet);
+        setting.pos = sheet.getParent().pos;
         sheet.zh_setting = setting;
         this._initSheetDeafult(sheet);
         this._initSheetHeader(sheet);
@@ -314,6 +331,8 @@ const SpreadJsObj = {
             }
             if (col.formatter) {
                 cell.formatter(col.formatter);
+            } else if (col.type === 'Number') {
+                cell.formatter('0.######');
             }
             if (sheet.zh_setting.getColor && Object.prototype.toString.apply(sheet.zh_setting.getColor) === "[object Function]") {
                 cell.backColor(sheet.zh_setting.getColor(data, col, sheet.getDefaultStyle().backColor));
@@ -327,6 +346,12 @@ const SpreadJsObj = {
             }
             sheet.getRange(-1, col, -1, 1).cellType(sheet.extendCellType.ellipsis);
         }
+        if(colSetting.cellType === 'ellipsisAutoTip') {
+            if (!sheet.extendCellType.ellipsisAutoTip) {
+                sheet.extendCellType.ellipsisAutoTip = this.CellType.getEllipsisTextAutoTipCellType();
+            }
+            sheet.getRange(-1, col, -1, 1).cellType(sheet.extendCellType.ellipsisAutoTip);
+        }
         if(colSetting.cellType === 'html') {
             if (!sheet.extendCellType.html) {
                 sheet.extendCellType.html = this.CellType.getHtmlCellType();
@@ -357,6 +382,12 @@ const SpreadJsObj = {
             }
             sheet.getRange(-1, col, -1, 1).cellType(sheet.extendCellType.tip);
         }
+        if (colSetting.cellType === 'autoTip') {
+            if (!sheet.extendCellType.autoTip) {
+                sheet.extendCellType.autoTip = this.CellType.getAutoTipCellType();
+            }
+            sheet.getRange(-1, col, -1, 1).cellType(sheet.extendCellType.autoTip);
+        }
         if (colSetting.cellType === 'checkbox') {
             if (!sheet.extendCellType.checkbox) {
                 sheet.extendCellType.checkbox = new spreadNS.CellTypes.CheckBox();
@@ -366,6 +397,19 @@ const SpreadJsObj = {
         if (colSetting.cellType === 'unit') {
             if (!sheet.extendCellType.unit) {
                 sheet.extendCellType.unit = this.CellType.getUnitCellType();
+                sheet.bind(spreadNS.Events.LeaveCell, function (e, info) {
+                    const cellType = info.sheet.getCell(info.row, info.col).cellType();
+                    if (cellType === sheet.extendCellType.unit) {
+                        info.sheet.leaveCell = {row: info.row, col: info.col};
+                    } else {
+                        delete info.sheet.leaveCell;
+                    }
+                });
+                sheet.bind(spreadNS.Events.EnterCell, function (e, info) {
+                    if (info.sheet.leaveCell) {
+                        info.sheet.repaint(info.sheet.getCellRect(info.sheet.leaveCell.row, info.sheet.leaveCell.col));
+                    }
+                });
             }
             sheet.getRange(-1, col, -1, 1).cellType(sheet.extendCellType.unit);
         }
@@ -413,7 +457,7 @@ const SpreadJsObj = {
      * @param {Number} count
      */
     reLoadRowData: function (sheet, row, count = 1) {
-        //if (row < 0) { return; }
+        if (row < 0) { return; }
         const self = this;
         const sortData = sheet.zh_dataType === 'tree' ? sheet.zh_tree.nodes : sheet.zh_data;
         this.beginMassOperation(sheet);
@@ -566,16 +610,45 @@ const SpreadJsObj = {
      * @param {GC.Spread.Sheets.Worksheet} sheet - 需要定位的sheet
      * @param {Number} id - 定位节点的id
      */
-    locateTreeNode: function (sheet, id) {
+    locateTreeNode: function (sheet, id, autoExpand = false) {
         const tree = sheet.zh_tree;
         if (!tree) { return }
         const node = tree.getItems(id);
         if (!node) { return }
+        if (autoExpand && !node.visible) {
+            const parents = tree.autoExpandNode(node);
+            if (parents && parents.length > 0) {
+                SpreadJsObj.reLoadNodesData(sheet, parents);
+                SpreadJsObj.refreshTreeRowVisible(sheet);
+            }
+        }
         const index = tree.nodes.indexOf(node);
         const sels = sheet.getSelections();
         sheet.setSelection(index, sels[0].col, 1, 1);
         sheet.showRow(index, spreadNS.VerticalPosition.center);
     },
+    saveTopAndSelect: function (sheet, cacheKey) {
+        const sel = sheet.getSelections()[0];
+        const top = sheet.getViewportTopRow(1);
+        setLocalCache(cacheKey, JSON.stringify({top: top, sel: sel}));
+    },
+    loadTopAndSelect: function (sheet, cacheKey) {
+        let ts = getLocalCache(cacheKey);
+        if (ts !== '') {
+            ts = JSON.parse(ts);
+            if (ts === undefined || ts === null) return;
+            if (ts.sel) {
+                sheet.setSelection(ts.sel.row, ts.sel.col, ts.sel.rowCount, ts.sel.colCount);
+            }
+            if (ts.top) {
+                sheet.showRow(ts.top, spreadNS.VerticalPosition.top);
+            }
+        }
+    },
+    resetTopAndSelect: function (sheet) {
+        sheet.setSelection(0, 0, 1, 1);
+        sheet.showRow(0, spreadNS.VerticalPosition.top);
+    },
     /**
      * 获取当前选行的数据对象
      * @param {GC.Spread.Sheets.Worksheet} sheet
@@ -932,11 +1005,24 @@ const SpreadJsObj = {
          * @returns {TipCellType}
          */
         getTipCellType: function () {
+            const maxHintWidth = 200, indent = 15, borderIndent = 10;
             const TipCellType = function () {};
             // 继承 SpreadJs定义的 普通的TextCellType
             TipCellType.prototype = new spreadNS.CellTypes.Text();
             const proto = TipCellType.prototype;
-
+            proto.getTextDisplayWidth = function(hitinfo, str, font) {
+                const xs = hitinfo.sheet.getParent().xs;
+                const ctx = xs.childNodes[0].getContext("2d");
+                if (font && font !== '') {
+                    ctx.font = font;
+                } else {
+                    ctx.font = hitinfo.cellStyle.font;
+                }
+                return ctx.measureText(str).width;
+            };
+            proto.showTip = function (hitinfo, text) {
+                return text && text !== '';
+            };
             /**
              * 获取点击信息
              * @param {Number} x
@@ -955,7 +1041,8 @@ const SpreadJsObj = {
                     cellStyle: cellStyle,
                     cellRect: cellRect,
                     sheet: context.sheet,
-                    sheetArea: context.sheetArea
+                    sheetArea: context.sheetArea,
+                    ctx: context.sheet.getParent().xs,
                 };
             };
             /**
@@ -969,8 +1056,8 @@ const SpreadJsObj = {
                     const sortData = SpreadJsObj.getSortData(hitinfo.sheet);
                     text = col.getTip(sortData[hitinfo.row]);
                 }
-                const setting = hitinfo.sheet.zh_setting;
-                if (setting.pos && text && text !== '') {
+                const pos = SpreadJsObj.getObjPos(hitinfo.sheet.getParent().qo);
+                if (pos && this.showTip(hitinfo, text)) {
                     if (!this._toolTipElement) {
                         let div = $('#autoTip')[0];
                         if (!div) {
@@ -981,13 +1068,23 @@ const SpreadJsObj = {
                                 .css("font", "9pt Arial")
                                 .css("background", "white")
                                 .css("padding", 5)
+                                .css("z-index", 999)
+                                .css("max-width", maxHintWidth)
+                                .css("word-wrap", "break-word")
                                 .attr("id", 'autoTip');
-                            //$(div).hide();
                             document.body.insertBefore(div, null);
                         }
+                        const validWidth = $(window).width() - (pos.x + hitinfo.x + indent) - borderIndent;
+                        const textWidth = this.getTextDisplayWidth(hitinfo, text, "9pt Arial");
+                        if (validWidth >= maxHintWidth || textWidth <= validWidth) {
+                            $(div).text(text).css("top", pos.y + hitinfo.y + indent).css("left", pos.x + hitinfo.x + indent);
+                        } else if (textWidth > maxHintWidth) {
+                            $(div).text(text).css("top", pos.y + hitinfo.y + indent).css("left", pos.x + hitinfo.x - indent - maxHintWidth);
+                        } else {
+                            $(div).text(text).css("top", pos.y + hitinfo.y + indent).css("left", pos.x + hitinfo.x - indent - textWidth);
+                        }
                         this._toolTipElement = div;
-                        $(this._toolTipElement).text(text).css("top", setting.pos.y + hitinfo.y + 15).css("left", setting.pos.x + hitinfo.x + 15);
-                        $(this._toolTipElement).show("fast");
+                        $(div).show("fast");
                     }
                 }
             };
@@ -1004,6 +1101,17 @@ const SpreadJsObj = {
 
             return new TipCellType();
         },
+        getAutoTipCellType: function () {
+            const AutoTipCellType = function () {};
+            // 继承 TipCellType
+            AutoTipCellType.prototype = SpreadJsObj.CellType.getTipCellType();
+            const proto = AutoTipCellType.prototype
+            proto.showTip = function (hitinfo, text) {
+                return text && text !== '' && this.getTextDisplayWidth(hitinfo, text) > hitinfo.cellRect.width;
+            };
+
+            return new AutoTipCellType();
+        },
         /**
          * 获取 带图片的cellType(图片需在document中定义好img,并写入col的img属性)
          *
@@ -1357,7 +1465,7 @@ const SpreadJsObj = {
             const EllipsisTextCellType = function (){};
             EllipsisTextCellType.prototype = new spreadNS.CellTypes.Text;
             const proto = EllipsisTextCellType.prototype;
-            const getEllipsisText = function(c, str, maxWidth) {
+            proto.getEllipsisText = function(c, str, maxWidth) {
                 var width = c.measureText(str).width;
                 var ellipsis = '…';
                 var ellipsisWidth = c.measureText(ellipsis).width;
@@ -1374,17 +1482,28 @@ const SpreadJsObj = {
             };
             proto.paint = function (ctx, value, x, y, w, h, style, context) {
                 ctx.font = style.font;
-                value = getEllipsisText(ctx, value, w - 2);
+                value = this.getEllipsisText(ctx, value, w - 2);
                 spreadNS.CellTypes.Text.prototype.paint.apply(this, [ctx, value, x, y, w, h, style, context]);
             };
             return new EllipsisTextCellType();
         },
+        getEllipsisTextAutoTipCellType: function () {
+            const CellType = function () {};
+            // 继承 TipCellType
+            CellType.prototype = SpreadJsObj.CellType.getAutoTipCellType();
+            const proto = CellType.prototype;
+            const ellipsisTextCellType = SpreadJsObj.CellType.getEllipsisTextCellType();
+            proto.getEllipsisText = ellipsisTextCellType.getEllipsisText;
+            proto.paint = ellipsisTextCellType.paint;
+
+            return new CellType();
+        },
         /**
          * 获取 动态显示ComboBox的cellType
          * @returns {ActiveComboCellType}
          */
         getActiveComboCellType: function () {
-            const ActiveComboCellType = function () { };
+            const ActiveComboCellType = function () {};
             ActiveComboCellType.prototype = new spreadNS.CellTypes.ComboBox();
             const proto = ActiveComboCellType.prototype;
             proto.paintValue = function (ctx, value, x, y, w, h, style, options) {

Plik diff jest za duży
+ 996 - 139
app/public/js/stage.js


+ 7 - 1
app/public/js/stage_audit.js

@@ -134,8 +134,14 @@ $(document).ready(function () {
     $('#hideSp').click(function () {
         $('#sp-list2').modal('hide');
     });
+    $('a[target]').click(function () {
+        if (stage.check_detail) {
+            $('#sub-sp3').modal('show');
+        } else {
+            $($(this).attr('target')).modal('show');
+        }
+    })
 });
-
 // 检查上报情况
 function checkAuditorFrom () {
     if ($('#auditors li').length === 0) {

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

@@ -39,7 +39,7 @@ class ChangeAnalysis {
         change.bills = change.detail.addUsedBills;
         for (const b of change.bills) {
             b.qty = _.toNumber(b.samount);
-            b.valid_qty = _.round(_.subtract(b.qty - b.used_qty), 6);
+            b.valid_qty = ZhCalc.sub(b.qty - b.used_qty);
             b.pos = _.filter(change.detail.curUsedBills, {cbid: b.id});
             b.cur_qty = 0;
             for (const p of b.pos) {
@@ -50,7 +50,7 @@ class ChangeAnalysis {
                     p.leaf_xmj_code = leafXmj.code;
                     p.leaf_xmj_name = leafXmj.name;
                 }
-                b.cur_qty = _.round(_.add(b.cur_qty, p.qty), 6);
+                b.cur_qty = ZhCalc.add(b.cur_qty, p.qty);
             }
 
         }

+ 44 - 16
app/public/js/stage_compare.js

@@ -33,14 +33,13 @@ $(document).ready(function () {
 
     function calculateStageLedgerData(datas) {
         for (const d of datas) {
-            d.gather_qty = _.add(d.contract_qty, d.qc_qty);
-            d.gather_tp = _.add(d.contract_tp, d.qc_tp);
+            d.gather_qty = ZhCalc.add(d.contract_qty, d.qc_qty);
+            d.gather_tp = ZhCalc.add(d.contract_tp, d.qc_tp);
         }
     }
     function calculateStagePosData(datas) {
         for (const d of datas) {
-            d.gather_qty = _.add(d.contract_qty, d.qc_qty);
-            d.gather_tp = _.add(d.contract_tp, d.qc_tp);
+            d.gather_qty = ZhCalc.add(d.contract_qty, d.qc_qty);
         }
     }
 
@@ -70,23 +69,21 @@ $(document).ready(function () {
         keys: ['id', 'tender_id', 'ledger_id'],
         masterId: 'id',
         minorId: 'lid',
-    }
-    //scTreeSetting.updateFields = ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'];
-    scTreeSetting.calcFields = ['gather_tp0'];
+        calcFields: [],
+    };
     const scTree = createNewPathTree('master', scTreeSetting);
     scTree.loadDatas(ledger);
     calculateStageLedgerData(orgStageLedger);
-    scTree.loadMinorData(orgStageLedger, '0', ['gather_qty', 'gather_tp']);
+    scTree.loadMinorData(orgStageLedger, '0', ['gather_qty', 'gather_tp'], ['gather_tp']);
     treeCalc.calculateAll(scTree);
     scTree.expandByCalcFields();
     SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), SpreadJsObj.DataType.Tree, scTree);
     // 加载 部位 数据
     const scPosSetting = {
         id: 'id', ledgerId: 'lid', masterId: 'id', minorId: 'pid',
-        updateFields: ['contract_qty', 'qc_qty'],
     };
     scPosSetting.calcFun = function (pos) {
-        pos.gather_qty = _.add(pos.contract_qty, pos.qc_qty);
+        pos.gather_qty = ZhCalc.add(pos.contract_qty, pos.qc_qty);
     };
     const scPos = new MasterPosData(scPosSetting);
     scPos.loadDatas(pos);
@@ -101,6 +98,7 @@ $(document).ready(function () {
         } else {
             SpreadJsObj.loadSheetData(posSpread.getActiveSheet(), SpreadJsObj.DataType.Data, []);
         }
+        SpreadJsObj.resetTopAndSelect(posSpread.getActiveSheet());
     }
     loadPosData(0);
     // 切换清单行,读取所属项目节数据
@@ -125,9 +123,9 @@ $(document).ready(function () {
             scTreeSetting.calcFields = [];
             const fieldSufs = ['0'], roles = ['原报'], trs = $('tr[auditorId]');
             for (let order = 0, iLength = trs.length; order < iLength; order++) {
-                fieldSufs.push(order + '');
-                roles.push(tr.children()[0].text);
-                scTreeSetting.calcFields.push('gather_tp' + order);
+                const tr = trs[order];
+                fieldSufs.push((order + 1) + '');
+                roles.push(tr.children[0].textContent);
             }
             setSpreadSettingCols(ledgerSpreadSetting, fieldSufs, roles);
             SpreadJsObj.initSheet(ledgerSpread.getActiveSheet(), ledgerSpreadSetting);
@@ -141,8 +139,8 @@ $(document).ready(function () {
         let loadData = [], showData = [], trs = $('tr[auditorId]');
         for (let order = 0, iLength = trs.length; order < iLength; order++) {
             const tr = trs[order];
-            if ($('input[type=check]', tr)[0].checked) {
-                if (!scTree.minorData[tr]) {
+            if ($('input[type=checkbox]', tr)[0].checked) {
+                if (!scTree.minorData[order + 1]) {
                     loadData.push(order + 1);
                 }
                 showData.push(order + 1);
@@ -152,7 +150,7 @@ $(document).ready(function () {
             postData(window.location.pathname + '/load', {auditors: loadData}, function (result) {
                 for (const aData of result) {
                     calculateStageLedgerData(aData.bills);
-                    scTree.loadMinorData(aData.bills, aData.order + '', ['gather_qty', 'gather_tp']);
+                    scTree.loadMinorData(aData.bills, aData.order + '', ['gather_qty', 'gather_tp'], ['gather_tp']);
                     treeCalc.calculateAll(scTree);
                     calculateStagePosData(aData.pos);
                     scPos.loadMinorData(aData.pos, aData.order + '', ['gather_qty']);
@@ -165,4 +163,34 @@ $(document).ready(function () {
             $('#select-qi').modal('hide');
         }
     });
+    // 显示层次
+    (function (select, sheet) {
+        if (!sheet.zh_tree) return;
+        $(select).click(function () {
+            const tag = $(this).attr('tag');
+            const tree = sheet.zh_tree;
+            switch (tag) {
+                case "1":
+                case "2":
+                case "3":
+                case "4":
+                case "5":
+                    tree.expandByLevel(parseInt(tag));
+                    SpreadJsObj.refreshTreeRowVisible(sheet);
+                    break;
+                case "last":
+                    tree.expandByCustom(() => { return true; });
+                    SpreadJsObj.refreshTreeRowVisible(sheet);
+                    break;
+                case "leafXmj":
+                    tree.expandToLeafXmj();
+                    SpreadJsObj.refreshTreeRowVisible(sheet);
+                    break;
+                case "curMeasure":
+                    tree.expandByCalcFields();
+                    SpreadJsObj.refreshTreeRowVisible(sheet);
+                    break;
+            }
+        });
+    })('a[name=showLevel]', ledgerSpread.getActiveSheet());
 });

+ 236 - 113
app/public/js/stage_detail.js

@@ -9,99 +9,87 @@
  */
 
 $(document).ready(() => {
-    let gsSpread;
-    stageIm.init(stage, imType);
-    const gsTree = stageIm.getGsTree();
-
-    function getSelectDetailData() {
-        const rowIndex = parseInt($('#im-list').attr('rowIndex'));
-        const data = stageIm.getImData()[rowIndex];
-        return data;
-    }
-
-    function reLoadDetailData() {
-        const data = getSelectDetailData();
-        $('#edit-detail').show();
-        $('#save-detail').hide();
-        $('#cancel-detail').hide();
-        $('#bgl-code').val(data && data.bgl_code ? data.bgl_code : '');
-        $('#bw-name').val(data && data.bw ? data.bw : '');
-        $('#start-peg').val(data && data.start_peg ? data.start_peg : '');
-        $('#end-peg').val(data && data.end_peg ? data.end_peg : '');
-        $('#unit-name').val(data && data.jldy ? data.jldy : '');
-        $('#drawing-code').val(data && data.drawing_code ? data.drawing_code : '');
-        $('#calc-memo').val(data && data.calc_memo ? data.calc_memo : '');
-        if (data && data.calc_img) {
-            $('#calc-img').html('<img src="/' + data.calc_img + '" class="d-100" width="100%">');
-        } else {
-            $('#calc-img').html('');
-        }
-    }
-
-    function loadPosData() {
-        const data = getSelectDetailData();
-        const rowIndex = parseInt($('#leaf-xmj-list').attr('rowIndex'));
-        const leafXmj = data.leafXmjs[rowIndex];
-        const html = [];
-        if (leafXmj) {
-            for (const p of leafXmj.pos) {
-                html.push('<tr>');
-                html.push('<td>', p.name, '</td>');
-                html.push('<td>', p.quantity, '</td>');
-                html.push('<td>', p.jl, '</td>');
-                html.push('</tr>');
+    autoFlashHeight();
+    const detailSpreadSetting = {
+        cols: [
+            {title: '编号', colSpan: '1', rowSpan: '1', field: 'code', hAlign: 0, width: 80, formatter: '@', readOnly: true},
+            {title: '中间计量表号', colSpan: '1', rowSpan: '1', field: 'im_code', hAlign: 0, width: 150, formatter: '@', readOnly: true},
+            {title: '交工证书/凭证号', colSpan: '1', rowSpan: '1', field: 'doc_code', hAlign: 0, width: 180, formatter: '@'},
+            {title: '分布分项工程', colSpan: '1', rowSpan: '1', field: 'fbfx', hAlign: 0, width: 150, formatter: '@', readOnly: true},
+            {title: '本期计量数量/金额', colSpan: '1', rowSpan: '1', field: 'jl', hAlign: 2, width: 220, formatter: '@', readOnly: true},
+        ],
+        headRows: 1,
+        emptyRows: 0,
+        headRowHeight: [32],
+        defaultRowHeight: 32,
+        readOnly: readOnly,
+    };
+    const detailSpread = SpreadJsObj.createNewSpread($('#detail-spread')[0]);
+    SpreadJsObj.initSheet(detailSpread.getActiveSheet(), detailSpreadSetting);
+    const detailOperationObj = {
+        // 部位明细
+        loadPosData: function () {
+            const data = SpreadJsObj.getSelectObject(detailSpread.getActiveSheet());
+            const html = [];
+            if (data) {
+                const rowIndex = parseInt($('#leaf-xmj-list').attr('rowIndex'));
+                const leafXmj = data.leafXmjs[rowIndex];
+                if (leafXmj) {
+                    for (const p of leafXmj.pos) {
+                        html.push('<tr>');
+                        html.push('<td>', p.name, '</td>');
+                        html.push('<td>', p.qty, '</td>');
+                        html.push('<td>', p.jl, '</td>');
+                        html.push('</tr>');
+                    }
+                }
             }
-        }
-        $('#pos-list').html(html.join(''));
-    }
-
-    function loadLeafXmjsData() {
-        const data = getSelectDetailData();
-        const html = [];
-        for (const lx of data.leafXmjs) {
-            html.push('<tr>');
-            html.push('<td>', lx.code , '</td>');
-            html.push('<td>', lx.name , '</td>');
-            html.push('<td>', lx.jl , '</td>');
-            html.push('</tr>');
-        }
-        $('#leaf-xmj-list').html(html.join(''));
-        $('#leaf-xmj-list').attr('rowIndex', 0);
-        loadPosData();
-    }
-
-    function reBuildImData() {
-        const imData = stageIm.buildImData();
-        const html = [];
-        for (const im of imData) {
-            html.push('<tr>');
-            html.push('<td>');
-            html.push(im.code);
-            html.push('</td>');
-            html.push('<td>');
-            html.push(im.im_code);
-            html.push('</td>');
-            html.push('<td>');
-            html.push(im.doc_code);
-            html.push('</td>');
-            html.push('<td>');
-            html.push(im.fbfx);
-            html.push('</td>');
-            html.push('<td align="right">');
-            html.push(im.jl);
-            html.push('</td>');
-            html.push('</tr>');
-        }
-        $('#im-list').html(html.join(''));
-        $('tr:first', '#im-list').addClass('table-warning');
-        $('#im-list').attr('rowIndex', 0);
-        reLoadDetailData();
-        loadLeafXmjsData();
-        $('tr', '#im-list').click(function () {
-            $('tr.table-warning').removeClass('table-warning');
-            $(this).addClass('table-warning');
-            $('#im-list').attr('rowIndex', this.rowIndex - 1);
-
+            $('#pos-list').html(html.join(''));
+        },
+        // 项目节明细
+        loadLeafXmjsData: function () {
+            const data = SpreadJsObj.getSelectObject(detailSpread.getActiveSheet());
+            const html = [];
+            if (data && data.leafXmjs) {
+                for (const lx of data.leafXmjs) {
+                    html.push('<tr>');
+                    html.push('<td>', lx.code , '</td>');
+                    html.push('<td>', lx.name , '</td>');
+                    html.push('<td>', lx.jl , '</td>');
+                    html.push('</tr>');
+                }
+            }
+            $('#leaf-xmj-list').html(html.join(''));
+            $('#leaf-xmj-list').attr('rowIndex', 0);
+            $('tr:first', '#leaf-xmj-list').addClass('table-warning');
+            detailOperationObj.loadPosData();
+            $('tr', '#leaf-xmj-list').bind('click', function () {
+                $('tr.table-warning', '#leaf-xmj-list').removeClass('table-warning');
+                $(this).addClass('table-warning');
+                $('#leaf-xmj-list').attr('rowIndex', this.rowIndex - 1);
+                detailOperationObj.loadPosData();
+            });
+        },
+        // 中间计量数据
+        reLoadDetailData: function () {
+            const data = SpreadJsObj.getSelectObject(detailSpread.getActiveSheet());
+            $('#edit-detail').show();
+            $('#save-detail').hide();
+            $('#cancel-detail').hide();
+            $('#bgl-code').val(data && data.bgl_code ? data.bgl_code : '');
+            $('#bw-name').val(data && data.bw ? data.bw : '');
+            $('#start-peg').val(data && data.start_peg ? data.start_peg : '');
+            $('#end-peg').val(data && data.end_peg ? data.end_peg : '');
+            $('#unit-name').val(data && data.jldy ? data.jldy : '');
+            $('#drawing-code').val(data && data.drawing_code ? data.drawing_code : '');
+            $('#calc-memo').val(data && data.calc_memo ? data.calc_memo : '');
+            if (data && data.calc_img) {
+                $('#calc-img').html('<img src="/' + data.calc_img + '" class="d-100" width="100%">');
+            } else {
+                $('#calc-img').html('');
+            }
+        },
+        selectionChanged: function (e, info) {
             $('#edit-detail').show();
             $('#cancel-detail').hide();
             $('#save-detail').hide();
@@ -112,16 +100,132 @@ $(document).ready(() => {
             $('#unit-name').attr('readonly', '');
             $('#drawing-code').attr('readonly', '');
             $('#calc-memo').attr('readonly', '');
-            reLoadDetailData();
-            loadLeafXmjsData();
-        });
-        $('tr', '#leaf-xmj-list').click(function () {
-            loadPosData();
-        });
+            detailOperationObj.reLoadDetailData();
+            detailOperationObj.loadLeafXmjsData();
+        },
+        editEnded: function(e, info) {
+            if (info.sheet.zh_setting) {
+                const col = info.sheet.zh_setting.cols[info.col];
+                if (col.field !== 'doc_code') {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    return;
+                }
+                const data = SpreadJsObj.getSelectObject(info.sheet);
+                if (data) {
+                    const updateData = {lid: data.lid}
+                    if (data.uuid) {
+                        updateData.uuid = data.uuid;
+                    } else {
+                        updateData.code = data.code;
+                        updateData.name = data.name;
+                        updateData.unit = data.unit;
+                        updateData.unit_price = data.unit_price;
+                    }
+                    updateData.doc_code = info.editingText;
+                    postData(window.location.pathname + '/save', updateData, function (result) {
+                        stageIm.loadUpdateDetailData(result);
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    }, function () {
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    });
+                } else {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                }
+            }
+        },
+        clipboardPasted: function (e, info) {
+            if (info.sheet.zh_setting && info.sheet.zh_data) {
+                const col = info.sheet.zh_setting.cols[info.cellRange.col];
+                if (info.cellRange.colCount > 1) {
+                    toast('请勿同时复制粘贴多列数据', 'warning');
+                    SpreadJsObj.reLoadSheetData(paySpread.getActiveSheet());
+                    return;
+                }
+                if (col.field !== 'doc_code') {
+                    SpreadJsObj.reLoadSheetData(paySpread.getActiveSheet());
+                    return;
+                }
+
+                const datas = [];
+                for (let iRow = 0; iRow < info.cellRange.rowCount; iRow++) {
+                    const curRow = info.cellRange.row + iRow;
+                    const data = info.sheet.zh_data[curRow];
+                    if (data) {
+                        const updateData = {lid: data.lid};
+                        if (data.uuid) {
+                            updateData.uuid = data.uuid;
+                        } else {
+                            updateData.code = data.code;
+                            updateData.name = data.name;
+                            updateData.unit = data.unit;
+                            updateData.unit_price = data.unit_price;
+                        }
+                        updateData.doc_code = info.sheet.getText(curRow, info.cellRange.col).replace('\n', '');
+                        datas.push(updateData);
+                    }
+                }
+                if (datas.length > 0) {
+                    postData(window.location.pathname + '/save', datas, function (result) {
+                        stageIm.loadUpdateDetailData(result);
+                        SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
+                    }, function () {
+                        SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
+                    })
+                }
+            }
+        },
+        deletePress: function (sheet) {
+            if (sheet.zh_setting) {
+                const datas = [];
+                const sel = sheet.getSelections()[0];
+                for (let iCol = sel.col; iCol < sel.col + sel.colCount; iCol++) {
+                    const col = sheet.zh_setting.cols[iCol];
+                    for (let iRow = sel.row; iRow < sel.row + sel.rowCount; iRow++) {
+                        const data = sheet.zh_data[iRow];
+                        if (col.field === 'doc_code') {
+                            const updateData = {lid: data.lid};
+                            if (data.uuid) {
+                                updateData.uuid = data.uuid;
+                                updateData.doc_code = null;
+                                datas.push(updateData);
+                            }
+                        }
+                    }
+                }
+                if (datas.length > 0) {
+                    postData(window.location.pathname + '/save', datas, function (result) {
+                        stageIm.loadUpdateDetailData(result);
+                        SpreadJsObj.reLoadRowData(sheet, sel.row, sel.rowCount);
+                    }, function () {
+                        SpreadJsObj.reLoadRowData(sheet, sel.row, sel.rowCount);
+                    });
+                }
+            }
+        },
+    };
+    detailSpread.bind(spreadNS.Events.SelectionChanged, detailOperationObj.selectionChanged);
+    if (!readOnly) {
+        detailSpread.bind(spreadNS.Events.EditEnded, detailOperationObj.editEnded);
+        detailSpread.bind(spreadNS.Events.ClipboardPasted, detailOperationObj.clipboardPasted);
+        SpreadJsObj.addDeleteBind(detailSpread, detailOperationObj.deletePress);
+    }
+
+    let gsSpread;
+    stageIm.init(stage, imType);
+    const gsTree = stageIm.getGsTree();
+
+    function reBuildImData() {
+        const imData = stageIm.buildImData();
+        SpreadJsObj.loadSheetData(detailSpread.getActiveSheet(), SpreadJsObj.DataType.Data, imData);
+        detailOperationObj.reLoadDetailData();
+        detailOperationObj.loadLeafXmjsData();
     }
 
+    console.time('loadDetailRela');
     postData(window.location.pathname + '/load', { loadType: 'all' }, function (data) {
-        stageIm.loadData(data.ledger, data.curStage, data.pos, data.curPosStage, data.stageDetail);
+        console.timeEnd('loadDetailRela');
+        //stageIm.loadData(data.ledger, data.curStage, data.pos, data.curPosStage, data.stageDetail);
+        stageIm.loadData(data.ledger, data.pos, data.stageDetail);
         reBuildImData();
     });
 
@@ -345,7 +449,7 @@ $(document).ready(() => {
             }
         }
 
-        const data = getSelectDetailData();
+        const data = SpreadJsObj.getSelectObject(detailSpread.getActiveSheet());
         const updateData = {lid: data.lid};
         if (data.uuid) {
             updateData.uuid = data.uuid;
@@ -358,7 +462,7 @@ $(document).ready(() => {
         updateData.bgl_code = $('#bgl-code').val();
         updateData.bw = $('#bw-name').val();
         updateData.start_peg = $('#start-peg').val();
-        updateData.end_peg = $('#end_peg').val();
+        updateData.end_peg = $('#end-peg').val();
         updateData.jldy = $('#unit-name').val();
         updateData.drawing_code = $('#drawing-code').val();
         updateData.calc_memo = $('#calc-memo').val();
@@ -386,7 +490,7 @@ $(document).ready(() => {
     // 取消
     $('#cancel-detail').click(() => {
         $('#edit-detail').show();
-        $(this).hide();
+        $('#cancel-detail').hide();
         $('#save-detail').hide();
         $('#bgl-code').attr('readonly', '');
         $('#bw-name').attr('readonly', '');
@@ -395,9 +499,16 @@ $(document).ready(() => {
         $('#unit-name').attr('readonly', '');
         $('#drawing-code').attr('readonly', '');
         $('#calc-memo').attr('readonly', '');
-        reLoadDetailData();
+        detailOperationObj.reLoadDetailData();
     });
 
+    function setdraggrable(){
+        $( ".img-item" ).draggable({ containment: "parent" },{stop: function( event, ui ) {
+            }}).resizable({ containment: "parent" },{ handles: 'n, e, s, w, ne, se, sw, nw' },{ maxWidth: parseFloat($('#imgwidth').val())},{maxHeight: parseFloat($('#imgheight').val())},{
+            stop: function( event, ui ) {
+            }
+        });
+    }
 
     // 草图相关
     // 移动图片
@@ -439,27 +550,35 @@ $(document).ready(() => {
     };
     // 加载草图组成
     $('#edit-img').on('show.bs.modal', function () {
-        const data = getSelectDetailData();
-        const items = JSON.parse(data.calc_img_org) || [];
+        const data = SpreadJsObj.getSelectObject(detailSpread.getActiveSheet());
+        const items = data.calc_img_org ? JSON.parse(data.calc_img_org) : [];
         const html = [];
         for (const item of items) {
-            const itemStyle = 'top:' + item.top + ';' + 'left:' + item.left + ';';
+            const itemStyle = 'top:' + item.top + ';' + 'left:' + item.left + ';' + 'width:' + item.width + ';' + 'height:' + item.height + ';';
             html.push('<div class="img-item" style="' + itemStyle + '">');
             html.push('<div class="img-bar">');
             html.push('<a href="javascript: void(0);" class="text-danger" title="删除"><i class="fa fa-remove"></i></a>');
             html.push('</div>');
-            html.push('<img src="', item.src, '" id="draggable">');
+            html.push('<div class="focus" style="width:100%; height:100%"><img src="', item.src, '" id="draggable" style="width:100%; height:100%"></div>');
             html.push('</div>');
         }
         $('.img-view').html(html.join(''));
-        $('.img-item').mousedown(moveImageItem);
+        // $('.img-item').mousedown(moveImageItem);
         $('.img-bar').click(removeImageItem);
+        setdraggrable();
     });
     // 上传图片
     $('#upload-img').click(function () {
         $('#upload-img-file').trigger('click');
     });
     $('#upload-img-file').change(function () {
+        const file = this.files[0];
+        const ext = file.name.toLowerCase().split('.').splice(-1)[0];
+        const imgStr = /(jpg|jpeg|png|bmp|BMP|JPG|PNG|JPEG)$/;
+        if (!imgStr.test(ext)) {
+            toast('请上传正确的图片格式文件', 'error');
+            return
+        }
         if ($(this).val()) {
             const formData = new FormData();
             formData.append('file', this.files[0]);
@@ -467,13 +586,14 @@ $(document).ready(() => {
                 const html = [];
                 html.push('<div class="img-item">');
                 html.push('<div class="img-bar">');
-                html.push('<a href="" class="text-danger" title="删除"><i class="fa fa-remove"></i></a>');
+                html.push('<a href="javascript: void(0);" class="text-danger" title="删除"><i class="fa fa-remove"></i></a>');
                 html.push('</div>');
-                html.push('<img src="', '/' + result, '" id="draggable">');
+                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-item').mousedown(moveImageItem);
+                // $('.img-item').mousedown(moveImageItem);
                 $('.img-bar').click(removeImageItem);
+                setdraggrable();
             });
         }
     });
@@ -481,7 +601,7 @@ $(document).ready(() => {
     $('#edit-img-ok').click(function () {
         // 记录上传的图片的信息
         const items = $('.img-item');
-        const data = getSelectDetailData();
+        const data = SpreadJsObj.getSelectObject(detailSpread.getActiveSheet());
         if (items.length > 0) {
             const itemInfo = [];
             for (const item of items) {
@@ -489,6 +609,8 @@ $(document).ready(() => {
                     src: $('img', item).attr('src'),
                     left: item.style.left,
                     top: item.style.top,
+                    width: item.style.width,
+                    height: item.style.height,
                 };
                 itemInfo.push(itemData);
             }
@@ -517,6 +639,7 @@ $(document).ready(() => {
             }
             updateData.img = canvas.toDataURL('image/jpeg');
             updateData.imgInfo = itemInfo;
+            console.log(updateData);
             postData(window.location.pathname + '/merge-img', updateData, function (result) {
                 _.assign(data, result);
                 $('#calc-img').html('<img src="/' + data.calc_img + '" class="d-100" width="100%">');
@@ -530,4 +653,4 @@ $(document).ready(() => {
             });
         }
     });
-});
+});

+ 17 - 3
app/public/js/stage_gather.js

@@ -9,6 +9,10 @@
  */
 
 $(document).ready(function () {
+    let per = _.toNumber(getLocalCache('StageGatherOverPercent'));
+    if (per && !_.isNaN(per)) {
+        $('#over-percent').val(per >= 50 ? (per <= 100 ? per : 100) : 50);
+    }
     autoFlashHeight();
     // 初始化工程量清单
     const gclSpread = SpreadJsObj.createNewSpread($('#gcl-spread')[0]);
@@ -29,6 +33,7 @@ $(document).ready(function () {
     // 解析清单汇总数据
     gclGatherModel.loadLedgerData(ledger, curLedgerData, []);
     gclGatherModel.loadPosData(pos, curPosData);
+    gclGatherModel.loadDealBillsData(dealBills);
     const gclGatherData = gclGatherModel.gatherGclData();
     // 获取项目节数据
     function loadLeafXmjData(iGclRow) {
@@ -43,12 +48,13 @@ $(document).ready(function () {
     function checkOverRange() {
         const sheet = gclSpread.getActiveSheet();
         const bQty = $('#customRadio1')[0].checked, bDealQty = $('#customRadio2')[0].checked;
+        const nPercent = Math.min(Math.max(ZhCalc.div(parseFloat($('#over-percent').val()), 100), 0.5), 1);
         SpreadJsObj.beginMassOperation(sheet);
         for (let iRow = 0, iLength = sheet.getRowCount(); iRow < iLength; iRow++) {
             const node = sheet.zh_data[iRow];
             if (node) {
-                const bOverRangeQty = node.quantity ? node.end_gather_qty > node.quantity : node.end_gather_qty;
-                const bOverRangeDealQty = node.deal_qty ? node.end_gather_qty > node.deal_qty : node.end_gather_qty;
+                const bOverRangeQty = node.quantity ? node.end_gather_qty > ZhCalc.mul(node.quantity, nPercent) : node.end_gather_qty;
+                const bOverRangeDealQty = node.deal_bills_qty ? node.end_gather_qty > ZhCalc.mul(node.deal_bills_qty, nPercent) : node.end_gather_qty;
                 const bOverRange = bQty ? bOverRangeQty : (bDealQty ? bOverRangeDealQty : bOverRangeQty || bOverRangeDealQty);
                 const color = bOverRange ? '#f8d7da' : '';
                 sheet.getRange(iRow, -1, 1, -1).backColor(color);
@@ -67,5 +73,13 @@ $(document).ready(function () {
             loadLeafXmjData(iNewRow);
         }
     });
-    $('.custom-radio').click(checkOverRange);
+    $('.custom-radio').click(() => {
+        checkOverRange();
+        // 收起菜单
+        $('.dropdown-menu').click();
+    });
+    $('#over-percent').change(function () {
+        setLocalCache('StageGatherOverPercent', this.value);
+        checkOverRange();
+    });
 });

+ 60 - 28
app/public/js/stage_im.js

@@ -9,6 +9,7 @@
  */
 
 const stageIm = (function () {
+    const imFields = ['uuid', 'doc_code', 'bgl_code', 'start_peg', 'end_peg', 'bw', 'jldy', 'drawing_code', 'calc_memo', 'calc_img'];
     const splitChar = '-';
     let stage, imType, details, ImData, pre;
     const gsTreeSetting = {
@@ -24,20 +25,17 @@ const stageIm = (function () {
     gsTreeSetting.calcFields = ['deal_tp', 'total_price', 'contract_tp', 'qc_tp', 'gather_tp', 'end_contract_tp', 'end_qc_tp', 'end_gather_tp'];
     gsTreeSetting.calcFun = function (node) {
         if (node.children && node.children.length === 0) {
-            node.gather_qty = _.toNumber(node.contract_qty) + _.toNumber(node.qc_qty);
-            node.end_contract_qty = _.toNumber(node.pre_contract_qty) + _.toNumber(node.contract_qty);
-            node.end_qc_qty = _.toNumber(node.pre_qc_qty) + _.toNumber(node.qc_qty);
-            node.end_gather_qty = _.toNumber(node.pre_gather_qty) + _.toNumber(node.gather_qty);
-        }
-        node.gather_tp = _.toNumber(node.contract_tp) + _.toNumber(node.qc_tp);
-        node.end_contract_tp = _.toNumber(node.pre_contract_tp) + _.toNumber(node.contract_tp);
-        node.end_qc_tp = _.toNumber(node.pre_qc_tp) + _.toNumber(node.qc_tp);
-        node.end_gather_tp = _.toNumber(node.pre_gather_tp) + _.toNumber(node.gather_tp);
-        if (checkZero(node.dgn_qty1)) {
-            node.dgn_price = _.round(node.total_price/node.dgn_qty1, 2);
-        } else {
-            node.dgn_price = null;
+            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);
     };
     const gsTree = createNewPathTree('stage', gsTreeSetting);
     const gsPosSetting = {
@@ -45,11 +43,11 @@ const stageIm = (function () {
         updateFields: ['contract_qty', 'qc_qty', 'postil'],
     };
     gsPosSetting.calcFun = function (pos) {
-        pos.pre_gather_qty = _.add(pos.pre_contract_qty, pos.pre_qc_qty);
-        pos.gather_qty = _.add(pos.contract_qty, pos.qc_qty);
-        pos.end_contract_qty = _.add(pos.pre_contract_qty, pos.contract_qty);
-        pos.end_qc_qty = _.add(pos.pre_qc_qty, pos.qc_qty);
-        pos.end_gather_qty = _.add(pos.pre_gather_qty, pos.gather_qty);
+        pos.pre_gather_qty = ZhCalc.add(pos.pre_contract_qty, pos.pre_qc_qty);
+        pos.gather_qty = ZhCalc.add(pos.contract_qty, pos.qc_qty);
+        pos.end_contract_qty = ZhCalc.add(pos.pre_contract_qty, pos.contract_qty);
+        pos.end_qc_qty = ZhCalc.add(pos.pre_qc_qty, pos.qc_qty);
+        pos.end_gather_qty = ZhCalc.add(pos.pre_gather_qty, pos.gather_qty);
     };
     const gsPos = new StagePosData(gsPosSetting);
 
@@ -66,21 +64,32 @@ const stageIm = (function () {
 
     }
 
-    function loadData (ledger, curStage, pos, curPosStage, stageDetail) {
+    function loadData (ledger, pos, stageDetail) {
         gsTree.loadDatas(ledger);
-
-        gsTree.loadCurStageData(curStage);
-        // 根据设置 计算 台账树结构
         treeCalc.calculateAll(gsTree);
 
         gsPos.loadDatas(pos);
-        gsPos.loadCurStageData(curPosStage);
         gsPos.calculateAll();
 
         initCheck();
         details = stageDetail;
     }
 
+    // function loadData (ledger, curStage, pos, curPosStage, stageDetail) {
+    //     gsTree.loadDatas(ledger);
+    //
+    //     gsTree.loadCurStageData(curStage);
+    //     // 根据设置 计算 台账树结构
+    //     treeCalc.calculateAll(gsTree);
+    //
+    //     gsPos.loadDatas(pos);
+    //     gsPos.loadCurStageData(curPosStage);
+    //     gsPos.calculateAll();
+    //
+    //     initCheck();
+    //     details = stageDetail;
+    // }
+
     /**
      * 整数前补零
      * @param {Number} num - 数字
@@ -153,7 +162,6 @@ const stageIm = (function () {
     }
 
     function checkCustomDetail(im) {
-        const fields = ['uuid', 'doc_code', 'bgl_code', 'start_peg', 'end_peg', 'bw', 'jldy', 'drawing_code', 'calc_memo', 'calc_img'];
         const cd = _.find(details, function (d) {
             return im.lid === d.lid &&
                 (!im.code || im.code === d.code) &&
@@ -162,7 +170,7 @@ const stageIm = (function () {
         });
         if (cd) {
             _.assignInWith(im, cd, function (oV, sV, key) {
-                return fields.indexOf(key) > -1 && sV ? sV : oV;
+                return imFields.indexOf(key) > -1 && sV !== undefined ? sV : oV;
             });
             console.log(im);
         }
@@ -181,7 +189,7 @@ const stageIm = (function () {
                 lp = {name: p.name, qty: p.quantity};
                 lx.pos.push(lp);
             }
-            lp.jl = _.round(_.add(lp.jl, p.gather_qty), 6);
+            lp.jl = ZhCalc.add(lp.jl, p.gather_qty);
         }
     }
 
@@ -195,6 +203,7 @@ const stageIm = (function () {
             im.leafXmjs = [];
         }
         const leafXmj = gsTree.getLeafXmjParent(node);
+        if (!leafXmj) { return }
         let lx = _.find(im.leafXmjs, {lxid: leafXmj.id});
         if (!lx) {
             lx = {
@@ -204,7 +213,7 @@ const stageIm = (function () {
             };
             im.leafXmjs.push(lx);
         }
-        lx.jl = _.round(_.add(lx.jl, node[jlField]), 6);
+        lx.jl = ZhCalc.add(lx.jl, node[jlField]);
         generatePosData(node, lx);
     }
 
@@ -275,7 +284,7 @@ const stageIm = (function () {
                 ImData.push(im);
             }
             generateLeafXmjData(p, im, 'gather_qty');
-            im.jl = _.add(im.jl, p.gather_qty);
+            im.jl = ZhCalc.add(im.jl, p.gather_qty);
         }
     }
 
@@ -314,16 +323,39 @@ const stageIm = (function () {
         return ImData;
     }
 
+    function loadUpdateDetailData (data) {
+        const datas = data instanceof Array ? data : [data];
+        for (const d of datas) {
+            const detail = _.find(details, {uuid: d.uuid});
+            if (detail) {
+                _.assignIn(detail, d);
+            } else {
+                details.push(d);
+            }
+            let imData = _.find(ImData, {lid: d.lid, uuid: d.uuid});
+            if (!imData) {
+                imData = _.find(ImData, {lid: d.lid, code: d.code, name: d.name, unit: d.unit});
+            }
+            if (imData) {
+                _.assignInWith(imData, d, function (oV, sV, key) {
+                    return imFields.indexOf(key) > -1 && !_.isUndefined(sV) ? sV : oV;
+                });
+            }
+        }
+    }
+
     return {
         init,
         initCheck,
         loadData,
         buildImData,
+        loadUpdateDetailData,
         getGsTree: function () {
             return gsTree;
         },
         getImData: function () {
             return ImData;
         },
+
     }
 })();

+ 270 - 43
app/public/js/stage_pay.js

@@ -16,14 +16,30 @@ function getStageId() {
     return window.location.pathname.split('/')[5];
 }
 
-function loadUpdateDealPays(newPay) {
+function loadUpdateDealPays(newPay, fields) {
     const newPays = newPay instanceof Array ? newPay : [newPay];
     for (const np of newPays) {
         const op = _.find(dealPay, {id: np.id});
         for (const prop in np) {
-            op[prop] = np[prop];
+            if (!fields || fields.indexOf(prop) >= 0) {
+                op[prop] = np[prop];
+            }
+        }
+    }
+}
+
+function makeAttTable(id, attachment) {
+    let html = '';
+    if (attachment !== null) {
+        for (const [index, att] of attachment.entries()) {
+            const delhtml = uploadPermission && parseInt(att.uid) === parseInt(userID) ? '<a class="delete-att text-danger" href="javascript:void(0);" data-payid="'+ id +'" data-attindex="'+ index +'" title="删除"><i class="fa fa-remove "></i></a>' : '';
+            html += '<tr><td style="width: 200px">' + att.filename + att.fileext + '</td><td>' + att.username + '</td><td>' + att.in_time + '</td>' +
+                '<td><a href="/tender/'+ tender.id + '/measure/stage/' + tender.ledger_times +'/pay/download/file/'+ id +'/'+ index +'" title="下载"><i class="fa fa-download "></i></a> ' +
+                delhtml +
+                '</td></tr>';
         }
     }
+    $('#pay-attList').html(html);
 }
 
 $(document).ready(() => {
@@ -34,11 +50,11 @@ $(document).ready(() => {
         cols: [
             {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 150, formatter: '@', readOnly: 'readOnly.name'},
             {title: '扣款', colSpan: '1', rowSpan: '1', field: 'minus', hAlign: 1, width: 50, cellType: 'checkbox', readOnly: 'readOnly.minus'},
-            {title: '本期金额(表达式)', colSpan: '1', rowSpan: '1', field: 'tp', hAlign: 2, width: 100, readOnly: 'readOnly.tp', cellType: 'tip', getTip: function (data) {return data ? data.expr : '';}},
+            {title: '本期金额(表达式)', colSpan: '1', rowSpan: '1', field: 'tp', hAlign: 2, width: 100, readOnly: 'readOnly.tp', /*cellType: 'tip', getTip: function (data) {return data ? data.expr : '';}*/},
             {title: '截止上期金额',  colSpan: '1', rowSpan: '1', field: 'pre_tp', hAlign: 2, width: 100, readOnly: true},
             {title: '截止本期金额',  colSpan: '1', rowSpan: '1', field: 'end_tp', hAlign: 2, width: 100, readOnly: true},
-            {title: '起扣金额',  colSpan: '1', rowSpan: '1', field: 'sprice', hAlign: 2, width: 100, readOnly: 'readOnly.sprice', cellType: 'tip', getTip: function (data) {return data ? data.expr : '';}},
-            {title: '付(扣)款限额',  colSpan: '1', rowSpan: '1', field: 'rprice', hAlign: 2, width: 100, readOnly: 'readOnly.rprice', cellType: 'tip', getTip: function (data) {return data ? data.expr : '';}},
+            {title: '起扣金额',  colSpan: '1', rowSpan: '1', field: 'sprice', hAlign: 2, width: 100, readOnly: 'readOnly.sprice', /*cellType: 'tip', getTip: function (data) {return data ? data.sexpr : '';}*/},
+            {title: '付(扣)款限额',  colSpan: '1', rowSpan: '1', field: 'rprice', hAlign: 2, width: 100, readOnly: 'readOnly.rprice', /*cellType: 'tip', getTip: function (data) {return data ? data.rexpr : '';}*/},
             {
                 title: '附件', colSpan: '1', rowSpan: '1', field: 'attachment', hAlign: 0, width: 60, readOnly: true, cellType: 'imageBtn',
                 normalImg: '#rela-file-icon', hoverImg: '#rela-file-hover', getValue: 'getValue.attachment'
@@ -53,6 +69,7 @@ $(document).ready(() => {
         pos: SpreadJsObj.getObjPos($('#pay-spread')[0]),
     };
     paySpreadSetting.imageClick = function (data) {
+        makeAttTable(data.id, data.attachment);
         $('#file').modal('show');
     };
     paySpreadSetting.getColor = function (data, col, defaultColor) {
@@ -73,7 +90,7 @@ $(document).ready(() => {
     const payCol = {
         getValue: {
             attachment: function (data) {
-                return data.attchement ? data.attachement.length : 0;
+                return data.attachment ? data.attachment.length : 0;
             },
             state: function (data) {
                 const value = [];
@@ -87,27 +104,58 @@ $(document).ready(() => {
             },
         },
         readOnly: {
+            isNonZero: function (num) {
+                return !(!num || num === 0)
+            },
+            isYF: function (data) {
+                return data.ptype === 2;
+            },
             isSpecial: function (data) {
                 return data.ptype !== 1;
             },
             isOld: function (data) {
-                return (data.csorder < getStageId()) && (getStageId() > 1);
+                if (data.csorder === 0) {
+                    return stage.order > 1 || stage.times > 1 || stage.curOrder > 0;
+                } else {
+                    return stage.order > data.csorder || stage.times > data.cstimes || stage.curOrder > data.csaorder;
+                }
+            },
+            isYB: function (data) {
+                return (stage.status === 1 || stage.status === 4);
+            },
+            isStarted: function (data) {
+                return ((payCol.readOnly.isNonZero(data.end_tp) || payCol.readOnly.isNonZero(data.tp)) && data.tp !== data.end_tp) || payCol.readOnly.isNonZero(data.pre_tp);
             },
             name: function (data) {
-                return payCol.readOnly.isSpecial(data) || payCol.readOnly.isOld(data);
+                return payCol.readOnly.isSpecial(data); //所有人,轮到自己时,均可修改
             },
             minus: function (data) {
-                return readOnly || (payCol.readOnly.isSpecial(data) || payCol.readOnly.isOld(data));
+                if (payCol.readOnly.isOld(data)) { // 上报或审批后,仅原报,在未开始计量前,可修改
+                    return payCol.readOnly.isSpecial(data) || payCol.readOnly.isStarted(data) || !payCol.readOnly.isYB(data);
+                } else { // 新增时,新增人可修改
+                    return payCol.readOnly.isSpecial(data);
+                }
             },
             tp: function (data) {
-                return data.ptype === 2 || data.ptype === 4 || payCol.readOnly.isOld(data);
+                return data.ptype === 2 || data.ptype === 4; // 仅本期完成计量、本期应付,不可修改
             },
             sprice: function (data) {
-                return payCol.readOnly.isSpecial(data) || payCol.readOnly.isOld(data);
+                if (payCol.readOnly.isOld(data)) { // 上报或审批后,仅原报,在未开始计量前,可修改
+                    return payCol.readOnly.isYF(data) || payCol.readOnly.isStarted(data) || !payCol.readOnly.isYB(data);
+                } else { // 新增时,新增人可修改
+                    return payCol.readOnly.isSpecial(data);
+                }
             },
             rprice: function (data) {
-                return payCol.readOnly.isSpecial(data) || payCol.readOnly.isOld(data);
+                return payCol.readOnly.sprice(data); // 同起扣金额
             },
+            pause: function (data) {
+                if (payCol.readOnly.isOld(data)) { // 上报或审批后,仅原报,可修改
+                    return payCol.readOnly.isSpecial(data) || !payCol.readOnly.isYB(data);
+                } else { // 新增时,新增人可修改
+                    return payCol.readOnly.isSpecial(data);
+                }
+            }
         }
     };
     SpreadJsObj.initSpreadSettingEvents(paySpreadSetting, payCol);
@@ -115,6 +163,21 @@ $(document).ready(() => {
     SpreadJsObj.loadSheetData(paySpread.getActiveSheet(), SpreadJsObj.DataType.Data, dealPay);
 
     const paySpreadObj = {
+        _checkExpr: function (text, data, priceField, exprField) {
+            if (text) {
+                const num = _.toNumber(text);
+                if (num) {
+                    data[priceField] = num;
+                    data[exprField] = null;
+                } else {
+                    data[exprField] = text.replace('=', '');
+                    data[priceField] = null;
+                }
+            } else {
+                data[priceField] = null;
+                data[exprField] = null;
+            }
+        },
         refreshActn: function () {
             const setObjEnable = function (obj, enable) {
                 if (enable) {
@@ -146,6 +209,15 @@ $(document).ready(() => {
         del: function () {
             const sheet = paySpread.getActiveSheet();
             const select = SpreadJsObj.getSelectObject(sheet);
+            if (payCol.readOnly.isNonZero(select.tp)) {
+                toast('该支付(扣款)项存在数据,如需删除请先清除本期金额!');
+                return;
+            } else if (payCol.readOnly.isOld(select)) {
+                if (payCol.readOnly.isStarted(select)) {
+                    toast('该合同支付项往期已进行计算,不允许删除');
+                    return;
+                }
+            }
             postData(window.location.pathname + '/save', {type: 'del', id: select.pid}, function (result) {
                 const index = dealPay.indexOf(select);
                 dealPay.splice(index, 1);
@@ -216,21 +288,6 @@ $(document).ready(() => {
             }
         },
         editEnded: function (e, info) {
-            const checkExpr = function (text, data, priceField, exprField) {
-                if (text) {
-                    const num = _.toNumber(text);
-                    if (num) {
-                        data[priceField] = num;
-                        data[exprField] = null;
-                    } else {
-                        data[exprField] = text;
-                        data[priceField] = null;
-                    }
-                } else {
-                    data[priceField] = null;
-                    data[exprField] = null;
-                }
-            };
             if (info.sheet.zh_setting) {
                 const select = SpreadJsObj.getSelectObject(info.sheet);
                 const col = info.sheet.zh_setting.cols[info.col];
@@ -255,20 +312,23 @@ $(document).ready(() => {
                 }
                 // 获取更新信息
                 const data = {
-                    type: col.field === 'tp' ? 'stage' : 'info',
+                    type: (col.field === 'tp' || col.field === 'name') ? 'stage' : 'info',
                     updateData: {}
                 };
                 // 获取更新数据
                 if (col.field === 'tp') {
                     data.updateData.pid = select.pid;
-                    checkExpr(validText, data.updateData, 'tp', 'expr');
+                    paySpreadObj._checkExpr(validText, data.updateData, 'tp', 'expr');
+                } else if (col.field === 'name') {
+                    data.updateData.pid = select.pid;
+                    data.updateData.name = validText;
                 } else {
                     data.updateData.id = select.pid;
                     if (validText) {
                         if (col.field === 'sprice') {
-                            checkExpr(validText, data.updateData, 'sprice', 'sexpr');
+                            paySpreadObj._checkExpr(validText, data.updateData, 'sprice', 'sexpr');
                         } else if (col.field === 'rprice') {
-                            checkExpr(validText, data.updateData, 'rprice', 'rexpr');
+                            paySpreadObj._checkExpr(validText, data.updateData, 'rprice', 'rexpr');
                         } else {
                             data.updateData[col.field] = validText;
                         }
@@ -278,7 +338,7 @@ $(document).ready(() => {
                 }
                 // 更新至服务器
                 postData(window.location.pathname + '/save', data, function (result) {
-                    loadUpdateDealPays(result);
+                    loadUpdateDealPays(result, col.field === 'name' ? ['name'] : null);
                     SpreadJsObj.reLoadSheetData(paySpread.getActiveSheet());
                 });
             }
@@ -327,14 +387,112 @@ $(document).ready(() => {
                     info.sheet.getCell(info.row, info.col).text(select.rexpr);
                 }
             }
-        }
+        },
+        deletePress: function (sheet) {
+            if (sheet.zh_setting && sheet.zh_data) {
+                const sel = sheet.getSelections()[0];
+                if (!sel) return;
+
+                const col = sheet.zh_setting.cols[sel.col];
+                if (col.readOnly === true) { return; }
+                if (sel.colCount > 1) {
+                    toast('请勿同时删除多列数据', 'warning');
+                }
+
+                const data = {type: col.field === 'tp' ? 'stage' : 'info', updateData: []};
+                for (let iRow = sel.row; iRow < sel.row + sel.rowCount; iRow++) {
+                    const node = sheet.zh_data[iRow];
+                    if (node && (node.ptype === 1 || node.ptype === 3)) {
+                        const updateData = {};
+                        if (col.field === 'tp') {
+                            updateData.pid = node.pid;
+                            updateData.tp = null;
+                            updateData.expr = null;
+                        } else {
+                            updateData.id = node.pid;
+                            if (col.field === 'sprice') {
+                                updateData.sprice = null;
+                                updateData.sexpr = null;
+                            } else if (col.field === 'rprice') {
+                                updateData.rprice = null;
+                                updateData.rexpr = null;
+                            } else {
+                                updateData[col.field] = null;
+                            }
+                        }
+                        data.updateData.push(updateData);
+                    }
+                }
+                if (data.updateData.length > 0) {
+                    if (data.updateData.length === 1 && sel.rowCount === 1) {
+                        data.updateData = data.updateData[0];
+                    }
+                    postData(window.location.pathname + '/save', data, function (result) {
+                        loadUpdateDealPays(result, col.field === 'name' ? ['name'] : null);
+                        SpreadJsObj.reLoadSheetData(paySpread.getActiveSheet());
+                    })
+                }
+            }
+        },
+        clipboardPasted: function (e, info) {
+            if (info.sheet.zh_setting && info.sheet.zh_data) {
+                const col = info.sheet.zh_setting.cols[info.cellRange.col];
+                if (col.readOnly === true) {
+                    SpreadJsObj.reLoadSheetData(paySpread.getActiveSheet());
+                    return;
+                }
+                if (info.cellRange.colCount > 1) {
+                    toast('请勿同时复制粘贴多列数据', 'warning');
+                }
+
+                const sortData = info.sheet.zh_data;
+                const data = {type: col.field === 'tp' ? 'stage' : 'info', updateData: []};
+
+                for (let iRow = 0; iRow < info.cellRange.rowCount; iRow++) {
+                    const curRow = info.cellRange.row + iRow;
+                    const node = sortData[curRow];
+                    if (node && (node.ptype === 1 || node.ptype === 3)) {
+                        const validText = info.sheet.getText(curRow, info.cellRange.col).replace('\n', '');
+                        const updateData = {};
+                        if (col.field === 'tp') {
+                            updateData.pid = node.pid;
+                            paySpreadObj._checkExpr(validText, updateData, 'tp', 'expr');
+                        } else {
+                            updateData.id = node.pid;
+                            if (validText) {
+                                if (col.field === 'sprice') {
+                                    paySpreadObj._checkExpr(validText, updateData, 'sprice', 'sexpr');
+                                } else if (col.field === 'rprice') {
+                                    paySpreadObj._checkExpr(validText, updateData, 'rprice', 'rexpr');
+                                } else {
+                                    updateData[col.field] = validText;
+                                }
+                            } else {
+                                updateData[col.field] = null;
+                            }
+                        }
+                        data.updateData.push(updateData);
+                    }
+                    if (data.updateData.length > 0) {
+                        postData(window.location.pathname + '/save', data, function (result) {
+                            loadUpdateDealPays(result, col.field === 'name' ? ['name'] : null);
+                            SpreadJsObj.reLoadSheetData(paySpread.getActiveSheet());
+                        }, function () {
+                            SpreadJsObj.reLoadSheetData(paySpread.getActiveSheet());
+                        });
+                    }
+                }
+            }
+        },
     };
     paySpreadObj.refreshActn();
+    paySpread.bind(spreadNS.Events.SelectionChanged, paySpreadObj.selectionChanged);
     if (!readOnly) {
         paySpread.bind(spreadNS.Events.EditEnded, paySpreadObj.editEnded);
-        paySpread.bind(spreadNS.Events.SelectionChanged, paySpreadObj.selectionChanged);
         paySpread.bind(spreadNS.Events.ButtonClicked, paySpreadObj.buttonClicked);
         paySpread.bind(spreadNS.Events.EditStarting, paySpreadObj.editStarting);
+        paySpread.bind(spreadNS.Events.ClipboardPasted, paySpreadObj.clipboardPasted);
+        SpreadJsObj.addDeleteBind(paySpread, paySpreadObj.deletePress);
         $('#add').click(paySpreadObj.add);
         $('#del').click(paySpreadObj.del);
         $('#up-move').click(paySpreadObj.upMove);
@@ -485,7 +643,8 @@ $(document).ready(() => {
                     return select.ptype === 1 && select.pause;
                 },
                 disabled: function (key, opt) {
-                    return readOnly;
+                    const select = SpreadJsObj.getSelectObject(paySpread.getActiveSheet());
+                    return readOnly || payCol.readOnly.pause(select);
                 }
             },
             'stop': {
@@ -511,7 +670,8 @@ $(document).ready(() => {
                     return select.ptype === 1 && !select.pause;
                 },
                 disabled: function (key, opt) {
-                    return readOnly;
+                    const select = SpreadJsObj.getSelectObject(paySpread.getActiveSheet());
+                    return readOnly || payCol.readOnly.pause(select);
                 }
             },
             'setDeadline': {
@@ -519,7 +679,8 @@ $(document).ready(() => {
                 icon: 'fa-clipboard',
                 visible: function (key, opt) {
                     const select = SpreadJsObj.getSelectObject(paySpread.getActiveSheet());
-                    return select.ptype === 1 && select.csorder == getStageId();
+                    const stageId = getStageId();
+                    return select.ptype === 1 && (select.csorder == stageId || stageId == 1);
                 },
                 callback: function (key, opt) {
                     const select = SpreadJsObj.getSelectObject(paySpread.getActiveSheet());
@@ -531,7 +692,8 @@ $(document).ready(() => {
                     }
                 },
                 disabled: function (key, opt) {
-                    return readOnly;
+                    const select = SpreadJsObj.getSelectObject(paySpread.getActiveSheet());
+                    return readOnly || payCol.readOnly.minus(select);
                 }
             },
             'dropYF': {
@@ -539,7 +701,8 @@ $(document).ready(() => {
                 icon: 'fa-chain-broken',
                 visible: function (key, opt) {
                     const select = SpreadJsObj.getSelectObject(paySpread.getActiveSheet());
-                    return select.ptype === 1 && select.is_yf && select.csorder == getStageId();
+                    const stageId = getStageId();
+                    return select.ptype === 1 && select.is_yf && (select.csorder == stageId || stageId == 1);
                 },
                 callback: function (key, opt) {
                     const select = SpreadJsObj.getSelectObject(paySpread.getActiveSheet());
@@ -557,7 +720,8 @@ $(document).ready(() => {
                     });
                 },
                 disabled: function (key, opt) {
-                    return readOnly;
+                    const select = SpreadJsObj.getSelectObject(paySpread.getActiveSheet());
+                    return readOnly || payCol.readOnly.minus(select);
                 }
             },
             'belongYF': {
@@ -565,7 +729,8 @@ $(document).ready(() => {
                 icon: 'fa-chain',
                 visible: function (key, opt) {
                     const select = SpreadJsObj.getSelectObject(paySpread.getActiveSheet());
-                    return select.ptype === 1 && !select.is_yf && select.csorder == getStageId();
+                    const stageId = getStageId();
+                    return select.ptype === 1 && !select.is_yf && (select.csorder == stageId || stageId == 1);
                 },
                 callback: function (key, opt) {
                     const select = SpreadJsObj.getSelectObject(paySpread.getActiveSheet());
@@ -583,7 +748,8 @@ $(document).ready(() => {
                     });
                 },
                 disabled: function (key, opt) {
-                    return readOnly;
+                    const select = SpreadJsObj.getSelectObject(paySpread.getActiveSheet());
+                    return readOnly || payCol.readOnly.minus(select);
                 }
             }
         }
@@ -610,4 +776,65 @@ $(document).ready(() => {
             $('#deadline').modal('hide');
         });
     });
-});
+
+    // 上传附件
+    $('#upload-file').change(function () {
+        const files = this.files;
+        const select = SpreadJsObj.getSelectObject(paySpread.getActiveSheet());
+        const formData = new FormData();
+        // const sizes = [];
+        formData.append('pay_id', select.id);
+        for (const file of files) {
+            if (file === undefined) {
+                toast('未选择上传文件!', 'error');
+                return false;
+            }
+            const filesize = file.size;
+            if (filesize > 10 * 1024 * 1024) {
+                toast('存在上传文件大小过大!', 'error');
+                return false;
+            }
+            const fileext = '.' + file.name.toLowerCase().split('.').splice(-1)[0];
+            if (whiteList.indexOf(fileext) === -1) {
+                toast('只能上传指定格式的附件!', 'error');
+                return false;
+            }
+            // sizes.push(filesize);
+            formData.append('size', filesize);
+            formData.append('file[]', file);
+        }
+        // formData.append('size', sizes.join(','));
+        postDataWithFile('/tender/' + tender.id + '/measure/stage/' + tender.ledger_times + '/pay/upload/file', formData, function (data) {
+            if (select.attachment === null) {
+                select.attachment = data;
+            } else {
+                select.attachment = data.concat(select.attachment);
+            }
+            makeAttTable(select.id, select.attachment);
+            const index = dealPay.indexOf(select);
+            dealPay.splice(index, 1, select);
+            SpreadJsObj.reLoadSheetData(paySpread.getActiveSheet());
+        }, function () {
+            toast('附件上传失败', 'error');
+        });
+        $('#upload-file').val('');
+    });
+
+    // 删除附件
+    $('body').on('click', '.delete-att' ,function () {
+        const id = $(this).attr('data-payid');
+        const index = $(this).attr('data-attindex');
+        const data = {
+            id,
+            index,
+        };
+        const select = SpreadJsObj.getSelectObject(paySpread.getActiveSheet());
+        postData('/tender/' + tender.id + '/measure/stage/' + tender.ledger_times + '/pay/delete/file', data, function (result) {
+            select.attachment.splice(index, 1);
+            makeAttTable(id, select.attachment);
+            const pay_index = dealPay.indexOf(select);
+            dealPay.splice(pay_index, 1, select);
+            SpreadJsObj.reLoadSheetData(paySpread.getActiveSheet());
+        });
+    })
+});

+ 254 - 84
app/public/js/tender.js

@@ -68,34 +68,10 @@ function loadCalculateProperty () {
     $('#decimal-pay')[0].checked = property.decimal.pay;
     $('#decimal-pay-tp').val(property.decimal.payTp);
 }
-// 清单精度
-function loadPrecisionProperty () {
-    $('#unit-t').val(property.precision.t.value);
-    $('#unit-km').val(property.precision.km.value);
-    $('#unit-m').val(property.precision.m.value);
-    $('#unit-m2').val(property.precision.m2.value);
-    $('#unit-m3').val(property.precision.m3.value);
-    $('#unit-kg').val(property.precision.kg.value);
-    $('#unit-ge').val(property.precision.ge.value);
-    $('#unit-tai').val(property.precision.tai.value);
-    $('#unit-tao').val(property.precision.tao.value);
-    $('#unit-ke').val(property.precision.ke.value);
-    $('#unit-zu').val(property.precision.zu.value);
-    $('#unit-xitong').val(property.precision.xitong.value);
-    $('#unit-other').val(property.precision.other.value);
-}
-// 合同参数
-function loadDealProperty () {
-    $('#contract-price').val(property.deal_param.contractPrice);
-    $('#zan-lie-price').val(property.deal_param.zanLiePrice);
-    const iDecimal = property.decimal.pay ? property.decimal.payTp : property.decimal.tp;
-    $('#c-zl').val(_.round(property.deal_param.contractPrice - property.deal_param.zanLiePrice, iDecimal));
-    $('#start-advance').val(property.deal_param.startAdvance);
-    $('#material-advance').val(property.deal_param.materialAdvance);
-}
 // 显示设置
 function loadDisplayProperty () {
     $('#ledger-dgn-qty')[0].checked = property.display.ledger.dgnQty;
+    $('#ledger-cl-qty')[0].checked = property.display.ledger.clQty;
 }
 // 设置某个div下全部的input、select是否只读
 function setReadOnly(obj, readOnly) {
@@ -109,31 +85,6 @@ function setReadOnly(obj, readOnly) {
         $('input[type=checkbox]', obj).removeAttr('disabled');
     }
 }
-// 获取当前合同支付应该使用的小数位数
-function getDealTpDecimal() {
-    const spec = $('#decimal-pay')[0].checked;
-    return spec ? _.toNumber($('#decimal-pay-tp').val()) : _.toNumber($('#decimal-tp').val());
-}
-// 四舍五入
-function roundPrice(obj) {
-    const iDecimal = getDealTpDecimal();
-    obj.val(_.round(_.toNumber(obj.val()), iDecimal));
-}
-// 计算签约合同价(不含暂列金额)
-function calculateC2() {
-    const constract = _.toNumber($('#contract-price').val());
-    const zanLie = _.toNumber($('#zan-lie-price').val());
-    const iDecimal = getDealTpDecimal();
-    $('#c-zl').val(_.round(constract - zanLie, iDecimal));
-}
-// 根据小数位数,计算全部的合同参数
-function CalculateAllDealParam() {
-    roundPrice($('#contract-price'));
-    roundPrice($('#zan-lie-price'));
-    roundPrice($('#start-advance'));
-    roundPrice($('#material-advance'));
-    calculateC2();
-}
 // 根据Min Max限制Input输入
 function limitInputMinMax (obj) {
     if (_.toNumber(obj.value) > _.toNumber(obj.max)) {
@@ -170,6 +121,245 @@ function checkNumberValid(obj) {
 }
 
 $(document).ready(function() {
+    // 清单精度
+    const precisionObj = (function () {
+        const spread = SpreadJsObj.createNewSpread($('#precision-spread')[0]);
+        spread.options.showVerticalScrollbar = false;
+        spread.options.showHorizontalScrollbar = false;
+        const sheet = spread.getActiveSheet();
+        SpreadJsObj.protectedSheet(sheet);
+        sheet.options.rowHeaderVisible = false;
+        sheet.options.colHeaderVisible = false;
+        SpreadJsObj.massOperationSheet(sheet, function () {
+            sheet.defaults.rowHeight = 28;
+            sheet.setColumnCount(3);
+            sheet.setRowCount(14);
+            sheet.setColumnWidth(0, 1);
+            sheet.setColumnWidth(1, 100);
+            sheet.setColumnWidth(2, 60);
+            sheet.setRowHeight(0, 1);
+            sheet.getRange(1, 1, 14, 1).vAlign(1).backColor('#e4e7ea').locked(true);
+            sheet.getRange(1, 2, 14, 1).vAlign(1).hAlign(2).locked(true);
+            sheet.setText(1, 1, 't');
+            sheet.setText(2, 1, 'km');
+            sheet.setText(3, 1, 'm');
+            sheet.setText(4, 1, 'm2');
+            sheet.setText(5, 1, 'm3');
+            sheet.setText(6, 1, 'kg');
+            sheet.setText(7, 1, '个');
+            sheet.setText(8, 1, '台');
+            sheet.setText(9, 1, '套');
+            sheet.setText(10, 1, '棵');
+            sheet.setText(11, 1, '组');
+            sheet.setText(12, 1, '系统');
+            sheet.setText(13, 1, '其他未列单位');
+            const lineBorder = new spreadNS.LineBorder('#6a696e', spreadNS.LineStyle.thin);
+            sheet.getRange(0, 0, 14, 3).setBorder(lineBorder, {all: true});
+            sheet.getRange(0, 0, 14, 3).formatter('@');
+            sheet.setSelection(1, 2, 1, 1);
+        });
+
+        spread.bind(spreadNS.Events.EditEnded, function (e, info) {
+            const value = _.toNumber(info.editingText);
+            if (!_.isInteger(value)) {
+                toast('请输入0-6的整数', 'warning');
+                sheet.setText(info.row, info.col, '0');
+            } else if (value > 6) {
+                toast('请输入0-6的整数', 'warning');
+                sheet.setText(info.row, info.col, '6');
+            } else if (value < 0) {
+                toast('请输入0-6的整数', 'warning');
+                sheet.setText(info.row, info.col, '0');
+            }
+        });
+        SpreadJsObj.addDeleteBind(spread, function (sheet) {
+            const sel = sheet.getSelections()[0];
+            let calc = false;
+            if (sel) {
+                for (let iRow = sel.row, iRowLength = sel.row + sel.rowCount; iRow < iRowLength; iRow++) {
+                    if (iRow === 3) continue;
+                    for (let iCol = sel.col, iColLength = sel.col + sel.colCount; iCol < iColLength; iCol++) {
+                        if (iCol !== 2) continue;
+                        sheet.setText(iRow, iCol, '0');
+                        if (iRow === 1 || iRow === 2) calc = true;
+                    }
+                }
+            }
+            if (calc) calcHtjMinusZlj();
+        });
+        spread.bind(spreadNS.Events.ClipboardPasted, function (e, info) {
+            let bHint = false;
+            for (let iRow = 0; iRow < info.cellRange.rowCount; iRow++) {
+                const curRow = info.cellRange.row + iRow;
+                for (let iCol = 0; iCol < info.cellRange.colCount; iCol++) {
+                    const curCol = info.cellRange.col + iCol;
+                    const value = _.toNumber(info.sheet.getText(curRow, curCol));
+                    if (_.isNaN(value) || !_.isInteger(value)) {
+                        bHint = true;
+                        info.sheet.setText(curRow, curCol, '0');
+                    } else if (value > 6) {
+                        bHint = true;
+                        info.sheet.setText(curRow, curCol, '6');
+                    } else if (value < 0) {
+                        bHint = true;
+                        info.sheet.setText(curRow, curCol, '0');
+                    }
+
+                }
+            }
+            if (bHint) {
+                toast('请输入0-6的整数', 'warning');
+            }
+        });
+
+        function loadPrecisonProperty() {
+            $('#hint-3').hide();
+            sheet.setValue(1, 2, property.precision.t.value);
+            sheet.setValue(2, 2, property.precision.km.value);
+            sheet.setValue(3, 2, property.precision.m.value);
+            sheet.setValue(4, 2, property.precision.m2.value);
+            sheet.setValue(5, 2, property.precision.m3.value);
+            sheet.setValue(6, 2, property.precision.kg.value);
+            sheet.setValue(7, 2, property.precision.ge.value);
+            sheet.setValue(8, 2, property.precision.tai.value);
+            sheet.setValue(9, 2, property.precision.tao.value);
+            sheet.setValue(10, 2, property.precision.ke.value);
+            sheet.setValue(11, 2, property.precision.zu.value);
+            sheet.setValue(12, 2, property.precision.xitong.value);
+            sheet.setValue(13, 2, property.precision.other.value);
+        }
+        function setReadOnly(readOnly) {
+            sheet.getRange(1, 2, 14, 1).locked(readOnly);
+        }
+        function getNewPrecisionData() {
+            const precision = JSON.parse(JSON.stringify(property.precision));
+            precision.t.value = _.toNumber(sheet.getText(1, 2));
+            precision.km.value = _.toNumber(sheet.getText(2, 2));
+            precision.m.value = _.toNumber(sheet.getText(3, 2));
+            precision.m2.value = _.toNumber(sheet.getText(4, 2));
+            precision.m3.value = _.toNumber(sheet.getText(5, 2));
+            precision.kg.value = _.toNumber(sheet.getText(6, 2));
+            precision.ge.value = _.toNumber(sheet.getText(7, 2));
+            precision.tai.value = _.toNumber(sheet.getText(8, 2));
+            precision.tao.value = _.toNumber(sheet.getText(9, 2));
+            precision.ke.value = _.toNumber(sheet.getText(10, 2));
+            precision.zu.value = _.toNumber(sheet.getText(11, 2));
+            precision.xitong.value = _.toNumber(sheet.getText(12, 2));
+            precision.other.value = _.toNumber(sheet.getText(13, 2));
+            return precision;
+        }
+
+        return {loadPrecisonProperty, setReadOnly, getNewPrecisionData, };
+    })();
+    // 合同参数
+    const dealObj = (function () {
+        const spread = SpreadJsObj.createNewSpread($('#param-spread')[0]);
+        spread.options.showVerticalScrollbar = false;
+        spread.options.showHorizontalScrollbar = false;
+        const sheet = spread.getActiveSheet();
+        SpreadJsObj.protectedSheet(sheet);
+        SpreadJsObj.massOperationSheet(sheet, function () {
+            sheet.options.rowHeaderVisible = false;
+            sheet.options.colHeaderVisible = false;
+            sheet.defaults.rowHeight = 35;
+            sheet.setColumnCount(3);
+            sheet.setRowCount(6);
+            sheet.setColumnWidth(0, 1);
+            sheet.setColumnWidth(1, 200);
+            sheet.setColumnWidth(2, 200);
+            sheet.setRowHeight(0, 1);
+            sheet.getRange(1, 1, 5, 1).vAlign(1).backColor('#e4e7ea').locked(true);
+            sheet.getRange(1, 2, 5, 1).vAlign(1).hAlign(2).locked(true);
+            sheet.setText(1, 1, '签约合同价');
+            sheet.setText(2, 1, '暂列金额');
+            sheet.setText(3, 1, '签约合同价(不含暂列金)');
+            sheet.setText(4, 1, '签约开工预付款');
+            sheet.setText(5, 1, '签约材料预付款');
+            const lineBorder = new spreadNS.LineBorder('#6a696e', spreadNS.LineStyle.thin);
+            sheet.getRange(0, 0, 6, 3).setBorder(lineBorder, {all: true});
+            sheet.getRange(0, 0, 6, 3).formatter('@');
+            sheet.setSelection(1, 2, 1, 1);
+        });
+
+        function calcHtjMinusZlj() {
+            const htj = _.toNumber(sheet.getText(1, 2));
+            const zlj = _.toNumber(sheet.getText(2, 2));
+            sheet.setValue(3, 2, accSub(zlj, htj));
+        }
+
+        spread.bind(spreadNS.Events.EditEnded, function (e, info) {
+            const value = _.toNumber(info.editingText);
+            if (_.isNaN(value)) {
+                toast('请输入不超过万亿的数字', 'warning');
+                info.sheet.setText(info.row, info.col, '0');
+            } else if (value > Math.pow(10, 13)) {
+                toast('请输入不超过万亿的数字', 'warning');
+                info.sheet.setText(info.row, info.col, '0');
+            }
+            if (info.row === 1 || info.row === 2) {
+                calcHtjMinusZlj();
+            }
+        });
+        SpreadJsObj.addDeleteBind(spread, function (sheet) {
+            const sel = sheet.getSelections()[0];
+            let calc = false;
+            if (sel) {
+                for (let iRow = sel.row, iRowLength = sel.row + sel.rowCount; iRow < iRowLength; iRow++) {
+                    if (iRow === 3) continue;
+                    for (let iCol = sel.col, iColLength = sel.col + sel.colCount; iCol < iColLength; iCol++) {
+                        if (iCol !== 2) continue;
+                        sheet.setText(iRow, iCol, '0');
+                        if (iRow === 1 || iRow === 2) calc = true;
+                    }
+                }
+            }
+            if (calc) calcHtjMinusZlj();
+        });
+        spread.bind(spreadNS.Events.ClipboardPasted, function (e, info) {
+            let bHint = false;
+            for (let iRow = 0; iRow < info.cellRange.rowCount; iRow++) {
+                const curRow = info.cellRange.row + iRow;
+                for (let iCol = 0; iCol < info.cellRange.colCount; iCol++) {
+                    const curCol = info.cellRange.col + iCol;
+                    const value = _.toNumber(info.sheet.getText(curRow, curCol));
+                    if (_.isNaN(value) || value > Math.pow(10, 13)) {
+                        bHint = true;
+                        info.sheet.setText(curRow, curCol, '0');
+                    }
+                }
+            }
+            if (bHint) {
+                toast('请输入不超过万亿的数字', 'warning');
+            }
+            calcHtjMinusZlj();
+        });
+
+        function loadDealProperty() {
+            $('#hint-4').hide();
+            sheet.setValue(1, 2, property.deal_param.contractPrice);
+            sheet.setValue(2, 2, property.deal_param.zanLiePrice);
+            sheet.setValue(3, 2, accSub(property.deal_param.zanLiePrice, property.deal_param.contractPrice));
+            sheet.setValue(4, 2, property.deal_param.startAdvance);
+            sheet.setValue(5, 2, property.deal_param.materialAdvance);
+
+        }
+        function setReadOnly (readOnly) {
+            sheet.getCell(1, 2).locked(readOnly);
+            sheet.getCell(2, 2).locked(readOnly);
+            sheet.getCell(4, 2).locked(readOnly);
+            sheet.getCell(5, 2).locked(readOnly);
+        }
+        function getNewDealData () {
+            const result = {};
+            result.contractPrice = _.toNumber(sheet.getText(1, 2));
+            result.zanLiePrice = _.toNumber(sheet.getText(2, 2));
+            result.startAdvance = _.toNumber(sheet.getText(4, 2));
+            result.materialAdvance = _.toNumber(sheet.getText(5, 2));
+            return result;
+        }
+
+        return { loadDealProperty, setReadOnly, getNewDealData, };
+    })();
     // 章节设置
     const chapterObj = (function () {
         const spreadSetting = {
@@ -251,8 +441,8 @@ $(document).ready(function() {
         // 加载属性
         loadCommonProperty();
         loadCalculateProperty();
-        loadPrecisionProperty();
-        loadDealProperty();
+        precisionObj.loadPrecisonProperty();
+        dealObj.loadDealProperty();
         loadDisplayProperty();
         chapterObj.loadChapterProperty();
         // 设置只读
@@ -373,36 +563,23 @@ $(document).ready(function() {
      */
     // 编辑
     $('#edit-3').click(() => {
-        setReadOnly('#v-pills-3', false);
+        precisionObj.setReadOnly(false);
         $('#post-3').parent().show();
         $('#edit-3').parent().hide();
     });
     // 取消
     $('#cancel-3').click(() => {
-        setReadOnly('#v-pills-3', true);
-        loadPrecisionProperty();
+        precisionObj.setReadOnly(true);
+        precisionObj.loadPrecisonProperty();
         $('#post-3').parent().hide();
         $('#edit-3').parent().show();
     });
     // 提交
     $('#post-3').click(() => {
-        const prop = { precision: JSON.parse(JSON.stringify(property.precision)) };
-        prop.precision.t.value = _.toNumber($('#unit-t').val());
-        prop.precision.km.value = _.toNumber($('#unit-km').val());
-        prop.precision.m.value = _.toNumber($('#unit-m').val());
-        prop.precision.m2.value = _.toNumber($('#unit-m2').val());
-        prop.precision.m3.value = _.toNumber($('#unit-m3').val());
-        prop.precision.kg.value = _.toNumber($('#unit-kg').val());
-        prop.precision.ge.value = _.toNumber($('#unit-ge').val());
-        prop.precision.tai.value = _.toNumber($('#unit-tai').val());
-        prop.precision.tao.value = _.toNumber($('#unit-tao').val());
-        prop.precision.ke.value = _.toNumber($('#unit-ke').val());
-        prop.precision.zu.value = _.toNumber($('#unit-zu').val());
-        prop.precision.xitong.value = _.toNumber($('#unit-xitong').val());
-        prop.precision.other.value = _.toNumber($('#unit-other').val());
+        const prop = { precision: precisionObj.getNewPrecisionData() };
         const tenderId = window.location.pathname.split('/')[2];
         postData('/tender/' + tenderId + '/save', prop, function (data) {
-            setReadOnly('#v-pills-3', true);
+            precisionObj.setReadOnly(true);
             property.precision = data.precision;
             $('#post-3').parent().hide();
             $('#edit-3').parent().show();
@@ -414,33 +591,27 @@ $(document).ready(function() {
      */
     // 编辑
     $('#edit-4').click(() => {
-        setReadOnly('#v-pills-4', false);
+        dealObj.setReadOnly(false);
         $('#post-4').parent().show();
         $('#edit-4').parent().hide();
     });
     // 取消
     $('#cancel-4').click(() => {
-        setReadOnly('#v-pills-4', true);
-        loadDealProperty();
+        dealObj.setReadOnly(true);
+        dealObj.loadDealProperty();
         $('#post-4').parent().hide();
         $('#edit-4').parent().show();
     });
     // 提交
     $('#post-4').click(() => {
-        const prop = {
-            deal_param: {
-                contractPrice: _.toNumber($('#contract-price').val()),
-                zanLiePrice: _.toNumber($('#zan-lie-price').val()),
-                startAdvance: _.toNumber($('#start-advance').val()),
-                materialAdvance: _.toNumber($('#material-advance').val()),
-            }
-        };
+        const prop = { deal_param: dealObj.getNewDealData() };
         const tenderId = window.location.pathname.split('/')[2];
         postData('/tender/' + tenderId + '/save', prop, function (data) {
-            setReadOnly('#v-pills-4', true);
+            dealObj.setReadOnly(true);
             property.deal_param = data.deal_param;
             $('#post-4').parent().hide();
             $('#edit-4').parent().show();
+            dealObj.loadDealProperty();
         });
     });
 
@@ -464,11 +635,10 @@ $(document).ready(function() {
     $('#post-5').click(() => {
         const prop = {
             display: {
-                ledger: { dgnQty: $('#ledger-dgn-qty')[0].checked, },
+                ledger: { dgnQty: $('#ledger-dgn-qty')[0].checked, clQty: $('#ledger-cl-qty')[0].checked, },
             },
         };
         const tenderId = window.location.pathname.split('/')[2];
-        console.log(prop);
         postData('/tender/' + tenderId + '/save', prop, function (data) {
             setReadOnly('#v-pills-5', true);
             property.display = data.display;

+ 21 - 8
app/public/js/tender_list.js

@@ -244,14 +244,19 @@ function recursiveGetTenderNodeHtml (node, arr) {
         html.push('</span>');
         //html.push('<a href="/tender/' + node.id + '">', node[c.field], '</a>');
         html.push('<a href="javascript: void(0)" id="' + node.id + '">', node.name, '</a>');
-        if (node.measure_type) {
-            html.push('<span class="small">', ' (', node.measure_type === measureType.tz.value ? '台账' : '清单', ')', '</span>');
-        }
+    }
+    html.push('</td>');
+    // 计量模式
+    html.push('<td>');
+    if (node.measure_type) {
+        html.push(node.measure_type === measureType.tz.value ? '0号台账' : '工程量清单');
     }
     html.push('</td>');
     // 计量期数
     html.push('<td>');
-    html.push(node.lastStage ? '第' + node.lastStage.order + '期' : '台账');
+    if (!node.cid) {
+        html.push(node.lastStage ? '第' + node.lastStage.order + '期' : '台账');
+    }
     html.push('</td>');
     // 审批状态
     html.push('<td>');
@@ -297,11 +302,19 @@ function recursiveGetTenderNodeHtml (node, arr) {
 function getTenderTreeHtml () {
     if (tenderTree.length > 0) {
         const html = [];
-        html.push('<table class="table table-bordered">');
+        html.push('<table class="table table-hover table-bordered">');
         html.push('<thead>', '<tr>');
-        for (const c of TenderTableCol) {
-            html.push('<th>', c.title, '</th>');
-        }
+        html.push('<th>', '名称', '</th>');
+        html.push('<th>', '计量模式', '</th>');
+        html.push('<th>', '计量期数', '</th>');
+        html.push('<th>', '审批状态', '</th>');
+        html.push('<th>', '0号台帐', '</th>');
+        html.push('<th>', '本期完成', '</th>');
+        html.push('<th>', '截止本期合同', '</th>');
+        html.push('<th>', '截止本期变更', '</th>');
+        html.push('<th>', '截止本期完成', '</th>');
+        html.push('<th>', '截止上期完成', '</th>');
+        html.push('<th>', '本期应付', '</th>');
         html.push('</tr>', '</thead>');
         for (const t of tenderTree) {
             html.push(recursiveGetTenderNodeHtml(t, tenderTree));

+ 16 - 7
app/public/js/tender_list_manage.js

@@ -259,9 +259,12 @@ function recursiveGetTenderNodeHtml (node, arr) {
     html.push('<td tid="' + node.id + '">');
     if (!node.cid) {
         html.push('<a href="#javascript: void(0)" name="edit" class="btn btn-outline-primary btn-sm">编辑</a>');
-        // if (node.ledger_status === auditConst.ledger.status.uncheck) {
-            html.push('<a href="javascript: void(0)" name="del" class="btn btn-outline-danger btn-sm">删除</a>');
-        // }
+        console.log(node.ledger_status);
+        if (node.ledger_status === auditConst.ledger.status.uncheck) {
+            html.push('<a href="javascript: void(0)" name="del" class="btn btn-outline-danger btn-sm ml-1">删除</a>');
+        } else {
+            html.push('<button class="btn btn-outline-secondary btn-sm ml-1" data-toggle="tooltip" data-placement="top" title="请先删除所有期">删除</button>');
+        }
     }
     html.push('</td>');
     html.push('</tr>');
@@ -287,7 +290,7 @@ function getTenderTreeHeaderHtml() {
 function getTenderTreeHtml () {
     if (tenderTree.length > 0) {
         const html = [];
-        html.push('<table class="table table-bordered">');
+        html.push('<table class="table table-hover table-bordered">');
         html.push(getTenderTreeHeaderHtml());
         for (const t of tenderTree) {
             html.push(recursiveGetTenderNodeHtml(t, tenderTree));
@@ -315,13 +318,14 @@ function bindTenderUrl() {
         }
     });
     // 编辑
-    $('a[name=edit]', '.c-body').bind('click', function () {
+    $('a[name=edit]', '.c-body').on('click', function () {
         const tid = parseInt($(this).parent().attr('tid'));
         const tender = _.find(tenders, {id: tid});
         $('[name=name]', '#edit-bd').val(tender.name);
+        $('input[type=radio]', '#add-bd').prop('checked', false);
         for (const c of tender.category) {
-            $('input[value=' + c.value + ']', '#edit-bd').attr('checked', 'checked');
-            $('option[value=' + c.value + ']', '#edit-bd').attr('selected', true);
+            $('input[value=' + c.value + ']', '#edit-bd').prop('checked', 'checked');
+            $('option[value=' + c.value + ']', '#edit-bd').prop('selected', true);
         }
         $('#edit-bd-ok').attr('tid', tid);
         $('#edit-bd').modal('show');
@@ -371,6 +375,11 @@ $(document).ready(() => {
             $('#cate-set').modal('hide');
         });
     });
+
+    $('a[name=add]').click(function () {
+        $('input[type=radio]', '#edit-bd').prop('checked', false);
+        $('input[type=radio]', '#add-bd').eq(0).prop('checked', true);
+    });
     // 新增标段
     $('#add-bd-ok').click(function () {
         const data = {

+ 13 - 6
app/public/js/tender_list_progress.js

@@ -246,10 +246,16 @@ function recursiveGetTenderNodeHtml (node, arr) {
         html.push('<a href="javascript: void(0)" id="' + node.id + '">', node.name, '</a>');
     }
     html.push('</td>');
-    // 完成期数
+    // 计量期数
     html.push('<td>');
-    html.push(node.completeStage ? '第' + node.completeStage.order + '期' : '');
+    if (!node.cid) {
+        html.push(node.lastStage ? '第' + node.lastStage.order + '期' : '台账');
+    }
     html.push('</td>');
+    // 完成期数
+    // html.push('<td>');
+    // html.push(node.completeStage ? '第' + node.completeStage.order + '期' : '');
+    // html.push('</td>');
     // 累计合同计量
     html.push('<td>');
     //html.push(node[c.field] ? node[c.field] : '');
@@ -270,11 +276,12 @@ function recursiveGetTenderNodeHtml (node, arr) {
 function getTenderTreeHtml () {
     if (tenderTree.length > 0) {
         const html = [];
-        html.push('<table class="table table-bordered">');
+        html.push('<table class="table table-hover table-bordered">');
         html.push('<thead>', '<tr>');
-        for (const c of TenderTableCol) {
-            html.push('<th>', c.title, '</th>');
-        }
+        html.push('<th>', '名称', '</th>');
+        html.push('<th>', '计量期数', '</th>');
+        html.push('<th>', '累计合同计量', '</th>');
+        html.push('<th>', '截止本期累计完成/本期完成/未完成', '</th>');
         html.push('</tr>', '</thead>');
         for (const t of tenderTree) {
             html.push(recursiveGetTenderNodeHtml(t, tenderTree));

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

@@ -0,0 +1,125 @@
+'use strict';
+
+/**
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+;const zhBaseCalc = (function () {
+    const zeroPrecision = 12, mulPrecision = 12, divPrecision = 12;
+
+    function digitLength (num) {
+        // 兼容科学计数
+        var eSplit = num.toString().split(/[eE]/);
+        var len = (eSplit[0].split('.')[1] || '').length - (+(eSplit[1] || 0));
+        return len > 0 ? len : 0;
+    }
+
+    function powLength (num) {
+        var rs = num.toString();
+        if (rs.indexOf('+') > 0) {
+            return rs.match(/0*$/g).length();
+        } else {
+            const eSplit = rs.split(/[eE]/);
+            const len = Number(eSplit[1]) - this.digitLength(eSplit[0]);
+            return len > 0 ? len : 0;
+        }
+    }
+
+    function round (num, digit) {
+        return Math.round(num * Math.pow(10, digit)) / Math.pow(10, digit);
+    }
+
+    function add(num1, num2) {
+        var d1 = this.digitLength(num1), d2 = this.digitLength(num2);
+        return this.round(num1 + num2, Math.max(d1, d2));
+    }
+
+    function sub(num1, num2) {
+        var d1 = this.digitLength(num1), d2 = this.digitLength(num2);
+        return this.round(num1 - num2, Math.max(d1, d2));
+    }
+
+    function mul(num1, num2) {
+        return this.round(num1 * num2, mulPrecision);
+    }
+
+    function div(num1, num2) {
+        return this.round(num1 / num2, divPrecision);
+    }
+
+    function isNonZero(num) {
+        return num && round(num, zeroPrecision) !== 0;
+    }
+
+    return {
+        digitLength: digitLength,
+        powLength: powLength,
+        round: round,
+        add: add, sub: sub, mul: mul, div: div,
+        isNonZero: isNonZero,
+    }
+})();
+
+/**
+ * 计算(四则、舍入) 统一,方便以后置换
+ * @type {{add, sub, mul, div, round}}
+ */
+const ZhCalc = (function () {
+    Decimal.set({precision: 50, defaults: true});
+    /**
+     * 加法 num1 + num2
+     * @param num1
+     * @param num2
+     * @returns {number}
+     */
+    function add(num1, num2) {
+        //return zhBaseCalc.add(num1 ? num1 : 0, num2 ? num2: 0);
+        return num1 ? (num2 ? zhBaseCalc.add(num1, num2) : num1) : num2;
+    };
+    /**
+     * 减法 num1 - num2
+     * @param num1
+     * @param num2
+     * @returns {number}
+     */
+    function sub(num1, num2) {
+        return zhBaseCalc.sub(num1 ? num1 : 0, num2 ? num2 : 0);
+    }
+    /**
+     * 乘法 num1 * num2
+     * @param num1
+     * @param num2
+     * @returns {*}
+     */
+    function mul(num1, num2, digit = 6) {
+        //return Decimal.mul(num1 ? num1 : 0, num2 ? num2 : 0).toDecimalPlaces(digit).toNumber();
+        return (num1 && num2) ? (Decimal.mul(num1, num2).toDecimalPlaces(digit).toNumber()) : 0;
+    }
+    /**
+     * 除法 num1 / num2
+     * @param num1 - 被除数
+     * @param num2 - 除数
+     * @returns {*}
+     */
+    function div(num1, num2, digit = 6) {
+        if (num2 && !checkZero(num2)) {
+            //return Decimal.div(num1 ? num1: 0, num2).toDecimalPlaces(digit).toNumber();
+            return num1 ? (Decimal.div(num1, num2).toDecimalPlaces(digit).toNumber()) : 0;
+        } else {
+            return null;
+        }
+    }
+    /**
+     * 四舍五入
+     * @param {Number} value - 舍入的数字
+     * @param {Number} decimal - 要保留的小数位数
+     * @returns {*}
+     */
+    function round(value, decimal) {
+        return value ? new Decimal(value).toDecimalPlaces(decimal).toNumber() : null;
+    }
+
+    return {add, sub, mul, div, round, isNonZero: zhBaseCalc.isNonZero}
+})();

+ 1 - 0
app/public/upload/pay/page.html

@@ -0,0 +1 @@
+当前页存合同支付附件

+ 1 - 0
app/public/upload/sign/page.html

@@ -0,0 +1 @@
+当前页存签名图片

+ 1 - 0
app/public/upload/stage/page.html

@@ -0,0 +1 @@
+当前页存计量台账附件

+ 27 - 3
app/router.js

@@ -19,6 +19,9 @@ module.exports = app => {
     app.get('/logout', 'loginController.logout');
     app.post('/login', 'loginController.login');
 
+    app.get('/sign', 'signController.index');
+    app.post('/sign/save', 'signController.save');
+
     // 用户信息初始化相关
     app.get('/boot', sessionAuth, 'bootController.index');
     app.post('/boot', sessionAuth, 'bootController.boot');
@@ -97,9 +100,16 @@ module.exports = app => {
     app.post('/tender/:id/pos', sessionAuth, tenderCheck, 'ledgerController.pos');
     app.post('/tender/:id/pos/update', sessionAuth, tenderCheck, 'ledgerController.posUpdate');
     app.post('/tender/:id/pos/paste', sessionAuth, tenderCheck, 'ledgerController.posPaste');
-
-    app.get('/tender/:id/ledger/change', sessionAuth, tenderCheck, 'ledgerController.change');
-    app.get('/tender/:id/ledger/index', sessionAuth, 'ledgerController.index');
+    // 台账修订
+    app.get('/tender/:id/revise', sessionAuth, tenderCheck, 'reviseController.index');
+    app.post('/tender/:id/revise/add', sessionAuth, tenderCheck, 'reviseController.add');
+    app.post('/tender/:id/revise/cancel', sessionAuth, tenderCheck, 'reviseController.cancel');
+    app.post('/tender/:id/revise/save', sessionAuth, tenderCheck, 'reviseController.save');
+    app.get('/tender/:id/revise/info', sessionAuth, tenderCheck, 'reviseController.info');
+    app.post('/tender/:id/revise/info/base-opr', sessionAuth, tenderCheck, 'reviseController.baseOpr');
+    app.post('/tender/:id/revise/info/batch-insert', sessionAuth, tenderCheck, 'reviseController.batchInsert')
+    //app.post('/tender/:id/ledger/revise/audit/', sessionAuth, tenderCheck, 'ledgerController.reviseStatus');
+    //app.get('/tender/:id/ledger/index', sessionAuth, 'ledgerController.index');
 
     // 台账审批相关
     app.get('/tender/:id/ledger/audit', sessionAuth, tenderCheck, 'ledgerAuditController.index');
@@ -139,10 +149,15 @@ module.exports = app => {
     app.post('/tender/:id/measure/stage/:order/detail/save', sessionAuth, tenderCheck, stageCheck, 'stageController.saveDetailData');
     app.post('/tender/:id/measure/stage/:order/detail/add-img', sessionAuth, tenderCheck, stageCheck, 'stageController.addCalcImage');
     app.post('/tender/:id/measure/stage/:order/detail/merge-img', sessionAuth, tenderCheck, stageCheck, 'stageController.mergeCalcImage');
+    app.get('/tender/:id/measure/stage/:order/detail/done', sessionAuth, tenderCheck, stageCheck, 'stageController.doneDetail');
+    app.get('/tender/:id/measure/stage/:order/detail/unlock', sessionAuth, tenderCheck, stageCheck, 'stageController.unlockDetail');
     // 合同支付
     app.get('/tender/:id/measure/stage/:order/pay', sessionAuth, tenderCheck, stageCheck, 'stageController.pay');
     app.post('/tender/:id/measure/stage/:order/pay/detail', sessionAuth, tenderCheck, stageCheck, 'stageController.chapterDetail');
     app.post('/tender/:id/measure/stage/:order/pay/save', sessionAuth, tenderCheck, stageCheck, 'stageController.savePayData');
+    app.post('/tender/:id/measure/stage/:order/pay/upload/file', sessionAuth, tenderCheck, stageCheck, 'stageController.payUploadFile');
+    app.get('/tender/:id/measure/stage/:order/pay/download/file/:pid/:index', sessionAuth, 'stageController.payDownloadFile');
+    app.post('/tender/:id/measure/stage/:order/pay/delete/file', sessionAuth, 'stageController.payDeleteFile');
     // 变更令
     app.get('/tender/:id/measure/stage/:order/change', sessionAuth, tenderCheck, stageCheck, 'stageController.change');
     app.post('/tender/:id/measure/stage/:order/change/detail', sessionAuth, tenderCheck, stageCheck, 'stageController.changeDetail');
@@ -160,6 +175,11 @@ module.exports = app => {
     app.get('/tender/:id/report', sessionAuth, tenderCheck, 'reportController.index');
     app.get('/tender/:id/measure/stage/:order/report', sessionAuth, tenderCheck, stageCheck, 'reportController.index');
     app.post('/tender/report_api/getReport', sessionAuth, 'reportController.getReport');
+    // 计量附件
+    app.post('/tender/:id/measure/stage/:order/upload/file', sessionAuth, 'stageController.uploadFile');
+    app.get('/tender/:id/measure/stage/:order/download/file/:fid', sessionAuth, 'stageController.downloadFile');
+    app.post('/tender/:id/measure/stage/:order/delete/file', sessionAuth, 'stageController.deleteFile');
+    app.post('/tender/:id/measure/stage/:order/save/file', sessionAuth, 'stageController.saveFile');
     // 变更管理
     app.get('/tender/:id/change', sessionAuth, tenderCheck, 'changeController.index');
     app.get('/tender/:id/change/status/:status', sessionAuth, tenderCheck, 'changeController.status');
@@ -181,10 +201,14 @@ module.exports = app => {
 
     // 个人账号相关
     app.get('/profile/info', sessionAuth, 'profileController.info');
+    app.get('/profile/sms', sessionAuth, 'profileController.sms');
+    app.get('/profile/sign', sessionAuth, 'profileController.sign');
+    app.get('/profile/safe', sessionAuth, 'profileController.safe');
     app.post('/profile/save', sessionAuth, 'profileController.saveBase');
     app.post('/profile/password', sessionAuth, 'profileController.modifyPassword');
     app.post('/profile/code', sessionAuth, 'profileController.getCode');
     app.post('/profile/bind', sessionAuth, 'profileController.bindMobile');
+    app.get('/profile/qrCode', sessionAuth, 'profileController.qrCode');
 
     // 中间计量 - 计量编制相关
     // app.get('/measure/wlist', sessionAuth, tenderSelect, 'measureController.list');

+ 17 - 15
app/service/change.js

@@ -191,7 +191,7 @@ module.exports = app => {
             switch (status) {
                 case 0:// 包含你的所有变更令
                     sql = 'SELECT a.* FROM ?? AS a WHERE a.tid = ? AND ' +
-                        '(a.uid = ? OR (a.status != 1 AND a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? GROUP BY b.cid))) ORDER BY a.in_time DESC';
+                        '(a.uid = ? OR (a.status != 1 AND a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? AND a.times = b.times GROUP BY b.cid))) ORDER BY a.in_time DESC';
                     sqlParam = [this.tableName, tenderId, this.ctx.session.sessionUser.accountId,
                         this.ctx.service.changeAudit.tableName, this.ctx.session.sessionUser.accountId];
                     break;
@@ -210,7 +210,7 @@ module.exports = app => {
                 case 3:// 已完成(所有的)
                 case 4:// 终止(所有的)
                     sql = 'SELECT a.* FROM ?? AS a WHERE ' +
-                        'a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? GROUP BY b.cid) AND ' +
+                        'a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? AND a.times = b.times GROUP BY b.cid) AND ' +
                         'a.status = ? AND a.tid = ? ORDER BY a.in_time DESC';
                     sqlParam = [this.tableName, this.ctx.service.changeAudit.tableName,
                         this.ctx.session.sessionUser.accountId, status, tenderId];
@@ -237,7 +237,7 @@ module.exports = app => {
             switch (status) {
                 case 0:// 包含你的所有变更令
                     const sql = 'SELECT count(*) AS count FROM ?? AS a WHERE a.tid = ? AND ' +
-                        '(a.uid = ? OR a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? GROUP BY b.cid))';
+                        '(a.uid = ? OR a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? AND a.times = b.times GROUP BY b.cid))';
                     const sqlParam = [this.tableName, tenderId, this.ctx.session.sessionUser.accountId,
                         this.ctx.service.changeAudit.tableName, this.ctx.session.sessionUser.accountId];
                     const result = await this.db.query(sql, sqlParam);
@@ -250,7 +250,7 @@ module.exports = app => {
                     });
                 case 5:// 待上报(所有的)PS:取未上报和退回的变更令
                     const sql2 = 'SELECT count(*) AS count FROM ?? AS a WHERE ' +
-                        'a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? GROUP BY b.cid) ' +
+                        'a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? AND a.times = b.times GROUP BY b.cid) ' +
                         'AND (a.status = 1 OR a.status = 5) AND a.tid = ?';
                     const sqlParam2 = [this.tableName, this.ctx.service.changeAudit.tableName,
                         this.ctx.session.sessionUser.accountId, tenderId];
@@ -260,7 +260,7 @@ module.exports = app => {
                 case 3:// 已完成(所有的)
                 case 4:// 终止(所有的)
                     const sql3 = 'SELECT count(*) AS count FROM ?? AS a WHERE ' +
-                        'a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? GROUP BY b.cid) AND a.status = ? AND a.tid = ?';
+                        'a.cid IN (SELECT b.cid FROM ?? AS b WHERE b.uid = ? AND a.times = b.times GROUP BY b.cid) AND a.status = ? AND a.tid = ?';
                     const sqlParam3 = [this.tableName, this.ctx.service.changeAudit.tableName,
                         this.ctx.session.sessionUser.accountId, status, tenderId];
                     const result3 = await this.db.query(sql3, sqlParam3);
@@ -341,15 +341,16 @@ module.exports = app => {
                         const clArray = {
                             tid: tenderId,
                             cid: changeInfo.cid,
-                            lid: clInfo[7],
+                            lid: clInfo[8],
                             code: clInfo[0],
                             name: clInfo[1],
-                            unit: clInfo[2],
-                            unit_price: clInfo[3],
-                            oamount: clInfo[4],
-                            camount: clInfo[5],
+                            bwmx: clInfo[2],
+                            unit: clInfo[3],
+                            unit_price: clInfo[4],
+                            oamount: clInfo[5],
+                            camount: clInfo[6],
                             samount: '',
-                            detail: clInfo[6],
+                            detail: clInfo[7],
                         };
                         insertCL.push(clArray);
                         total_price = this.ctx.helper.accAdd(total_price, this.ctx.helper.accMul(clArray.unit_price, clArray.camount));
@@ -681,10 +682,10 @@ module.exports = app => {
          * @returns {Promise<*>} - 可用的变更令列表
          */
         async getValidChanges(tid, bills, pos) {
-            const filter = 'cb.`code` = ' + this.db.escape(bills.b_code) + (pos ? ' And cb.`detail` = ' + this.db.escape(pos.name) : '');
+            const filter = 'cb.`code` = ' + this.db.escape(bills.b_code) + (pos ? ' And cb.`bwmx` = ' + this.db.escape(pos.name) : '');
             const sql = 'SELECT c.cid, c.code, c.name, c.w_code, c.p_code, c.peg, c.org_name, c.org_code, c.new_name, c.new_code,' +
                         '    c.content, c.basis, c.memo, c.type, c.class, c.quality, c.company, c.charge, ' +
-                        '    cb.id As cbid, cb.code As b_code, cb.name As b_name, cb.unit As b_unit, cb.samount As b_amount, cb.detail As b_detail, ' +
+                        '    cb.id As cbid, cb.code As b_code, cb.name As b_name, cb.unit As b_unit, cb.samount As b_amount, cb.detail As b_detail, cb.bwmx As b_bwmx, ' +
                         '    scb.used_amount' +
                         '  FROM ' + this.tableName + ' As c ' +
                         '  Left Join ' + this.ctx.service.changeAuditList.tableName +' As cb On c.cid = cb.cid ' +
@@ -699,7 +700,8 @@ module.exports = app => {
                         '      ON sc.stimes = MF.stimes And sc.sorder = MF.sorder And sc.cbid = MF.cbid' +
                         '    GROUP BY sc.cbid' +
                         '  ) As scb ON cb.id = scb.cbid' +
-                        '  WHERE c.tid = ? And c.status = ? And ' + filter;
+                        '  WHERE c.tid = ? And c.status = ? And ' + filter +
+                        '  ORDER BY c.in_time';
             const sqlParam = [tid, tid, audit.flow.status.checked];
             const changes = await this.db.query(sql, sqlParam);
             for (const c of changes) {
@@ -727,7 +729,7 @@ module.exports = app => {
                     [lastStage.order, lastStage.order, this.ctx.stage.curTimes, this.ctx.stage.curTimes, this.ctx.stage.curOrder]);
             } else {
                 if (lastStage.status === audit.stage.status.uncheck) {
-                    filter = 'And s.order < ' + lastStage.order;
+                    filter = ' And s.order < ' + lastStage.order;
                 } else if (lastStage.status === audit.stage.status.checked) {
                     filter = '';
                 } else if (lastStage.status === audit.stage.status.checkNo) {

+ 76 - 1
app/service/deal_bills.js

@@ -21,7 +21,7 @@ module.exports = app => {
         }
 
         /**
-         * 导入Excel数据
+         * 导入Excel数据(node-xlsx)
          *
          * @param {Array} sheet - Excel文件中的全部工作表
          * @param {Number} tenderId - 所属标段Id
@@ -90,6 +90,81 @@ module.exports = app => {
             }
             return result;
         }
+
+        /**
+         * 导入Excel数据(js-xlsx)
+         *
+         * @param {Array} sheet - Excel文件中的全部工作表
+         * @param {Number} tenderId - 所属标段Id
+         * @returns {Promise<boolean>}
+         */
+        async importDataJsXlsx(sheet, tenderId) {
+            let result = false;
+            // 整理数据
+            const bills = [];
+            let iCode = -1, iName = -1, iUnit = -1, iUp = -1, iQty = -1, iTp = -1, bCheckCol = false;
+            for (let iRow = 0; iRow < sheet.rows.length; iRow++) {
+                const row = sheet.rows[iRow];
+                if (!bCheckCol) {
+                    for (let iCol = 0; iCol < row.length; iCol++) {
+                        const value = row[iCol];
+                        if (typeof value !== "string") { continue }
+                        if (value === '子目号' || value === '清单编号') iCode = iCol;
+                        if (value.indexOf('名称') >= 0) iName = iCol;
+                        if (value.indexOf('单位') >= 0) iUnit = iCol;
+                        if (value.indexOf('单价') >= 0) iUp = iCol;
+                        if (value.indexOf('数量') >= 0) iQty = iCol;
+                        if (value.indexOf('金额') >= 0 || value.indexOf('合价') >= 0) iTp = iCol;
+                    }
+                    bCheckCol = (iCode >= 0 && iName >= 0 && iUnit >= 0 && iUp >= 0 && iQty >= 0 && iTp >= 0);
+                    if (!bCheckCol) {
+                        iCode = -1;
+                        iName = -1;
+                        iUnit = -1;
+                        iUp = -1;
+                        iQty = -1;
+                        iTp = -1;
+                    }
+                } else if (this.ctx.helper.validBillsCode(row[iCode])) {
+                    const data = {
+                        deal_id: bills.length + 1,
+                        tender_id: tenderId,
+                        code: row[iCode],
+                        name: row[iName],
+                        unit: row[iUnit],
+                        unit_price: (row[iUp] === undefined || row[iUp] === null) ? 0 : this._.toNumber(row[iUp]),
+                        quantity: (row[iQty] === undefined || row[iQty] === null) ? 0 : this._.toNumber(row[iQty]),
+                        total_price: (row[iTp] === undefined || row[iTp] === null) ? 0 : this._.toNumber(row[iTp]),
+                    };
+                    if (this._.isNaN(data.unit_price) || this._.isNaN(data.quantity) || this._.isNaN(data.total_price)) {
+                        throw '导入的Excel的数据类型有误,请检查第' + (iRow + 1) + '行';
+                    }
+                    bills.push(data);
+                }
+            }
+            if (!bCheckCol) {
+                throw '导入的Excel表头定义有误,请下载示例检查';
+            }
+            // 写入数据
+            const transaction = await this.db.beginTransaction();
+            try {
+                if (bills.length > 0) {
+                    await transaction.delete(this.tableName, {tender_id: tenderId});
+                    const billsResult = await transaction.insert(this.tableName, bills);
+                    if (billsResult.affectedRows !== bills.length) {
+                        throw '导入签约清单数据出错';
+                    }
+                } else {
+                    throw 'Excel文件中无签约清单数据';
+                }
+                await transaction.commit();
+                result = true;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+            return result;
+        }
     }
 
     return DealBills;

+ 158 - 68
app/service/ledger.js

@@ -22,10 +22,10 @@ const keyFields = {
 };
 // 以下字段仅可通过树结构操作改变,不可直接通过update方式从接口提交,发现时过滤
 const readOnlyFields = ['id', 'tender_id', 'ledger_id', 'ledger_pid', 'order', 'level', 'full_path', 'is_leaf'];
-const calcFields = ['quantity', 'unit_price', 'total_price', 'deal_qty', 'deal_tp', 'dgn_qty1', 'dgn_qty2'];
+const calcFields = ['unit_price', 'sgfh_qty', 'sgfh_tp', 'sjcl_qty', 'sjcl_tp', 'qtcl_qty', 'qtcl_tp', 'deal_qty', 'deal_tp', 'dgn_qty1', 'dgn_qty2'];
 const upFields = ['unit_price'];
-const qtyFields = ['quantity', 'deal_qty', 'dgn_qty1', 'dgn_qty2'];
-const tpFields = ['total_price', 'deal_tp'];
+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 rootId = -1;
 const keyPre = 'tender_node_maxId:';
 
@@ -70,6 +70,7 @@ module.exports = app => {
                 tmp.tender_id = tenderId;
                 delete tmp.id;
                 delete tmp.pid;
+                tmp.id = this.uuid.v4();
                 insertData.push(tmp);
             }
             const operate = await transaction.insert(this.tableName, insertData);
@@ -180,7 +181,9 @@ module.exports = app => {
             const [sql, sqlParam] = this.sqlBuilder.build(this.tableName);
             const data = await this.db.query(sql, sqlParam);
 
-            return data;
+            return this._.sortBy(data, function (d) {
+                return nodesIds.indexOf(d.ledger_id);
+            });
         }
         /**
          * 根据主键id获取数据
@@ -624,6 +627,7 @@ module.exports = app => {
                 this.cache.set(cacheKey, maxId, 'EX', this.ctx.app.config.cacheTime);
             }
 
+            data.id = this.uuid.v4();
             data.tender_id = tenderId;
             data.ledger_id = maxId + 1;
             data.ledger_pid = selectData.ledger_pid;
@@ -661,6 +665,7 @@ module.exports = app => {
             }
             this.cache.set(cacheKey, maxId + 1, 'EX', this.ctx.app.config.cacheTime);
 
+            data.id = this.uuid.v4();
             data.tender_id = tenderId;
             data.ledger_id = maxId + 1;
             data.ledger_pid = pid;
@@ -804,6 +809,7 @@ module.exports = app => {
                 this.cache.set(cacheKey, maxId, 'EX', this.ctx.app.config.cacheTime);
             }
 
+            data.id = this.uuid.v4();
             data.tender_id = tenderId;
             data.ledger_id = maxId + 1;
             data.ledger_pid = selectData.ledger_id;
@@ -822,7 +828,6 @@ module.exports = app => {
                 }
                 await this.transaction.commit();
             } catch(err) {
-                console.log(err);
                 this.transaction.rollback();
                 throw err;
             }
@@ -1486,10 +1491,11 @@ module.exports = app => {
          * @return {Object} - 提价后的数据(其中新增粘贴数据,只返回第一层)
          */
         async pasteBlock(tenderId, selectId, block) {
+            console.log(1);
             if ((tenderId <= 0) || (selectId <= 0)) {
                 return [];
             }
-            const selectData = await this.getDataByNodeId(tenderId, selectId);
+            const selectData = await this.getDataByNodeId(this.ctx.tender.id, selectId);
             if (!selectData) {
                 throw '位置数据错误';
             }
@@ -1510,6 +1516,7 @@ module.exports = app => {
                 throw '复制数据错误:仅可操作同层节点';
             }
             const orgParentPath = copyNodes[0].full_path.replace(copyNodes[0].ledger_id, '');
+            console.log(2);
 
             const newIds = [];
             this.transaction = await this.db.beginTransaction();
@@ -1517,13 +1524,15 @@ module.exports = app => {
                 // 选中节点的所有后兄弟节点,order+粘贴节点个数
                 await this._updateSelectNextsOrder(selectData, copyNodes.length);
                 // 数据库创建新增节点数据
-                for (const node of copyNodes) {
-                    const datas = await this.getDataByFullPath(tenderId, node.full_path + '%');
+                for (let iNode = 0; iNode < copyNodes.length; iNode++) {
+                    const node = copyNodes[iNode];
+                    let datas = await this.getDataByFullPath(tenderId, node.full_path + '%');
+                    datas = this._.sortBy(datas, 'level');
 
-                    const cacheKey = keyPre + tenderId;
+                    const cacheKey = keyPre + this.ctx.tender.id;
                     let maxId = parseInt(await this.cache.get(cacheKey));
                     if (!maxId) {
-                        maxId = await this._getMaxNodeId(tenderId);
+                        maxId = await this._getMaxNodeId(this.ctx.tender.id);
                     }
                     this.cache.set(cacheKey, maxId + datas.length, 'EX', this.ctx.app.config.cacheTime);
 
@@ -1535,7 +1544,9 @@ module.exports = app => {
                         const idChange = {
                             org: data.id,
                         };
-                        delete data.id;
+                        data.id = this.uuid.v4();
+                        idChange.new = data.id;
+                        data.tender_id = this.ctx.tender.id;
                         if (!data.is_leaf) {
                             for (const children of datas) {
                                 children.full_path = children.full_path.replace('.' + data.ledger_id, '.' + newId);
@@ -1548,18 +1559,17 @@ module.exports = app => {
                         }
                         data.ledger_id = newId;
                         data.full_path = data.full_path.replace(orgParentPath, newParentPath);
-                        if (data.ledger_pid === copyNodes[0].ledger_pid) {
+                        if (data.ledger_pid === node.ledger_pid) {
                             data.ledger_pid = selectData.ledger_pid;
-                            data.order = selectData.order + index + 1;
+                            data.order = selectData.order + iNode + 1;
                         }
                         data.level = data.level + selectData.level - copyNodes[0].level;
-                        const newData = await this.transaction.insert(this.tableName, data);
                         if (data.is_leaf) {
-                            idChange.new = newData.insertId;
                             leafBillsId.push(idChange);
                         }
-                        newIds.push(newData.insertId);
+                        newIds.push(data.id);
                     }
+                    const newData = await this.transaction.insert(this.tableName, datas);
                     for (const id of leafBillsId) {
                         await this.ctx.service.pos.copyBillsPosData(id.org, id.new, this.transaction);
                     }
@@ -1569,6 +1579,7 @@ module.exports = app => {
                 await this.transaction.rollback();
                 throw err;
             }
+            console.log(3);
 
             // 查询应返回的结果
             const order = [];
@@ -1582,6 +1593,7 @@ module.exports = app => {
                 ledger: { create: createData, update: updateData },
                 pos: posData,
             };
+            console.log(4);
         }
 
         /**
@@ -1623,9 +1635,10 @@ module.exports = app => {
          */
         async updateCalc(tenderId, data) {
             // 简单验证数据
-            if (tenderId <= 0) {
+            if (tenderId <= 0 || !this.ctx.tender) {
                 throw '标段不存在';
             }
+            const info = this.ctx.tender.info;
             if (!data) {
                 throw '提交数据错误';
             }
@@ -1647,8 +1660,10 @@ module.exports = app => {
                     }
                     let updateData;
                     if (row.unit) {
-                        if (!row.quantity) { row.quantity = updateNode.quantity; }
-                        if (!row.deal_qty) { row.deal_qty = updateNode.deal_qty; }
+                        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;
@@ -1656,39 +1671,46 @@ module.exports = app => {
                     }
                     if (this._checkCalcField(row)) {
                         let calcData = JSON.parse(JSON.stringify(row));
-                        const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, row.unit ? row.unit : updateNode.unit);
+                        const precision = this.ctx.helper.findPrecision(info.precision, row.unit ? row.unit : updateNode.unit);
                         // 数量保留小数位数
                         this.ctx.helper.checkFieldPrecision(calcData, qtyFields, precision.value);
                         // 单位保留小数位数
-                        this.ctx.helper.checkFieldPrecision(calcData, upFields, this.ctx.tender.info.decimal.up);
+                        this.ctx.helper.checkFieldPrecision(calcData, upFields, info.decimal.up);
+                        // 未提交单价则读取数据库单价
+                        if (row.unit_price === undefined) calcData.unit_price = updateNode.unit_price;
                         // 计算
-                        if (row.quantity !== undefined) {
-                            if (row.unit_price  !== undefined) {
-                                calcData.total_price = this.ctx.helper.times(calcData.quantity, calcData.unit_price);
-                            } else {
-                                calcData.total_price = this.ctx.helper.times(calcData.quantity, updateNode.unit_price);
-                            }
-                        } else if (row.unit_price !== undefined) {
-                            calcData.total_price = this.ctx.helper.times(updateNode.quantity, calcData.unit_price);
-                        }
-                        if (row.total_price !== undefined) {
+                        if (row.sgfh_qty !== undefined || row.sjcl_qty !== undefined || row.qtcl_qty !== undefined ||
+                            row.deal_qty !== undefined || row.unit_price) {
+                            if (row.sgfh_qty === undefined) calcData.sgfh_qty = updateNode.sgfh_qty;
+                            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);
+                        } 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;
-                        }
-                        if (row.deal_qty !== undefined) {
-                            if (row.unit_price !== undefined) {
-                                calcData.deal_tp = this.ctx.helper.times(calcData.deal_qty, calcData.unit_price);
-                            } else {
-                                calcData.deal_tp = this.ctx.helper.times(calcData.deal_qty, updateNode.unit_price);
-                            }
-                        } else if (row.unit_price !== undefined) {
-                            calcData.deal_tp = this.ctx.helper.times(updateNode.deal_qty, calcData.unit_price);
-                        }
-                        if (row.deal_tp !== undefined) {
                             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;
+                        } 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);
                         }
                         updateData = this._filterUpdateInvalidField(updateNode.id, calcData);
                     } else {
-
                         updateData = this._filterUpdateInvalidField(updateNode.id, row);
                     }
                     await this.transaction.update(this.tableName, updateData);
@@ -1749,8 +1771,8 @@ module.exports = app => {
                     quantity: gcl.quantity,
                 };
                 child.full_path = parent.full_path + '.' + child.ledger_id;
-                child.total_price = child.unit_price * child.quantity;
-                tp = tp + child.total_price;
+                child.total_price = this.ctx.helper.mul(child.unit_price, child.quantity, this.ctx.tender.info.decimal.tp);
+                tp = this.ctx.helper.add(tp, child.total_price);
                 result.push(child);
                 newIds.push(child.ledger_id);
             }
@@ -1800,10 +1822,12 @@ module.exports = app => {
                 if (!maxId) {
                     maxId = await this._getMaxNodeId(tenderId);
                 }
+
                 // 数据库创建新增节点数据
                 for (let i = 0, iLen = data.length; i < iLen; i++) {
                     // 合并新增数据
                     const qd = {
+                        id: this.uuid.v4(),
                         tender_id: tenderId,
                         ledger_id: maxId + i + 1,
                         ledger_pid: selectData.ledger_id,
@@ -1816,14 +1840,11 @@ module.exports = app => {
                         unit_price: data[i].price,
                     };
                     qd.full_path = selectData.full_path + '.' + qd.ledger_id;
-                    const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, qd.unit);
-                    this.ctx.helper.checkFieldPrecision(qd, qtyFields, precision.value);
                     const insertResult = await this.transaction.insert(this.tableName, qd);
-                    qd.id = insertResult.insertId;
-                    newIds.push(insertResult.insertId);
+                    newIds.push(qd.id);
                     if (data[i].pos.length > 0) {
                         await this.ctx.service.pos.insertLedgerPosData(this.transaction, tenderId, qd, data[i].pos);
-                        await this.calc(tenderId, insertResult.insertId, this.transaction);
+                        await this._calcNode(qd, this.transaction);
                     }
                 }
                 this.cache.set(cacheKey, maxId + data.length + 1, 'EX', this.ctx.app.config.cacheTime);
@@ -1879,6 +1900,7 @@ module.exports = app => {
                 for (let i = 0, iLen = data.length; i < iLen; i++) {
                     // 合并新增数据
                     const qd = {
+                        id: this.uuid.v4(),
                         tender_id: tenderId,
                         ledger_id: maxId + i + 1,
                         ledger_pid: parentData.ledger_id,
@@ -1891,14 +1913,11 @@ module.exports = app => {
                         unit_price: data[i].price,
                     };
                     qd.full_path = parentData.full_path + '.' + qd.ledger_id;
-                    const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, qd.unit);
-                    this.ctx.helper.checkFieldPrecision(qd, qtyFields, precision.value);
                     const insertResult = await this.transaction.insert(this.tableName, qd);
-                    qd.id = insertResult.insertId;
-                    newIds.push(insertResult.insertId);
+                    newIds.push(qd.id);
                     if (data[i].pos.length > 0) {
                         await this.ctx.service.pos.insertLedgerPosData(this.transaction, tenderId, qd, data[i].pos);
-                        await this.calc(tenderId, insertResult.insertId, this.transaction);
+                        await this._calcNode(qd, this.transaction);
                     }
                 }
                 this.cache.set(cacheKey, maxId + data.length + 1, 'EX', this.ctx.app.config.cacheTime);
@@ -1917,21 +1936,42 @@ module.exports = app => {
 
         /**
          *
+         * @param node
+         * @param transaction
+         * @returns {Promise<void>}
+         * @private
+         */
+        async _calcNode(node, transaction) {
+            const info = this.ctx.tender.info;
+            const precision = this.ctx.helper.findPrecision(info.precision, node.unit);
+
+            const calcQtySql = 'SELECT SUM(`sgfh_qty`) As `sgfh_qty`, SUM(`sjcl_qty`) As `sjcl_qty`, SUM(`qtcl_qty`) As `qtcl_qty`, SUM(`quantity`) As `quantity` FROM ?? WHERE `lid` = ?';
+            const data = await transaction.queryOne(calcQtySql, [this.ctx.service.pos.tableName, node.id]);
+            data.id = node.id;
+            data.sgfh_qty = this.round(data.sgfh_qty, precision.value);
+            data.sjcl_qty = this.round(data.sjcl_qty, precision.value);
+            data.qtcl_qty = this.round(data.qtcl_qty, precision.value);
+            data.quantity = this.round(data.quantity, precision.value);
+            data.sgfh_tp = this.ctx.helper.mul(data.sgfh_qty, node.unit_price, info.decimal.tp);
+            data.sjcl_tp = this.ctx.helper.mul(data.sjcl_qty, node.unit_price, info.decimal.tp);
+            data.qtcl_tp = this.ctx.helper.mul(data.qtcl_qty, node.unit_price, info.decimal.tp);
+            data.total_price = this.ctx.helper.mul(data.quantity, node.unit_price, info.decimal.tp);
+            const result = await transaction.update(this.tableName, data);
+        }
+
+        /**
+         *
          * @param {Number} tid - 标段id
          * @param {Number} id - 需要计算的节点的id
          * @param {Object} transaction - 操作所属事务,没有则创建
          * @return {Promise<void>}
          */
         async calc(tid, id, transaction) {
-            const node = await this.getAllDataByCondition({ id });
+            const node = await transaction.get(this.tableName, {id: id});
             if (!node) {
                 throw '数据错误';
             }
-
-            const calcQtySql = 'UPDATE ??' +
-                ' SET `quantity` = (SELECT SUM(`quantity`) FROM ?? WHERE `lid` = ?), `total_price` = `quantity` * `unit_price`' +
-                ' WHERE `id` = ?';
-            await transaction.query(this.db.format(calcQtySql, [this.tableName, this.ctx.service.pos.tableName, id, id]));
+            await this._calcNode(node, transaction);
         }
 
         /**
@@ -1953,6 +1993,7 @@ module.exports = app => {
 
         async _importCacheTreeNode(transaction, node) {
             const data = {
+                id: this.uuid.v4(),
                 tender_id: this.ctx.tender.id,
                 ledger_id: node.ledger_id,
                 ledger_pid: node.ledger_pid,
@@ -1964,6 +2005,8 @@ module.exports = app => {
                 b_code: node.b_code,
                 name: node.name,
                 unit: node.unit,
+                sgfh_qty: node.sgfh_qty,
+                sgfh_tp: node.sgfh_tp,
                 quantity: node.quantity,
                 unit_price: node.unit_price,
                 total_price: node.total_price,
@@ -1972,10 +2015,8 @@ module.exports = app => {
                 memo: node.memo,
                 drawing_code: node.drawing_code,
             };
-            const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, data.unit);
-            this.ctx.helper.checkFieldPrecision(data, qtyFields, precision.value);
             const result = await transaction.insert(this.tableName, data);
-            data.id = result.insertId;
+            //data.id = result.insertId;
             if (node.children && node.children.length > 0) {
                 for (const child of node.children) {
                     await this._importCacheTreeNode(transaction, child);
@@ -1983,6 +2024,53 @@ module.exports = app => {
             } else if (node.pos && node.pos.length > 0) {
                 await this.ctx.service.pos.insertLedgerPosData(transaction, this.ctx.tender.id, data, node.pos);
             }
+            if (node.pos && node.pos.length > 0) {
+                for (const p of node.pos) {
+                    //p.lid = result.insertId;
+                    p.lid = node.id;
+                }
+            }
+        }
+
+        async _importCacheTreeNodes(transaction, nodes) {
+            const datas = [];
+            for (const node of nodes) {
+                datas.push({
+                    id: this.uuid.v4(),
+                    tender_id: this.ctx.tender.id,
+                    ledger_id: node.ledger_id,
+                    ledger_pid: node.ledger_pid,
+                    level: node.level,
+                    order: node.order,
+                    is_leaf: !node.children || node.children.length === 0,
+                    full_path: node.full_path,
+                    code: node.code,
+                    b_code: node.b_code,
+                    name: node.name,
+                    unit: node.unit,
+                    sgfh_qty: node.sgfh_qty,
+                    sgfh_tp: node.sgfh_tp,
+                    quantity: node.quantity,
+                    unit_price: node.unit_price,
+                    total_price: node.total_price,
+                    dgn_qty1: node.dgn_qty1,
+                    dgn_qty2: node.dgn_qty2,
+                    memo: node.memo,
+                    drawing_code: node.drawing_code,
+                });
+            }
+            await transaction.insert(this.tableName, datas);
+            const sql = 'SELECT id, ledger_id FROM ' + this.tableName + ' WHERE tender_id = ?';
+            const sqlParam = [this.ctx.tender.id];
+            const insertDatas = await transaction.query(sql, sqlParam);
+            for (const iD of insertDatas) {
+                const node = this.ctx.helper._.find(nodes, {ledger_id: iD.ledger_id});
+                if (node.pos && node.pos.length > 0) {
+                    for (const p of node.pos) {
+                        p.lid = iD.id;
+                    }
+                }
+            }
         }
 
         /**
@@ -1992,7 +2080,7 @@ module.exports = app => {
          */
         async importExcel(excelData) {
             const AnalysisExcel = require('../lib/analysis_excel');
-            const analysisExcel = new AnalysisExcel();
+            const analysisExcel = new AnalysisExcel(this.ctx);
             const tempData = await this.ctx.service.tenderNodeTemplate.getData(true);
             const cacheTree = analysisExcel.analysisData(excelData, tempData);
             const cacheKey = keyPre + this.ctx.tender.id;
@@ -2001,9 +2089,11 @@ module.exports = app => {
             try {
                 await transaction.delete(this.tableName, {tender_id: this.ctx.tender.id});
                 await transaction.delete(this.ctx.service.pos.tableName, {tid: this.ctx.tender.id});
-                for (const node of cacheTree.roots) {
-                    await this._importCacheTreeNode(transaction, node);
-                }
+                // for (const node of cacheTree.roots) {
+                //     await this._importCacheTreeNode(transaction, node);
+                // }
+                await this._importCacheTreeNodes(transaction, cacheTree.items);
+                await transaction.insert(this.ctx.service.pos.tableName, cacheTree.pos);
                 await transaction.commit();
                 this.cache.set(cacheKey, cacheTree.items.length + 1, 'EX', this.ctx.app.config.cacheTime);
             } catch (err) {

+ 0 - 16
app/service/ledger_change.js

@@ -1,16 +0,0 @@
-'use strict';
-
-/**
- *
- *
- * @author Mai
- * @date 2018/6/12
- * @version
- */
-
-module.exports = app => {
-    class LedgerChange extends app.BaseService {
-    }
-
-    return LedgerChange;
-};

+ 142 - 0
app/service/ledger_revise.js

@@ -0,0 +1,142 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2018/6/12
+ * @version
+ */
+
+const audit = require('../const/audit').revise;
+
+module.exports = app => {
+    class LedgerRevise extends app.BaseService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'ledger_revise';
+        }
+
+        /**
+         * 获取标段下,修订(分页,且按时间倒序)
+         * @param {Number}tid - 标段id
+         * @returns {Promise<*>} - ledger_change下所有数据,并关联 project_account(读取提交人名称、单位、公司)
+         */
+        async getReviseList (tid) {
+            const sql = 'SELECT lc.*, pa.name As user_name, pa.role As user_role, pa.company As user_company' +
+                '  FROM ' + this.tableName + ' As lc' +
+                '  INNER JOIN ' + this.ctx.service.projectAccount.tableName + ' As pa ON lc.uid = pa.id' +
+                '  WHERE lc.tid = ?' +
+                '  ORDER BY lc.in_time DESC' +
+                '  LIMIT ?, ?';
+            const Len = this.app.config.pageSize;
+            const sqlParam = [tid, (this.ctx.page - 1) * Len, Len];
+            return await this.db.query(sql, sqlParam);
+        }
+
+        async getLastestRevise(tid) {
+            const sql = 'SELECT lc.*, pa.name As user_name, pa.role As user_role, pa.company As user_company' +
+                '  FROM ' + this.tableName + ' As lc' +
+                '  INNER JOIN ' + this.ctx.service.projectAccount.tableName + ' As pa ON lc.uid = pa.id' +
+                '  WHERE lc.tid = ?' +
+                '  ORDER BY lc.in_time DESC' +
+                '  LIMIT 0, 1';
+            const sqlParam = [tid];
+            return await this.db.queryOne(sql, sqlParam);
+            // const revise = await this.db.select(this.tableName, {
+            //     where: {tid: tid},
+            //     orders: [['in_time', 'DESC']],
+            //     limit: 1,
+            //     offset: 0,
+            // });
+            // return revise.length > 0 ? revise[0] : null;
+        }
+
+        /**
+         * 获取新增修订的序号
+         * @param {Number}tid - 标段id
+         * @returns {Promise<number>}
+         */
+        async getNewOrder(tid) {
+            const sql = 'SELECT Max(`corder`) As max_order FROM ' + this.tableName + ' Where `tid` = ? and `valid`';
+            const sqlParam = [tid];
+            const result = await this.db.queryOne(sql, sqlParam);
+            return result.max_order || 0;
+            // if (result && result.max_order) {
+            //     return result.max_order;
+            // } else {
+            //     return 0;
+            // }
+        }
+
+        async _initReviseBills(transaction, tid) {
+            const sql = 'Insert Into ' + this.ctx.service.reviseBills.tableName +
+                '  Select * From ' + this.ctx.service.ledger.tableName +
+                '  Where `tender_id` = ?';
+            const sqlParam = [tid];
+            await transaction.query(sql, sqlParam);
+        }
+
+        async _initRevisePos(transaction, tid) {
+            const sql = 'Insert Into ' + this.ctx.service.revisePos.tableName +
+                '  Select * From ' + this.ctx.service.pos.tableName +
+                '  Where `tid` = ?';
+            const sqlParam = [tid];
+            await transaction.query(sql, sqlParam);
+        }
+
+        /**
+         * 新增修订
+         * @param {Number}tid - 标段id
+         * @param {Number}uid - 提交人id
+         * @returns {Promise<void>}
+         */
+        async add(tid, uid) {
+
+            if (!tid && !uid) {
+                throw '数据错误';
+            }
+            const maxOrder = await this.getNewOrder(tid);
+            const data = {
+                id: this.uuid.v4(), tid: tid, uid: uid,
+                corder: maxOrder + 1, in_time: new Date(), status: audit.status.uncheck,
+            };
+            const transaction = await this.db.beginTransaction();
+            try {
+                const result = await transaction.insert(this.tableName, data);
+                if (result.affectedRows !== 1) {
+                    throw '新增台账修订失败';
+                }
+                await transaction.delete(this.ctx.service.reviseBills.tableName, {tender_id: tid});
+                await transaction.delete(this.ctx.service.revisePos.tableName, {tid: tid});
+                await this._initReviseBills(transaction, tid, data.id);
+                await this._initRevisePos(transaction, tid, data.id);
+                await transaction.commit();
+                return data;
+            } catch(err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        /**
+         * 作废修订
+         * @param id
+         * @returns {Promise<void>}
+         */
+        async cancelRevise(id) {
+            const result = await this.db.update(this.tableName, {id: id, valid: false});
+            return result.affectedRows === 1;
+        }
+
+    }
+
+    return LedgerRevise;
+};

+ 36 - 0
app/service/ledger_revise_bills.js

@@ -0,0 +1,36 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+module.exports = app => {
+    class LedgerReviseBills extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'ledger_revise_bills';
+        }
+
+        /**
+         * 初始化 修订的 清单
+         * @param tid
+         * @param lrid
+         * @returns {Promise<void>}
+         */
+        async initReviseBills(tid, lrid) {
+
+        }
+    }
+
+    return LedgerReviseBills;
+};

+ 36 - 0
app/service/ledger_revise_pos.js

@@ -0,0 +1,36 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+module.exports = app => {
+    class LedgerRevisePos extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'ledger_revise_pos';
+        }
+
+        /**
+         * 初始化 修订的 部位明细
+         * @param tid
+         * @param lrid
+         * @returns {Promise<void>}
+         */
+        async initRevisePos(tid, lrid) {
+
+        }
+    }
+
+    return LedgerRevisePos;
+};

+ 51 - 12
app/service/pay.js

@@ -70,7 +70,7 @@ module.exports = app => {
                 csid: this.ctx.stage.id,
                 cstimes: this.ctx.stage.times,
                 csorder: this.ctx.stage.order,
-                csaorder: this.ctx.stage.curAuditor ? this.ctx.stage.curAuditor : 0,
+                csaorder: this.ctx.stage.curOrder,
                 uid: this.ctx.session.sessionUser.accountId,
                 minus: false,
                 ptype: payConst.payType.normal,
@@ -123,6 +123,7 @@ module.exports = app => {
                     throw '删除合同支付项失败'
                 }
                 await transaction.commit();
+                return true;
             } catch(err) {
                 await transaction.rollback();
                 throw err;
@@ -153,32 +154,70 @@ module.exports = app => {
                 await transaction.update(this.tableName, {id: pay1.id, order: pay1.order});
                 await transaction.update(this.tableName, {id: pay2.id, order: pay2.order});
                 await transaction.commit();
+                return true;
             } catch (err) {
                 await transaction.rollback();
                 throw err;
             }
         }
 
-        /**
-         * 保存合同支付项数据
-         * @param data
-         * @returns {Promise<boolean>}
-         */
-        async save(data) {
-            console.log(data);
-            if (!this.ctx.tender || !this.ctx.stage) { return false; }
+        async _save(data, transaction) {
             const pay = await this.getDataByCondition({tid: this.ctx.tender.id, id: data.id});
             if(!pay) {
                 throw '数据错误';
             }
             const updateData = this.ctx.helper.filterValidFields(data, writableFields);
             updateData.id = data.id;
-            const result = await this.db.update(this.tableName, updateData);
-            if (result.affectedRows !== 1) {
-                throw '更新数据失败';
+            if (transaction) {
+                const result = await transaction.update(this.tableName, updateData);
+                if (result.affectedRows !== 1) {
+                    throw '更新数据失败';
+                }
+            } else {
+                const result = await this.db.update(this.tableName, updateData);
+                if (result.affectedRows !== 1) {
+                    throw '更新数据失败';
+                }
             }
             return updateData;
         }
+
+        /**
+         * 保存合同支付项数据
+         * @param data
+         * @returns {Promise<boolean>}
+         */
+        async save(data) {
+            if (!this.ctx.tender || !this.ctx.stage) { return false; }
+
+            if (data instanceof Array) {
+                const transaction = await this.db.beginTransaction();
+                const result = [];
+                try {
+                    for (const d of data) {
+                        const updateData = await this._save(d, transaction);
+                        result.push(updateData);
+                    }
+                    await transaction.commit();
+                } catch(err) {
+                    await transaction.rollback();
+                    throw err;
+                }
+                return result;
+            } else {
+                const pay = await this.getDataByCondition({tid: this.ctx.tender.id, id: data.id});
+                if(!pay) {
+                    throw '数据错误';
+                }
+                const updateData = this.ctx.helper.filterValidFields(data, writableFields);
+                updateData.id = data.id;
+                const result = await this.db.update(this.tableName, updateData);
+                if (result.affectedRows !== 1) {
+                    throw '更新数据失败';
+                }
+                return updateData;
+            }
+        }
     }
 
     return Pay;

+ 94 - 30
app/service/pos.js

@@ -23,25 +23,52 @@ module.exports = app => {
         }
 
         async getPosData(condition) {
-            return await this.db.select(this.tableName, {
-                where: condition,
-                columns: ['id', 'tid', 'lid', 'name', 'quantity', 'drawing_code'],
-            });
+            const sql = 'SELECT p.id, p.tid, p.lid, p.name, p.quantity, 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 ' +
+                '  FROM ' + this.tableName + ' p ' +
+                '  LEFT JOIN ' + this.ctx.service.stage.tableName + ' s' +
+                '  ON add_stage = s.id'
+                + this.ctx.helper.whereSql(condition, 'p');
+            return await this.db.query(sql);
+            // return await this.db.select(this.tableName, {
+            //     where: condition,
+            //     columns: ['id', 'tid', 'lid', 'name', 'quantity', 'drawing_code', 'sgfh_qty', 'sjcl_qty', 'qtcl_qty', 'in_time', 'porder', 'add_stage'],
+            //     order: [['porder', 'ASC']],
+            // });
+        }
+
+        async getPosDataByIds(ids) {
+            if (ids instanceof Array && ids.length > 0) {
+                const sql = 'SELECT id, tid, lid, name, quantity, drawing_code, sgfh_qty, sjcl_qty, qtcl_qty' +
+                    '  FROM ' + this.tableName +
+                    '  WHERE id in (' + this.ctx.helper.getInArrStrSqlFilter(ids) + ')';
+                return await this.db.query(sql, []);
+            } else {
+                return [];
+            }
+            // this.initSqlBuilder();
+            // this.sqlBuilder.setAndWhere('id', {
+            //     operate: 'in',
+            //     value: ids
+            // });
+            // this.sqlBuilder.columns = ['id', 'tid', 'lid', 'name', 'quantity', 'drawing_code', 'sgfh_qty', 'sjcl_qty', 'qtcl_qty'];
+            // const [sql, sqlParam] = this.sqlBuilder.build(this.tableName)
+            // return await this.db.query(sql, sqlParam);
         }
 
         async _insertPosData(transaction, data, tid) {
+            data.id = this.uuid.v4();
             data.tid = tid;
             // todo 新增期
             data.add_stage = 0;
             data.add_times = 0;
+            data.in_time = new Date();
             data.add_user = this.ctx.session.sessionUser.accountId;
             if (data.quantity) {
                 const bills = await this.ctx.service.ledger.getDataById(data.lid);
                 const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, bills.unit);
-                data.quantity = this._.round(data.quantity, precision.value);
+                data.quantity = this.round(data.quantity, precision.value);
             }
             const addRst = await transaction.insert(this.tableName, data);
-            data.id = addRst.insertId;
         }
 
         /**
@@ -51,9 +78,9 @@ module.exports = app => {
          * @returns {Promise<{ledger: {}, pos: null}>}
          */
         async savePosData(data, tid) {
+            const result = { ledger: {}, pos: null };
             const transaction = await this.db.beginTransaction();
             try {
-                const result = { ledger: {}, pos: null };
                 if (data.updateType === 'add') {
                     const tender = await this.ctx.service.tender.getTender(tid);
                     if (data.updateData instanceof Array) {
@@ -69,10 +96,25 @@ module.exports = app => {
                     const orgPos = await this.getPosData({tid: tid, id: this._.map(datas, 'id')});
                     for (const d of datas) {
                         const op = this._.find(orgPos, function (p) { return p.id = d.id; });
-                        if (d.quantity) {
+                        if (d.sgfh_qty !== undefined || d.qtcl_qty !== undefined || d.sjcl_qty !== undefined) {
                             const bills = await this.ctx.service.ledger.getDataById(op.lid);
                             const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, bills.unit);
-                            d.quantity = this._.round(d.quantity, precision.value);
+                            if (d.sgfh_qty !== undefined) {
+                                d.sgfh_qty = this.round(d.sgfh_qty, precision.value);
+                            } else if (op) {
+                                d.sgfh_qty = op.sgfh_qty;
+                            }
+                            if (d.sjcl_qty !== undefined) {
+                                d.sjcl_qty = this.round(d.sjcl_qty, precision.value);
+                            } else if (op) {
+                                d.sjcl_qty = op.sjcl_qty;
+                            }
+                            if (d.qtcl_qty !== undefined) {
+                                d.qtcl_qty = this.round(d.qtcl_qty, precision.value);
+                            } else if (op) {
+                                d.qtcl_qty = op.qtcl_qty;
+                            }
+                            d.quantity = this.ctx.helper.sum([d.sgfh_qty, d.qtcl_qty, d.sjcl_qty]);
                         }
                         await transaction.update(this.tableName, d, {tid: tid, id: d.id});
                         if (d.quantity !== undefined && op && (result.ledger.update.indexOf(op.lid) === -1)) {
@@ -88,7 +130,6 @@ module.exports = app => {
                     }
                     const pos = await this.getPosData({tid: tid, id: data.updateData});
                     const ledgerIds = this._.map(pos, 'lid');
-                    console.log(ledgerIds);
                     await transaction.delete(this.tableName, {tid: tid, id: data.updateData});
                     for (const lid of ledgerIds) {
                         await this.ctx.service.ledger.calc(tid, lid, transaction);
@@ -98,13 +139,13 @@ module.exports = app => {
                     throw '提交数据错误';
                 }
                 await transaction.commit();
-                result.pos = data.updateData;
-                result.ledger.update = await this.ctx.service.ledger.getDataByIds(result.ledger.update);
-                return result;
             } catch (err) {
                 await transaction.rollback();
                 throw err;
             }
+            result.pos = data.updateData;
+            result.ledger.update = await this.ctx.service.ledger.getDataByIds(result.ledger.update);
+            return result;
         }
 
         /**
@@ -120,24 +161,42 @@ module.exports = app => {
 
             const transaction = await this.db.beginTransaction();
             const result = { ledger: {}, pos: null }, updateLid = [];
+            const orgPos = await this.getPosData({tid: tid, id: this._.map(data, 'id')});
+            let bills = null, precision = null;
             try {
                 for (const d of data) {
-                    if (d.quantity) {
-                        const bills = await this.ctx.service.ledger.getDataById(d.lid);
-                        const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, bills.unit);
-                        d.quantity = this._.round(d.quantity, precision.value);
-                        if (updateLid.indexOf(d.lid) === -1) {
+                    const op = d.id ? this._.find(orgPos, {id: d.id}) : null;
+                    if (d.sgfh_qty || d.sjcl_qty || d.qtcl_qty) {
+                        if (!bills || bills.id !== d.lid) {
+                            bills = await this.ctx.service.ledger.getDataById(d.lid);
+                            precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, bills.unit);
                             updateLid.push(d.lid);
                         }
+                        if (d.sgfh_qty !== undefined) {
+                            d.sgfh_qty = this.round(d.sgfh_qty, precision.value);
+                        } else if (op) {
+                            d.sgfh_qty = op.sgfh_qty;
+                        }
+                        if (d.sjcl_qty !== undefined) {
+                            d.sjcl_qty = this.round(d.sjcl_qty, precision.value);
+                        } else if (op) {
+                            d.sjcl_qty = op.sjcl_qty;
+                        }
+                        if (d.qtcl_qty) {
+                            d.qtcl_qty = this.round(d.qtcl_qty, precision.value);
+                        } else if (op) {
+                            d.qtcl_qty = op.qtcl_qty;
+                        }
+                        d.quantity = this.ctx.helper.sum([d.sgfh_qty, d.qtcl_qty, d.sjcl_qty]);
                     }
                     if (d.id) {
                         await transaction.update(this.tableName, d);
                     } else {
                         this._insertPosData(transaction, d, tid);
                     }
-                    for (const lid of updateLid) {
-                        await this.ctx.service.ledger.calc(tid, lid, transaction);
-                    }
+                }
+                for (const lid of updateLid) {
+                    await this.ctx.service.ledger.calc(tid, lid, transaction);
                 }
                 await transaction.commit();
             } catch (err) {
@@ -174,8 +233,10 @@ module.exports = app => {
             const posData = await this.getAllDataByCondition({ where: { lid: orgLid } });
             if (posData.length > 0) {
                 for (const pd of posData) {
-                    delete pd.id;
+                    pd.id = this.uuid.v4();
                     pd.lid = newLid;
+                    pd.tid = this.ctx.tender.id;
+                    pd.in_time = new Date();
                 }
                 await transaction.insert(this.tableName, posData);
             }
@@ -191,18 +252,21 @@ module.exports = app => {
          */
         async insertLedgerPosData(transaction, tid, bills, data) {
             const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, bills.unit);
+            const insertDatas = [];
             for (const d of data) {
-                d.tid = tid;
-                d.lid = bills.id;
-                // todo 新增期
-                d.add_stage = 0;
-                d.add_times = 0;
-                d.add_user = this.ctx.session.sessionUser.accountId;
+                const inD = {
+                    id: this.uuid.v4(), tid: tid, lid: bills.id,
+                    add_stage: 0, add_times: 0, add_user: this.ctx.session.sessionUser.accountId,
+                    in_time: new Date(), porder: data.indexOf(d) + 1,
+                    name: d.name, drawing_code: d.drawing_code,
+                };
                 if (d.quantity) {
-                    d.quantity = this._.round(d.quantity, precision.value);
+                    inD.sgfh_qty = this.round(d.quantity, precision.value);
+                    inD.quantity = inD.sgfh_qty;
                 }
+                insertDatas.push(inD);
             }
-            await transaction.insert(this.tableName, data);
+            await transaction.insert(this.tableName, insertDatas);
         }
     }
 

+ 14 - 13
app/service/project_account.js

@@ -145,16 +145,16 @@ module.exports = app => {
                     // cooperation = accountData.cooperation;
 
                     // 判断密码
-                    // if (accountData.is_admin === 1) {
-                    //     // 管理员则用sso通道判断
-                    //     const sso = new SSO(this.ctx);
-                    //     result = await sso.loginValid(data.account, data.project_password.toString());
+                    // if (accountData.password === 'SSO password') {
+                    //      // 用sso通道判断
+                    //      const sso = new SSO(this.ctx);
+                    //      result = await sso.loginValid(data.account, data.project_password.toString());
                     // } else {
-                    // 加密密码
-                    const encryptPassword = crypto.createHmac('sha1', data.account).update(data.project_password)
-                        .digest().toString('base64');
-                    result = encryptPassword === accountData.password;
-                    // }
+                        // 加密密码
+                        const encryptPassword = crypto.createHmac('sha1', data.account).update(data.project_password)
+                            .digest().toString('base64');
+                        result = encryptPassword === accountData.password;
+                    //}
                 } else {
                     // sso登录(演示版)
                     const sso = new SSO(this.ctx);
@@ -163,19 +163,20 @@ module.exports = app => {
                     accountData.id = sso.accountID;
                 }
 
-
                 // 如果成功则更新登录时间
                 if (result) {
                     const currentTime = new Date().getTime() / 1000;
+                    // 加密token
+                    const sessionToken = crypto.createHmac('sha1', currentTime + '').update(accountData.account)
+                        .digest().toString('base64');
+
                     if (loginType === 2) {
                         const updateData = {
                             last_login: currentTime,
+                            session_token: sessionToken,
                         };
                         await this.update(updateData, { id: accountData.id });
                     }
-                    // 加密token
-                    const sessionToken = crypto.createHmac('sha1', currentTime + '').update(accountData.account)
-                        .digest().toString('base64');
                     // 存入session
                     this.ctx.session.sessionUser = {
                         account: accountData.account,

+ 507 - 0
app/service/revise_bills.js

@@ -0,0 +1,507 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const keyPre = 'revise_bills_maxLid:';
+const tidField = 'tender_id';
+const lidField = 'ledger_id';
+const pidField = 'ledger_pid';
+const pathField = 'full_path';
+
+module.exports = app => {
+    class ReviseBills extends app.BaseService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'revise_bills';
+        }
+
+        /**
+         * 获取 修订 清单数据
+         * @param {Number}tid - 标段id
+         * @returns {Promise<void>}
+         */
+        async getData(tid) {
+            return await this.db.select(this.tableName, {
+                where: {tender_id: tid}
+            });
+        }
+
+        /**
+         * 获取节点数据
+         * @param {Number} tid - 标段id
+         * @param {Number} lid - 标段内,台账id
+         * @returns {Promise<void>}
+         */
+        async getDataByLid (tid, lid) {
+            return await this.db.get(this.tableName, {tender_id: tid, ledger_id: lid});
+        }
+
+        /**
+         * 获取节点数据
+         * @param id
+         * @returns {Promise<Array>}
+         */
+        async getDataById(id) {
+            if (id instanceof Array) {
+                return await this.db.select(this.tableName, { where: {id: id} });
+            } else {
+                return await this.db.get(this.tableName, { id: id });
+            }
+        }
+
+        /**
+         * 获取最末的子节点
+         * @param {Number} tenderId - 标段id
+         * @param {Number} pid - 父节点id
+         * @return {Object}
+         */
+        async getLastChildData(tid, pid) {
+            this.initSqlBuilder();
+            this.sqlBuilder.setAndWhere('tender_id', {
+                value: tid,
+                operate: '=',
+            });
+            this.sqlBuilder.setAndWhere('ledger_pid', {
+                value: pid,
+                operate: '=',
+            });
+            this.sqlBuilder.orderBy = [['order', 'DESC']];
+            const [sql, sqlParam] = this.sqlBuilder.build(this.tableName);
+            const resultData = await this.db.queryOne(sql, sqlParam);
+
+            return resultData;
+        }
+        /**
+         * 根据 父节点id 和 节点排序order 获取数据
+         *
+         * @param {Number} tid - 标段id
+         * @param {Number} pid - 父节点id
+         * @param {Number|Array} order - 排序
+         * @return {Object|Array} - 查询结果
+         */
+        async getDataByParentAndOrder(tid, pid, order) {
+            const result = await this.db.select(this.tableName, {where: {
+                tender_id: tid, ledger_pid: pid, order: order
+            }});
+            return order instanceof Array ? result : (result.length > 0 ? result[0] : null);
+        }
+        /**
+         * 根据 父节点ID 和 节点排序order 获取全部后节点数据
+         * @param {Number} tid - 标段id
+         * @param {Number} pid - 父节点id(ledger_pid)
+         * @param {Number} order - 排序
+         * @return {Array}
+         */
+        async getNextsData(tid, pid, order) {
+            this.initSqlBuilder();
+            this.sqlBuilder.setAndWhere('tender_id', {
+                value: tid,
+                operate: '=',
+            });
+            this.sqlBuilder.setAndWhere('ledger_pid', {
+                value: pid,
+                operate: '=',
+            });
+            this.sqlBuilder.setAndWhere('order', {
+                value: order,
+                operate: '>',
+            });
+
+            const [sql, sqlParam] = this.sqlBuilder.build(this.tableName);
+            const data = await this.db.query(sql, sqlParam);
+
+            return data;
+        }
+
+        /**
+         * 获取最大节点id
+         *
+         * @param {Number} tid - 台账id
+         * @return {Number}
+         * @private
+         */
+        async _getMaxLid(tid) {
+            const cacheKey = keyPre + tid;
+            let maxId = parseInt(await this.cache.get(cacheKey));
+            if (!maxId) {
+                const sql = 'SELECT Max(??) As max_id FROM ?? Where tender_id = ?';
+                const sqlParam = ['ledger_id', this.tableName, tid];
+                const queryResult = await this.db.queryOne(sql, sqlParam);
+                maxId = queryResult.max_id || 0;
+                this.cache.set(cacheKey, maxId, 'EX', this.ctx.app.config.cacheTime);
+            }
+            return maxId;
+        }
+
+        /**
+         * 缓存最大节点id
+         *
+         * @param {Number} tid - 台账id
+         * @param {Number} maxId - 当前最大节点id
+         * @returns {Promise<void>}
+         * @private
+         */
+        async _cacheMaxLid(tid, maxId) {
+            this.cache.set(keyPre + tid , maxId, 'EX', this.ctx.app.config.cacheTime);
+        }
+
+        /**
+         * 更新order
+         * @param {Number} tid - 台账id
+         * @param {Number} pid - 父节点id(ledger_id)
+         * @param {Number} order - 开始更新的order
+         * @param {Number} incre - 更新的增量
+         * @returns {Promise<*>}
+         * @private
+         */
+        async _updateChildrenOrder(tid, pid, order, incre = 1) {
+            this.initSqlBuilder();
+            this.sqlBuilder.setAndWhere('tender_id', {
+                value: tid,
+                operate: '=',
+            });
+            this.sqlBuilder.setAndWhere('order', {
+                value: order,
+                operate: '>=',
+            });
+            this.sqlBuilder.setAndWhere('ledger_pid', {
+                value: pid,
+                operate: '=',
+            });
+            this.sqlBuilder.setUpdateData('order', {
+                value: Math.abs(incre),
+                selfOperate: incre > 0 ? '+' : '-',
+            });
+            const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'update');
+            const data = await this.transaction.query(sql, sqlParam);
+
+            return data;
+        }
+
+        /**
+         * 新增数据(新增为selectData的后项,该方法不可单独使用)
+         *
+         * @param {Number} tid - 台账id
+         * @param {uuid} rid - 修订id
+         * @param {Object} select - 选中节点的数据
+         * @param {Object} data - 新增节点的初始数据
+         * @return {Object} - 新增结果
+         * @private
+         */
+        async _addNodeData(tid, rid, select, data) {
+            if (!data) {
+                data = {};
+            }
+            const maxId = await this._getMaxLid(tid);
+
+            data.id = this.uuid.v4();
+            data.crid = rid;
+            data.ledger_id = maxId + 1;
+            data.ledger_pid = select.ledger_pid;
+            data.tender_id = tid;
+            data.level = select.level;
+            data.order = select.order + 1;
+            data.full_path = select.full_path.replace('.' + select.ledger_id, '.' + data.ledger_id);
+            data.is_leaf = true;
+            const result = await this.transaction.insert(this.tableName, data);
+
+            this._cacheMaxLid(tid, maxId + 1);
+
+            return result;
+        }
+
+        /**
+         * 新增节点
+         * @param {Number} tid - 台账id
+         * @param {uuid} rid - 修订id
+         * @param {Number} lid - 清单节点id
+         * @returns {Promise<void>}
+         */
+        async addNode(tid, rid, lid) {
+            if (!tid || !rid || !lid) return null;
+            const select = await this.getDataByLid(tid, lid);
+            if (!select) {
+                throw '新增节点数据错误';
+            }
+
+            this.transaction = await this.db.beginTransaction();
+            try {
+                await this._updateChildrenOrder(tid, select.ledger_pid, select.order+1);
+                const newNode = await this._addNodeData(tid, rid, select);
+                if (newNode.affectedRows !== 1) {
+                    throw '新增节点数据额错误';
+                }
+                await this.transaction.commit();
+                this.transaction = null;
+            } catch (err) {
+                await this.transaction.rollback();
+                this.transaction = null;
+                throw err;
+            }
+
+            const createData = await this.getDataByParentAndOrder(tid, select.ledger_pid, [select.order + 1]);
+            const updateData = await this.getNextsData(tid, select.ledger_pid, select.order + 1);
+            return {create: createData, update: updateData};
+        }
+
+        /**
+         * 上移节点
+         *
+         * @param {Number} tid - 台账id
+         * @param {Number} lid - 选中节点id
+         * @return {Array} - 发生改变的数据
+         */
+        async upMoveNode(tid, lid) {
+            if (!tid || !lid) return null;
+            const select = await this.getDataByLid(tid, lid);
+            if (!select) {
+                throw '上移节点数据错误';
+            }
+            const pre = await this.getDataByParentAndOrder(tid, select.ledger_pid, select.order - 1);
+            if (!pre) {
+                throw '节点不可上移';
+            }
+
+            this.transaction = await this.db.beginTransaction();
+            try {
+                const sData = await this.transaction.update(this.tableName, { id: select.id, order: select.order - 1 });
+                const pData = await this.transaction.update(this.tableName, { id: pre.id, order: pre.order + 1 });
+                await this.transaction.commit();
+            } catch (err) {
+                await this.transaction.rollback();
+                throw err;
+            }
+
+            const resultData = await this.getDataByParentAndOrder(tid, select.ledger_pid, [select.order, pre.order]);
+            return { update: resultData };
+        }
+
+        /**
+         * 下移节点
+         *
+         * @param {Number} tid - 台账id
+         * @param {Number} lid - 选中节点id
+         * @return {Array} - 发生改变的数据
+         */
+        async downMoveNode(tid, lid) {
+            if (!tid || !lid) return null;
+            const select = await this.getDataByLid(tid, lid);
+            if (!select) {
+                throw '下移节点数据错误';
+            }
+            const next = await this.getDataByParentAndOrder(tid, select.ledger_pid, select.order + 1);
+            if (!next) {
+                throw '节点不可下移';
+            }
+
+            this.transaction = await this.db.beginTransaction();
+            try {
+                const sData = await this.transaction.update(this.tableName, { id: select.id, order: select.order + 1 });
+                const pData = await this.transaction.update(this.tableName, { id: next.id, order: next.order - 1 });
+                await this.transaction.commit();
+            } catch (err) {
+                await this.transaction.rollback();
+                throw err;
+            }
+
+            const resultData = await this.getDataByParentAndOrder(tid, select.ledger_pid, [select.order, next.order]);
+            return { update: resultData };
+        }
+
+        /**
+         * 批量插入子项
+         * @param {uuid} rid - 修订id
+         * @param {Number} lid - 节点id
+         * @param {Object} data - 批量插入数据
+         * @return {Promise<void>}
+         */
+        async batchInsertChild(tid, rid, lid, data) {
+            const result = { ledger: {}, pos: null };
+            if (!tid || !rid || !lid) return result;
+
+            const select = await this.getDataByLid(tid, lid);
+            if (!select) {
+                throw '位置数据错误';
+            }
+
+            // 计算id和order
+            const maxId = await this._getMaxLid(tid);
+            const lastChild = await this.getLastChildData(tid, lid);
+            const order = lastChild ? lastChild.order : 0;
+            // 整理数据
+            const bills = [], pos = [], newIds = [];
+            for (let i = 0, iLen = data.length; i < iLen; i++) {
+                // 合并新增数据
+                const qd = {
+                    crid: rid,
+                    id: this.uuid.v4(),
+                    tender_id: tid,
+                    ledger_id: maxId + i + 1,
+                    ledger_pid: select.ledger_id,
+                    is_leaf: true,
+                    order: order + i + 1,
+                    level: select.level + 1,
+                    b_code: data[i].b_code,
+                    name: data[i].name,
+                    unit: data[i].unit,
+                    unit_price: data[i].price,
+                };
+
+                const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, bills.unit);
+                qd.sgfh_qty = 0;
+                for (const p of data[i].pos) {
+                    const inP = {
+                        id: this.uuid.v4(), tid: tid, lid: qd.id, crid: rid,
+                        add_stage: 0, add_times: 0, add_user: this.ctx.session.sessionUser.accountId,
+                        name: p.name, drawing_code: p.drawing_code,
+                    };
+                    if (p.quantity) {
+                        inP.sgfh_qty = this.round(p.quantity, precision.value);
+                        inP.quantity = inP.sgfh_qty;
+                    }
+                    qd.sgfh_qty = this.ctx.helper.add(qd.sgfh_qty, inP.sgfh_qty);
+                    pos.push(inP);
+                }
+                qd.full_path = select.full_path + '.' + qd.ledger_id;
+                qd.sgfh_tp = this.ctx.helper.mul(qd.sgfh_qty, qd.unit_price, this.ctx.tender.info.decimal.tp);
+                qd.quantity = qd.sgfh_qty;
+                qd.total_price = qd.sgfh_tp;
+                bills.push(qd);
+                newIds.push(qd.id);
+            }
+
+            this.transaction = await this.db.beginTransaction();
+            try {
+                // 更新父项isLeaf
+                if (!lastChild) {
+                    await this.transaction.update(this.tableName, {
+                        is_leaf: false,
+                        unit_price: null,
+                        quantity: null,
+                        total_price: null,
+                        deal_qty: null,
+                        deal_tp: null,
+                    }, {tender_id: rid, id: select.id});
+                }
+                // 数据库创建新增节点数据
+                await this.transaction.insert(this.tableName, bills);
+                await this.transaction.insert(this.ctx.service.revisePos.tableName, pos);
+                this._cacheMaxLid(tid, maxId + data.length);
+                await this.transaction.commit();
+            } catch (err) {
+                await this.transaction.rollback();
+                throw err;
+            }
+
+            // 查询应返回的结果
+            result.ledger.create = await this.getDataById(newIds);
+            if (!lastChild) {
+                result.ledger.update = await this.getDataByLid(select.id);
+            }
+            result.pos = await this.ctx.service.revisePos.getDataByLid(tid, newIds);
+            return result;
+        }
+
+        /**
+         * 批量插入后项
+         * @param {Number} tid - 台账id
+         * @param {uuid} rid - 修订id
+         * @param {Number} lid - 节点id
+         * @param {Object} data - 批量插入数据
+         * @return {Promise<void>}
+         */
+        async batchInsertNext(tid, rid, lid, data) {
+            const result = { ledger: {}, pos: null };
+            if (!tid || !rid || !lid) return result;
+
+            const select = await this.getDataByLid(tid, lid);
+            if (!select) {
+                throw '位置数据错误';
+            }
+            const parentData = await this.getDataByLid(tid, select.ledger_pid);
+            if (!parentData) {
+                throw '位置数据错误';
+            }
+
+            // 计算id和order
+            const maxId = await this._getMaxLid(tid);
+            const order = select.order;
+            // 整理数据
+            const bills = [], pos = [], newIds = [];
+            for (let i = 0, iLen = data.length; i < iLen; i++) {
+                // 合并新增数据
+                const qd = {
+                    crid: rid,
+                    id: this.uuid.v4(),
+                    tender_id: tid,
+                    ledger_id: maxId + i + 1,
+                    ledger_pid: parentData.ledger_id,
+                    is_leaf: true,
+                    order: order + i + 1,
+                    level: parentData.level + 1,
+                    b_code: data[i].b_code,
+                    name: data[i].name,
+                    unit: data[i].unit,
+                    unit_price: data[i].price,
+                };
+
+                const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, bills.unit);
+                qd.sgfh_qty = 0;
+                for (const p of data[i].pos) {
+                    const inP = {
+                        id: this.uuid.v4(), tid: tid, lid: qd.id, crid: rid,
+                        add_stage: 0, add_times: 0, add_user: this.ctx.session.sessionUser.accountId,
+                        name: p.name, drawing_code: p.drawing_code,
+                    };
+                    if (p.quantity) {
+                        inP.sgfh_qty = this.round(p.quantity, precision.value);
+                        inP.quantity = inP.sgfh_qty;
+                    }
+                    qd.sgfh_qty = this.ctx.helper.add(qd.sgfh_qty, inP.sgfh_qty);
+                    pos.push(inP);
+                }
+                qd.full_path = parentData.full_path + '.' + qd.ledger_id;
+                qd.sgfh_tp = this.ctx.helper.mul(qd.sgfh_qty, qd.unit_price, this.ctx.tender.info.decimal.tp);
+                qd.quantity = qd.sgfh_qty;
+                qd.total_price = qd.sgfh_tp;
+                bills.push(qd);
+                newIds.push(qd.id);
+            }
+
+            this.transaction = await this.db.beginTransaction();
+            try {
+                // 选中节点的所有后兄弟节点,order+粘贴节点个数
+                await this._updateChildrenOrder(tid, select.ledger_pid, select.order+1);
+                // 数据库创建新增节点数据
+                await this.transaction.insert(this.tableName, bills);
+                await this.transaction.insert(this.ctx.service.revisePos.tableName, pos);
+                this._cacheMaxLid(tid, maxId + data.length);
+                await this.transaction.commit();
+            } catch (err) {
+                await this.transaction.rollback();
+                throw err;
+            }
+
+            // 查询应返回的结果
+            result.ledger.create = await this.getDataById(newIds);
+            result.ledger.update = await this.getNextsData(select.tender_id, select.ledger_pid, select.order + data.length);
+            result.pos = await this.ctx.service.revisePos.getDataByLid(tid, newIds);
+            return result;
+        }
+    }
+
+    return ReviseBills;
+};

+ 63 - 0
app/service/revise_pos.js

@@ -0,0 +1,63 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+module.exports = app => {
+    class RevisePos extends app.BaseService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'revise_pos';
+        }
+
+        /**
+         * 获取 修订 清单数据
+         * @param {Number}tid - 标段id
+         * @param {uuid}rid - 修订id
+         * @returns {Promise<void>}
+         */
+        async getData(tid) {
+            return await this.db.select(this.tableName, {
+                where: {tid: tid}
+            });
+        }
+
+        async getDataByLid(tid, lid) {
+            return await this.db.select(this.tableName, {
+                where: {tid: tid, lid: lid}
+            });
+        }
+
+        async insertLedgerPosData(transaction, tid, rid, bills, data) {
+            const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, bills.unit);
+            const insertDatas = [];
+            for (const d of data) {
+                const inD = {
+                    id: this.uuid.v4(), tid: tid, lid: bills.id, crid: rid,
+                    add_stage: 0, add_times: 0, add_user: this.ctx.session.sessionUser.accountId,
+                    name: d.name, drawing_code: d.drawing_code,
+                };
+                if (d.quantity) {
+                    inD.sgfh_qty = this.round(d.quantity, precision.value);
+                    inD.quantity = inD.sgfh_qty;
+                }
+                insertDatas.push(inD);
+            }
+            await transaction.insert(this.tableName, insertDatas);
+        }
+    }
+
+    return RevisePos;
+};

+ 21 - 10
app/service/stage.js

@@ -91,9 +91,9 @@ module.exports = app => {
                 orders: [['order', 'desc']],
             });
             for (const s of stages) {
-                s.tp = this.ctx.helper.plus(s.contract_tp, s.qc_tp);
-                s.pre_tp = this.ctx.helper.plus(s.pre_contract_tp, s.pre_qc_tp);
-                s.end_tp = this.ctx.helper.plus(s.pre_tp, s.tp);
+                s.tp = this.ctx.helper.add(s.contract_tp, s.qc_tp);
+                s.pre_tp = this.ctx.helper.add(s.pre_contract_tp, s.pre_qc_tp);
+                s.end_tp = this.ctx.helper.add(s.pre_tp, s.tp);
             }
             if (stages.length !== 0) {
                 const lastStage = stages[stages.length - 1];
@@ -110,8 +110,8 @@ module.exports = app => {
                     const tpData = await this.ctx.service.stageBills.getSumTotalPrice(stage);
                     stage.contract_tp = tpData.contract_tp;
                     stage.qc_tp = tpData.qc_tp;
-                    stage.tp = this.ctx.helper.plus(stage.contract_tp, stage.qc_tp);
-                    stage.end_tp = this.ctx.helper.plus(stage.pre_tp, stage.tp);
+                    stage.tp = this.ctx.helper.add(stage.contract_tp, stage.qc_tp);
+                    stage.end_tp = this.ctx.helper.add(stage.pre_tp, stage.tp);
                 }
             }
             return stages;
@@ -146,8 +146,8 @@ module.exports = app => {
                 user_id: this.ctx.session.sessionUser.accountId,
             };
             if (preStage) {
-                newStage.pre_contract_tp = this.ctx.helper.plus(preStage.pre_contract_tp, preStage.contract_tp);
-                newStage.pre_qc_tp = this.ctx.helper.plus(preStage.pre_qc_tp, preStage.qc_tp);
+                newStage.pre_contract_tp = this.ctx.helper.add(preStage.pre_contract_tp, preStage.contract_tp);
+                newStage.pre_qc_tp = this.ctx.helper.add(preStage.pre_qc_tp, preStage.qc_tp);
             }
             const transaction = await this.db.beginTransaction();
             try {
@@ -230,7 +230,7 @@ module.exports = app => {
                         cb.value = param.zanLiePrice;
                         break;
                     case 'htjszl':
-                        cb.value = this.ctx.helper.minus(param.contractPrice, param.zanLiePrice);
+                        cb.value = this.ctx.helper.sub(param.contractPrice, param.zanLiePrice);
                         break;
                     case 'kgyfk':
                         cb.value = param.startAdvance;
@@ -240,11 +240,11 @@ module.exports = app => {
                         break;
                     case 'bqwc':
                         const sum = await this.ctx.service.stageBills.getSumTotalPrice(this.ctx.stage);
-                        cb.value = this.app._.add(sum.contract_tp, sum.qc_tp);
+                        cb.value = this.ctx.helper.add(sum.contract_tp, sum.qc_tp);
                         break;
                     case 'ybbqwc':
                         const sumGcl = await this.ctx.service.stageBills.getSumTotalPriceGcl(this.ctx.stage, '^1[0-9]{2}-');
-                        cb.value = this.app._.add(sumGcl.contract_tp, sumGcl.qc_tp);
+                        cb.value = this.ctx.helper.add(sumGcl.contract_tp, sumGcl.qc_tp);
                         break;
                     default:
                         cb.value = 0;
@@ -252,6 +252,17 @@ module.exports = app => {
             }
             return calcBase;
         }
+
+        /**
+         * 更新 check_detail 标识
+         * @param {Integer}sid - 期id
+         * @param {Boolean}check - 标记
+         * @returns {Promise<void>}
+         */
+        async updateCheckDetailFlag(sid, check) {
+            const result = await this.db.update(this.tableName, {id: sid, check_detail: check});
+            return result.affectedRows === 1;
+        }
     }
 
     return Stage;

+ 90 - 0
app/service/stage_att.js

@@ -0,0 +1,90 @@
+'use strict';
+
+/**
+ *
+ *  附件
+ * @author Ellisran
+ * @date 2019/1/11
+ * @version
+ */
+
+module.exports = app => {
+    class StageAtt extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'stage_attachment';
+        }
+
+        /**
+         * 添加附件
+         * @param {Object} postData - 表单信息
+         * @param {Object} fileData - 文件信息
+         * @param {int} uid - 上传者id
+         * @return {void}
+         */
+        async save(postData, fileData, uid) {
+            const data = {
+                lid: postData.lid,
+                uid,
+                remark: '',
+            };
+            Object.assign(data, fileData);
+            const result = await this.db.insert(this.tableName, data);
+            return result;
+        }
+
+        /**
+         * 添加附件
+         * @param {Object} postData - 表单信息
+         * @param {Object} fileData - 文件信息
+         * @param {int} uid - 上传者id
+         * @return {void}
+         */
+        async updateByID(postData, fileData) {
+            delete postData.size;
+            const data = {};
+            Object.assign(data, fileData);
+            Object.assign(data, postData);
+            const result = await this.db.update(this.tableName, data);
+            return result.affectedRows === 1;
+        }
+
+        /**
+         * 获取所有附件
+         * @param {int} tid - 标段id
+         * @param {int} sid - 当前期数
+         * @return {void}
+         */
+        async getDataByTenderIdAndStageId(tid, sid) {
+            const sql = 'SELECT att.id, att.lid, att.uid, att.filename, att.fileext, att.filesize, att.remark, att.in_time,' +
+                ' pa.name as `username`, leg.name as `lname`, leg.code as `code`, leg.b_code as `b_code`' +
+                ' FROM ?? AS att,?? AS pa,?? AS leg' +
+                ' WHERE leg.id = att.lid AND pa.id = att.uid AND att.tid = ? AND att.sid = ? ORDER BY att.in_time DESC';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, this.ctx.service.ledger.tableName, tid, sid];
+            return await this.db.query(sql, sqlParam);
+        }
+
+        /**
+         * 获取单个附件
+         * @param {int} tid - 标段id
+         * @param {int} sid - 当前期数
+         * @return {void}
+         */
+        async getDataByFid(id) {
+            const sql = 'SELECT att.id, att.lid, att.uid, att.filename, att.fileext, att.filesize, att.remark, att.in_time,' +
+                ' pa.name as `username`, leg.name as `lname`, leg.code as `code`, leg.b_code as `b_code`' +
+                ' FROM ?? AS att,?? AS pa,?? AS leg' +
+                ' WHERE leg.id = att.lid AND pa.id = att.uid AND att.id = ? ORDER BY att.in_time DESC';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, this.ctx.service.ledger.tableName, id];
+            return await this.db.queryOne(sql, sqlParam);
+        }
+    }
+
+    return StageAtt;
+};

+ 252 - 108
app/service/stage_audit.js

@@ -202,132 +202,87 @@ module.exports = app => {
             return true;
         }
 
-        /**
-         * 审批
-         * @param {Number} stageId - 标段id
-         * @param {auditConst.status.checked|auditConst.status.checkNo} checkType - 审批结果
-         * @param {Number} times - 第几次审批
-         * @returns {Promise<void>}
-         */
-        async check(stageId, checkData, times = 1) {
-            if (checkData.checkType !== auditConst.status.checked && checkData.checkType !== auditConst.status.checkNo && checkData.checkType !== auditConst.status.checkNoPre) {
-                throw '提交数据错误';
+        async _checked(stageId, checkData, times) {
+            const time = new Date();
+            // 整理当前流程审核人状态更新
+            const audit = await this.getDataByCondition({sid: stageId, times: times, status: auditConst.status.checking});
+            if (!audit) {
+                throw '审核数据错误';
             }
+            const nextAudit = await this.getDataByCondition({sid: stageId, times: times, order: audit.order + 1});
+            const tpData = await this.ctx.service.stageBills.getSumTotalPrice(this.ctx.stage);
 
             const transaction = await this.db.beginTransaction();
             try {
-                // 整理当前流程审核人状态更新
-                const time = new Date();
-                const audit = await this.getDataByCondition({sid: stageId, times: times, status: auditConst.status.checking});
-                if (!audit) {
-                    throw '审核数据错误';
-                }
-                // 更新当前审核流程
                 await transaction.update(this.tableName, {id: audit.id, status: checkData.checkType, opinion: checkData.opinion, end_time: time});
-                if (checkData.checkType === auditConst.status.checked) { // 审批通过
-                    const nextAudit = await this.getDataByCondition({sid: stageId, times: times, order: audit.order + 1});
-                    // 无下一审核人表示,审核结束
-                    if (nextAudit) {
-                        // 计算该审批人最终数据
-                        await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
-                        // 复制一份下一审核人数据
-                        await this.ctx.service.stagePay.copyAuditStagePays(this.ctx.stage, this.ctx.stage.times, nextAudit.order, transaction);
-                        // 流程至下一审批人
-                        await transaction.update(this.tableName, {id: nextAudit.id, status: auditConst.status.checking, begin_time: time});
-                        // 同步 期信息
-                        const tpData = await this.ctx.service.stageBills.getSumTotalPrice(this.ctx.stage);
-                        await transaction.update(this.ctx.service.stage.tableName, {
-                            id: stageId, status: auditConst.status.checking,
-                            contract_tp: tpData.contract_tp,
-                            qc_tp: tpData.qc_tp,
-                        });
-                    } else {
-                        // 本期结束
-                        // 生成截止本期数据 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.stagePay.calcAllStagePays(this.ctx.stage, transaction);
-                        // 同步 期信息
-                        const tpData = await this.ctx.service.stageBills.getSumTotalPrice(this.ctx.stage);
-                        await transaction.update(this.ctx.service.stage.tableName, {
-                            id: stageId, status: checkData.checkType,
-                            contract_tp: tpData.contract_tp,
-                            qc_tp: tpData.qc_tp,
-                        });
-                    }
-                } else if (checkData.checkType === auditConst.status.checkNo) { // 审批退回 原报, times+1
+                // 计算并合同支付最终数据
+                await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
+                // 无下一审核人表示,审核结束
+                if (nextAudit) {
+                    // 复制一份下一审核人数据
+                    await this.ctx.service.stagePay.copyAuditStagePays(this.ctx.stage, this.ctx.stage.times, nextAudit.order, transaction);
+                    // 流程至下一审批人
+                    await transaction.update(this.tableName, {id: nextAudit.id, status: auditConst.status.checking, begin_time: time});
                     // 同步 期信息
-                    const tpData = await this.ctx.service.stageBills.getSumTotalPrice(this.ctx.stage);
                     await transaction.update(this.ctx.service.stage.tableName, {
-                        id: stageId, status: checkData.checkType,
+                        id: stageId, status: auditConst.status.checking,
                         contract_tp: tpData.contract_tp,
                         qc_tp: tpData.qc_tp,
-                        times: times + 1,
                     });
-                    // 拷贝新一次审核流程列表
-                    // const auditors = await this.getAllDataByCondition({
-                    //     where: {sid: stageId, times: times},
-                    //     columns: ['tid', 'sid', 'aid', 'order']
-                    // });
-                    const sql = 'SELECT `tid`, `sid`, `aid`, `order` FROM ?? WHERE `sid` = ? and `times` = ? GROUP BY `aid`';
-                    const sqlParam = [this.tableName, stageId, times];
-                    const auditors = await this.db.query(sql, sqlParam);
-                    let order = 1;
-                    for (const a of auditors) {
-                        a.times = times + 1;
-                        a.order = order;
-                        a.status = auditConst.status.uncheck;
-                        order++;
-                    }
-                    await transaction.insert(this.tableName, auditors);
-                    // 计算该审批人最终数据
-                    await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
-                    // 复制一份最新数据给原报
-                    await this.ctx.service.stagePay.copyAuditStagePays(this.ctx.stage, this.ctx.stage.times + 1, 0, transaction);
-                } else if (checkData.checkType === auditConst.status.checkNoPre) { // 审批退回 上一审批人
+                } else {
+                    // 本期结束
+                    // 生成截止本期数据 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);
                     // 同步 期信息
-                    const tpData = await this.ctx.service.stageBills.getSumTotalPrice(this.ctx.stage);
                     await transaction.update(this.ctx.service.stage.tableName, {
                         id: stageId, status: checkData.checkType,
                         contract_tp: tpData.contract_tp,
                         qc_tp: tpData.qc_tp,
                     });
-                    // 将当前审批人 与 上一审批人再次添加至流程,顺移其后审批人流程顺序
-                    if (audit.order > 1) {
-                        // 顺移气候审核人流程顺序
-                        this.initSqlBuilder();
-                        this.sqlBuilder.setAndWhere('sid', { value: this.ctx.stage.id, operate: '=', });
-                        this.sqlBuilder.setAndWhere('order', { value: audit.order, operate: '>', });
-                        this.sqlBuilder.setUpdateData('order', { value: 2, selfOperate: '+', });
-                        const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'update');
-                        const data = await transaction.query(sql, sqlParam);
-
-                        // 上一审批人,当前审批人 再次添加至流程
-                        const preAuditor = await this.getDataByCondition({sid: stageId, times: times, order: audit.order - 1});
-                        const newAuditors = [];
-                        newAuditors.push({
-                            tid: preAuditor.tid, sid: preAuditor.sid, aid: preAuditor.aid,
-                            times: preAuditor.times, order: preAuditor.order + 2, status: auditConst.status.checking,
-                            begin_time: time,
-                        });
-                        newAuditors.push({
-                            tid: audit.tid, sid: audit.sid, aid: audit.aid,
-                            times: audit.times, order: audit.order + 2, status: auditConst.status.uncheck
-                        });
-                        await transaction.insert(this.tableName, newAuditors);
-
-                        // 计算该审批人最终数据
-                        await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
-                        // 复制一份最新数据给上一人
-                        await this.ctx.service.stagePay.copyAuditStagePays(this.ctx.stage, this.ctx.stage.times, audit.order + 1, transaction);
-                    } else {
-                        throw '审核数据错误';
-                    }
-                } else {
-                    throw '无效审批操作';
                 }
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        async _checkNo(stageId, checkData, times) {
+            const time = new Date();
+            // 整理当前流程审核人状态更新
+            const audit = await this.getDataByCondition({sid: stageId, times: times, status: auditConst.status.checking});
+            if (!audit) {
+                throw '审核数据错误';
+            }
+            const tpData = await this.ctx.service.stageBills.getSumTotalPrice(this.ctx.stage);
+            const sql = 'SELECT `tid`, `sid`, `aid`, `order` FROM ?? WHERE `sid` = ? and `times` = ? GROUP BY `aid`';
+            const sqlParam = [this.tableName, stageId, times];
+            const auditors = await this.db.query(sql, sqlParam);
+            let order = 1;
+            for (const a of auditors) {
+                a.times = times + 1;
+                a.order = order;
+                a.status = auditConst.status.uncheck;
+                order++;
+            }
 
+            const transaction = await this.db.beginTransaction();
+            try {
+                await transaction.update(this.tableName, {id: audit.id, status: checkData.checkType, opinion: checkData.opinion, end_time: time});
+                // 同步 期信息
+                await transaction.update(this.ctx.service.stage.tableName, {
+                    id: stageId, status: checkData.checkType,
+                    contract_tp: tpData.contract_tp,
+                    qc_tp: tpData.qc_tp,
+                    times: times + 1,
+                });
+                // 拷贝新一次审核流程列表
+                await transaction.insert(this.tableName, auditors);
+                // 计算该审批人最终数据
+                await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
+                // 复制一份最新数据给原报
+                await this.ctx.service.stagePay.copyAuditStagePays(this.ctx.stage, this.ctx.stage.times + 1, 0, transaction);
                 await transaction.commit();
             } catch (err) {
                 await transaction.rollback();
@@ -335,6 +290,195 @@ module.exports = app => {
             }
         }
 
+        async _checkNoPre(stageId, checkData, times) {
+            const time = new Date();
+            // 整理当前流程审核人状态更新
+            const audit = await this.getDataByCondition({sid: stageId, times: times, status: auditConst.status.checking});
+            if (!audit || audit.order <= 1) {
+                throw '审核数据错误';
+            }
+            const preAuditor = await this.getDataByCondition({sid: stageId, times: times, order: audit.order - 1});
+
+            const transaction = await this.db.beginTransaction();
+            try {
+                await transaction.update(this.tableName, {id: audit.id, status: checkData.checkType, opinion: checkData.opinion, end_time: time});
+                // 顺移气候审核人流程顺序
+                this.initSqlBuilder();
+                this.sqlBuilder.setAndWhere('sid', { value: this.ctx.stage.id, operate: '=', });
+                this.sqlBuilder.setAndWhere('order', { value: audit.order, operate: '>', });
+                this.sqlBuilder.setUpdateData('order', { value: 2, selfOperate: '+', });
+                const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'update');
+                const data = await transaction.query(sql, sqlParam);
+                // 上一审批人,当前审批人 再次添加至流程
+                const newAuditors = [];
+                newAuditors.push({
+                    tid: preAuditor.tid, sid: preAuditor.sid, aid: preAuditor.aid,
+                    times: preAuditor.times, order: preAuditor.order + 2, status: auditConst.status.checking,
+                    begin_time: time,
+                });
+                newAuditors.push({
+                    tid: audit.tid, sid: audit.sid, aid: audit.aid,
+                    times: audit.times, order: audit.order + 2, status: auditConst.status.uncheck
+                });
+                await transaction.insert(this.tableName, newAuditors);
+                // 计算该审批人最终数据
+                await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
+                // 复制一份最新数据给下一人
+                await this.ctx.service.stagePay.copyAuditStagePays(this.ctx.stage, this.ctx.stage.times, audit.order + 1, transaction);
+                await transaction.commit();
+            } catch(err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        /**
+         * 审批
+         * @param {Number} stageId - 标段id
+         * @param {auditConst.status.checked|auditConst.status.checkNo} checkType - 审批结果
+         * @param {Number} times - 第几次审批
+         * @returns {Promise<void>}
+         */
+        async check(stageId, checkData, times = 1) {
+            if (checkData.checkType !== auditConst.status.checked && checkData.checkType !== auditConst.status.checkNo && checkData.checkType !== auditConst.status.checkNoPre) {
+                throw '提交数据错误';
+            }
+            // // 整理当前流程审核人状态更新
+            // const audit = await this.getDataByCondition({sid: stageId, times: times, status: auditConst.status.checking});
+            // if (!audit) {
+            //     throw '审核数据错误';
+            // }
+            //const time = new Date();
+
+            switch (checkData.checkType) {
+                case auditConst.status.checked:
+                    await this._checked(stageId, checkData, times);
+                    break;
+                case auditConst.status.checkNo:
+                    await this._checkNo(stageId, checkData, times);
+                    break;
+                case auditConst.status.checkNoPre:
+                    await this._checkNoPre(stageId, checkData, times);
+                    break;
+                default:
+                    throw '无效审批操作';
+            }
+
+            // const transaction = await this.db.beginTransaction();
+            // try {
+            //     // 更新当前审核流程
+            //     await transaction.update(this.tableName, {id: audit.id, status: checkData.checkType, opinion: checkData.opinion, end_time: time});
+            //     if (checkData.checkType === auditConst.status.checked) { // 审批通过
+            //         const nextAudit = await this.getDataByCondition({sid: stageId, times: times, order: audit.order + 1});
+            //         // 无下一审核人表示,审核结束
+            //         if (nextAudit) {
+            //             // 计算该审批人最终数据
+            //             await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
+            //             // 复制一份下一审核人数据
+            //             await this.ctx.service.stagePay.copyAuditStagePays(this.ctx.stage, this.ctx.stage.times, nextAudit.order, transaction);
+            //             // 流程至下一审批人
+            //             await transaction.update(this.tableName, {id: nextAudit.id, status: auditConst.status.checking, begin_time: time});
+            //             // 同步 期信息
+            //             const tpData = await this.ctx.service.stageBills.getSumTotalPrice(this.ctx.stage);
+            //             await transaction.update(this.ctx.service.stage.tableName, {
+            //                 id: stageId, status: auditConst.status.checking,
+            //                 contract_tp: tpData.contract_tp,
+            //                 qc_tp: tpData.qc_tp,
+            //             });
+            //         } else {
+            //             // 本期结束
+            //             // 生成截止本期数据 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.stagePay.calcAllStagePays(this.ctx.stage, transaction);
+            //             // 同步 期信息
+            //             const tpData = await this.ctx.service.stageBills.getSumTotalPrice(this.ctx.stage);
+            //             await transaction.update(this.ctx.service.stage.tableName, {
+            //                 id: stageId, status: checkData.checkType,
+            //                 contract_tp: tpData.contract_tp,
+            //                 qc_tp: tpData.qc_tp,
+            //             });
+            //         }
+            //     } else if (checkData.checkType === auditConst.status.checkNo) { // 审批退回 原报, times+1
+            //         // 同步 期信息
+            //         const tpData = await this.ctx.service.stageBills.getSumTotalPrice(this.ctx.stage);
+            //         await transaction.update(this.ctx.service.stage.tableName, {
+            //             id: stageId, status: checkData.checkType,
+            //             contract_tp: tpData.contract_tp,
+            //             qc_tp: tpData.qc_tp,
+            //             times: times + 1,
+            //         });
+            //         // 拷贝新一次审核流程列表
+            //         // const auditors = await this.getAllDataByCondition({
+            //         //     where: {sid: stageId, times: times},
+            //         //     columns: ['tid', 'sid', 'aid', 'order']
+            //         // });
+            //         const sql = 'SELECT `tid`, `sid`, `aid`, `order` FROM ?? WHERE `sid` = ? and `times` = ? GROUP BY `aid`';
+            //         const sqlParam = [this.tableName, stageId, times];
+            //         const auditors = await this.db.query(sql, sqlParam);
+            //         let order = 1;
+            //         for (const a of auditors) {
+            //             a.times = times + 1;
+            //             a.order = order;
+            //             a.status = auditConst.status.uncheck;
+            //             order++;
+            //         }
+            //         await transaction.insert(this.tableName, auditors);
+            //         // 计算该审批人最终数据
+            //         await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
+            //         // 复制一份最新数据给原报
+            //         await this.ctx.service.stagePay.copyAuditStagePays(this.ctx.stage, this.ctx.stage.times + 1, 0, transaction);
+            //     } else if (checkData.checkType === auditConst.status.checkNoPre) { // 审批退回 上一审批人
+            //         // 同步 期信息
+            //         const tpData = await this.ctx.service.stageBills.getSumTotalPrice(this.ctx.stage);
+            //         await transaction.update(this.ctx.service.stage.tableName, {
+            //             id: stageId, status: checkData.checkType,
+            //             contract_tp: tpData.contract_tp,
+            //             qc_tp: tpData.qc_tp,
+            //         });
+            //         // 将当前审批人 与 上一审批人再次添加至流程,顺移其后审批人流程顺序
+            //         if (audit.order > 1) {
+            //             // 顺移气候审核人流程顺序
+            //             this.initSqlBuilder();
+            //             this.sqlBuilder.setAndWhere('sid', { value: this.ctx.stage.id, operate: '=', });
+            //             this.sqlBuilder.setAndWhere('order', { value: audit.order, operate: '>', });
+            //             this.sqlBuilder.setUpdateData('order', { value: 2, selfOperate: '+', });
+            //             const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'update');
+            //             const data = await transaction.query(sql, sqlParam);
+            //
+            //             // 上一审批人,当前审批人 再次添加至流程
+            //             const preAuditor = await this.getDataByCondition({sid: stageId, times: times, order: audit.order - 1});
+            //             const newAuditors = [];
+            //             newAuditors.push({
+            //                 tid: preAuditor.tid, sid: preAuditor.sid, aid: preAuditor.aid,
+            //                 times: preAuditor.times, order: preAuditor.order + 2, status: auditConst.status.checking,
+            //                 begin_time: time,
+            //             });
+            //             newAuditors.push({
+            //                 tid: audit.tid, sid: audit.sid, aid: audit.aid,
+            //                 times: audit.times, order: audit.order + 2, status: auditConst.status.uncheck
+            //             });
+            //             await transaction.insert(this.tableName, newAuditors);
+            //
+            //             // 计算该审批人最终数据
+            //             await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
+            //             // 复制一份最新数据给上一人
+            //             await this.ctx.service.stagePay.copyAuditStagePays(this.ctx.stage, this.ctx.stage.times, audit.order + 1, transaction);
+            //         } else {
+            //             throw '审核数据错误';
+            //         }
+            //     } else {
+            //         throw '无效审批操作';
+            //     }
+            //
+            //     await transaction.commit();
+            // } catch (err) {
+            //     await transaction.rollback();
+            //     throw err;
+            // }
+        }
+
         /**
          * 获取审核人需要审核的期列表
          *

+ 75 - 26
app/service/stage_bills.js

@@ -32,8 +32,15 @@ module.exports = app => {
          * @returns {Promise<*>}
          */
         async getLastestStageData(tid, sid, lid) {
-            const lidSql = lid ? ' And lid in (?)' : '';
-            const sql = 'SELECT * FROM ' + this.tableName + ' As Bills ' +
+            let lidSql = '', result;
+            if (lid) {
+                if (lid instanceof Array) {
+                    lidSql = lid.length > 0 ? ' And lid in (' + this.ctx.helper.getInArrStrSqlFilter(lid) + ')' : '';
+                } else {
+                    lidSql = ' And lid in (' + this.db.escape(lid) + ')';
+                }
+            }
+            const sql = 'SELECT Bills.* FROM ' + this.tableName + ' As Bills ' +
                         '  INNER JOIN ( ' +
                         '    SELECT MAX(`times` * ' + timesLen + ' + `order`) As `progress`, `lid`, `sid` From ' + this.tableName +
                         '      WHERE tid = ? And sid = ?' + lidSql +
@@ -44,10 +51,8 @@ module.exports = app => {
             if (!lid) {
                 return await this.db.query(sql, sqlParam);
             } else if (lid instanceof Array) {
-                sqlParam.push(lid.join(', '));
                 return await this.db.query(sql, sqlParam);
             } else {
-                sqlParam.push(lid);
                 return await this.db.queryOne(sql, sqlParam);
             }
         }
@@ -63,14 +68,14 @@ module.exports = app => {
          */
         async getAuditorStageData(tid, sid, times, order, lid) {
             const lidSql = lid ? ' And Bills.lid in (?)' : '';
-            const sql = 'SELECT * FROM ' + this.tableName + ' As Bills ' +
+            const sql = 'SELECT Bills.* FROM ' + this.tableName + ' As Bills ' +
                 '  INNER JOIN ( ' +
-                '    SELECT MAX(`times` * ' + timesLen + ' + `order`) As `progress`, `lid` From ' + this.tableName +
-                '      WHERE `times` < ? OR (`times` = ? AND `order` <= ?)' +
+                '    SELECT MAX(`times` * ' + timesLen + ' + `order`) As `progress`, `lid`, `tid`, `sid` From ' + this.tableName +
+                '      WHERE `times` < ? OR (`times` = ? AND `order` <= ?) And tid = ? And sid = ?' + lidSql +
                 '      GROUP BY `lid`' +
                 '  ) As MaxFilter ' +
                 '  ON (Bills.times * ' + timesLen + ' + `order`) = MaxFilter.progress And Bills.lid = MaxFilter.lid' +
-                '  WHERE Bills.tid = ? And Bills.sid = ?' + lidSql;
+                '    AND Bills.sid = MaxFilter.sid';
             const sqlParam = [times, times, order, tid, sid];
             if (!lid) {
                 return await this.db.query(sql, sqlParam);
@@ -83,6 +88,46 @@ module.exports = app => {
             }
         }
 
+        /**
+         * 获取截止本期数据
+         * @param {Number} tid - 标段id
+         * @param {Number} sorder - 截止期序号
+         * @param {String|Array[String]} lid - 台账id
+         * @returns {Promise<*>}
+         */
+        async getEndStageData(tid, sorder, lid) {
+            let lidSql = '';
+            if (lid) {
+                if (lid instanceof Array) {
+                    lidSql = lid.length > 0 ? ' And lid in (' + this.ctx.helper.getInArrStrSqlFilter(lid) + ')' : '';
+                } else {
+                    lidSql = ' And lid in (' + this.db.escape(lid) + ')';
+                }
+            }
+
+            const sql = 'SELECT Bills.tid, Bills.lid,' +
+                '  Sum(Bills.contract_qty) As contract_qty, Sum(Bills.contract_tp) As contract_tp,' +
+                '  Sum(Bills.qc_qty) As qc_qty, Sum(Bills.qc_tp) As qc_tp FROM ' + this.tableName + ' As Bills ' +
+                '  INNER JOIN ( ' +
+                '    SELECT MAX(`times` * ' + timesLen + ' + `order`) As `progress`, `lid`, `sid` From ' + this.tableName +
+                '      WHERE tid = ? ' + lidSql +
+                '      GROUP BY `lid`, `sid`' +
+                '  ) As MaxFilter ' +
+                '  ON (Bills.times * ' + timesLen + ' + `order`) = MaxFilter.progress And Bills.lid = MaxFilter.lid And Bills.`sid` = MaxFilter.`sid`' +
+                '  INNER JOIN ' + this.ctx.service.stage.tableName + ' As Stage' +
+                '  ON Bills.sid = Stage.id' +
+                '  WHERE Stage.order <= ?' +
+                '  GROUP BY `lid`';
+            const sqlParam = [tid, sorder];
+            if (!lid) {
+                return await this.db.query(sql, sqlParam);
+            } else if (lid instanceof Array) {
+                return await this.db.query(sql, sqlParam);
+            } else {
+                return await this.db.queryOne(sql, sqlParam);
+            }
+        }
+
         async getStageBills(tid, sid, lid) {
             const sql = 'SELECT Stage.*, Ledger.unit_price FROM ?? As Stage, ?? As Ledger ' +
                         '  Where Stage.tid = ?, Stage.sid = ?, Stage.lid = ?, Stage.lid = Ledger.id ' +
@@ -95,6 +140,7 @@ module.exports = app => {
         }
 
         async _insertStageBillsData(transaction, insertData, ledgerData) {
+            const info = this.ctx.tender.info;
             const d = {
                 tid: this.ctx.tender.id,
                 lid: ledgerData.id,
@@ -107,11 +153,11 @@ module.exports = app => {
             const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, ledgerData.unit);
             if (insertData.contract_qty) {
                 d.contract_qty = this.round(insertData.contract_qty, precision.value);
-                d.contract_tp = d.contract_qty * ledgerData.unit_price;
+                d.contract_tp = this.ctx.helper.mul(d.contract_qty, ledgerData.unit_price, info.decimal.tp);
             }
             if (insertData.qc_qty) {
                 d.qc_qty = this.round(insertData.qc_qty, precision.value);
-                d.qc_tp = d.qc_qty * ledgerData.unit_price;
+                d.qc_tp = this.ctx.helper.mul(d.qc_qty, ledgerData.unit_price, info.decimal.tp);
             }
             if (insertData.postil) {
                 d.postil = insertData.postil;
@@ -125,20 +171,21 @@ module.exports = app => {
          * @returns {Promise<void>}
          */
         async updateStageData(data) {
+            const info = this.ctx.tender.info;
             const datas = data instanceof Array ? data : [data];
             const transaction = await this.db.beginTransaction();
             try {
                 for (const d of datas) {
                     const stageBills = await this.getLastestStageData(this.ctx.tender.id, this.ctx.stage.id, d.lid);
                     const ledgerBills = await this.ctx.service.ledger.getDataById(d.lid);
-                    const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, ledgerBills.unit);
+                    const precision = this.ctx.helper.findPrecision(info.precision, ledgerBills.unit);
                     if (d.contract_qty !== undefined) {
                         d.contract_qty = this.round(d.contract_qty, precision.value);
-                        d.contract_tp = d.contract_qty * ledgerBills.unit_price;
+                        d.contract_tp = this.ctx.helper.mul(d.contract_qty, ledgerBills.unit_price, info.decimal.tp);
                     }
                     if (d.qc_qty !== undefined) {
                         d.qc_qty = this.round(d.qc_qty, precision.value);
-                        d.qc_tp = d.qc_qty * ledgerBills.unit_price;
+                        d.qc_tp = this.ctx.helper.mul(d.qc_qty, ledgerBills.unit_price, info.decimal.tp);
                     }
                     if (!stageBills) {
                         d.tid = this.ctx.tender.id;
@@ -170,15 +217,16 @@ module.exports = app => {
          * @private
          */
         async updateStageBillsQty(transaction, ledgerBills, stageBills, data) {
-            const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, ledgerBills.unit);
+            const info = this.ctx.tender.info;
+            const precision = this.ctx.helper.findPrecision(info.precision, ledgerBills.unit);
             const updateData = {};
-            if (data.contract_qty) {
+            if (data.contract_qty !== undefined) {
                 updateData.contract_qty = this.round(data.contract_qty, precision.value);
-                updateData.contract_tp = this.round(updateData.contract_qty * ledgerBills.unit_price);
+                updateData.contract_tp = this.ctx.helper.mul(updateData.contract_qty, ledgerBills.unit_price, info.decimal.tp);
             }
-            if (data.qc_qty) {
+            if (data.qc_qty !== undefined) {
                 updateData.qc_qty = this.round(data.qc_qty, precision.value);
-                updateData.qc_tp = this.round(updateData.qc_qty * ledgerBills.unit_price);
+                updateData.qc_tp = this.ctx.helper.mul(updateData.qc_qty, ledgerBills.unit_price, info.decimal.tp);
             }
             if (stageBills) {
                 if ((updateData.contract_qty === undefined || stageBills.contract_qty !== updateData.contract_qty) ||
@@ -203,6 +251,7 @@ module.exports = app => {
          * @returns {Promise<void>}
          */
         async calc(tid, sid, lid, transaction) {
+            const info = this.ctx.tender.info;
             const stageBills = await this.getLastestStageData(tid, sid, lid);
             const ledgerBills = await this.ctx.service.ledger.getDataById(lid);
             if (!ledgerBills) {
@@ -211,15 +260,15 @@ module.exports = app => {
             const posGather = await this.ctx.service.stagePos.getPosGather(tid, sid, lid, transaction);
             if (!posGather) { return; }
 
-            const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, ledgerBills.unit);
+            const precision = this.ctx.helper.findPrecision(info.precision, ledgerBills.unit);
             // 计算
             if (posGather.contract_qty !== undefined) {
                 posGather.contract_qty = this.round(posGather.contract_qty, precision.value);
-                posGather.contract_tp = this.round(posGather.contract_qty * ledgerBills.unit_price);
+                posGather.contract_tp = this.ctx.helper.mul(posGather.contract_qty, ledgerBills.unit_price, info.decimal.tp);
             }
             if (posGather.qc_qty !== undefined) {
                 posGather.qc_qty = this.round(posGather.qc_qty, precision.value);
-                posGather.qc_tp = this.round(posGather.qc_qty * ledgerBills.unit_price);
+                posGather.qc_tp = this.ctx.helper.mul(posGather.qc_qty, ledgerBills.unit_price, info.decimal.tp);
             }
             if (stageBills) {
                 if (stageBills.contract_qty === posGather.contract_qty && stageBills.qc_qty === posGather.qc_qty) {
@@ -253,19 +302,19 @@ 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 ' +
+            const sql = 'SELECT Sum(`contract_tp`) As `contract_tp`, Sum(`qc_tp`) As `qc_tp`' +
+                '  FROM ' + this.tableName + ' As Bills ' +
                 '  INNER JOIN ( ' +
                 '    SELECT MAX(`times` * ' + timesLen + ' + `order`) As `flow`, `lid` From ' + this.tableName +
                 '      WHERE `times` <= ? AND `order` <= ?' +
                 '      GROUP BY `lid`' +
                 '  ) As MaxFilter ' +
-                '  ON (Bills.times * ' + timesLen + ' + `order`) = MaxFilter.flow And Bills.lid = MaxFilter.lid, ' +
-                '  ' + this.ctx.service.ledger.tableName + ' As Ledger ) ' +
-                '  WHERE Bills.sid = ? AND Bills.lid = Ledger.id And Ledger.b_code ' + operate + ' ?';
+                '  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' +
+                '  WHERE Bills.sid = ? And Ledger.b_code ' + operate + ' ?';
             const sqlParam = [stage.times, stage.curAuditor ? stage.curAuditor.order : 0, stage.id, filter];
             const result = await this.db.queryOne(sql, sqlParam);
             return result;
-            //return this._.add(result.contract_tp, result.qc_tp);
         }
 
         async getSumTotalPriceGcl(stage, regText) {

+ 137 - 0
app/service/stage_bills_dgn.js

@@ -0,0 +1,137 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const validField = ['id', 'tid', 'deal_dgn_qty1', 'deal_dgn_qty2', 'c_dgn_qty1', 'c_dgn_qty2'];
+
+module.exports = app => {
+
+    class StageBillsDgn extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'stage_bills_dgn';
+        }
+
+        /**
+         * 获取截止本期数据
+         * @param {Number} tid - 标段id
+         * @returns {Promise<void>}
+         */
+        async getDgnData(tid) {
+            return await this.db.select(this.tableName, {
+                where: {tid: tid},
+                columns: ['id', 'deal_dgn_qty1', 'deal_dgn_qty2', 'c_dgn_qty1', 'c_dgn_qty2'],
+            });
+        }
+
+        /**
+         * 过滤无效字段,容错
+         * @param data
+         * @private
+         */
+        _filterInvalidField(data) {
+            for (const prop in data) {
+                if (validField.indexOf(prop) === -1) {
+                    delete data[prop];
+                }
+            }
+        }
+
+        /**
+         * 保存设计数量 - 提交单条数据
+         * @param data
+         * @returns {Promise<*>}
+         * @private
+         */
+        async _saveDgnData(data) {
+            if (!data.id) {
+                throw '数据错误';
+            }
+            const orgData = await this.getDataById(data.id);
+            this._filterInvalidField(data);
+            if (orgData) {
+                await this.db.update(this.tableName, data);
+            } else {
+                data.tid = this.ctx.tender.id;
+                await this.db.insert(this.tableName, data);
+            }
+            return await this.getDataById(data.id);
+        }
+
+        /**
+         * 保存设计数量 - 提交多条数据(事务)
+         * @param datas
+         * @returns {Promise<void>}
+         * @private
+         */
+        async _saveDgnDatas(datas) {
+            const orgDatas = await this.db.select(this.tableName, {
+                where: {
+                    id: this._.map(datas, 'id'),
+                }
+            });
+            const updateDatas = [], newDatas = [];
+            for(const d of datas) {
+                this._filterInvalidField(d);
+                const orgD = this._.find(orgDatas, {id: d.id});
+                if (orgD) {
+                    updateDatas.push(d);
+                } else {
+                    d.tid = this.ctx.tender.id;
+                    newDatas.push(d);
+                }
+            }
+            const transaction = await this.db.beginTransaction();
+            try {
+                if (newDatas.length > 0) {
+                    await transaction.insert(this.tableName, newDatas);
+                }
+                if (updateDatas.length > 0) {
+                    for (const u of updateDatas) {
+                        await transaction.update(this.tableName, u);
+                    }
+                }
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+            await this.db.select(this.tableName, {
+                where: {
+                    id: this._.map(datas, 'id')
+                }
+            });
+        }
+
+        /**
+         * 保存设计数量 - 外部调用
+         * @param data
+         * @returns {Promise<*>}
+         */
+        async saveDgnData(data) {
+            if (data instanceof Array) {
+                if (data.length > 1) {
+                    return await this._saveDgnDatas(data);
+                } else if (data.length === 1) {
+                    return await this._saveDgnData(data[0]);
+                }
+            } else {
+                return await this._saveDgnData(data);
+            }
+        }
+    }
+
+    return StageBillsDgn;
+};

+ 22 - 19
app/service/stage_bills_final.js

@@ -36,8 +36,7 @@ module.exports = app => {
                 '      WHERE tid = ? AND sorder < ?' +
                 '      GROUP BY `lid`' +
                 '  ) As MaxFilter ' +
-                '  ON Bills.sorder = MaxFilter.sorder And Bills.lid = MaxFilter.lid' +
-                '  WHERE Bills.tid = ?';
+                '  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);
         }
@@ -73,22 +72,26 @@ module.exports = app => {
                 throw '数据错误';
             }
             if (stage.order > 1) {
-                const sql = 'Insert Into ??(tid, sid, lid, sorder, contract_qty, contract_tp, qc_qty, qc_tp)' +
-                    '  SELECT cur.tid, cur.sid, cur.lid, ? As `sorder`, ' +
-                    '      IFNULL(cur.contract_qty, 0) + IFNULL(pre.contract_qty, 0), IFNULL(cur.contract_tp, 0) + IFNULL(pre.contract_tp, 0),' +
-                    '      IFNULL(cur.qc_qty, 0) + IFNULL(pre.qc_qty, 0), IFNULL(cur.qc_tp, 0) + IFNULL(pre.qc_tp, 0)' +
-                    '  FROM (SELECT b.tid, b.sid, b.lid, b.contract_qty, b.contract_tp, b.qc_qty, b.qc_tp' +
-                    '    FROM ' + this.ctx.service.stageBills.tableName + ' AS b' +
-                    '    INNER JOIN(' +
-                    '      SELECT Max(`times` * ' + timesLen + ' + `order`) As `flow`, `lid` FROM ' + this.ctx.service.stageBills.tableName +
-                    '      WHERE `sid` = ?' +
-                    '      GROUP By `lid`) As MF' +
-                    '    ON (b.times * ' + timesLen + ' + b.order) = MF.flow AND b.lid = MF.lid' +
-                    '    WHERE b.sid = ?) As cur' +
-                    '  LEFT JOIN ?? As pre' +
-                    '  ON cur.lid = pre.lid And pre.sorder = ?';
-                const sqlParam = [this.tableName, stage.order, stage.id, stage.id, this.tableName, stage.order - 1];
-                await transaction.query(sql, sqlParam);
+                const cur = await this.ctx.service.stageBills.getLastestStageData(tender.id, stage.id);
+                if (!cur || cur.length === 0) return;
+                const pre = await this.getFinalData(tender, stage);
+                for (const c of cur) {
+                    delete c.id;
+                    delete c.said;
+                    delete c.times;
+                    delete c.order;
+                    delete c.postil;
+                    c.sorder = stage.order;
+                    const p = this.ctx.helper._.find(pre, function (x) {
+                        return x.lid === c.lid;
+                    });
+                    if (!p) continue;
+                    c.contract_qty = this.ctx.helper.add(c.contract_qty, p.contract_qty);
+                    c.contract_tp = this.ctx.helper.add(c.contract_tp, p.contract_tp);
+                    c.qc_qty = this.ctx.helper.add(c.qc_qty, p.qc_qty);
+                    c.qc_tp = this.ctx.helper.add(c.qc_tp, p.qc_tp);
+                }
+                await transaction.insert(this.tableName, cur);
             } else {
                 const sql = 'Insert Into ??(tid, sid, lid, sorder, contract_qty, contract_tp, qc_qty, qc_tp)' +
                     '  SELECT b.tid, b.sid, b.lid, ? As `sorder`, b.contract_qty, b.contract_tp, b.qc_qty, b.qc_tp' +
@@ -106,4 +109,4 @@ module.exports = app => {
     }
 
     return StageBillsFinal;
-}
+};

+ 10 - 10
app/service/stage_change.js

@@ -105,7 +105,7 @@ module.exports = app => {
                 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);
-                    billsQty = this.ctx.helper.plus(billsQty, change.qty);
+                    billsQty = this.ctx.helper.add(billsQty, change.qty);
                     if (oc.stimes === this.ctx.stage.curTimes && oc.sorder === this.ctx.stage.curOrder) {
                         change.id = oc.id;
                         updateChanges.push(change);
@@ -113,14 +113,14 @@ module.exports = app => {
                         newChanges.push(change);
                     }
                 } else {
-                    billsQty = this.ctx.helper.plus(billsQty, oc.qty);
+                    billsQty = this.ctx.helper.add(billsQty, oc.qty);
                 }
             }
             for (const c of changes) {
                 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.plus(billsQty, change.qty);
+                    billsQty = this.ctx.helper.add(billsQty, change.qty);
                     newChanges.push(change);
                 }
             }
@@ -141,7 +141,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: result };
+            return { bills: {curStageData: result} };
         }
 
         /**
@@ -180,7 +180,7 @@ module.exports = app => {
                 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);
-                    posQty = this.ctx.helper.plus(posQty, change.qty);
+                    posQty = this.ctx.helper.add(posQty, change.qty);
                     if (oc.stimes === this.ctx.stage.curTimes && oc.sorder === this.ctx.stage.curOrder) {
                         change.id = oc.id;
                         updateChanges.push(change);
@@ -188,14 +188,14 @@ module.exports = app => {
                         newChanges.push(change);
                     }
                 } else {
-                    posQty = this.ctx.helper.plus(posQty, oc.qty);
+                    posQty = this.ctx.helper.add(posQty, oc.qty);
                 }
             }
             for (const c of changes) {
                 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.plus(posQty, change.qty);
+                    posQty = this.ctx.helper.add(posQty, change.qty);
                     newChanges.push(change);
                 }
             }
@@ -217,9 +217,9 @@ module.exports = app => {
 
             // 获取返回数据
             try {
-                const data = {};
-                data.bills = await this.ctx.service.stageBills.getLastestStageData(this.ctx.tender.id, this.ctx.stage.id, [pos.lid]);
-                data.pos = await this.ctx.service.stagePos.getLastestStageData(this.ctx.tender.id, this.ctx.stage.id, [pos.id]);
+                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.getLastestStageData(this.ctx.tender.id, this.ctx.stage.id, {pid: pos.id});
                 return data;
             } catch(err) {
                 throw '获取数据错误,请刷新页面';

+ 71 - 12
app/service/stage_detail.js

@@ -86,20 +86,38 @@ module.exports = app => {
          * @param {Number} tid - 标段id
          * @param {Number} sid - 期id
          * @param {Number} lid - 关联的台账节点id
-         * @param {String} uuid - 中间计量单 唯一id
+         * @param {String|Array[String]} uuid - 中间计量单 唯一id
          * @returns {Promise<*>}
          */
         async getLastestImStageData(tid, sid, lid, uuid) {
-            const sql = 'SELECT * FROM ' + this.tableName + ' As Bills ' +
-                '  INNER JOIN ( ' +
-                '    SELECT MAX(`times` * ' + timesLen + ' + `order`) As `flow`, `lid`, `uuid` From ' + this.tableName +
-                '      WHERE lid = ? And uuid = ?' +
-                '      GROUP BY `lid`, `uuid`' +
-                '  ) As MaxFilter ' +
-                '  ON (Bills.times * ' + timesLen + ' + Bills.order) = MaxFilter.flow And Bills.lid = MaxFilter.lid And Bills.uuid = MaxFilter.uuid' +
-                '  WHERE Bills.tid = ? And Bills.sid = ?';
-            const sqlParam = [lid, uuid, tid, sid];
-            return await this.db.queryOne(sql, sqlParam);
+            if (uuid instanceof Array) {
+                if (uuid.length === 0) { return []; }
+                let uuidSql = '';
+                for (const u of uuid) {
+                    uuidSql = uuidSql === '' ? this.db.escape(u) : uuidSql + ',' + this.db.escape(u);
+                }
+                const sql = 'SELECT * FROM ' + this.tableName + ' As Bills ' +
+                    '  INNER JOIN ( ' +
+                    '    SELECT MAX(`times` * ' + timesLen + ' + `order`) As `flow`, `lid`, `uuid` From ' + this.tableName +
+                    '      WHERE lid = ? And uuid In (' + uuidSql + ')' +
+                    '      GROUP BY `lid`, `uuid`' +
+                    '  ) As MaxFilter ' +
+                    '  ON (Bills.times * ' + timesLen + ' + Bills.order) = MaxFilter.flow And Bills.lid = MaxFilter.lid And Bills.uuid = MaxFilter.uuid' +
+                    '  WHERE Bills.tid = ? And Bills.sid = ?';
+                const sqlParam = [lid, tid, sid];
+                return await this.db.query(sql, sqlParam);
+            } else {
+                const sql = 'SELECT Bills.* FROM ' + this.tableName + ' As Bills ' +
+                    '  INNER JOIN ( ' +
+                    '    SELECT MAX(`times` * ' + timesLen + ' + `order`) As `flow`, `lid`, `uuid` From ' + this.tableName +
+                    '      WHERE lid = ? And uuid = ?' +
+                    '      GROUP BY `lid`, `uuid`' +
+                    '  ) As MaxFilter ' +
+                    '  ON (Bills.times * ' + timesLen + ' + Bills.order) = MaxFilter.flow And Bills.lid = MaxFilter.lid And Bills.uuid = MaxFilter.uuid' +
+                    '  WHERE Bills.tid = ? And Bills.sid = ?';
+                const sqlParam = [lid, uuid, tid, sid];
+                return await this.db.queryOne(sql, sqlParam);
+            }
         }
 
         /**
@@ -112,6 +130,7 @@ module.exports = app => {
                 const org = await this.getLastestImStageData(this.ctx.tender.id, this.ctx.stage.id, data.lid, data.uuid);
                 const order = this.ctx.stage.curAuditor ? this.ctx.stage.curAuditor.order : 0;
                 if (org.times === this.ctx.stage.times && org.order === order) {
+                    delete org.flow;
                     const newData = this._.assign(org, data);
                     await this.db.update(this.tableName, newData);
                     return newData;
@@ -134,7 +153,47 @@ module.exports = app => {
                 return data;
             }
         }
+
+        async saveDetailDatas(data) {
+            if (!data instanceof Array) {
+                throw '数据错误';
+            }
+            const transaction = await this.db.beginTransaction();
+            const result = [];
+            try {
+                for (const d of data) {
+                    if (d.uuid) {
+                        const od = await this.getLastestImStageData(this.ctx.tender.id, this.ctx.stage.id, d.lid, d.uuid);
+                        delete od.flow;
+                        if (od.times === this.ctx.stage.curTimes && od.order === this.ctx.stage.curOrder) {
+                            d.id = od.id;
+                            await transaction.update(this.tableName, d);
+                            result.push(d);
+                        } else {
+                            const nd = this._.assign(od, data);
+                            nd.times = this.ctx.stage.curTimes;
+                            nd.order = this.ctx.stage.curOrder;
+                            await transaction.insert(this.tableName, nd);
+                            result.push(nd);
+                        }
+                    } else {
+                        d.uuid = this.uuid.v4();
+                        d.tid = this.ctx.tender.id;
+                        d.sid = this.ctx.stage.id;
+                        d.times = this.ctx.stage.curTimes;
+                        d.order = this.ctx.stage.curOrder;
+                        await transaction.insert(this.tableName, d);
+                        result.push(d);
+                    }
+                }
+                await transaction.commit();
+                return result;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
     }
 
     return StageDetail;
-};
+};

+ 81 - 40
app/service/stage_pay.js

@@ -34,12 +34,17 @@ module.exports = app => {
          * @returns {Promise<*>}
          */
         async getAuditorStagePay(pid, sid, times, order) {
-            const sql = 'SELECT SP.*, P.`csorder`, P.`cstimes`, P.`csaorder`, P.`order`, P.uid, P.name, P.minus, P.ptype, P.sprice, P.sexpr, P.rprice, P.rexpr, P.is_yf, P.dl_type, P.dl_count, P.dl_tp_type, P.dl_tp ' +
+            const pidSql = pid instanceof Array ? ' And SP.`pid` in (' + pid.join(',') + ')' : 'And SP.`pid` = ' + pid;
+            const sql = 'SELECT SP.*, P.`csorder`, P.`cstimes`, P.`csaorder`, P.`order`, P.uid, P.minus, P.ptype, P.sprice, P.sexpr, P.rprice, P.rexpr, P.is_yf, P.dl_type, P.dl_count, P.dl_tp_type, P.dl_tp ' +
                 '  FROM ?? As SP, ?? As P ' +
-                '  WHERE SP.`sid` = ? AND SP.`stimes` = ? AND SP.`sorder` = ? AND SP.`pid` = P.`id` AND SP.`pid` = ? AND P.`valid`' +
+                '  WHERE SP.`sid` = ? AND SP.`stimes` = ? AND SP.`sorder` = ? AND SP.`pid` = P.`id` AND P.`valid`' + pidSql +
                 '  ORDER BY P.`order`';
             const sqlParam = [this.tableName, this.ctx.service.pay.tableName, sid, times, order, pid];
-            return await this.db.queryOne(sql, sqlParam);
+            if (pid instanceof Array) {
+                return await this.db.query(sql, sqlParam);
+            } else {
+                return await this.db.queryOne(sql, sqlParam);
+            }
         }
 
         /**
@@ -52,7 +57,7 @@ module.exports = app => {
          */
         async getAuditorStageData(sid, times, order) {
             const sql = 'SELECT SP.*,' +
-                '    P.`csorder`, P.`cstimes`, P.`csaorder`, P.`order`, P.uid, P.name, P.minus, P.ptype, P.sprice, P.sexpr, P.rprice, P.rexpr, P.is_yf, P.dl_type, P.dl_count, P.dl_tp_type, P.dl_tp ' +
+                '    P.`csorder`, P.`cstimes`, P.`csaorder`, P.`order`, P.uid, P.minus, P.ptype, P.sprice, P.sexpr, P.rprice, P.rexpr, P.is_yf, P.dl_type, P.dl_count, P.dl_tp_type, P.dl_tp ' +
                 '  FROM ?? As SP, ?? As P ' +
                 '  WHERE SP.`sid` = ? AND SP.`stimes` = ? AND SP.`sorder` = ? AND SP.`pid` = P.`id` AND P.`valid`' +
                 '  ORDER BY P.`order`';
@@ -83,7 +88,7 @@ module.exports = app => {
             for (const p of pays) {
                 stagePays.push({
                     tid: p.tid, sid: stage.id, pid: p.id,
-                    stimes: stage.times, sorder: 0, expr: p.expr,
+                    stimes: stage.times, sorder: 0, expr: p.expr, name: p.name,
                 });
             }
             // 获取截止上期数据
@@ -95,7 +100,10 @@ module.exports = app => {
                 const prePays = await this.getStageLastestPays(preStage.id);
                 for (const pp of prePays) {
                     const sp = this._.find(stagePays, {pid: pp.pid});
+                    sp.name = pp.name;
+                    sp.expr = pp.expr;
                     sp.pre_tp= pp.end_tp;
+                    sp.pause = pp.pause;
                 }
             }
             let result;
@@ -128,16 +136,17 @@ module.exports = app => {
          * @returns {Promise<*>}
          */
         async getStageLastestPays(sid) {
-            const sql = 'SELECT SP.*, P.`order`, P.uid, P.name, P.minus, P.ptype, P.sprice, P.sexpr, P.rprice, P.rexpr, P.is_yf, P.dl_type, P.dl_count, P.dl_tp_type, P.dl_tp ' +
-                '  FROM ?? As SP, ?? As P, ( ' +
-                '    SELECT MAX(`times` * ' + timesLen + ' + `order`) As `sprogress` ' +
-                '      FROM ?? ' +
+            const sql = 'SELECT SP.*, P.`order`, P.uid, P.minus, P.ptype, P.sprice, P.sexpr, P.rprice, P.rexpr, P.is_yf, P.dl_type, P.dl_count, P.dl_tp_type, P.dl_tp ' +
+                '  FROM ' + this.tableName + ' As SP' +
+                '  INNER JOIN ' + this.ctx.service.pay.tableName + ' As P ON SP.pid = P.id' +
+                '  INNER JOIN ( ' +
+                '    SELECT MAX(`stimes` * ' + timesLen + ' + `sorder`) As `sprogress` ' +
+                '      FROM ' + this.tableName +
                 '      WHERE `sid` = ? ' +
-                '      GROUP BY `sid`) As M' +
-                '  WHERE SP.`sid` = ? AND (SP.`stimes` * ' + timesLen + ' + `order`) = M.`sprogress` AND SP.`pid` = P.`id` AND P.`valid` = true' +
+                '      GROUP BY `sid`) As M ON (SP.`stimes` * ' + timesLen + ' + SP.`sorder`) = M.`sprogress`' +
+                '  WHERE SP.`sid` = ? AND P.`valid` = true' +
                 '  ORDER BY P.`order`';
-            const sqlParam = [this.tableName, this.ctx.service.pay.tableName, this.ctx.service.stageAudit.tableName,
-                sid, sid];
+            const sqlParam = [sid, sid];
             return await this.db.query(sql, sqlParam);
         }
 
@@ -168,32 +177,23 @@ module.exports = app => {
          * @param data
          * @returns {Promise<void>}
          */
-        async save(data) {
-            const transaction = await this.db.beginTransaction();
-            try {
-                // 更新数据
-                const stagePay = await this.getStagePay(this.ctx.stage, data.pid);
-                const updateData = { id: stagePay.id };
+        async save(saveData) {
+            const datas = saveData instanceof Array ? saveData : [saveData], updateDatas = [];
+            const stagePays = await this.getStagePay(this.ctx.stage, this._.map(datas, 'pid'));
+            for (const data of datas) {
+                const stagePay = stagePays.find(function (x) {
+                    return x.pid === data.pid;
+                });
+                const updateData = {id: stagePay.id};
+                if (data.name !== undefined) { updateData.name = data.name }
                 if (data.expr !== undefined) { updateData.expr = data.expr }
                 if (data.tp !== undefined) { updateData.tp = data.tp }
                 if (data.pause !== undefined) { updateData.pause = data.pause }
-                const result = await transaction.update(this.tableName, updateData);
-                if (result.affectedRows !== 1) {
-                    throw '保存数据失败';
-                }
-                // 缓存至pay
-                if (data.expr !== undefined) {
-                    const pr = await transaction.update(this.ctx.service.pay.tableName, {
-                        id: data.pid, expr: data.expr
-                    });
-                    if (pr.affectedRows !== 1) {
-                        throw '保存数据失败';
-                    }
-                }
-                await transaction.commit();
-            } catch(err) {
-                await transaction.rollback();
-                throw err;
+                updateDatas.push(updateData);
+            }
+            const result = await this.db.updateRows(this.tableName, updateDatas);
+            if (result.affectedRows !== updateDatas.length) {
+                throw '保存数据失败';
             }
         }
 
@@ -211,12 +211,24 @@ module.exports = app => {
             const PayCalculator = require('../lib/pay_calc');
             const payCalculator = new PayCalculator(this.ctx, this.ctx.tender.info.decimal);
             await payCalculator.calculateAll(stagePays);
+            const srUpdate = [], update = [];
             for (const sp of stagePays) {
-                await transaction.update(this.tableName, {
+                update.push({
                     id: sp.id,
                     tp: sp.tp,
-                    end_tp: sp.end_tp
+                    end_tp: sp.end_tp,
                 });
+                if (stage.order === 1 || sp.csorder >= stage.order) {
+                    srUpdate.push({
+                        id: sp.pid,
+                        sprice: sp.sprice,
+                        rprice: sp.rprice,
+                    });
+                }
+            }
+            await transaction.updateRows(this.tableName, update);
+            if (srUpdate.length > 0) {
+                await transaction.updateRows(this.ctx.service.pay.tableName, srUpdate);
             }
         }
 
@@ -232,14 +244,43 @@ module.exports = app => {
             if (!stage || !transaction || !times || order === undefined) {
                 throw '数据错误';
             }
-            const sql = 'INSERT INTO ?? (`tid`, `sid`, `pid`, `stimes`, `sorder`, `tp`, `expr`, `pause`, `attachment`, `pre_tp`) ' +
-                        '  SELECT SP.`tid`, SP.`sid`, SP.`pid`, ?, ?, SP.`tp`, SP.`expr`, SP.`pause`, SP.`attachment`, SP.`pre_tp` ' +
+            const sql = 'INSERT INTO ?? (`tid`, `sid`, `pid`, `stimes`, `sorder`, `name`, `tp`, `expr`, `pause`, `attachment`, `pre_tp`) ' +
+                        '  SELECT SP.`tid`, SP.`sid`, SP.`pid`, ?, ?, SP.name, SP.`tp`, SP.`expr`, SP.`pause`, SP.`attachment`, SP.`pre_tp` ' +
                         '  FROM ?? As SP, ?? As P ' +
                         '  WHERE SP.`sid` = ? AND SP.`stimes` = ? AND SP.`sorder` = ? And SP.`pid` = P.`id` And P.`valid`';
             const sqlParam = [this.tableName, times, order, this.tableName, this.ctx.service.pay.tableName,
                 stage.id, stage.curTimes, stage.curOrder];
             return await transaction.query(sql, sqlParam);
         }
+
+        /**
+         * 保存附件
+         * @param data
+         * @returns {Promise<void>}
+         */
+        async saveAtt(id, att) {
+            // 获取attachment
+            const info = await this.getDataById(id);
+            let attachment = null;
+            if (info.attachment !== null && JSON.parse(info.attachment).length !== 0) {
+                attachment = JSON.parse(info.attachment);
+                attachment.unshift(att);
+            } else {
+                attachment = new Array(att);
+            }
+            const result = await this.db.update(this.tableName, { id, attachment: JSON.stringify(attachment) });
+            return result.affectedRows === 1;
+        }
+
+        /**
+         * 删除附件
+         * @param data
+         * @returns {Promise<void>}
+         */
+        async deleteAtt(id, attachment) {
+            const result = await this.db.update(this.tableName, { id, attachment: JSON.stringify(attachment) });
+            return result.affectedRows === 1;
+        }
     }
 
     return StagePay;

+ 218 - 154
app/service/stage_pos.js

@@ -25,6 +25,26 @@ module.exports = app => {
             this.qtyFields = ['contract_qty', 'qc_qty']
         }
 
+        _getPosFilterSql(where, asTable = '') {
+            let whereSql = '';
+            if (!where) return whereSql;
+            if (where.pid) {
+                if (where.pid instanceof Array) {
+                    whereSql += ' And ' + asTable + 'pid in ('  + this.ctx.helper.getInArrStrSqlFilter(where.pid) + ')';
+                } else if (typeof where.pid === "string") {
+                    whereSql += ' And ' + asTable + 'pid = ' + this.db.escape(where.pid);
+                }
+            }
+            if (where.lid) {
+                if (where.lid instanceof Array) {
+                    whereSql += ' And ' + asTable + 'lid in ('  + this.ctx.helper.getInArrStrSqlFilter(where.lid) + ')';
+                } else if (typeof where.pid === "string") {
+                    whereSql += ' And ' + asTable + 'lid = ' + this.db.escape(where.lid);
+                }
+            }
+            return whereSql;
+        }
+
         /**
          * 查询期计量最后审核人数据
          * @param {Number} tid - 标段id
@@ -32,31 +52,18 @@ module.exports = app => {
          * @param {Number|Array} pid - 部位明细id(可以为空)
          * @returns {Promise<*>}
          */
-        async getLastestStageData(tid, sid, pid) {
-            let pidSql = '';
-            if (pid) {
-                if (pid instanceof Array) {
-                    pidSql = pid.length > 0 ? (' And pid in (' + pid.join(', ') + ')') : '';
-                } else {
-                    pidSql = (pid instanceof String || pid instanceof Number) ? ' And pid = ' + pid : '';
-                }
-            }
-            const sql = 'SELECT * FROM ' + this.tableName + ' As Pos ' +
+        async getLastestStageData(tid, sid, where) {
+            const filterSql = this._getPosFilterSql(where);
+            const sql = 'SELECT Pos.id, Pos.tid, Pos.sid, Pos.lid, Pos.pid, Pos.contract_qty, Pos.qc_qty, Pos.postil, Pos.times, Pos.order FROM ' +
+                '  (SELECT * FROM ' + this.tableName + ' WHERE tid = ? And sid = ?) As Pos ' +
                 '  INNER JOIN ( ' +
                 '    SELECT MAX(`times` * ' + timesLen + ' + `order`) As `flow`, `tid`, `sid`, `pid` From ' + this.tableName +
-                '      WHERE `tid` = ? And sid = ?' + pidSql +
+                '      WHERE `tid` = ? And sid = ?' + filterSql +
                 '      GROUP BY `pid`' +
                 '  ) As MaxFilter ' +
-                '  ON (Pos.times * ' + timesLen + ' + Pos.order) = MaxFilter.flow And Pos.pid = MaxFilter.pid' +
-                '    And Pos.`tid` = MaxFilter.`tid` And Pos.`sid` = MaxFilter.`sid`';
-            const sqlParam = [tid, sid];
-            if (!pid) {
-                return await this.db.query(sql, sqlParam);
-            } else if (pid instanceof Array) {
-                return await this.db.query(sql, sqlParam);
-            } else {
-                return await this.db.queryOne(sql, sqlParam);
-            }
+                '  ON (Pos.times * ' + timesLen + ' + Pos.order) = MaxFilter.flow And Pos.pid = MaxFilter.pid And Pos.sid = MaxFilter.sid';
+            const sqlParam = [tid, sid, tid, sid];
+            return await this.db.query(sql, sqlParam);
         }
         /**
          * 查询 某期 某轮审批 某审核人数据
@@ -67,29 +74,18 @@ module.exports = app => {
          * @param {Number|Array|Null} pid - 部位明细id - 为空则查询全部
          * @returns {Promise<*>}
          */
-        async getAuditorStageData(tid, sid, times, order, pid) {
-            let pidSql;
-            if (pid instanceof Array) {
-                pidSql = pid.length > 0 ? ' And Pos.pid in (' + pid.join(', ') + ')' : '';
-            } else {
-                pidSql = pid ? 'And Pos.pid = ' + pid.toString() : '';
-            }
-            const sql = 'SELECT * FROM ' + this.tableName + ' As Pos ' +
+        async getAuditorStageData(tid, sid, times, order, where) {
+            const filterSql = this._getPosFilterSql(where);
+            const sql = 'SELECT Pos.id, Pos.tid, Pos.sid, Pos.pid, Pos.lid, Pos.contract_qty, Pos.qc_qty, Pos.postil, Pos.times, Pos.order FROM ' +
+                '  (SELECT * FROM '+ this.tableName + ' WHERE tid = ? And sid = ?) As Pos ' +
                 '  INNER JOIN ( ' +
-                '    SELECT MAX(`times` * ' + timesLen + ' + `order`) As `flow`, `pid` From ' + this.tableName +
-                '      WHERE `times` < ? OR (`times` = ? AND `order` <= ?)' +
+                '    SELECT MAX(`times` * ' + timesLen + ' + `order`) As `flow`, `pid`, `sid` From ' + this.tableName +
+                '      WHERE `times` < ? OR (`times` = ? AND `order` <= ?) And tid = ? And sid = ?' + filterSql +
                 '      GROUP BY `pid`' +
                 '  ) As MaxFilter ' +
-                '  ON (Pos.times * ' + timesLen + ' + Pos.order) = MaxFilter.flow And Pos.pid = MaxFilter.pid' +
-                '  WHERE Pos.tid = ? And Pos.sid = ?' + pidSql;
-            const sqlParam = [times, times, order, tid, sid];
-            if (!pid) {
-                return await this.db.query(sql, sqlParam);
-            } else if (pid instanceof Array) {
-                return await this.db.query(sql, sqlParam);
-            } else {
-                return await this.db.queryOne(sql, sqlParam);
-            }
+                '  ON (Pos.times * ' + timesLen + ' + Pos.order) = MaxFilter.flow And Pos.pid = MaxFilter.pid And Pos.sid = MaxFilter.sid';
+            const sqlParam = [tid, sid, times, times, order, tid, sid];
+            return await this.db.query(sql, sqlParam);
         }
 
         /**
@@ -100,49 +96,71 @@ module.exports = app => {
          * @returns {Promise<{}>}
          * @private
          */
-        async _addStagePosData(transaction, data) {
-            const bills = await this.ctx.service.ledger.getDataById(data.lid);
-            const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, bills.unit);
-            const  result = {};
-            // 在主表pos中新增数据
-            const p = JSON.parse(JSON.stringify(data.updateData));
-            p.tid = this.ctx.tender.id;
-            p.add_stage = this.ctx.stage.id;
-            p.add_times = this.ctx.stage.curTimes;
-            p.add_user = this.ctx.session.sessionUser.accountId;
-            if (p.contract_qty) { delete p.contract_qty; }
-            if (p.qc_qty) { delete p.qc_qty; }
-            if (p.postil) { delete p.postil; }
-            if (p.quantity) {
-                p.quantity = this.round(p.quantity, precision.value);
-            }
-            const addRst = await transaction.insert(this.ctx.service.pos.tableName, data.updateData);
-            p.id = addRst.insertId;
-            result.pos = p.id;
-            // 如果存在复核数据,更新计算主表清单
-            if (p.quantity) {
-                await this.ctx.service.ledger.calc(this.ctx.tender.id, p.lid, transaction);
-                result.ledger = p.lid;
-            }
-            // 如果存在本期计算数据,更新计算清单本期计量数据
-            if (data.contract_qty || data.qc_qty || data.postil) {
-                const ps = {
-                    pid: p.id,
-                    lid: p.lid,
-                    tid: this.ctx.tender.id,
-                    sid: this.ctx.stage.id,
-                    said: this.ctx.session.sessionUser.accountId,
-                    times: this.ctx.stage.curTimes,
-                    order: this.ctx.stage.curOrder,
-                };
-                if (data.contract_qty) { ps.contract_qty = this.round(data.contract_qty, precision.value); }
-                if (data.qc_qty) { ps.qc_qty = this.round(data.qc_qty, precision.value); }
-                if (data.postil) { ps.postil = data.postil; }
-                await transaction.insert(ps);
-                await this.ctx.service.stageBills.calc(ctx.tender.id, ctx.stage.id, ps.lid, transaction);
-                result.stageUpdate = true;
+        async _addStagePosData(data) {
+            let bills , precision;
+            const  result = {pos: [], ledger: []};
+            const datas = data instanceof Array ? data : [data], calcBills = [], calcStageBills = [];
+
+            const transaction = await this.db.beginTransaction();
+            try {
+                for (const d of datas) {
+                    if (d.sgfh_qty !== undefined || d.sjcl_qty !== undefined || d.qtcl_qty !== undefined) {
+                        if (!bills || bills.id !== data.lid) {
+                            bills = await this.ctx.service.ledger.getDataById(d.lid);
+                            precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, bills.unit);
+                        }
+                    }
+                    // 在主表pos中新增数据
+                    const p = {
+                        id: this.uuid.v4(), tid: this.ctx.tender.id, lid: d.lid, name: d.name, porder: d.porder,
+                        add_stage: this.ctx.stage.id,
+                        add_times: this.ctx.stage.curTimes,
+                        add_user: this.ctx.session.sessionUser.accountId,
+                    };
+                    if (d.sgfh_qty) p.sgfh_qty = this.round(d.sgfh_qty, precision.value);
+                    if (d.sjcl_qty) p.sjcl_qty = this.round(d.sjcl_qty, precision.value);
+                    if (d.qtcl_qty) p.qtcl_qty = this.round(d.qtcl_qty, precision.value);
+                    p.quantity = this.ctx.helper.sum([p.sgfh_qty, p.sjcl_qty, p.qtcl_qty]);
+                    const addRst = await transaction.insert(this.ctx.service.pos.tableName, p);
+                    result.pos.push(p.id);
+                    // 如果存在复核数据,更新计算主表清单
+                    if (p.sgfh_qty || p.sjcl_qty || p.qtcl_qty) {
+                        calcBills.push(p.lid);
+                        result.ledger.push(p.lid);
+                    }
+                    // 如果存在本期计算数据,更新计算清单本期计量数据
+                    if (d.contract_qty || d.qc_qty || d.postil) {
+                        const ps = {
+                            pid: d.id,
+                            lid: d.lid,
+                            tid: this.ctx.tender.id,
+                            sid: this.ctx.stage.id,
+                            said: this.ctx.session.sessionUser.accountId,
+                            times: this.ctx.stage.curTimes,
+                            order: this.ctx.stage.curOrder,
+                        };
+                        if (d.contract_qty) ps.contract_qty = this.round(d.contract_qty, precision.value);
+                        if (d.qc_qty) ps.qc_qty = this.round(d.qc_qty, precision.value);
+                        if (d.postil) ps.postil = d.postil;
+                        await transaction.insert(ps);
+                        if (d.contract_qty || d.qc_qty) {
+                            calcStageBills.push(ps.lid);
+                        }
+                        result.stageUpdate = true;
+                    }
+                }
+                for (const lid of calcBills) {
+                    await this.ctx.service.ledger.calc(this.ctx.tender.id, lid, transaction);
+                }
+                for (const lid of calcStageBills) {
+                    await this.ctx.service.stageBills.calc(ctx.tender.id, ctx.stage.id, lid, transaction);
+                }
+                await transaction.commit();
+                return result;
+            } catch(err) {
+                await transaction.rollback();
+                throw err;
             }
-            return result;
         }
 
         /**
@@ -153,39 +171,75 @@ module.exports = app => {
          * @returns {Promise<{ledger: Array, pos: Array}>}
          * @private
          */
-        async _updateStagePosData(transaction, data) {
+        async _updateStagePosData(data) {
             let bills, precision;
-            const result = {ledger: [], pos: [], stageUpdate: true};
+            const result = {ledger: [], pos: [], stageUpdate: true}, ledgerCalc = [];
             const datas = data instanceof Array ? data : [data];
-            const orgStagePos = await this.getLastestStageData(this.ctx.tender.id, this.ctx.stage.id, this._.map(datas, 'pid'));
-            for (const d of datas) {
-                const osp = this._.find(orgStagePos, function (p) { return p.pid === d.pid; });
-                if (d.contract_qty || d.qc_qty) {
-                    if (!bills || bills.id !== data.lid) {
-                        bills = await this.ctx.service.ledger.getDataById(d.lid);
-                        precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, bills.unit);
+            const orgPos = await this.ctx.service.pos.getPosDataByIds(this._.map(datas, 'pid'));
+            const orgStagePos = await this.getLastestStageData(this.ctx.tender.id, this.ctx.stage.id, {pid: this._.map(datas, 'pid')});
+
+            const transaction = await this.db.beginTransaction();
+            try {
+                for (const d of datas) {
+                    if (d.sgfh_qty || d.qtcl_qty || d.sjcl_qty || d.contract_qty || d.qc_qty) {
+                        if (!bills || bills.id !== data.lid) {
+                            bills = await this.ctx.service.ledger.getDataById(d.lid);
+                            precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, bills.unit);
+                        }
+                    }
+                    if (d.name !== undefined || d.sgfh_qty !== undefined || d.qtcl_qty !== undefined || d.sjcl_qty !== undefined || d.drawing_code !== undefined) {
+                        const op = this._.find(orgPos, {id: d.pid});
+                        if (op.add_stage !== this.ctx.stage.id) throw '不可修改数据';
+                        const p = {id: d.pid};
+                        if (d.name !== undefined) p.name = d.name;
+                        if (d.sgfh_qty !== undefined || d.qtcl_qty !== undefined || d.sjcl_qty !== undefined) {
+                            p.sgfh_qty = d.sgfh_qty !== undefined ? d.sgfh_qty : op.sgfh_qty;
+                            p.sjcl_qty = d.sjcl_qty !== undefined ? d.sjcl_qty : op.sjcl_qty;
+                            p.qtcl_qty = d.qtcl_qty !== undefined ? d.qtcl_qty : op.qtcl_qty;
+                            p.quantity = this.ctx.helper.sum([p.sgfh_qty, p.sjcl_qty, p.qtcl_qty]);
+                            if (ledgerCalc.indexOf(op.lid) === -1) {
+                                ledgerCalc.push(op.lid);
+                            }
+                        }
+                        if (d.drawing_code !== undefined) p.drawing_code = d.drawing_code;
+                        await transaction.update(this.ctx.service.pos.tableName, p);
+                    }
+
+                    if (d.contract_qty !== undefined || d.qc_qty !== undefined || d.postil !== undefined) {
+                        const sp = {pid: d.pid, lid: d.lid, contract_qty: d.contract_qty, qc_qty: d.qc_qty, postil: d.postil};
+                        const osp = this._.find(orgStagePos, function (p) { return p.pid === d.pid; });
+                        if (precision) {
+                            this.ctx.helper.checkFieldPrecision(sp, this.qtyFields, precision.value);
+                        }
+                        if (osp && osp.times === this.ctx.stage.curTimes && osp.order === this.ctx.stage.curOrder) {
+                            await transaction.update(this.tableName, d, {where: {id: osp.id}});
+                        } else {
+                            sp.tid = this.ctx.tender.id;
+                            sp.sid = this.ctx.stage.id;
+                            sp.said = this.ctx.session.sessionUser.accountId;
+                            sp.times = this.ctx.stage.curTimes;
+                            sp.order = this.ctx.stage.curOrder;
+                            await transaction.insert(this.tableName, sp);
+                        }
+                    }
+                    result.pos.push(d.pid);
+                    if ((d.sgfh_qty !== undefined || d.qtcl_qty !== undefined || d.sjcl_qty !== undefined ||
+                            d.contract_qty === undefined || d.qc_qty === undefined) && (result.ledger.indexOf(d.lid) === -1)) {
+                        result.ledger.push(d.lid);
                     }
-                    this.ctx.helper.checkFieldPrecision(d, this.qtyFields, precision.value);
                 }
-                if (osp && osp.times === this.ctx.stage.curTimes && osp.order === this.ctx.stage.curOrder) {
-                    await transaction.update(this.tableName, d, {where: {id: osp.id}});
-                } else {
-                    d.tid = this.ctx.tender.id;
-                    d.sid = this.ctx.stage.id;
-                    d.said = this.ctx.session.sessionUser.accountId;
-                    d.times = this.ctx.stage.curTimes;
-                    d.order = this.ctx.stage.curOrder;
-                    await transaction.insert(this.tableName, d);
+                for (const lid of ledgerCalc) {
+                    await this.ctx.service.ledger.calc(this.ctx.tender.id, lid, transaction);
                 }
-                result.pos.push(d.pid);
-                if ((d.contract_qty === undefined || d.qc_qty === undefined) && (result.ledger.indexOf(d.lid) === -1)) {
-                    result.ledger.push(d.lid);
+                for (const lid of result.ledger) {
+                    await this.ctx.service.stageBills.calc(this.ctx.tender.id, this.ctx.stage.id, lid, transaction);
                 }
+                await transaction.commit();
+                return result;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
             }
-            for (const lid of result.ledger) {
-                await this.ctx.service.stageBills.calc(this.ctx.tender.id, this.ctx.stage.id, lid, transaction);
-            }
-            return result;
         }
 
         /**
@@ -196,33 +250,41 @@ module.exports = app => {
          * @returns {Promise<{}>}
          * @private
          */
-        async _deleteStagePosData(transaction, data) {
-            const result = {};
+        async _deleteStagePosData(data) {
             const pos = await this.ctx.service.pos.getPosData({tid: this.ctx.tender.id, id: data});
+            const result = { pos: data, isDeletePos: true};
             if (pos instanceof Array) {
                 for (const p of pos) {
-                    if (p.add_stage !== this.ctx.stage.id || p.add_times !== this.ctx.stage.curTimes || p.add_user !== this.ctx.session.sessionUser.accountId) {
-                        throw '您无权删除该数据';
+                    if (p.add_stage !== this.ctx.stage.id /*|| p.add_times !== this.ctx.stage.curTimes || p.add_user !== this.ctx.session.sessionUser.accountId*/) {
+                        throw '不可删除该数据';
                     }
                 }
-            } else if (pos.add_stage !== this.ctx.stage.id || pos.add_times !== this.ctx.stage.curTimes || pos.add_user !== this.ctx.session.sessionUser.accountId) {
-                throw '您无权删除该数据';
+            } else if (pos.add_stage !== this.ctx.stage.id /*|| pos.add_times !== this.ctx.stage.curTimes || pos.add_user !== this.ctx.session.sessionUser.accountId*/) {
+                throw '不可删除该数据';
             }
             const ledgerIds = this._.map(pos, 'lid');
-            // 删除部位明细
-            await transaction.delete(this.ctx.service.pos.tableName, {tid: this.ctx.tender.id, id: data});
-            for (const lid of ledgerIds) {
-                await this.ctx.service.ledger.calc(tid, lid, transaction);
-            }
-            // 删除部位明细计量数据
-            await transaction.delete(this.tableName, {tid: this.ctx.tender.id, lid: data});
-            for (const lid of ledgerIds) {
-                await this.ctx.service.stageBills.calc(this.ctx.tender.id, this.ctx.stage.id, lid, transaction);
+
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 删除部位明细
+                await transaction.delete(this.ctx.service.pos.tableName, {tid: this.ctx.tender.id, id: data});
+                for (const lid of ledgerIds) {
+                    await this.ctx.service.ledger.calc(this.ctx.tender.id, lid, transaction);
+                }
+                // 删除部位明细计量数据
+                await transaction.delete(this.tableName, {tid: this.ctx.tender.id, lid: data});
+                for (const lid of ledgerIds) {
+                    await this.ctx.service.stageBills.calc(this.ctx.tender.id, this.ctx.stage.id, lid, transaction);
+                }
+                await transaction.commit();
+                // 获取需要更新的数据
+                result.ledger = ledgerIds;
+                result.stageUpdate = true;
+                return result;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
             }
-            // 获取需要更新的数据
-            result.ledger = ledgerIds;
-            result.stageUpdate = true;
-            return result;
         }
 
         /**
@@ -232,28 +294,21 @@ module.exports = app => {
          * @returns {Promise<{ledger: {}, pos: {}}>}
          */
         async updateStageData(data) {
+            if ((data.updateType === 'add' || data.upateType === 'delete') && this.ctx.tender.measure_type === measureType.tz) {
+                throw '台账模式下,不可在计量中新增或删除部位明细,如需操作,请进行台账修订';
+            }
             let refreshData;
-            const transaction = await this.db.beginTransaction();
-            try {
-                if ((data.updateType === 'add' || data.upateType === 'delete') && this.ctx.tender.measure_type === measureType.tz) {
-                    throw '台账模式下,不可在计量中新增或删除部位明细,如需操作,请进行台账修订';
-                }
-                if (data.updateType === 'add') {
-                    refreshData = await this._addStagePosData(transaction, data.updateData);
-                } else if (data.updateType === 'update') {
-                    refreshData = await this._updateStagePosData(transaction, data.updateData);
-                } else if (data.updateType === 'delete') {
-                    if (!data.updateData || data.updateData.length === 0) {
-                        throw '提交数据错误';
-                    }
-                    refreshData = await this._deleteStagePosData(transaction, data.updateData);
-                } else {
+            if (data.updateType === 'add') {
+                refreshData = await this._addStagePosData(data.updateData);
+            } else if (data.updateType === 'update') {
+                refreshData = await this._updateStagePosData(data.updateData);
+            } else if (data.updateType === 'delete') {
+                if (!data.updateData || data.updateData.length === 0) {
                     throw '提交数据错误';
                 }
-                await transaction.commit();
-            } catch (err) {
-                await transaction.rollback();
-                throw err;
+                refreshData = await this._deleteStagePosData(data.updateData);
+            } else {
+                throw '提交数据错误';
             }
 
             try {
@@ -261,13 +316,17 @@ module.exports = app => {
                 if (refreshData.ledger && refreshData.ledger.length > 0) {
                     result.ledger.bills = await this.ctx.service.ledger.getDataByIds(refreshData.ledger);
                     if (refreshData.stageUpdate) {
-                        result.ledger.curStageData = await await this.ctx.service.stageBills.getLastestStageData(this.ctx.tender.id, this.ctx.stage.id, refreshData.ledger);
+                        result.ledger.curStageData = await this.ctx.service.stageBills.getLastestStageData(this.ctx.tender.id, this.ctx.stage.id, refreshData.ledger);
                     }
                 }
-                if (refreshData.pos && refreshData.pos.length > 0) {
-                    result.pos.pos = await this.ctx.service.pos.getPosData({id: refreshData.pos});
-                    if (refreshData.stageUpdate) {
-                        result.pos.curStageData = await this.getLastestStageData(this.ctx.tender.id, this.ctx.stage.id, refreshData.pos);
+                if (refreshData.pos) {
+                    if (refreshData.isDeletePos) {
+                        result.pos.pos = refreshData.pos;
+                    } else if (refreshData.pos.length > 0) {
+                        result.pos.pos = await this.ctx.service.pos.getPosData({id: refreshData.pos});
+                        if (refreshData.stageUpdate) {
+                            result.pos.curStageData = await this.getLastestStageData(this.ctx.tender.id, this.ctx.stage.id, {pid: refreshData.pos});
+                        }
                     }
                 }
                 return result;
@@ -277,9 +336,14 @@ module.exports = app => {
         }
 
         async updateChangeQuantity(transaction, pos, qty) {
-            const orgPos = await this.getLastestStageData(this.ctx.tender.id, this.ctx.stage.id, pos.id);
+            let orgPos = await this.getLastestStageData(this.ctx.tender.id, this.ctx.stage.id, {pid: pos.id});
+            if (orgPos.length > 1) {
+                throw '数据错误';
+            } else {
+                orgPos = orgPos[0];
+            }
             if (orgPos && orgPos.times === this.ctx.stage.curTimes && orgPos.order === this.ctx.stage.curOrder) {
-                await transaction.update(this.tableName, {qc_qty: qty}, {where: {id: orgPos.id}});
+                await transaction.update(this.tableName, {id: orgPos.id, qc_qty: qty});
             } else {
                 await transaction.insert(this.tableName, {
                     tid: this.ctx.tender.id,

+ 26 - 19
app/service/stage_pos_final.js

@@ -31,15 +31,17 @@ module.exports = app => {
          * @returns {Promise<void>}
          */
         async getFinalData(tender, stage) {
-            const sql = 'SELECT * FROM ' + this.tableName + ' As Pos' +
+            const sql = 'SELECT Pos.* FROM ' +
+                //'  (SELECT pid, contract_qty, qc_qty, sorder FROM ' + this.tableName + ' WHERE tid = ?) As Pos' +
+                this.tableName + ' As Pos' +
                 '  INNER JOIN ( ' +
                 '    SELECT MAX(`sorder`) As `sorder`, `pid` From ' + this.tableName +
                 '      WHERE tid = ? AND sorder < ?' +
                 '      GROUP BY `pid`' +
                 '  ) As MaxFilter ' +
-                '  ON Pos.sorder = MaxFilter.sorder And Pos.pid = MaxFilter.pid' +
-                '  WHERE Pos.tid = ?';
-            const sqlParam = [tender.id, stage.order, tender.id];
+                '  ON Pos.sorder = MaxFilter.sorder And Pos.pid = MaxFilter.pid';
+            //const sqlParam = [tender.id, tender.id, stage.order];
+            const sqlParam = [tender.id, stage.order];
             return await this.db.query(sql, sqlParam);
         }
 
@@ -55,21 +57,26 @@ module.exports = app => {
                 throw '数据错误';
             }
             if (stage.order > 1) {
-                const sql = 'Insert Into ??(tid, sid, lid, pid, sorder, contract_qty, qc_qty)' +
-                            '  SELECT cur.tid, cur.sid, cur.lid, cur.pid, ? As `sorder`,' +
-                            '      IFNULL(cur.contract_qty, 0) + IFNULL(pre.contract_qty, 0), IFNULL(cur.qc_qty, 0) + IFNULL(pre.qc_qty, 0)' +
-                            '    FROM (SELECT p.tid, p.sid, p.lid, p.pid, p.contract_qty, p.qc_qty' +
-                            '      FROM ' + this.ctx.service.stagePos.tableName + ' AS p' +
-                            '      INNER JOIN(' +
-                            '        SELECT Max(`times` * ' + timesLen + ' + `order`) As `flow`, `pid` FROM ' + this.ctx.service.stagePos.tableName +
-                            '        WHERE `sid` = ?' +
-                            '        GROUP By `pid`) As MF' +
-                            '      ON (p.times * ' + timesLen + ' + p.order) = MF.flow AND p.pid = MF.pid' +
-                            '      WHERE p.sid = ?) As cur' +
-                            '    LEFT JOIN ?? As pre' +
-                            '    ON cur.pid = pre.pid AND pre.sorder = ?';
-                const sqlParam = [this.tableName, stage.order, stage.id, stage.id, this.tableName, stage.order - 1];
-                await transaction.query(sql, sqlParam);
+                const cur = await this.ctx.service.stagePos.getLastestStageData(tender.id, stage.id);
+                if (!cur || cur.length === 0) return;
+                const pre = await this.getFinalData(tender, stage);
+                for (const c of cur) {
+                    delete c.id;
+                    delete c.said;
+                    delete c.times;
+                    delete c.order;
+                    delete c.postil;
+                    c.sorder = stage.order;
+                    const p = this.ctx.helper._.find(pre, function (x) {
+                        return x.pid === c.pid;
+                    });
+                    if (!p) continue;
+                    c.contract_qty = this.ctx.helper.add(c.contract_qty, p.contract_qty);
+                    c.qc_qty = this.ctx.helper.add(c.qc_qty, p.qc_qty);
+                    //pre.splice(pre.indexOf(p), 1);
+                }
+                await transaction.insert(this.tableName, cur);
+                //await transaction.insert(this.tableName, cur.concat(pre));
             } else {
                 const sql = 'Insert Into ??(tid, sid, lid, pid, sorder, contract_qty, qc_qty)' +
                             '  SELECT p.tid, p.sid, p.lid, p.pid, ? As `sorder`, p.contract_qty, p.qc_qty' +

+ 1 - 1
app/service/standard_lib.js

@@ -1,6 +1,6 @@
 'use strict';
 /**
- * 标准库基类
+ * 标准库基类(请勿使用该类创造示例)
  *
  * @author Mai
  * @date 2018/3/13

+ 3 - 3
app/service/tender.js

@@ -237,7 +237,7 @@ module.exports = app => {
                 status: this.status.DISABLE,
                 id,
             };
-            const result = this.db.update(this.tableName, updateData);
+            const result = await this.db.update(this.tableName, updateData);
 
             return result.affectedRows > 0;
         }
@@ -256,13 +256,13 @@ module.exports = app => {
                 await transaction.delete(this.ctx.service.ledgerAudit.tableName, {tender_id: id});
                 await transaction.delete(this.ctx.service.ledgerAudit.tableName + '_copy', {tender_id: id});
                 await transaction.delete(this.ctx.service.pos.tableName, {tid: id});
-                //await transaction.delete(this.ctx.service.pay.tableName, {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.stageDetail.tableName, {tid: id});
-                //await transaction.delete(this.ctx.service.stagePay.tableName, {tid: id});
+                await transaction.delete(this.ctx.service.stagePay.tableName, {tid: id});
                 await transaction.delete(this.ctx.service.change.tableName, {tid: id});
                 await transaction.delete(this.ctx.service.changeAudit.tableName, {tid: id});
                 await transaction.delete(this.ctx.service.changeAuditList.tableName, {tid: id});

+ 0 - 20
app/service/tree_sort.js

@@ -1,20 +0,0 @@
-'use strict';
-
-/**
- * 树结构整理相关
- *
- * @author Mai
- * @date
- * @version
- */
-
-const Service = require('egg').Service;
-module.exports = app => {
-    class TreeSort extends Service {
-        fxExcelTreeSort(sheet) {
-
-        }
-    }
-
-    return TreeSort;
-}

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

@@ -28,9 +28,9 @@
             <table class="table">
                 <thead>
                 <tr>
-                    <th width="20%">变更令号</th><th width="30%">工程名称</th>
-                    <th width="10%">变更类别</th><th width="10%">变更金额</th>
-                    <th width="10%">审批状态</th><th>审批进度</th><th></th>
+                    <th width="32%">申请编号/变更令号</th><th width="30%">工程名称</th>
+                    <th width="90">变更类别</th><th width="90">变更金额</th>
+                    <th width="100">审批状态</th><th>审批进度</th><th width="80"></th>
                 </tr>
                 </thead>
                 <tbody id="changeList">
@@ -41,7 +41,7 @@
                 <% } %>
                 <% for (const c of changes) { %>
                 <tr>
-                    <td><a href="/tender/<%- tender.id %>/change/<%- c.cid %>/info"><%- c.code %></a></td>
+                    <td><a href="/tender/<%- tender.id %>/change/<%- c.cid %>/info"><% if (c.status !== auditConst.status.checked) { %><%- c.code %><% } else { %><%- c.p_code %><% } %></a></td>
                     <td><%- c.name %></td>
                     <td><%- classArray[c.class] %><% c.class %></td>
                     <td><%- c.total_price %></td>
@@ -66,7 +66,7 @@
                         <span class="<%- auditConst.auditStatusClass[c.changeAudit.status] %>"><%- auditConst.auditStatusString[c.changeAudit.status] %></span>
                     </td>
                     <% } %>
-                    <td><% if (c.status === auditConst.status.uncheck || c.status === auditConst.status.back) { %><a href="#del-bg" cid="<%= c.cid %>" data-toggle="modal" data-target="#del-bg" class="btn btn-outline-danger btn-sm delete-cid-modal">删除</a><% } %></td>
+                    <td><% if (c.status === auditConst.status.uncheck || (c.status === auditConst.status.back && c.uid === uid)) { %><a href="#del-bg" cid="<%= c.cid %>" data-toggle="modal" data-target="#del-bg" class="btn btn-outline-danger btn-sm delete-cid-modal">删除</a><% } %></td>
                 </tr>
                 <% } %>
                 </tbody>

+ 47 - 33
app/view/change/info.ejs

@@ -73,7 +73,7 @@
 <div class="panel-sidebar">
     <div class="panel-title">
         <div class="title-bar">
-            <h2><%- tender.name %></h2>
+            <h2 class="text-truncate" style="white-space:nowrap; overflow:hidden; text-overflow:ellipsis;" data-toggle="tooltip" data-placement="right" title=""  data-original-title="<%- tender.name %>"><%- tender.name %></h2>
         </div>
     </div>
     <div class="scrollbar-auto">
@@ -118,7 +118,10 @@
                 <div>
                     <% if (auditStatus === 1 || auditStatus === 2) { %>
                     <div class="d-inline-block">
-                        <a href="#addlist" data-toggle="modal" class="btn btn-sm" id="open-list-modal" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="添加清单"><i class="fa fa-plus" aria-hidden="true"></i> 添加清单</a>
+                        <a href="#addlist" data-toggle="modal" class="btn btn-sm btn-light text-primary" id="open-list-modal" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="添加清单"><i class="fa fa-plus" aria-hidden="true"></i> 添加台帐清单</a>
+                    </div>
+                    <div class="d-inline-block">
+                        <a href="javascript:void(0);" class="btn btn-sm btn-light text-primary" id="add-white-btn" data-original-title="添加清单"><i class="fa fa-plus" aria-hidden="true"></i> 添加空白清单</a>
                     </div>
                     <% } else { %>
                     <div class="d-inline-block">
@@ -169,7 +172,7 @@
                     <div class="col-md-4">
                         <div class="form-group">
                             <label><b class="text-danger">*&nbsp;</b>申请编号</label>
-                            <a href="javascript:void(0);" class="pull-right reduction-code" data-toggle="tooltip" data-placement="bottom" title="" data-code="<%- change.code %>" data-original-title="自动编号"><i class="fa fa-repeat"></i></a>
+                            <a href="javascript:void(0);" class="pull-right reduction-code" data-toggle="tooltip" data-placement="bottom" title="" data-code="<%- change.code %>" data-original-title="重置"><i class="fa fa-repeat"></i></a>
                             <input class="form-control" name="code" value="<%- change.code %>" type="text">
                         </div>
                         <div class="form-group">
@@ -282,7 +285,7 @@
                         </div>
                         <% if (auditStatus === 4) { %>
                         <div class="form-group">
-                            <label>批复编号</label>
+                            <label>变更令号(批复编号</label>
                             <input class="form-control" value="<%- change.p_code %>" type="text" readonly>
                         </div>
                         <% } %>
@@ -403,6 +406,7 @@
                 <tr>
                     <th rowspan="2" class="text-center">清单编号</th>
                     <th rowspan="2" class="text-center">名称</th>
+                    <th rowspan="2" class="text-center">变更部位</th>
                     <th rowspan="2" class="text-center">变更详情</th>
                     <th rowspan="2" class="text-center">单位</th>
                     <th rowspan="2" class="text-center">单价</th>
@@ -424,13 +428,14 @@
                     <tr class="clist clid" data-lid="<%= cl.lid %>_<%= index %>" data-index="<%= index %>">
                         <td data-site="0"><%= cl.code %></td>
                         <td data-site="1"><%= cl.name %></td>
-                        <td data-site="2"><input class="form-control form-control-sm" placeholder="变更详情" type="text" value="<%= cl.detail %>"></td>
-                        <td data-site="3"><%= cl.unit %></td>
-                        <td data-site="4"><%= ctx.helper.roundNum(cl.unit_price, tpUnit) %></td>
-                        <td data-site="5"><%= ctx.helper.roundNum(cl.oamount, ctx.helper.findDecimal(cl.unit)) %></td>
-                        <td data-site="6"><%= ctx.helper.roundNum(ctx.helper.accMul(cl.unit_price, cl.oamount), tpUnit) %></td>
-                        <td data-site="7"><input class="form-control form-control-sm" placeholder="变更数量" type="text" onkeyup="RegNum(this,event,<%= ctx.helper.findDecimal(cl.unit) %>)"  value="<%= ctx.helper.roundNum(cl.camount, ctx.helper.findDecimal(cl.unit)) %>"></td>
-                        <td data-site="8"><%= ctx.helper.roundNum(ctx.helper.accMul(cl.unit_price, cl.camount), tpUnit) %></td>
+                        <td data-site="2"><%= cl.bwmx %></td>
+                        <td data-site="3"><input class="form-control form-control-sm" placeholder="变更详情" type="text" value="<%= cl.detail %>"></td>
+                        <td data-site="4"><%= cl.unit %></td>
+                        <td data-site="5"><%= ctx.helper.roundNum(cl.unit_price, tpUnit) %></td>
+                        <td data-site="6"><%= ctx.helper.roundNum(cl.oamount, ctx.helper.findDecimal(cl.unit)) %></td>
+                        <td data-site="7"><%= ctx.helper.roundNum(ctx.helper.accMul(cl.unit_price, cl.oamount), tpUnit) %></td>
+                        <td data-site="8"><input class="form-control form-control-sm" placeholder="变更数量" type="text" onkeyup="RegNum(this,event,<%= ctx.helper.findDecimal(cl.unit) %>)"  value="<%= ctx.helper.roundNum(cl.camount, ctx.helper.findDecimal(cl.unit)) %>"></td>
+                        <td data-site="9"><%= ctx.helper.roundNum(ctx.helper.accMul(cl.unit_price, cl.camount), tpUnit) %></td>
                         <td><a class="text-danger">移除</a>
                         </td>
                     </tr>
@@ -438,8 +443,9 @@
                     <tr class="clist" data-lid="<%= windex %>" data-index="<%= windex %>">
                         <td data-site="0"><input class="form-control form-control-sm" placeholder="清单编号" type="text" value="<%= cl.code %>"></td>
                         <td data-site="1"><input class="form-control form-control-sm" placeholder="名称" type="text" value="<%= cl.name %>"></td>
-                        <td data-site="2"><input class="form-control form-control-sm" placeholder="变更详情" type="text" value="<%= cl.detail %>"></td>
-                        <td data-site="3">
+                        <td data-site="2"><input class="form-control form-control-sm" placeholder="变更部位" type="text" value="<%= cl.bwmx %>"></td>
+                        <td data-site="3"><input class="form-control form-control-sm" placeholder="变更详情" type="text" value="<%= cl.detail %>"></td>
+                        <td data-site="4">
                             <select class="form-control input-sm">
                                 <% for (const j in changeUnits) { %>
                                     <% if (changeUnits[j].unit !== undefined && changeUnits[j].unit === cl.unit) { %>
@@ -450,11 +456,11 @@
                                 <% } %>
                             </select>
                         </td>
-                        <td data-site="4"><input class="form-control form-control-sm" placeholder="单价" onkeyup="RegNum(this,event,upUnit)" type="text" value="<%= ctx.helper.roundNum(cl.unit_price, upUnit) %>"></td>
-                        <td data-site="5"><input class="form-control form-control-sm" placeholder="数量" onkeyup="RegNum(this,event,<%= ctx.helper.findDecimal(cl.unit) %>)" type="text" value="<%= ctx.helper.roundNum(cl.oamount, ctx.helper.findDecimal(cl.unit)) %>"></td>
-                        <td data-site="6"><%= ctx.helper.roundNum(ctx.helper.accMul(cl.unit_price, cl.oamount), tpUnit) %></td>
-                        <td data-site="7"><input class="form-control form-control-sm" placeholder="变更数量" onkeyup="RegNum(this,event,<%= ctx.helper.findDecimal(cl.unit) %>)" type="text" value="<%= ctx.helper.roundNum(cl.camount, ctx.helper.findDecimal(cl.unit)) %>"></td>
-                        <td data-site="8"><%= ctx.helper.roundNum(ctx.helper.accMul(cl.unit_price, cl.camount), tpUnit) %></td>
+                        <td data-site="5"><input class="form-control form-control-sm" placeholder="单价" onkeyup="RegNum(this,event,upUnit)" type="text" value="<%= ctx.helper.roundNum(cl.unit_price, upUnit) %>"></td>
+                        <td data-site="6"><input class="form-control form-control-sm" placeholder="数量" onkeyup="RegNum(this,event,<%= ctx.helper.findDecimal(cl.unit) %>)" type="text" value="<%= ctx.helper.roundNum(cl.oamount, ctx.helper.findDecimal(cl.unit)) %>"></td>
+                        <td data-site="7"><%= ctx.helper.roundNum(ctx.helper.accMul(cl.unit_price, cl.oamount), tpUnit) %></td>
+                        <td data-site="8"><input class="form-control form-control-sm" placeholder="变更数量" onkeyup="RegNum(this,event,<%= ctx.helper.findDecimal(cl.unit) %>)" type="text" value="<%= ctx.helper.roundNum(cl.camount, ctx.helper.findDecimal(cl.unit)) %>"></td>
+                        <td data-site="9"><%= ctx.helper.roundNum(ctx.helper.accMul(cl.unit_price, cl.camount), tpUnit) %></td>
                         <td><a class="text-danger">移除</a>
                         </td>
                     </tr>
@@ -464,7 +470,7 @@
                 </tbody>
                 <tfoot>
                 <tr class="info">
-                    <td>合计</td><td></td><td></td><td></td><td></td><td></td>
+                    <td>合计</td><td></td><td></td><td></td><td></td><td></td><td></td>
                     <td class="otatalamount"><%= ctx.helper.roundNum(ototalCost, tpUnit) %></td><td></td>
                     <td class="ctatalamount"><%= ctx.helper.roundNum(ctotalCost, tpUnit) %></td><td></td>
                 </tr>
@@ -476,6 +482,7 @@
                     <tr>
                         <th rowspan="2" class="text-center">清单编号</th>
                         <th rowspan="2" class="text-center">名称</th>
+                        <th rowspan="2" class="text-center">变更部位</th>
                         <th rowspan="2" class="text-center">变更详情</th>
                         <th rowspan="2" class="text-center">单位</th>
                         <th rowspan="2" class="text-center">单价</th>
@@ -497,6 +504,7 @@
                         <tr>
                             <td><%= cl.code %></td>
                             <td><%= cl.name %></td>
+                            <td><%= cl.bwmx %></td>
                             <td><%= cl.detail %></td>
                             <td><%= cl.unit %></td>
                             <td><%= ctx.helper.roundNum(cl.unit_price, upUnit) %></td>
@@ -512,7 +520,7 @@
                     </tbody>
                     <tfoot>
                     <tr class="info">
-                        <td>合计</td><td></td><td></td><td></td><td></td><td></td>
+                        <td>合计</td><td></td><td></td><td></td><td></td><td></td><td></td>
                         <td><%= ctx.helper.roundNum(ototalCost, tpUnit) %></td><td></td>
                         <td><%= ctx.helper.roundNum(ctotalCost, tpUnit) %></td><td></td>
                         <td><%= stotalCost !== 0 ? ctx.helper.roundNum(stotalCost, tpUnit) : '' %></td>
@@ -525,6 +533,7 @@
                     <tr>
                         <th rowspan="2" class="text-center">清单编号</th>
                         <th rowspan="2" class="text-center">名称</th>
+                        <th rowspan="2" class="text-center">变更部位</th>
                         <th rowspan="2" class="text-center">变更详情</th>
                         <th rowspan="2" class="text-center">单位</th>
                         <th rowspan="2" class="text-center">单价</th>
@@ -555,6 +564,7 @@
                         <tr>
                             <td><%= cl.code %></td>
                             <td><%= cl.name %></td>
+                            <td><%= cl.bwmx %></td>
                             <td><%= cl.detail %></td>
                             <td><%= cl.unit %></td>
                             <td><%= ctx.helper.roundNum(cl.unit_price, upUnit) %></td>
@@ -574,7 +584,7 @@
                     </tbody>
                     <tfoot>
                     <tr class="info">
-                        <td>合计</td><td></td><td></td><td></td><td></td><td></td>
+                        <td>合计</td><td></td><td></td><td></td><td></td><td></td><td></td>
                         <td><%= ctx.helper.roundNum(ototalCost, tpUnit) %></td><td></td>
                         <td><%= ctx.helper.roundNum(ctotalCost, tpUnit) %></td>
                         <% for (const audit of auditList2) { %>
@@ -591,6 +601,7 @@
                     <tr>
                         <th rowspan="2" class="text-center">清单编号</th>
                         <th rowspan="2" class="text-center">名称</th>
+                        <th rowspan="2" class="text-center">变更部位</th>
                         <th rowspan="2" class="text-center">变更详情</th>
                         <th rowspan="2" class="text-center">单位</th>
                         <th rowspan="2" class="text-center">单价</th>
@@ -621,9 +632,10 @@
                         <tr class="clist" data-lid="<%= cl.id %>">
                             <td><%= cl.code %></td>
                             <td><%= cl.name %></td>
+                            <td><%= cl.bwmx %></td>
                             <td><%= cl.detail %></td>
                             <td><%= cl.unit %></td>
-                            <td data-site="4"><%= ctx.helper.roundNum(cl.unit_price, upUnit) %></td>
+                            <td data-site="5"><%= ctx.helper.roundNum(cl.unit_price, upUnit) %></td>
                             <td><%= ctx.helper.roundNum(cl.oamount, ctx.helper.findDecimal(cl.unit)) %></td>
                             <td><%= ctx.helper.roundNum(ctx.helper.accMul(cl.unit_price, cl.oamount), tpUnit) %></td>
                             <td><%= ctx.helper.roundNum(cl.camount, ctx.helper.findDecimal(cl.unit)) %></td>
@@ -649,7 +661,7 @@
                     </tbody>
                     <tfoot>
                     <tr class="info">
-                        <td>合计</td><td></td><td></td><td></td><td></td><td></td>
+                        <td>合计</td><td></td><td></td><td></td><td></td><td></td><td></td>
                         <td><%= ctx.helper.roundNum(ototalCost, tpUnit) %></td><td></td>
                         <td><%= ctx.helper.roundNum(ctotalCost, tpUnit) %></td>
                         <% for (const audit of auditList) { %>
@@ -735,19 +747,21 @@
     const billsTable = {
         columnDefs: [
             { className: 'allwidth1', width: 100, targets: 0 },
-            { className: 'allwidth2', width: 150, targets: [1,2] },
-            { className: 'allwidth4', width: 40, targets: 9 },
-            { className: 'allwidth5', width: 60, targets: 3 },
+            { className: 'allwidth2', width: 150, targets: [1,2,3] },
+            { className: 'allwidth4', width: 40, targets: 10 },
+            { className: 'allwidth5', width: 60, targets: 4 },
             { className: 'allwidth3',width: 80, targets: '_all' }
         ],
         fixedColumns: {
-            leftColumns: 5
+            leftColumns: 6
         }
     }
     const ledger = JSON.parse('<%- JSON.stringify(ledger) %>');
     const pos = JSON.parse('<%- JSON.stringify(pos) %>');
     const dealBillList = JSON.parse('<%- JSON.stringify(dealBillList) %>');
 </script>
+<script src="/public/js/decimal.min.js"></script>
+<script src="/public/js/zh_calc.js"></script>
 <script src="/public/js/path_tree.js"></script>
 <script src="/public/js/gcl_gather.js"></script>
 <script src="/public/js/jquery/jquery.form.min.js"></script>
@@ -757,12 +771,12 @@
     const billsTable = {
         columnDefs: [
             { className: 'allwidth1', width: 100, targets: 0 },
-            { className: 'allwidth2', width: 150, targets: [1,2] },
-            { className: 'allwidth5', width: 60, targets: 3 },
+            { className: 'allwidth2', width: 150, targets: [1,2,3] },
+            { className: 'allwidth5', width: 60, targets: 4 },
             { className: 'allwidth3',width: 80, targets: '_all' }
         ],
         fixedColumns: {
-            leftColumns: 5
+            leftColumns: 6
         }
     }
 </script>
@@ -772,12 +786,12 @@
     const billsTable = {
         columnDefs: [
             { className: 'allwidth1', width: 100, targets: 0 },
-            { className: 'allwidth2', width: 150, targets: [1,2] },
-            { className: 'allwidth5', width: 60, targets: 3 },
+            { className: 'allwidth2', width: 150, targets: [1,2,3] },
+            { className: 'allwidth5', width: 60, targets: 4 },
             { className: 'allwidth3',width: 80, targets: '_all' }
         ],
         fixedColumns: {
-            leftColumns: 5
+            leftColumns: 6
         }
     }
 </script>

+ 0 - 0
app/view/change/info_modal.ejs


Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików