瀏覽代碼

Merge branch 'master' into olym

olym 7 年之前
父節點
當前提交
6fddf5f8a0
共有 100 個文件被更改,包括 6600 次插入1979 次删除
  1. 4 1
      .gitignore
  2. 10 3
      config/gulpConfig.js
  3. 36 0
      lib/jquery-ui/jquery-ui-datepickerCN.js
  4. 1312 0
      lib/jquery-ui/jquery-ui.css
  5. 13 0
      lib/jquery-ui/jquery-ui.min.js
  6. 0 2
      modules/complementary_glj_lib/controllers/gljController.js
  7. 1 2
      modules/complementary_glj_lib/models/gljModel.js
  8. 1 0
      modules/complementary_glj_lib/models/schemas.js
  9. 56 28
      modules/glj/models/glj_list_model.js
  10. 2 5
      modules/glj/models/schemas/glj.js
  11. 23 13
      modules/glj/models/unit_price_model.js
  12. 116 0
      modules/main/controllers/quantity_detail_controller.js
  13. 33 0
      modules/main/facade/decimal_facade.js
  14. 2 1
      modules/main/facade/prj_properties_facade.js
  15. 216 297
      modules/ration_glj/facade/quantity_detail_facade.js
  16. 2 1
      modules/main/models/bills.js
  17. 3 3
      modules/main/models/project.js
  18. 2 2
      modules/main/models/project_consts.js
  19. 20 17
      modules/main/models/ration.js
  20. 2 2
      modules/main/models/schemas/proj_counter.js
  21. 20 0
      modules/main/routes/quantity_detail_route.js
  22. 2 2
      modules/pm/controllers/copy_proj_controller.js
  23. 1 1
      modules/pm/controllers/pm_controller.js
  24. 12 11
      modules/pm/models/project_model.js
  25. 122 0
      modules/pm/models/project_property_template.js
  26. 20 3
      modules/ration_glj/controllers/ration_glj_controller.js
  27. 7 7
      modules/ration_glj/facade/glj_calculate_facade.js
  28. 54 11
      modules/ration_glj/facade/ration_glj_facade.js
  29. 2 0
      modules/ration_glj/models/ration_glj.js
  30. 1 0
      modules/ration_glj/routes/ration_glj_route.js
  31. 53 1
      modules/reports/controllers/rpt_controller.js
  32. 2 2
      modules/reports/routes/report_router.js
  33. 40 18
      modules/reports/rpt_component/jpc_bill_tab.js
  34. 19 13
      modules/reports/rpt_component/jpc_data.js
  35. 26 5
      modules/reports/rpt_component/jpc_ex.js
  36. 2 1
      modules/reports/rpt_component/jpc_flow_tab.js
  37. 312 24
      modules/reports/util/rpt_construct_data_util.js
  38. 42 12
      modules/reports/util/rpt_excel_util.js
  39. 132 50
      modules/reports/util/rpt_pdf_util.js
  40. 2 0
      modules/volume_price/models/volume_price_model.js
  41. 6 0
      modules/volume_price/models/volume_price_schema.js
  42. 0 474
      public/calc_util.js
  43. 0 20
      public/common_util.js
  44. 0 28
      public/debug.js
  45. 1 1
      public/fsUtil.js
  46. 9 0
      public/stringUtil.js
  47. 15 0
      public/web/common_util.js
  48. 21 1
      public/web/rpt_value_define.js
  49. 20 0
      public/web/scMathUtil.js
  50. 8 3
      public/web/sheet/sheet_common.js
  51. 1 1
      public/web/tree_sheet/tree_sheet_helper.js
  52. 16 0
      public/web/tree_table/tree_table.js
  53. 3 82
      test/tmp_data/test_ration_calc/ration_calc_base.js
  54. 115 0
      test/unit/reports/test_cover_01.js
  55. 9 8
      test/unit/reports/test_tpl_09_1.js
  56. 3 0
      tmp/forDeployment.js
  57. 4 4
      web/building_saas/complementary_glj_lib/js/gljComponent.js
  58. 51 45
      web/building_saas/css/main.css
  59. 1 1
      web/building_saas/glj/html/glj_index.html
  60. 0 1
      web/building_saas/glj/js/composition_spread.js
  61. 35 17
      web/building_saas/glj/js/project_glj.js
  62. 3 1
      web/building_saas/glj/js/project_glj_spread.js
  63. 二進制
      web/building_saas/img/FirstPageSimple.cur
  64. 二進制
      web/building_saas/img/LastPageSimple.cur
  65. 二進制
      web/building_saas/img/NextPageSimple.cur
  66. 二進制
      web/building_saas/img/PreviousPageSimple.cur
  67. 二進制
      web/building_saas/img/baobiao.png
  68. 58 23
      web/building_saas/main/html/main.html
  69. 1 1
      web/building_saas/main/js/calc/bills_calc.js
  70. 2 1
      web/building_saas/main/js/calc/calc_fees.js
  71. 16 21
      web/building_saas/main/js/controllers/project_controller.js
  72. 6 3
      web/building_saas/main/js/models/bills.js
  73. 718 141
      web/building_saas/main/js/models/calc_program.js
  74. 87 10
      web/building_saas/main/js/models/main_consts.js
  75. 6 5
      web/building_saas/main/js/models/project.js
  76. 15 1
      web/building_saas/main/js/models/project_glj.js
  77. 84 62
      web/building_saas/main/js/models/quantity_detail.js
  78. 15 17
      web/building_saas/main/js/models/ration.js
  79. 55 12
      web/building_saas/main/js/models/ration_glj.js
  80. 4 2
      web/building_saas/main/js/models/volume_price.js
  81. 2 2
      web/building_saas/main/js/views/calc_program_manage.js
  82. 6 2
      web/building_saas/main/js/views/calc_program_view.js
  83. 178 37
      web/building_saas/main/js/views/glj_view.js
  84. 15 1
      web/building_saas/main/js/views/glj_view_contextMenu.js
  85. 94 23
      web/building_saas/main/js/views/main_tree_col.js
  86. 3 0
      web/building_saas/main/js/views/project_info.js
  87. 463 0
      web/building_saas/main/js/views/project_property_basicInfo.js
  88. 348 0
      web/building_saas/main/js/views/project_property_bills_quantity_decimal.js
  89. 42 7
      web/building_saas/main/js/views/project_property_decimal_view.js
  90. 409 0
      web/building_saas/main/js/views/project_property_projFeature.js
  91. 111 85
      web/building_saas/main/js/views/project_view.js
  92. 2 2
      web/building_saas/main/js/views/std_ration_lib.js
  93. 5 7
      web/building_saas/main/js/views/sub_view.js
  94. 51 37
      web/building_saas/pm/html/project-management.html
  95. 7 25
      web/building_saas/pm/js/pm_gc.js
  96. 597 193
      web/building_saas/pm/js/pm_main.js
  97. 45 4
      web/building_saas/report/html/rpt_main.html
  98. 105 29
      web/building_saas/report/js/jpc_output.js
  99. 4 1
      web/building_saas/report/js/jpc_output_value_define.js
  100. 0 0
      web/building_saas/report/js/rpt_main.js

+ 4 - 1
.gitignore

@@ -2,4 +2,7 @@ node_modules/
 .git/
 dist/
 .idea/
-tmp/
+tmp/*.xlsx
+tmp/*.pdf
+test/unit/logs
+*.log

+ 10 - 3
config/gulpConfig.js

@@ -31,6 +31,7 @@ module.exports = {
     ],
     main_css:[
         'lib/ztree/css/zTreeStyle.css',
+        'lib/jquery-ui/jquery-ui.css',
         'lib/spreadjs/sheets/css/gc.spread.sheets.excel2013lightGray.10.0.1.css',
         'lib/spreadjs/views/gc.spread.views.dataview.10.0.0.css',
         'lib/jquery-contextmenu/jquery.contextMenu.css'
@@ -38,9 +39,13 @@ module.exports = {
     main_jspaths:[
         'lib/JSExpressionEval_src/*.js',
         '!lib/JSExpressionEval_src/JsHashMap.js',
+        'lib/jquery-ui/jquery-ui.min.js',
+        'lib/jquery-ui/jquery-ui-datepickerCN.js',
         'lib/jquery-contextmenu/*.js',
         'lib/lodash/lodash.js',
+        // 'test/tmp_data/test_ration_calc/ration_calc_base.js',
         'web/building_saas/main/js/models/main_consts.js',
+        'public/web/common_util.js',
         'web/building_saas/glj/js/project_glj.js',
         'web/building_saas/glj/js/composition.js',
         'web/building_saas/glj/js/common_spread.js',
@@ -71,15 +76,14 @@ module.exports = {
         'web/building_saas/main/js/models/ration_glj.js',
         'web/building_saas/main/js/models/ration_coe.js',
         'web/building_saas/main/js/models/ration_ass.js',
-        'web/building_saas/main/js/models/volume_price.js',
+        // 'web/building_saas/main/js/models/volume_price.js',
         'web/building_saas/main/js/models/labour_coe.js',
         'public/web/id_tree.js',
-        'test/tmp_data/test_ration_calc/ration_calc_base.js',
         'web/building_saas/main/js/models/cache_tree.js',
         'web/building_saas/main/js/calc/calc_fees.js',
         'web/building_saas/main/js/calc/ration_calc.js',
         'web/building_saas/main/js/calc/bills_calc.js',
-        'public/calc_util.js',
+        // 'public/calc_util.js',
         'public/web/tree_sheet/tree_sheet_controller.js',
         'public/web/tree_sheet/tree_sheet_helper.js',
         'public/web/sheet/sheet_data_helper.js',
@@ -87,7 +91,10 @@ module.exports = {
         'web/building_saas/main/js/views/project_info.js',
         'web/building_saas/main/js/views/project_view.js',
         'web/building_saas/main/js/views/options_view.js',
+        'web/building_saas/main/js/views/project_property_bills_quantity_decimal.js',
         'web/building_saas/main/js/views/project_property_decimal_view.js',
+        'web/building_saas/main/js/views/project_property_basicInfo.js',
+        'web/building_saas/main/js/views/project_property_projFeature.js',
         'web/building_saas/main/js/main_ajax.js',
         'web/building_saas/main/js/main.js',
         'web/building_saas/main/js/controllers/project_controller.js',

+ 36 - 0
lib/jquery-ui/jquery-ui-datepickerCN.js

@@ -0,0 +1,36 @@
+/**
+ * Created by Zhong on 2017/11/27.
+ */
+jQuery(function($){
+    $.datepicker.regional['zh-CN'] = {
+        clearText: '清除',
+        clearStatus: '清除已选日期',
+        closeText: '关闭',
+        closeStatus: '不改变当前选择',
+        prevText: '< 上月',
+        prevStatus: '显示上月',
+        prevBigText: '<<',
+        prevBigStatus: '显示上一年',
+        nextText: '下月>',
+        nextStatus: '显示下月',
+        nextBigText: '>>',
+        nextBigStatus: '显示下一年',
+        currentText: '今天',
+        currentStatus: '显示本月',
+        monthNames: ['一月','二月','三月','四月','五月','六月', '七月','八月','九月','十月','十一月','十二月'],
+        monthNamesShort: ['一月','二月','三月','四月','五月','六月', '七月','八月','九月','十月','十一月','十二月'],
+        monthStatus: '选择月份',
+        yearStatus: '选择年份',
+        weekHeader: '周',
+        weekStatus: '年内周次',
+        dayNames: ['星期日','星期一','星期二','星期三','星期四','星期五','星期六'],
+        dayNamesShort: ['周日','周一','周二','周三','周四','周五','周六'],
+        dayNamesMin: ['日','一','二','三','四','五','六'],
+        dayStatus: '设置 DD 为一周起始',
+        dateStatus: '选择 m月 d日, DD',
+        dateFormat: 'yy-mm-dd',
+        firstDay: 1,
+        initStatus: '请选择日期',
+        isRTL: false};
+    $.datepicker.setDefaults($.datepicker.regional['zh-CN']);
+});

文件差異過大導致無法顯示
+ 1312 - 0
lib/jquery-ui/jquery-ui.css


文件差異過大導致無法顯示
+ 13 - 0
lib/jquery-ui/jquery-ui.min.js


+ 0 - 2
modules/complementary_glj_lib/controllers/gljController.js

@@ -128,8 +128,6 @@ class GljController extends BaseController{
     mixUpdateGljItems(req, res){
         let user_id = req.session.sessionUser.ssoId,
             compilation_id = req.session.sessionCompilation._id;
-        console.log(user_id);
-        console.log(compilation_id);
         let data = JSON.parse(req.body.data);
         let updateItems = data.updateItems,
             addItems = data.addItems,

+ 1 - 2
modules/complementary_glj_lib/models/gljModel.js

@@ -262,11 +262,10 @@ class GljDao {
      */
     async getComponent(gljId) {
         let result = [];
-        let libGljData = await complementaryGljModel.find({ID: gljId});
+        let libGljData = await complementaryGljModel.findOne({ID: gljId});
         if (libGljData === null || libGljData.component.length <= 0) {
             return result;
         }
-
         // 标准工料机库
         let componentIdListStd = [];
         // 补充工料机库

+ 1 - 0
modules/complementary_glj_lib/models/schemas.js

@@ -52,6 +52,7 @@ let stdGljSchema = new Schema({
     gljType: Number,
     shortName: String,
     unit: String,
+    adjCoe: Number,
     component: [stdGjlComponentSchema]
 },{versionKey: false});
 

+ 56 - 28
modules/glj/models/glj_list_model.js

@@ -17,6 +17,7 @@ import MixRatioModel from "./mix_ratio_model";
 import GljModel from "../../complementary_glj_lib/models/gljModel";
 const ProjectModel = require('../../pm/models/project_model').project;
 const scMathUtil = require('../../../public/scMathUtil').getUtil();
+import decimal_facade from "../../main/facade/decimal_facade";
 
 class GLJListModel extends BaseModel {
 
@@ -75,8 +76,11 @@ class GLJListModel extends BaseModel {
      */
     async getListByProjectId(projectId, unitPriceFileId) {
         let gljData = null;
+        let decimal =await decimal_facade.getProjectDecimal(projectId);
+        let quantity_decimal = decimal&&decimal.glj.quantity?decimal.glj.quantity:6;//取消耗量保留小数据位,默认6位
         let mixRatioConnectData = {};
         let mixRationMap={};
+        let keyMap={};
         try {
             // 首先获取对应标段下所有的项目工料机数据
             let condition = {project_id: projectId};
@@ -96,6 +100,8 @@ class GLJListModel extends BaseModel {
             let gljIdList = [];
             for(let tmp of gljData) {
                 gljIdList.push(tmp.id);
+                let c_key = this.getIndex(tmp,['code','name','specs','unit','type']);
+                keyMap[tmp.id] = c_key; //工料机ID和连接key的对照表;
             }
             // 从定额工料机库中获取消耗量
             condition = {
@@ -104,22 +110,25 @@ class GLJListModel extends BaseModel {
             };
             let quantityData = await RationGLJFacade.getQuantityByProjectGLJ(condition);
             let quantityList = {};
-            // 整理数据
+            // 整理数据 得到总定额消耗量
             for (let tmp of quantityData) {
                 let tmpNum = parseFloat(tmp.rationQuantity);
                 tmpNum = isNaN(tmpNum) ||tmpNum==0? 1 : tmpNum;
-                if (quantityList[tmp.projectGLJID] === undefined) {
-                    quantityList[tmp.projectGLJID] = tmp.quantity * tmpNum;
+                let tmp_con_key = keyMap[tmp.projectGLJID];
+
+                if (quantityList[tmp_con_key] === undefined) {
+                    quantityList[tmp_con_key] = scMathUtil.roundTo(tmp.quantity * tmpNum,-quantity_decimal);
                 } else {
-                    quantityList[tmp.projectGLJID] += tmp.quantity * tmpNum;
+                    quantityList[tmp_con_key] = scMathUtil.roundTo(quantityList[tmp_con_key]+tmp.quantity * tmpNum,-quantity_decimal);
                 }
             }
             // 整理获取有组成物的项目工料机的数据
             let connect_keys = [];
             for(let tmp of gljData) {
-                // 有组成物的类型且消耗量大于0才查找
-                if (quantityList[tmp.id] !== undefined) {
-                   let key = this.getIndex(tmp,['code','name','specs','unit','type']);
+                // 有组成物的类型才查找
+                let key = keyMap[tmp.id];
+                if (quantityList[key]!=undefined&&(tmp.type === GLJTypeConst.CONCRETE || tmp.type === GLJTypeConst.MORTAR ||
+                    tmp.type === GLJTypeConst.MIX_RATIO || tmp.type === GLJTypeConst.GENERAL_MACHINE)){
                     connect_keys.push(key);
                 }
             }
@@ -132,11 +141,18 @@ class GLJListModel extends BaseModel {
                 let mixRatioList = await mixRatioModel.findDataByCondition(condition, null, false);
                 for (let tmp of mixRatioList) {
                    let t_index = tmp.connect_key;
-                    let consumption=parseFloat(tmp.consumption);
-                    totalComposition[t_index] = totalComposition[t_index] === undefined ? consumption :
+                   let consumption=parseFloat(tmp.consumption);
+                   let m_index = this.getIndex(tmp,['code','name','specs','unit','type']);
+                   let r_quantity = quantityList[t_index]?quantityList[t_index]:0;
+                   if(quantityList[m_index]!==undefined){
+                       quantityList[m_index]=quantityList[m_index]+r_quantity*consumption;
+                   }else {
+                       quantityList[m_index] = r_quantity*consumption;
+                   }
+                    quantityList[m_index] = scMathUtil.roundTo(quantityList[m_index],-quantity_decimal);
+                /*    totalComposition[t_index] = totalComposition[t_index] === undefined ? consumption :
                         totalComposition[t_index] + consumption;
-                    totalComposition[t_index] = scMathUtil.roundTo(totalComposition[t_index], -4);
-
+                    totalComposition[t_index] = scMathUtil.roundTo(totalComposition[t_index], -quantity_decimal);*/
                     if (mixRatioData[t_index] !== undefined) {
                         mixRatioData[t_index].push(tmp);
                     } else {
@@ -209,11 +225,11 @@ class GLJListModel extends BaseModel {
             let gljId = glj.glj_id + '';
             let projectGljId = glj.id + '';
             // 消耗量赋值
-            glj.quantity = quantityList[projectGljId] !== undefined ? quantityList[projectGljId] : 0;
+         /*   glj.quantity = quantityList[projectGljId] !== undefined ? quantityList[projectGljId] : 0;
             glj.quantity = totalComposition[g_index] !== undefined ? totalComposition[g_index] : glj.quantity;
             glj.quantity = compositionConsumption[g_index] !== undefined ?  glj.quantity + compositionConsumption[g_index] : glj.quantity;
-            glj.quantity = scMathUtil.roundTo(parseFloat(glj.quantity), -3);
-
+            glj.quantity = scMathUtil.roundTo(parseFloat(glj.quantity), -3);*/
+            glj.quantity = quantityList[g_index]?quantityList[g_index]:0;
             // 组成物数据
             gljList[index].ratio_data = mixRatioData[g_index] !== undefined ? mixRatioData[g_index] : [];
             //因为schema中设置base_price 为string 类型,所以要通过中间变量转换为数字再做计算,不然会自动变成字符串类型
@@ -227,7 +243,7 @@ class GLJListModel extends BaseModel {
         glj.unit_price.base_price = glj_basePrice;
         glj.unit_price.market_price = scMathUtil.roundTo(parseFloat(glj.unit_price.market_price), -2);
         // 计算调整基价
-        switch (glj.unit_price.type + '') {
+     /*   switch (glj.unit_price.type + '') {
             // 人工: 调整基价=基价单价*调整系数
             case GLJTypeConst.LABOUR:
                 glj.adjust_price = scMathUtil.roundTo(parseFloat(glj.adjustment * glj_basePrice), -2);
@@ -239,7 +255,7 @@ class GLJListModel extends BaseModel {
             // 材料、主材、设备
             default:
                 glj.adjust_price = glj.unit_price.base_price;
-        }
+        }*/
     }
 
     /**
@@ -281,6 +297,7 @@ class GLJListModel extends BaseModel {
                 throw '没有对应的单价文件';
             }
             let CompositionGLJ=[];
+            let unitPriceModel = new UnitPriceModel();
             // 判断类型,如果是混凝土、砂浆或者配合比则查找对应的组成物(前提是没有对应的项目工料机数据)
             if (data.type === GLJTypeConst.CONCRETE || data.type === GLJTypeConst.MORTAR ||
                 data.type === GLJTypeConst.MIX_RATIO || data.type === GLJTypeConst.GENERAL_MACHINE) {
@@ -289,11 +306,20 @@ class GLJListModel extends BaseModel {
                     await this.compositionInit(data, unitPriceFileId);
                 }
                 CompositionGLJ=await this.getCompositionGLJByData(data,unitPriceFileId);
+                if(isAddProjectGLJ==false&&CompositionGLJ.length==0){//如果不是新增,并且是有组成物的类型但又没有发现组成物的情况下,有可能是错误数据,重新在库中查找一下组成物,有则插入
+                    await this.compositionInit(data, unitPriceFileId);
+                    CompositionGLJ=await this.getCompositionGLJByData(data,unitPriceFileId);
+
+                    if(CompositionGLJ.length>0){//如果这次发现又有组成物了,则把旧的单价数据删除,在后面的操作中会重新增加
+                        let de_condition ={unit_price_file_id: unitPriceFileId,code:data.code,name:data.name,unit:data.unit,type:data.type};
+                        data.specs!=null&&data.specs!=undefined&&data.specs!=""?de_condition.specs = data.specs:de_condition;
+                        await unitPriceModel.db.delete(de_condition);
+                    }
+                }
             }
             projectGljData.subList=CompositionGLJ;
 
             // 新增单价文件
-            let unitPriceModel = new UnitPriceModel();
             let [unitPriceInsertData, isAdd] = await unitPriceModel.addUnitPrice(data, unitPriceFileId);
 
             if (!unitPriceInsertData) {
@@ -350,7 +376,7 @@ class GLJListModel extends BaseModel {
         //查找单价信息,有则返回,没有则新增并返回
         let unitPriceFileId = unitPriceFile.id;
         let unitPriceModel = new UnitPriceModel();
-        let [unitPriceData, isAdd] = await unitPriceModel.addUnitPrice(data, unitPriceFileId);
+        let [unitPriceData, isAdd] = await unitPriceModel.addUnitPrice(data, unitPriceFileId,"modify");
         let gljData={};
         if(isAdd){ //如果是新增,则新增一条新的项目工料机
             data.code = unitPriceData.code;
@@ -448,7 +474,7 @@ class GLJListModel extends BaseModel {
             let unitPriceModel = new UnitPriceModel();
 
             let gljCount = gljListData.length;
-            let [unitPriceData, isAdd] = await unitPriceModel.addUnitPrice(updateData, unitPriceFileId, gljCount);
+            let [unitPriceData, isAdd] = await unitPriceModel.addUnitPrice(updateData, unitPriceFileId,"modify", gljCount);
 
             // 判断是否已存在对应数据
             let includeField = [
@@ -534,7 +560,6 @@ class GLJListModel extends BaseModel {
         // 查找对应组成物的项目工料机数据
         let indexs=['code','name','specs','unit','type'];
         let [projectGljList, compositionGljList] = await this.getCompositionGLJList(gljId, projectId, indexs, fromTable);
-
         // 整理配合比待插入数据
         let mixRatioInsertData = [];
         for (let tmp of compositionGljList) {
@@ -556,13 +581,16 @@ class GLJListModel extends BaseModel {
 
         // 插入配合比表
         // 因为有可能项目工料机与单价数据已存在,但配合比数据不存在,所以先插入配合比,后续判断如果存在项目工料机则可以省下数据库操作
-        let mixRatioModel = new MixRatioModel();
-        let addMixRatioResult = await mixRatioModel.add(mixRatioInsertData);
-        if (!addMixRatioResult) {
-            throw '组成物插入单价数据失败!';
+        if(mixRatioInsertData.length>0){
+            let mixRatioModel = new MixRatioModel();
+            let addMixRatioResult = await mixRatioModel.add(mixRatioInsertData);
+            if (!addMixRatioResult) {
+                throw '组成物插入单价数据失败!';
+            }
         }
+        let pglj_length = projectGljList instanceof Array ? projectGljList.length:Object.getOwnPropertyNames(projectGljList).length;
         // 如果已经存在则后续操作停止
-        if(Object.getOwnPropertyNames(projectGljList).length === compositionGljList.length) {
+        if(pglj_length === compositionGljList.length) {
             return
         }
 
@@ -583,6 +611,7 @@ class GLJListModel extends BaseModel {
                 specs: tmp.specs,
                 unit: tmp.unit === undefined ? '' : tmp.unit,
                 type: tmp.gljType,
+                adjCoe:tmp.adjCoe,
                 original_code:tmp.code
             };
             gljInsertData.push(gljData);
@@ -675,9 +704,8 @@ class GLJListModel extends BaseModel {
         // 获取对应的组成物数据
         let gljListModel = fromTable === 'std' ? new STDGLJLibGLJListModel() : new GljModel();
         let componentGljList = await gljListModel.getComponent(gljId);
-
         if (componentGljList.length <= 0) {
-            throw '不存在对应的组成物';
+            return [{},[]];
         }
 
         let codeList = [];
@@ -726,7 +754,7 @@ class GLJListModel extends BaseModel {
         let typeList = [];
         let unitList = [];
         if(mixRatios.length<=0){
-            throw  '不存在对应的组成物';
+            return [[],[],[]];
         }
         let mixRatioData={};
         for(let tmp of mixRatios) {

+ 2 - 5
modules/glj/models/schemas/glj.js

@@ -62,11 +62,8 @@ let modelSchema = {
         type: Number,
         default: 0
     },
-    // 调整系数
-    adjustment: {
-        type: Number,
-        default: 1
-    },
+    // 调整系数ID
+    adjCoe: Number,
 
     // 规格型号
     specs: {

+ 23 - 13
modules/glj/models/unit_price_model.js

@@ -80,7 +80,7 @@ class UnitPriceModel extends BaseModel {
      * @param {Number} gljCount
      * @return {Promise} 返回数据以及是否新增
      */
-    async addUnitPrice(data, unitPriceFileId, gljCount = 0) {
+    async addUnitPrice(data, unitPriceFileId,operation='add', gljCount = 0) {
         if (data.original_code===undefined||data.code === undefined || data.project_id === undefined || data.name === undefined
             || data.market_price === undefined) {
             return [null, false];
@@ -88,7 +88,12 @@ class UnitPriceModel extends BaseModel {
         // 先查找是否有原始code相同的记录
         let unitPriceData = await this.findDataByCondition({original_code: data.original_code, unit_price_file_id: unitPriceFileId}, null, false);
         // 如果有记录,判断是否存在一样的名称,单位...等,有则直接返回数据
-        let unitPrice =  this.isPropertyInclude(unitPriceData,['name','specs','unit','type'],data);
+        let unitPrice=null;
+        if(operation=='add'){//新增操作时,要把code也一起判断,是否完全一样
+            unitPrice =  this.isPropertyInclude(unitPriceData,['code','name','specs','unit','type'],data);
+        }else {//修改操作时,code不用加入判断,因为code是需要改变的
+            unitPrice =  this.isPropertyInclude(unitPriceData,['name','specs','unit','type'],data);
+        }
         if(unitPrice){
             return [unitPrice, false];
         }
@@ -111,11 +116,12 @@ class UnitPriceModel extends BaseModel {
             unit:data.unit,
             type: data.type,
             short_name: data.shortName !== undefined ? data.shortName : '',
-            glj_id: data.glj_id
+            glj_id: data.glj_id,
+            is_add:0
         };
-
-        // 如果原始编码能找到,但不存在一样的编号,名称,单位.更改 等,更改code和添加新增标记
-        if (unitPriceData&&unitPriceData.length>0) {
+        if(data.from=='cpt'){//如果是来自补充工料机,则都添加新增标记
+            insertData.is_add=1;
+        }else if (unitPriceData&&unitPriceData.length>0) {// 如果原始编码能找到,但不存在一样的编号,名称,单位.型号等,更改code和添加新增标记
             insertData.code = data.original_code+"-"+unitPriceData.length;
             insertData.is_add=1;
         }
@@ -322,21 +328,27 @@ class UnitPriceModel extends BaseModel {
         currentUnitList = JSON.parse(currentUnitList);
 
         let codeList = [];
+        let nameList =[];
         for (let tmp of currentUnitList) {
             if (codeList.indexOf(tmp.code) >= 0) {
                 continue;
             }
             codeList.push(tmp.code);
+            if(nameList.indexOf(tmp.name)>=0){
+                continue
+            }
+            nameList.push(tmp.name);
         }
 
-        // 查找即将更替的单价文件是否存在对应的工料机数据
-        let condition = {unit_price_file_id: changeUnitPriceId, code: {"$in": codeList}};
-        let targetUnitList = await this.findDataByCondition(condition, null, false, 'code');
+        // 查找即将更替的单价文件是否存在对应的工料机数据 -- (这里只根据code和名称初步过滤,因为其它的几项更改的概率不大,在下一步的比较中再精确匹配)
+        let condition = {unit_price_file_id: changeUnitPriceId, code: {"$in": codeList},name:{"$in": nameList}};
+        let targetUnitList = await this.findDataByCondition(condition, null, false, ['code','name','specs','unit','type']);
 
         // 如果没有重叠的数据则原有的数据都复制一份
         let insertData = [];
         for (let tmp of currentUnitList) {
-            if (targetUnitList !== null && targetUnitList[tmp.code] !== undefined) {
+            let t_index = this.getIndex(tmp,['code','name','specs','unit','type']);
+            if (targetUnitList !== null && targetUnitList[t_index] !== undefined) {
                 continue;
             }
             // 删除原有id信息
@@ -345,9 +357,7 @@ class UnitPriceModel extends BaseModel {
             tmp.unit_price_file_id = changeUnitPriceId;
             insertData.push(tmp);
         }
-
-        return insertData.length > 0 ? this.add(currentUnitList) : true;
-
+        return insertData.length > 0 ? this.add(insertData) : true;
     }
 
 

+ 116 - 0
modules/main/controllers/quantity_detail_controller.js

@@ -0,0 +1,116 @@
+/**
+ * Created by zhang on 2017/11/24.
+ */
+
+var quantity_detail_data = require('../facade/quantity_detail_facade');
+let logger = require("../../../logs/log_helper").logger;
+module.exports={
+    save:save,
+    update:update,
+    updateRegex:updateRegex,
+    insertRecode:insertRecode,
+    deleteRecode:deleteRecode,
+    swapRow:swapRow
+}
+
+async function save(req, res) {
+    let result={
+        error:0
+    }
+    try {
+        let data = req.body.data;
+        data = JSON.parse(data);
+        let datas= await quantity_detail_data.saveQuantityDetail(data);
+        result.data=datas;
+    }catch (err){
+        logger.err(err);
+        result.error=1;
+        result.message = err.message;
+    }
+    res.json(result);
+}
+
+async function update(req, res) {
+    let result={
+        error:0
+    }
+    try {
+        let data = req.body.data;
+        data = JSON.parse(data);
+        let datas= await quantity_detail_data.update(data);
+        result.data=datas;
+    }catch (err){
+        logger.err(err);
+        result.error=1;
+        result.message = err.message;
+    }
+    res.json(result);
+}
+
+async function updateRegex(req, res) {
+    let result={
+        error:0
+    }
+    try {
+        let data = req.body.data;
+        data = JSON.parse(data);
+        let datas= await quantity_detail_data.updateRegex(data);
+        result.data=datas;
+    }catch (err){
+        logger.err(err);
+        result.error=1;
+        result.message = err.message;
+    }
+    res.json(result);
+}
+
+async function insertRecode(req, res) {
+    let result={
+        error:0
+    }
+    try {
+        let data = req.body.data;
+        data = JSON.parse(data);
+        let datas= await quantity_detail_data.insertRecode(data);
+        result.data=datas;
+    }catch (err){
+        logger.err(err);
+        result.error=1;
+        result.message = err.message;
+    }
+    res.json(result);
+}
+
+async function deleteRecode(req, res) {
+    let result={
+        error:0
+    }
+    try {
+        let data = req.body.data;
+        data = JSON.parse(data);
+        let datas= await quantity_detail_data.deleteRecode(data);
+        result.data=datas;
+    }catch (err){
+        logger.err(err);
+        result.error=1;
+        result.message = err.message;
+    }
+    res.json(result);
+}
+
+async function swapRow(req, res) {
+    let result={
+        error:0
+    }
+    try {
+        let data = req.body.data;
+        data = JSON.parse(data);
+        let datas= await quantity_detail_data.swapRow(data);
+        result.data=datas;
+    }catch (err){
+        logger.err(err);
+        result.error=1;
+        result.message = err.message;
+    }
+    res.json(result);
+}

+ 33 - 0
modules/main/facade/decimal_facade.js

@@ -0,0 +1,33 @@
+/**
+ * Created by zhang on 2017/11/22.
+ */
+let projectsModel = require("../../pm/models/project_schema");
+let _ = require('lodash');
+
+module.exports ={
+    getProjectDecimal:getProjectDecimal,
+    getBillsQuantityDecimal:getBillsQuantityDecimal
+}
+
+async function getProjectDecimal(projectID) {
+    let decimal = null;
+    let project =await projectsModel.findOne({ID:projectID});
+    if(project){
+        decimal = project.property.decimal
+    }
+    return decimal;
+}
+
+async function getBillsQuantityDecimal(projectID,unit) {
+    let decimal = null;
+    let project =await projectsModel.findOne({ID:projectID});
+    if(project){
+        let billsQuantityDecimal = project.property.billsQuantityDecimal;
+        let el = _.find(billsQuantityDecimal,{'unit':unit});
+        if(!el){
+            el = billsQuantityDecimal[0];
+        }
+        decimal = el.decimal
+    }
+    return decimal;
+}

+ 2 - 1
modules/main/facade/prj_properties_facade.js

@@ -5,6 +5,7 @@ let mongoose = require("mongoose");
 let prj_prop_mdl = mongoose.model("project_property");
 let std_prj_prop_mdl = mongoose.model("cfg_prj_property");
 
+
 module.exports = {
     createPrjProperties: createPrjProperties,
     removePrjProperties : removePrjProperties,
@@ -126,4 +127,4 @@ function addOrChangePropertyWithCallBack(prjId, newItem, cb) {
 
 function removeOnePropertyWithCallBack(prjId, key, cb) {
     prj_prop_mdl.update({"projectID": prjId}, {"$pull": {"properties": {"key": key}}}, cb);
-}
+}

+ 216 - 297
modules/ration_glj/facade/quantity_detail_facade.js

@@ -15,6 +15,7 @@ const uuidV1 = require('uuid/v1');
 let ration_model = mongoose.model('ration');
 let bill_model=mongoose.model("bills");
 const scMathUtil = require('../../../public/scMathUtil').getUtil();
+let decimal_facade = require('./decimal_facade');
 
 module.exports={
     save:save,
@@ -22,58 +23,143 @@ module.exports={
     deleteByRation:deleteByRation,
     deleteByBill:deleteByBill,
     quantityEditChecking:quantityEditChecking,
+    saveQuantityDetail:saveQuantityDetail,
+    update:update,
+    updateRegex:updateRegex,
+    insertRecode:insertRecode,
+    deleteRecode:deleteRecode,
+    swapRow:swapRow
 };
 
 let operationMap={
-    'ut_create':create_quantity_detail,
     'ut_update':update_quantity_detail,
-    'ut_delete':delete_quantity_detail
 };
 
 let updateFunctionMap = {
-    'normalUpdate':normalUpdate,
-    'updateQuantityRegex':updateQuantityRegex,
     'insertRecode':insertRecode
 }
 
-function create_quantity_detail(user_id,datas) {
-    return function (callback) {
-        let doc = datas.doc;
-        doc.ID = uuidV1();
-        if(doc.hasOwnProperty('regex')){
-            insertRecodeWithReg(doc).then(function (resultObject) {
-                if(resultObject.err){
-                    callback(null,{
-                        moduleName:consts.projectConst.QUANTITY_DETAIL,
-                        err:{
-                            message:result.err.message
-                        }
-                    });
-                } else {
-                    callback(null,resultObject.return_list);
-                }
-            })
-        }else {
-            createNormalRecode(doc,callback)
-        }
+async function saveQuantityDetail(datas) {
+    console.log(datas);
+    let doc = datas;
+    doc.ID = uuidV1();
+    if(doc.refreshQuantity==false){ //如果选择了不替换工程量,则清空
+        await cleanQuantityDetail(doc);
+        return
+    }
+    if(doc.hasOwnProperty('regex')){
+       return await insertRecodeWithReg(doc);
+    }else {
+        return await createNormalRecode(doc);
+    }
+}
 
+async function update(datas) {
+    let doc = datas.doc;
+    if(doc.hasOwnProperty('isSummation')){
+        return await doIsSummationUpdate(datas.query,doc);
+    }else {
+       await quantity_detail_model.update(datas.query,doc);
+       return datas;
     }
 }
 
-function insertRecode(user_id,datas) {
-    return function (callback) {
-        let doc = datas.doc;
-        doc.ID = uuidV1();
-        doInsertRecode(doc).then(function (result) {
-            //console.log(result);
-            if(result.err){
-                callback(result.err,'')
-            }else {
-                callback(null,result.returndata)
-            }
+async function swapRow(datas) {
+    for(let task of datas){
+        await update(task);
+    }
+    return {refreshList:datas};
+}
+
+async function updateRegex(datas) {
+    let resultObjec ={};
 
-        })
+    let detailList = [];
+    let node ={};
+    let decimal =await decimal_facade.getProjectDecimal(datas.query.projectID);
+    if(datas.query.hasOwnProperty('rationID')){
+        detailList = await quantity_detail_model.find({'projectID':datas.query.projectID,'rationID':datas.query.rationID}).sort('seq').exec();
+        node.type = consts.projectConst.RATION;
+        node.ID = datas.query.rationID;
+    }else {
+        detailList = await quantity_detail_model.find({'projectID':datas.query.projectID,'billID':datas.query.billID}).sort('seq').exec();
+        node.type = consts.projectConst.BILLS;
+        node.ID=datas.query.billID;
     }
+    let regex;
+    let result;
+    if(datas.doc.regex==null){
+        result=0;
+        datas.doc.referenceIndexs=[];
+    }else {
+        regex = datas.doc.regex.toUpperCase();
+        let referenceIndexs = datas.doc.referenceIndexs;
+        result = getEvalResult(referenceIndexs,detailList,regex,decimal);
+    }
+    detailList[datas.query.index].result =result;
+    detailList[datas.query.index].regex=datas.doc.regex;
+    detailList[datas.query.index].referenceIndexs =datas.doc.referenceIndexs;
+    let updateTasks =[];
+    datas.doc.result=result;
+    updateTasks.push({
+        query:{
+            ID:datas.query.ID,
+            projectID:datas.query.projectID
+        },
+        doc:datas.doc
+    })
+    updateReferenceRecode(datas.query.index+1,detailList,updateTasks);
+    await quantity_detail_model.bulkWrite(generateUpdateTaks(updateTasks));
+    resultObjec.refreshList=updateTasks;
+    if(datas.query.refreshQuantity==true){
+        let data = {};
+        data.quantity = await summateResults(datas.query,detailList,decimal);
+        data.isFromDetail = 1;
+        node.data = data;
+        resultObjec.node = node;
+    }
+    return resultObjec;
+
+}
+
+
+async function cleanQuantityDetail(doc) {
+    let query = {
+        projectID:doc.projectID
+    }
+    if(doc.hasOwnProperty('rationID')){
+        query.rationID = doc.rationID;
+    }else {
+        query.billID = doc.billID;
+    }
+    await quantity_detail_model.deleteMany(query);
+}
+
+
+async function insertRecode(doc){
+    let query = {
+        projectID:doc.projectID,
+        seq : { $gte: doc.seq }
+    }
+    if(doc.hasOwnProperty('rationID')){
+        query.rationID = doc.rationID;
+    }else {
+        query.billID = doc.billID;
+    }
+    let quantity_detail_List = await getDatailList(doc,{data:{}});
+    let update_task = getUpdateReferenceTask(quantity_detail_List,doc.seq,1);
+    await quantity_detail_model.update(query,{$inc:{seq:1}},{multi: true});
+    if(update_task.length>0){
+        await quantity_detail_model.bulkWrite(generateUpdateTaks(update_task));
+    }
+    doc.ID = uuidV1();
+    let newrecode = await quantity_detail_model.create(doc);
+    let data={
+        doc:newrecode,
+        resort:true,
+        update_task:update_task
+    }
+    return data;
 }
 
 async function doInsertRecode(doc) {
@@ -150,229 +236,78 @@ function getUpdateReferenceTask(quantity_detail_List,seq,re) {
 }
 
 
-function createNormalRecode(doc,callback) {
-    quantity_detail_model.create(doc,(err,result)=>{
-        if(err){
-            callback(err,null);
-        }else {
-            console.log(result);
-            let returndata ={
-                updateTpye:commonConsts.UT_CREATE,
-                moduleName:consts.projectConst.QUANTITY_DETAIL,
-                data:result
-            }
-            callback(null,returndata)
-        }
-    });
+async function createNormalRecode(doc) {
+    let result ={};
+    result.newRecord= await quantity_detail_model.create(doc);
+    return result;
 }
 
 
 async function insertRecodeWithReg (doc) {
-    let returnObjec={
-        err:null,
-        return_list:[]
-    }
+    let returnObjec={}
     try {
-        let returnData={
-            moduleName:'',
-            data:{
-                updateTpye:commonConsts.UT_UPDATE,
-            }
-        }
+        let decimal =await decimal_facade.getProjectDecimal(doc.projectID);
         let regex = doc.regex.toUpperCase();
         let referenceIndexs = doc.referenceIndexs;
-        let detailList = await getDatailList(doc,returnData);
-        doc.result =getEvalResult(referenceIndexs,detailList,regex);
+        let detailList = await getDatailList(doc,returnObjec);
+        doc.result =getEvalResult(referenceIndexs,detailList,regex,decimal);
         let refreshQuantity =false;
         if(doc.refreshQuantity==true){
             refreshQuantity = true;
         }
         delete doc.refreshQuantity;
-        let newRecode = await quantity_detail_model.create(doc) ;
-        detailList.push(newRecode);
+        let newRecord = await quantity_detail_model.create(doc) ;
+        detailList.push(newRecord);
         if(refreshQuantity==true){
-            returnData.data.quantity = await summateResuts(doc,detailList);
-            returnData.data.quantityRefresh = true;
-            returnObjec.return_list.push(returnData);
+            let data = {};
+            data.quantity = await summateResults(doc,detailList,decimal);
+            data.isFromDetail = 1;
+            returnObjec.node.data = data;
         }
-        returnObjec.return_list.push({
-            updateTpye:commonConsts.UT_CREATE,
-            moduleName:consts.projectConst.QUANTITY_DETAIL,
-            data:newRecode
-        });
+        returnObjec.newRecord = newRecord;
         return returnObjec;
     }catch (error){
-        returnObjec.err = new Error('输入的表达式有误,请重新输入!');
-        console.log(error)
-        return returnObjec;
-    }
-
-
-}
-
-
-
-function normalUpdate(user_id,datas) {
-    return function(callback) {
-        if(datas.doc.hasOwnProperty('isSummation')){
-            doIsSummationUpdate(datas.query,datas.doc).then(function (sresult) {
-                let returndata ={
-                    moduleName:consts.projectConst.QUANTITY_DETAIL,
-                    data:{
-                        updateTpye:commonConsts.UT_UPDATE,
-                        query:datas.query,
-                        doc:datas.doc
-                    }
-                }
-                let retrunArr = [];
-                retrunArr.push(returndata);
-                if(sresult){
-                    retrunArr.push(sresult);
-                }
-                callback(null,retrunArr);
-            })
-        }else {
-            updateRecored(datas.query,datas.doc,callback);
-        }
-    }
-}
-function updateQuantityRegex(user_id,datas) {
-    return function(callback){
-        doRegexUpdate(datas).then(function (result) {
-            if(result.err){
-                callback(null,{
-                    moduleName:consts.projectConst.QUANTITY_DETAIL,
-                    err:{
-                        message:result.err.message
-                    }
-                });
-            }else {
-                callback(null,result.rList);
-            }
-
-        })
+        console.log(error);
+        throw new Error('输入的表达式有误,请重新输入!');
     }
 }
 
 async function doIsSummationUpdate(query,doc) {
-    try {
-        let returnData={
-            moduleName:'',
-            data:{
-                updateTpye:commonConsts.UT_UPDATE,
-            }
-        }
-        let  refreshQuantity=false;
-        if(query.refreshQuantity==true){
-            refreshQuantity=true;
-        }
-        delete query.refreshQuantity;
-        let updateDoc = await quantity_detail_model.update(query,doc);
-        let detailList = await getDatailList(query,returnData);
-        if(refreshQuantity==true){
-            let quantity = await summateResuts(query,detailList);
-            returnData.data.quantity = quantity;
-            returnData.data.quantityRefresh = true;
-            return returnData;
-        }else {
-            return null;
-        }
-    }catch (error){
-        console.log(error)
+    let returnObjec={query:query,doc:doc};
+    let decimal =await decimal_facade.getProjectDecimal(query.projectID);
+    let  refreshQuantity=false;
+    if(query.refreshQuantity==true){
+        refreshQuantity=true;
     }
+    delete query.refreshQuantity;
+    await quantity_detail_model.update(query,doc);
+    let detailList = await getDatailList(query,returnObjec);
+    if(refreshQuantity==true){
+        let data = {};
+        data.quantity = await summateResults(query,detailList,decimal);
+        data.isFromDetail = 1;
+        returnObjec.node.data = data;
+    }
+    return returnObjec;
 }
 
 async function getDatailList(query,resultObject) {
     let detailList = [];
+    let node={};
     if(query.hasOwnProperty('rationID')){
         detailList = await quantity_detail_model.find({'projectID':query.projectID,'rationID':query.rationID}).sort('seq').exec();
-        resultObject.moduleName = consts.projectConst.RATION;
-        resultObject.data.rationID=query.rationID;
-
+        node.type = consts.projectConst.RATION;
+        node.ID = query.rationID;
     }else {
         detailList = await quantity_detail_model.find({'projectID':query.projectID,'billID':query.billID}).sort('seq').exec();
-        resultObject.moduleName = consts.projectConst.BILLS;
-        resultObject.data.billID=query.billID;
+        node.type = consts.projectConst.BILLS;
+        node.ID=query.billID;
     }
+    resultObject.node = node;
     return detailList;
 }
 
 
-async function doRegexUpdate(datas) {
-    let resultObjec ={
-        err:null,
-        rList:[]
-    }
-    try {
-        let detailList = [];
-        let quantityResult ={
-            moduleName:'',
-            data:{
-                updateTpye:commonConsts.UT_UPDATE,
-                quantityRefresh:true
-            }
-        }
-        if(datas.query.hasOwnProperty('rationID')){
-            detailList = await quantity_detail_model.find({'projectID':datas.query.projectID,'rationID':datas.query.rationID}).sort('seq').exec();
-            quantityResult.moduleName = consts.projectConst.RATION;
-            quantityResult.data.rationID = datas.query.rationID;
-        }else {
-            detailList = await quantity_detail_model.find({'projectID':datas.query.projectID,'billID':datas.query.billID}).sort('seq').exec();
-            quantityResult.data.billID = datas.query.billID;
-            quantityResult.moduleName = consts.projectConst.BILLS;
-        }
-        let regex;
-        let result;
-        if(datas.doc.regex==null){
-            result=0
-            datas.doc.referenceIndexs=[];
-        }else {
-            regex = datas.doc.regex.toUpperCase();
-            let referenceIndexs = datas.doc.referenceIndexs;
-            result =getEvalResult(referenceIndexs,detailList,regex);
-        }
-        detailList[datas.query.index].result =result;
-        detailList[datas.query.index].regex=datas.doc.regex;
-        detailList[datas.query.index].referenceIndexs =datas.doc.referenceIndexs;
-        let updateTasks =[];
-        datas.doc.result=result;
-        updateTasks.push({
-            query:{
-                ID:datas.query.ID,
-                projectID:datas.query.projectID
-            },
-            doc:datas.doc
-        })
-        updateReferenceRecode(datas.query.index+1,detailList,updateTasks);
-        let updateEdit = await quantity_detail_model.bulkWrite(generateUpdateTaks(updateTasks));
-        resultObjec.rList.push(gernerateResultList(updateTasks));
-        if(datas.query.refreshQuantity==true){
-            quantityResult.data.quantity = await summateResuts(datas.query,detailList);
-            resultObjec.rList.push(quantityResult);
-        }
-        return resultObjec;
-    }catch (error){
-        console.log(error);
-        resultObjec.err=error;
-        return resultObjec
-
-    }
-}
-
-function gernerateResultList(updateTasks) {
-    let returndata ={
-        moduleName:consts.projectConst.QUANTITY_DETAIL,
-        data:{
-            updateTpye:commonConsts.UT_UPDATE,
-            refreshList:updateTasks
-        }
-    }
-    return returndata;
-
-}
-
-
-
 function updateReferenceRecode(index,detailList,updateTasks) {
     for(let d of detailList){
         if(_.includes(d.referenceIndexs,index)){
@@ -394,34 +329,52 @@ function updateReferenceRecode(index,detailList,updateTasks) {
 }
 
 
-async function summateResuts (query,detailList) {
+async function summateResults (query,detailList,decimal) {
     let quantity = 0;
+     decimal = decimal?decimal:await decimal_facade.getProjectDecimal(query.projectID);
     for(let d of detailList){
         if(d.isSummation==1){
-            let result = d.result==null||d.result==undefined?0:d.result
+            let result = d.result==null||d.result==undefined?0:d.result;
             quantity+=result;
         }
     }
-    quantity = quantity.toFixed(4);
     if(query.hasOwnProperty('rationID')){
+        let ration = await  ration_model.findOne({'ID':query.rationID,'projectID':query.projectID,deleteInfo: null});
+        quantity = getQuantityByUnit(quantity,ration.unit);
+        quantity = scMathUtil.roundTo(quantity, -decimal.ration.quantity);
         await ration_model.update({'ID':query.rationID,'projectID':query.projectID,deleteInfo: null},{quantity:quantity,isFromDetail:1});
     }else {
+        let bill = await bill_model.findOne({'ID':query.billID,'projectID':query.projectID,deleteInfo: null});
+        decimal = await decimal_facade.getBillsQuantityDecimal(query.projectID,bill.unit);
+        quantity = getQuantityByUnit(quantity,bill.unit);
+        quantity = scMathUtil.roundTo(quantity, -decimal);
         await bill_model.update({'ID':query.billID,'projectID':query.projectID,deleteInfo: null},{quantity:quantity,isFromDetail:1});
     }
     return quantity
 }
 
+function getQuantityByUnit(quantity,unit) {
+    if(unit){
+        let times = parseInt(unit);
+        if(isNaN(times)){
+            times = 1
+        }
+        quantity = quantity/times;
+    }
+    return quantity;
+}
 
 
-function  getEvalResult(referenceIndexs,detailList,regex) {
+ function  getEvalResult(referenceIndexs,detailList,regex,decimal={}) {
     try {
+        decimal = decimal.quantity_detail?decimal.quantity_detail:4;
         for(let i of referenceIndexs){
             regex = replaceReference(i,detailList,regex)
         }
         console.log('replace all C reference -----'+regex);
         regex =replaceSqr(regex);
         console.log('replace all sqar reference -----'+regex);
-        return scMathUtil.roundTo(eval(regex), -4);
+        return scMathUtil.roundTo(eval(regex), -decimal);
     }catch (error){
         console.log(error);
         throw new Error('输入的表达式有误,请重新输入!');
@@ -492,26 +445,6 @@ function converSqrByArr (sqararr,text) {
     return temp;
 };
 
-
-function updateRecored(query,doc,callback) {
-    quantity_detail_model.update(query,doc,(err,result)=>{
-        if(err){
-            callback(err,'');
-        }else {
-            let returndata ={
-                moduleName:consts.projectConst.QUANTITY_DETAIL,
-                data:{
-                    updateTpye:commonConsts.UT_UPDATE,
-                    query:query,
-                    doc:doc
-                }
-            }
-            callback(null,returndata);
-        }
-    })
-}
-
-
 function update_quantity_detail(user_id,datas) {
     if(datas.updateFunction){
         return updateFunctionMap[datas.updateFunction](user_id,datas);
@@ -520,57 +453,43 @@ function update_quantity_detail(user_id,datas) {
     }
 }
 
-function delete_quantity_detail(user_id,datas) {
-    return function (callback) {
-        doQuantityDelete(datas.doc).then(function (result) {
-            if(result.err){
-                callback(result.err,'')
-            }else {
-                callback(null,result.returndata)
-            }
-        });
-    }
-}
 
-async function doQuantityDelete(doc) {;
-    let result={
-        err:null
+async function deleteRecode(doc) {
+    let resultObject = {};
+    let decimal =await decimal_facade.getProjectDecimal(doc.projectID);
+
+    let query = {
+        projectID:doc.projectID,
+        seq : { $gt: doc.seq }
     }
-    try{
-        let query = {
-            projectID:doc.projectID,
-            seq : { $gt: doc.seq }
-        }
-        if(doc.hasOwnProperty('rationID')){
-            query.rationID = doc.rationID;
-        }else {
-            query.billID = doc.billID;
-        }
-        let quantity_detail_List = await getDatailList(doc,{data:{}});
-        let update_task = getUpdateReferenceTask(quantity_detail_List,doc.seq,-1);
-        await quantity_detail_model.update(query,{$inc:{seq:-1}},{multi: true});
-        if(update_task.length>0){
-            await quantity_detail_model.bulkWrite(generateUpdateTaks(update_task));
-        }
-        await quantity_detail_model.deleteOne({ID:doc.ID,projectID:doc.projectID});
-        let returndata ={
-            moduleName:consts.projectConst.QUANTITY_DETAIL,
-            data:{
-                doc:doc,
-                resort:true,
-                update_task:update_task,
-                updateTpye:commonConsts.UT_DELETE
-            }
-        };
-        result.returndata =returndata
-        return result;
-    }catch (error){
-        console.log(error)
-        result.err;
-        return result
+    if(doc.hasOwnProperty('rationID')){
+        query.rationID = doc.rationID;
+    }else {
+        query.billID = doc.billID;
     }
+    let quantity_detail_List = await getDatailList(doc,resultObject);
+    let update_task = getUpdateReferenceTask(quantity_detail_List,doc.seq,-1);
+    await quantity_detail_model.update(query,{$inc:{seq:-1}},{multi: true});
+    if(update_task.length>0){
+        await quantity_detail_model.bulkWrite(generateUpdateTaks(update_task));
+    }
+    await quantity_detail_model.deleteOne({ID:doc.ID,projectID:doc.projectID});
+    _.remove(quantity_detail_List,{ID:doc.ID,projectID:doc.projectID});
+
+    let n_data = {};//更新节点信息
+    n_data.quantity = await summateResults(query,quantity_detail_List,decimal);
+    n_data.isFromDetail = 1;
+    resultObject.node.data = n_data;
+
+    let r_data ={
+            doc:doc,
+            resort:true,
+            update_task:update_task,
+            updateTpye:commonConsts.UT_DELETE
+        };
+    resultObject.data = r_data;
 
-
+    return resultObject;
 }
 
 

+ 2 - 1
modules/main/models/bills.js

@@ -11,7 +11,7 @@ let counter = require("../../../public/counter/counter.js");
 let consts = require('./project_consts');
 let projectConsts = consts.projectConst;
 let commonConsts = consts.commonConst;
-let quantity_detial = require('../../ration_glj/facade/quantity_detail_facade');
+let quantity_detial = require('../facade/quantity_detail_facade');
 
 
 let billsSchema = new Schema({
@@ -30,6 +30,7 @@ let billsSchema = new Schema({
     feeRate:String,
     isFromDetail:{type: Number,default:0},//1 true 0 false
     programID: Number,
+    calcBase: String,
     // 工程量计算规则
     ruleText: String,
     // 说明

+ 3 - 3
modules/main/models/project.js

@@ -7,11 +7,11 @@ var GLJData = require('./glj');
 var ration_glj_data = require('../../ration_glj/facade/ration_glj_facade');
 var ration_coe_data = require('../../ration_glj/facade/ration_coe_facade');
 var ration_ass_data = require('../../ration_glj/facade/ration_ass_facade');
-var quantity_detail_data = require('../../ration_glj/facade/quantity_detail_facade');
+var quantity_detail_data = require('../facade/quantity_detail_facade');
 var fee_rate_data = require('../../fee_rates/facade/fee_rates_facade');
 let projCounter = require('./proj_counter_model');
 let projSetting = require('./proj_setting_model');
-let volumePriceData = require('../../volume_price/models/volume_price_model');
+// let volumePriceData = require('../../volume_price/models/volume_price_model');
 var labour_coe_facade = require('../facade/labour_coe_facade');
 var calc_program_facade = require('../facade/calc_program_facade');
 
@@ -33,7 +33,7 @@ moduleMap[projectConsts.RATION_ASS] = ration_ass_data;
 moduleMap[projectConsts.QUANTITY_DETAIL] = quantity_detail_data;
 moduleMap[projCounter.collectionName] = projCounter;
 moduleMap[projSetting.collectionName] = projSetting;
-moduleMap[volumePriceData.collectionName] = volumePriceData;
+// moduleMap[volumePriceData.collectionName] = volumePriceData;
 moduleMap[projectConsts.FEERATE] = fee_rate_data;
 moduleMap[projectConsts.LABOUR_COE] = labour_coe_facade;
 moduleMap[projectConsts.CALC_PROGRAM] = calc_program_facade;

+ 2 - 2
modules/main/models/project_consts.js

@@ -13,7 +13,7 @@ let projectConst = {
     GLJLIST: 'GLJList',
     UNITPRICEFILE: 'unitPriceFile',
     PROPERTIES: 'properties',
-    VOLUMEPRICE: 'volume_price',
+    // VOLUMEPRICE: 'volume_price',
     FEERATE:'feeRate',
     LABOUR_COE:'labour_coe',
     CALC_PROGRAM:'calc_program'
@@ -31,7 +31,7 @@ let projectConstList = [
     'GLJList',
     'unitPriceFile',
     'properties',
-    'volume_price',
+    // 'volume_price',
     'feeRate',
     'labour_coe',
     'calc_program'

+ 20 - 17
modules/main/models/ration.js

@@ -11,7 +11,7 @@ let counter = require("../../../public/counter/counter.js");
 let consts = require('./project_consts');
 let projectConsts = consts.projectConst;
 let commonConsts = consts.commonConst;
-let quantity_detial = require('../../ration_glj/facade/quantity_detail_facade');
+let quantity_detial = require('../facade/quantity_detail_facade');
 
 var rationAssItemSchema = mongoose.Schema({
     name: String,
@@ -26,35 +26,38 @@ var rationAssItemSchema = mongoose.Schema({
     maxValue: String
 }, { _id: false });
 
+// 定额、量价、工料机定额 合并存储
 let rationSchema = new Schema({
+    // 公用属性部分
     ID: Number,
     projectID: Number,
     billsItemID: Number,
     serialNo: Number,
-    libID: Number,
     code: String,
     name: String,
-    maskName: String,
-    caption: String,
     unit: String,
-    quantity: String, // Decimal
-    isFromDetail:{type: Number,default:0},  //1 true 2 false
+    quantity: String,
     programID: Number,
+    marketUnitFee: String,
+    marketTotalFee: String,
+    fees: [subSchema.feesSchema],
+    deleteInfo: deleteSchema,
+    type: Number,                               // 1 定额、2 量价、3 工料机定额
+    subType: Number,                            // 子类型:1人工、201材料、301机械、4主材、5设备
+
+    // 定额特有属性:
+    libID: Number,
+    maskName: String,
+    caption: String,
+    isFromDetail:{type: Number,default:0},       // 1 true 2 false
     adjustState: String,
     content: String,
     rationProjName: String,
-    // 说明
-    comments: String,
-    // 费用字段
-    fees: [subSchema.feesSchema],
-    // 标记字段
-    flags: [subSchema.flagsSchema],
-    deleteInfo: deleteSchema,
+    comments: String,                           // 说明
+    flags: [subSchema.flagsSchema],             // 标记字段
     rationAssList: [rationAssItemSchema],
-    // 工作内容
-    content: String,
-    // 计算规则
-    ruleText: String
+    content: String,                            // 工作内容
+    ruleText: String                            // 计算规则
 });
 
 let ration = db.model("ration", rationSchema, "ration");

+ 2 - 2
modules/main/models/schemas/proj_counter.js

@@ -8,8 +8,8 @@ let collectionName = 'projCounter';
 let projSettingSchema = {
     projectID: Number,
     bills: Number,
-    ration: Number,
-    volume_price: Number
+    ration: Number/*,
+    volume_price: Number*/
 };
 let model = mongoose.model(collectionName, new Schema(projSettingSchema, {versionKey: false, collection: collectionName}));
 export {model as default, collectionName as collectionName};

+ 20 - 0
modules/main/routes/quantity_detail_route.js

@@ -0,0 +1,20 @@
+/**
+ * Created by zhang on 2017/11/24.
+ */
+
+let express = require('express');
+let qdController = require('../controllers/quantity_detail_controller');
+
+module.exports = function (app) {
+
+    var qdRouter = express.Router();
+
+
+    qdRouter.post('/save', qdController.save);
+    qdRouter.post('/update',qdController.update);
+    qdRouter.post('/updateRegex',qdController.updateRegex);
+    qdRouter.post('/insertRecode',qdController.insertRecode);
+    qdRouter.post('/deleteRecode',qdController.deleteRecode);
+    qdRouter.post('/swapRow',qdController.swapRow);
+    app.use('/quantity_detail',qdRouter);
+}

+ 2 - 2
modules/pm/controllers/copy_proj_controller.js

@@ -6,7 +6,7 @@ let billsData = require('../../main/models/bills');
 let rationData = require('../../main/models/ration');
 let projCounter = require('../../main/models/proj_counter_model');
 let projSetting = require('../../main/models/proj_setting_model');
-let volumePriceData = require('../../volume_price/models/volume_price_model');
+// let volumePriceData = require('../../volume_price/models/volume_price_model');
 let async = require('async');
 
 module.exports = {
@@ -32,7 +32,7 @@ module.exports = {
         fun.push(copyData(rationData));
         fun.push(copyData(projCounter));
         fun.push(copyData(projSetting));
-        fun.push(copyData(volumePriceData));
+        // fun.push(copyData(volumePriceData));
         async.parallel(fun, (err) => callback(err));
     }
 };

+ 1 - 1
modules/pm/controllers/pm_controller.js

@@ -280,7 +280,7 @@ module.exports = {
                 proj.unitPriceFiles = [];
                 proj.feeRateFiles = [];
             }
-            callback(request, response, null, 'success', rst);
+            callback(request, response, 0, 'success', rst);
         }
         catch (error){
             callback(request, response, true, error, null);

+ 12 - 11
modules/pm/models/project_model.js

@@ -5,6 +5,7 @@ import mongoose from 'mongoose';
 import async_c from 'async';
 import UnitPriceFileModel from "../../glj/models/unit_price_file_model";
 import UnitPriceFiles from '../../glj/models/schemas/unit_price_file';
+import {defaultDecimal, billsQuantityDecimal, basicInformation, projectFeature} from './project_property_template';
 let FeeRateFiles = mongoose.model('fee_rate_file');
 let counter = require("../../../public/counter/counter.js");
 
@@ -26,13 +27,6 @@ let fileType = {
     unitPriceFile: 'UnitPriceFile',
     feeRateFile: 'FeeRateFile'
 };
-//默认的小数位数,用于定义用户可编辑的字段(入库),用户不可编辑的字段在前端defaultDecima._def中定义即可
-const defaultDecimal = {
-    bills: {unitPrice: 2, totalPrice: 2},
-    ration: {quantity: 3, unitPrice: 2, totalPrice: 2},
-    glj: {quantity: 3, unitPrice: 2},
-    feeRate: 2,
-};
 
 let ProjectsDAO = function(){};
 
@@ -76,9 +70,8 @@ ProjectsDAO.prototype.updateUserProjects = async function(userId, datas, callbac
             if (data.updateData.name !== undefined) {
                 data.updateData.name = data.updateData.name.trim();
             }
-
             if (data.updateType === 'update') {
-                Projects.update({userID: userId, ID: data.updateData.ID}, data.updateData, updateAll)
+                Projects.update({userID: userId, ID: data.updateData.ID}, data.updateData, updateAll);
             } else if (data.updateType === 'new') {
                 data.updateData['userID'] = userId;
                 data.updateData['createDateTime'] = new Date();
@@ -102,7 +95,15 @@ ProjectsDAO.prototype.updateUserProjects = async function(userId, datas, callbac
                     data.updateData.property.unitPriceFile.id = addResult.id;
                 }
                 if(data.updateData.projType === projectType.tender){
+                    //小数位数
                     data.updateData.property.decimal = defaultDecimal;
+                    //清单工程量精度
+                    data.updateData.property.billsQuantityDecimal = billsQuantityDecimal;
+                    //基本信息
+                    basicInformation[0]['items'][1]['value'] = data.updateData.property.engineeringName || '';
+                    data.updateData.property.basicInformation = basicInformation;
+                    //工程特征
+                    data.updateData.property.projectFeature = projectFeature;
                 }
                 newProject = new Projects(data.updateData);
                 // 查找同级是否存在同名数据
@@ -124,7 +125,7 @@ ProjectsDAO.prototype.updateUserProjects = async function(userId, datas, callbac
                 }
                 newProject.save(async function (err, result) {
                     if (!err && result._doc.projType === projectType.tender) {
-                        newProjController.copyTemplateData(data.updateData.property, newProject.ID, updateAll);
+                        newProjController.copyTemplateData(result._doc.property, newProject.ID, updateAll);
                     } else {
                         updateAll(err);
                     }
@@ -454,7 +455,7 @@ ProjectsDAO.prototype.recGC = async function(userID, datas, callback){
     }
     async_c.parallel(functions, function (err, results) {
         if(err) callback(err, 'fail', null);
-        else callback(false, 'success', null);
+        else callback(0, 'success', null);
     });
 };
 

+ 122 - 0
modules/pm/models/project_property_template.js

@@ -0,0 +1,122 @@
+/**
+ * Created by Zhong on 2017/11/16.
+ */
+
+//默认的小数位数,用于定义用户可编辑的字段(入库),用户不可编辑的字段在前端defaultDecima._def中定义即可
+const defaultDecimal = {
+    bills: {unitPrice: 2, totalPrice: 2},
+    ration: {quantity: 3, unitPrice: 2, totalPrice: 2},
+    glj: {quantity: 3, unitPrice: 2},
+    feeRate: 2,
+    quantity_detail: 4,
+    process: 6
+};
+
+/*
+* 单位工程清单工程量精度模板
+* */
+const billsQuantityDecimal = [
+    {unit: '其他未列单位', decimal: 2},
+    {unit: 't', decimal: 3},
+    {unit: '吨', decimal: 3},
+    {unit: 'km', decimal: 3},
+    {unit: '千米', decimal: 3},
+    {unit: 'm', decimal: 2},
+    {unit: '米', decimal: 2},
+    {unit: 'm2', decimal: 2},
+    {unit: '平方米', decimal: 2},
+    {unit: 'm3', decimal: 2},
+    {unit: '立方米', decimal: 2},
+    {unit: 'kg', decimal: 2},
+    {unit: '千克', decimal: 2},
+    {unit: '公斤', decimal: 2},
+    {unit: '元', decimal: 2},
+    {unit: '工日', decimal: 0},
+    {unit: '台班', decimal: 0},
+    {unit: '个', decimal: 0},
+    {unit: '件', decimal: 0},
+    {unit: '根', decimal: 0},
+    {unit: '组', decimal: 0},
+    {unit: '宗', decimal: 0},
+    {unit: '付', decimal: 0},
+    {unit: '处', decimal: 0},
+    {unit: '系统', decimal: 0},
+    {unit: '部', decimal: 0},
+    {unit: '台', decimal: 0},
+    {unit: '辆', decimal: 0},
+    {unit: '套', decimal: 0},
+    {unit: '株', decimal: 0},
+    {unit: '从', decimal: 0},
+    {unit: '缸', decimal: 0},
+    {unit: '支', decimal: 0},
+    {unit: '只', decimal: 0},
+    {unit: '块', decimal: 0},
+    {unit: '座', decimal: 0},
+    {unit: '对', decimal: 0},
+    {unit: '份', decimal: 0},
+    {unit: '樘', decimal: 0},
+    {unit: '攒', decimal: 0},
+    {unit: '榀', decimal: 0}
+];
+
+//项目属性基本信息
+const basicInformation = [
+    {dispName: '基本信息', key: 'basicInfo', items: [
+        {dispName: '合同号', key: 'contractNum', value: ''},
+        {dispName: '工程专业', key: 'engineering', value: ''},//只读,用户新建单位工程时选择的值
+        {dispName: '建设地点', key: 'constructionPlace', value: ''},
+        {dispName: '质量标准', key: 'qualityStandard', value: ''},
+        {dispName: '工程类别', key: 'projectCategory', value: ''},
+        {dispName: '建设日期', key: 'constructionDate', value: ''},
+        {dispName: '工程规模', key: 'projectScale', value: ''},
+        {dispName: '建设单位', key: 'constructionUnit', value: ''},
+        {dispName: '设计单位', key: 'designUnit', value: ''},
+        {dispName: '施工单位', key: 'buildingUnit', value: ''},
+        {dispName: '监理单位', key: 'supervisingUnit', value: ''},
+        {dispName: '审核单位', key: 'auditUnit', value: ''},
+        {dispName: '建设单位审核人', key: 'constructionUnitAudit', value: ''},
+        {dispName: '设计单位负责人', key: 'designUnitPrincipal', value: ''},
+        {dispName: '施工单位编制人', key: 'buildingUnitAuthor', value: ''},
+        {dispName: '监理单位审核人', key: 'supervisingUnitAuditor', value: ''},
+        {dispName: '审核单位审核人', key: 'auditUnitAuditor', value: ''},
+        {dispName: '编制时间', key: 'establishDate', value: ''}
+    ]},
+    {dispName: '招标信息', key: 'biddingInfo', items: [
+        {dispName: '招标人', key: 'bidInviter', value: ''},
+        {dispName: '法定代表人或其他授权人', key: 'representative', value: ''},
+        {dispName: '造价工程师', key: 'costEngineer', value: ''}
+    ]},
+    {dispName: '投标信息', key: 'bidInfo', items: [
+        {dispName: '投标人', key: 'bidder', value: ''},
+        {dispName: '法定代表人或其授权人', key: 'representative', value: ''},
+        {dispName: '造价工程师', key: 'costEngineer', value: ''}
+    ]},
+    {dispName: '工程造价咨询信息', key: 'engineeringCostConsultationInfo', items: [
+        {dispName: '工程造价咨询人', key: 'consultant', value: ''},
+        {dispName: '法定代表人或其授权人', key: 'representative', value: ''},
+        {dispName: '造价工程师', key: 'costEngineer', value: ''}
+    ]}
+];
+
+const projectFeature = [
+    {dispName: '工程类型', key: 'projType', value: ''},
+    {dispName: '结构类型', key: 'structureType', value: ''},
+    {dispName: '基础类型', key: 'baseType', value: ''},
+    {dispName: '建筑特征', key: 'buildingFeature', value: ''},
+    {dispName: '建筑面积(m2)', key: 'buildingArea', value: ''},
+    {dispName: '总层数', key: 'totalFloors', value: ''},
+    {dispName: '地下室层数(+/-0.00以下)', key: 'basementFloors', value: ''},
+    {dispName: '建筑层数(+/-0.00以下)', key: 'buildingFloors', value: ''},
+    {dispName: '建筑物总高度(m)', key: 'buildingHeight', value: ''},
+    {dispName: '地下室总高度(m)', key: 'basementHeight', value: ''},
+    {dispName: '首层高度(m)', key: 'firstFloorHeight', value: ''},
+    {dispName: '裙楼高度(m)', key: 'podiumBuildingHeight', value: ''},
+    {dispName: '标准层高度(m)', key: 'standardFloorHeight', value: ''},
+    {dispName: '基础材料及装饰', key: 'baseMaterial', value: ''},
+    {dispName: '楼地面材料及装饰', key: 'flooringMaterial', value: ''},
+    {dispName: '外墙材料及装饰', key: 'exteriorWallMaterial', value: ''},
+    {dispName: '屋面材料及装饰', key: 'roofingMaterial', value: ''},
+    {dispName: '门窗材料及装饰', key: 'doorsWindowsMaterial', value: ''}
+];
+
+export {defaultDecimal, billsQuantityDecimal, basicInformation, projectFeature};

+ 20 - 3
modules/ration_glj/controllers/ration_glj_controller.js

@@ -13,7 +13,8 @@ module.exports={
     addGLJ:addGLJ,
     replaceGLJ:replaceGLJ,
     mReplaceGLJ:mReplaceGLJ,
-    updateRationGLJByEdit:updateRationGLJByEdit
+    updateRationGLJByEdit:updateRationGLJByEdit,
+    getGLJClass:getGLJClass
 }
 function createRationGLJ() {
     let gls = mongoose.model('ration_glj');
@@ -102,7 +103,6 @@ async function mReplaceGLJ(req, res){
     try {
         let data = req.body.data;
         data = JSON.parse(data);
-        console.log(data);
         let mresult= await ration_glj_facade.mReplaceGLJ(data);
         result.data=mresult;
     }catch (err){
@@ -128,5 +128,22 @@ async  function updateRationGLJByEdit(req, res) {
         result.message = err.message;
     }
     res.json(result);
-    
+}
+
+async  function getGLJClass(req, res) {
+    let result={
+        error:0
+    }
+    try {
+        let data = req.body.data;
+        data = JSON.parse(data);
+        let info = await ration_glj_facade.getLibInfo(req);
+        let tresult= await ration_glj_facade.getGLJClass(info,data);
+        result.data=tresult;
+    }catch (err){
+        logger.err(err);
+        result.error=1;
+        result.message = err.message;
+    }
+    res.json(result);
 }

+ 7 - 7
modules/ration_glj/facade/glj_calculate_facade.js

@@ -28,7 +28,7 @@ let stateSeq ={
 }
 
 
-async function calculateQuantity(query,isMarkPriceAjust){
+async function calculateQuantity(query,noNeedCal){
     try {
          let  result ={
              glj_result:[],
@@ -51,11 +51,11 @@ async function calculateQuantity(query,isMarkPriceAjust){
                  }
          }
          for(let i =0;i<gljList.length;i++ ){
-             let r = await calculateQuantityPerGLJ(gljList[i],i,coeList,assList,adjustState,isMarkPriceAjust);
+             let r = await calculateQuantityPerGLJ(gljList[i],i,coeList,assList,adjustState,noNeedCal);
              result.glj_result.push(r);
          }
 
-        if(isMarkPriceAjust==null){
+        if(noNeedCal==null){
             await ration_glj.bulkWrite(generateUpdateTasks(result.glj_result));
         }
          adjustState= _.sortByOrder(adjustState, ['index'], ['asc']);
@@ -85,8 +85,8 @@ function generateUpdateTasks(result) {
 }
 
 
-async function calculateQuantityPerGLJ(glj,index,coeList,assList,adjustState,isMarkPriceAjust) {
-    let quantity =  glj.quantity;
+async function calculateQuantityPerGLJ(glj,index,coeList,assList,adjustState,noNeedCal) {
+    let quantity =  scMathUtil.roundTo(parseFloat(glj.quantity),-6);
     let result={
         query:{
             ID:glj.ID,
@@ -97,9 +97,9 @@ async function calculateQuantityPerGLJ(glj,index,coeList,assList,adjustState,isM
         }
     };
     try {
-        if(isMarkPriceAjust==null){
+        if(noNeedCal==null){
             if(!glj._doc.hasOwnProperty('customQuantity')||glj.customQuantity==null){
-                quantity =glj.rationItemQuantity;
+                quantity =scMathUtil.roundTo(parseFloat(glj.rationItemQuantity),-6);
                 quantity =calculateAss(quantity,assList,glj);
                 quantity = calculateQuantityByCoes(quantity,coeList,glj);
             }else {

+ 54 - 11
modules/ration_glj/facade/ration_glj_facade.js

@@ -16,11 +16,12 @@ let ration_coe_facade = require('./ration_coe_facade');
 let ration_coe = mongoose.model('ration_coe');
 let std_ration_lib_ration_items = mongoose.model('std_ration_lib_ration_items');
 let glj_calculate_facade = require('./glj_calculate_facade');
-let quantity_detail_facade = require('./quantity_detail_facade');
+let quantity_detail_facade = require('../../main/facade/quantity_detail_facade');
 let logger = require("../../../logs/log_helper").logger;
 import stdgljutil  from "../../../public/cache/std_glj_type_util";
 import EngineeringLibModel from "../../users/models/engineering_lib_model";
 import GljDao from "../../complementary_glj_lib/models/gljModel";
+import {complementaryGljModel, stdGljModel, gljClassModel} from "../../complementary_glj_lib/models/schemas";
 
 
 module.exports={
@@ -33,7 +34,8 @@ module.exports={
     addGLJ:addGLJ,
     replaceGLJ:replaceGLJ,
     mReplaceGLJ:mReplaceGLJ,
-    updateRationGLJByEdit:updateRationGLJByEdit
+    updateRationGLJByEdit:updateRationGLJByEdit,
+    getGLJClass:getGLJClass
 }
 
 let operationMap={
@@ -123,6 +125,7 @@ function get_lib_glj_info(ration_glj) {
                 ration_glj.shortName = glj.shortName;
                 ration_glj.type = glj.gljType;
                 ration_glj.repositoryId = glj.repositoryId;
+                ration_glj.adjCoe = glj.adjCoe;
                 getInfoFromProjectGLJ(ration_glj).then(function (info) {
                     if(info){
                         let tem={};
@@ -201,7 +204,9 @@ async function getInfoFromProjectGLJ(ration_glj) {
              marketPrice:pg.unit_price.market_price,
              adjustPrice:pg.adjust_price,
              isEstimate:pg.is_evaluate,
-             isMixRatio:true
+             isMixRatio:true,
+             isAdd:pg.unit_price.is_add,
+             GLJID:pg.glj_id
          }
          temRationGLJs.push(tem);
      }
@@ -551,6 +556,7 @@ function getGLJSearchInfo(ration_glj) {
         base_price: ration_glj.basePrice,
         market_price: ration_glj.basePrice,
         repositoryId:ration_glj.repositoryId,
+        adjCoe:ration_glj.adjCoe,
         from:ration_glj.from?ration_glj.from:'std'//std:标准工料机库, cpt:补充工料机库
     };
      if(data.from=='cpt'){//从补充工料机来的数据即为新增数据
@@ -568,6 +574,7 @@ async function addGLJ(rgList) {
        g.marketPrice=result.unit_price.market_price;
        g.adjustPrice=result.unit_price.base_price;
        g.basePrice=result.unit_price.base_price;
+       g.isAdd=result.unit_price.is_add,
        g.projectGLJID=result.id;
        g.isEstimate=result.is_evaluate;
        g.ID=uuidV1();
@@ -591,13 +598,18 @@ async function replaceGLJ(data) {
     let rdata={};
     let projectGljModel = new GLJListModel();
     let result = await projectGljModel.addList(getGLJSearchInfo(data));
+    data.projectGLJID=result.id;
+    let updateResult=await ration_glj.findOneAndUpdate({ID:data.ID,projectID:data.projectID},data);//更新定额工料机
+    //组装回传数据
     data.marketPrice=result.unit_price.market_price;
     data.adjustPrice=result.unit_price.base_price;
     data.basePrice=result.unit_price.base_price;
-    data.projectGLJID=result.id;
+    data.isAdd=result.unit_price.is_add;
     data.isEstimate=result.is_evaluate;
-    let updateResult=await ration_glj.findOneAndUpdate({ID:data.ID,projectID:data.projectID},data);
-    let stateResult =  await glj_calculate_facade.calculateQuantity({projectID:data.projectID,rationID:data.rationID});
+    if(result.hasOwnProperty('subList')&&result.subList.length>0){
+        data.subList=getMixRatioShowDatas(result.subList);
+    }
+    let stateResult =  await glj_calculate_facade.calculateQuantity({projectID:data.projectID,rationID:data.rationID},true);
     rdata.data=data;
     rdata.adjustState=stateResult.adjustState;
     return rdata;
@@ -606,13 +618,18 @@ async function mReplaceGLJ(data) {
     let mresult={};
     let projectGljModel = new GLJListModel();
     let result = await projectGljModel.addList(getGLJSearchInfo(data.doc));
+    data.doc.projectGLJID=result.id;
+    let rationList=await ration_glj.distinct('rationID',data.query);
+    let updateResult=await ration_glj.update(data.query,data.doc,{multi: true});
+
     data.doc.marketPrice=result.unit_price.market_price;
     data.doc.adjustPrice=result.unit_price.base_price;
     data.doc.basePrice=result.unit_price.base_price;
-    data.doc.projectGLJID=result.id;
+    data.doc.isAdd=result.unit_price.is_add;
     data.doc.isEstimate=result.is_evaluate;
-    let rationList=await ration_glj.distinct('rationID',data.query);
-    let updateResult=await ration_glj.update(data.query,data.doc,{multi: true});
+    if(result.hasOwnProperty('subList')&&result.subList.length>0){
+        data.doc.subList=getMixRatioShowDatas(result.subList);
+    }
     let stateList= await changAdjustState(data,rationList);
     mresult.data=data;
     mresult.stateList=stateList;
@@ -665,12 +682,38 @@ async function doRationGLJUpdate(data){
     return resutl;
 }
 
-
+async function getGLJClass(info,data) {
+    let result={
+        exist:false
+    }
+    //检查补充工料机中是否已经存在一样的记录了
+    let condition = {
+        userId:info.userID,
+        compilationId:info.compilationId,
+        code:data.code,
+        name:data.name,
+        unit:data.unit,
+        gljType:data.type,
+        basePrice:data.basePrice
+    }
+    if(data.specs!=null&&data.specs!=undefined&&data.specs!=''){
+        condition['specs']=data.specs;
+    }
+    let glj = await complementaryGljModel.find(condition);
+    if(glj.length>0){ //如果已存在就直接返回,不用再新增了
+        result.exist = true;
+        return result
+    }
+    //查找工料机类型树
+   let items = await gljClassModel.find({"repositoryId": info.gljLibId, "$or": [{"isDeleted": null}, {"isDeleted": false} ]});
+    result.items = items;
+   return result;
+}
 
 async function changAdjustState(data,rationList) {
     let stateList=[];
     for(let r of rationList){
-      let stateResult = await glj_calculate_facade.calculateQuantity({projectID:data.query.projectID,rationID:r});
+      let stateResult = await glj_calculate_facade.calculateQuantity({projectID:data.query.projectID,rationID:r},true);
       stateList.push({rationID:r,adjustState:stateResult.adjustState});
     }
     return stateList;

+ 2 - 0
modules/ration_glj/models/ration_glj.js

@@ -24,6 +24,8 @@ var ration_glj = new Schema({
     shortName:String,
     billsItemID: Number,
     type:Number,
+    // 调整系数ID
+    adjCoe: Number,
     quantity:String,
     customQuantity:String,
     rationItemQuantity:String,

+ 1 - 0
modules/ration_glj/routes/ration_glj_route.js

@@ -13,6 +13,7 @@ module.exports = function (app) {
     rgRouter.post('/replaceGLJ',rgController.replaceGLJ);
     rgRouter.post('/mReplaceGLJ',rgController.mReplaceGLJ);
     rgRouter.post('/updateRationGLJByEdit',rgController.updateRationGLJByEdit);
+    rgRouter.post('/getGLJClass', rgController.getGLJClass);
     app.use('/rationGlj',rgRouter);
 }
 

+ 53 - 1
modules/reports/controllers/rpt_controller.js

@@ -92,7 +92,7 @@ function getAllPagesCommon(user_id, prj_id, rpt_id, pageSize, option, cb) {
                     let maxPages = printCom.totalPages;
                     let pageRst = printCom.outputAsSimpleJSONPageArray(rptTpl, tplData, 1, maxPages, defProperties);
                     if (pageRst) {
-                        //fsUtil.wirteObjToFile(pageRst, "D:/GitHome/ConstructionCost/tmp/testBuiltPageResult.js");
+                        //fsUtil.writeObjToFile(pageRst, "D:/GitHome/ConstructionCost/tmp/testBuiltPageResult.js");
                         cb(null, pageRst);
                     } else {
                         cb('Have errors while on going...', null);
@@ -115,6 +115,7 @@ module.exports = {
             prj_id = params.prj_id,
             user_id = params.user_id,
             pageSize = params.pageSize;
+        // req.session.sessionUser.ssoId
         getAllPagesCommon(user_id, prj_id, rpt_id, pageSize, null, function (err, pageRst) {
             callback(req, res, err, pageRst);
         });
@@ -128,6 +129,33 @@ module.exports = {
             callback(req, res, err, pageRst);
         })
     },
+    getExcel: function(req, res) {
+        let prj_id = req.params.prj_id,
+            rpt_id = req.params.rpt_id,
+            pageSize = req.params.size,
+            rptName = req.params.rptName,
+            isOneSheet = req.params.isOneSheet,
+            option = req.params.option;
+        let user_id = req.session.sessionUser.ssoId;
+        let dftOption = option||JV.PAGING_OPTION_NORMAL;
+        getAllPagesCommon(user_id, prj_id, rpt_id, pageSize, dftOption, function(err, pageRst){
+            try {
+                rpt_xl_util.exportExcel(pageRst, pageSize, rptName, isOneSheet, null, function(newName){
+                    res.setHeader('Content-Type', 'application/vnd.openxmlformats');
+                    res.setHeader("Content-Disposition", "attachment; filename=" + strUtil.getPinYinCamelChars(rptName) + ".xlsx");
+                    let filestream = fs.createReadStream(__dirname.slice(0, __dirname.length - 28) + '/tmp/' + newName + '.xlsx');
+                    filestream.on('data', function(chunk) {
+                        res.write(chunk);
+                    });
+                    filestream.on('end', function() {
+                        res.end();
+                    });
+                });
+            } catch (e) {
+                console.log(e);
+            }
+        })
+    },
     getTestExcel: function(req, res) {
         let rpt_id = req.params.id,
             pageSize = req.params.size,
@@ -194,6 +222,30 @@ module.exports = {
             }
         })
     },
+    getPDF:function (req, res) {
+        let prj_id = req.params.prj_id,
+            rpt_id = req.params.rpt_id,
+            pageSize = req.params.size,
+            rptName = req.params.rptName
+        ;
+        let user_id = req.session.sessionUser.ssoId;
+        getAllPagesCommon(user_id, prj_id, rpt_id, pageSize, JV.PAGING_OPTION_NORMAL, function(err, pageRst){
+            rpt_pdf_util.export_pdf_file(pageRst, pageSize, rptName,function (newName) {
+                res.setHeader('Content-Type', 'application/vnd.openxmlformats');
+                res.setHeader("Content-Disposition", "attachment; filename=" + strUtil.getPinYinCamelChars(rptName) + ".pdf");
+
+                let filestream = fs.createReadStream(__dirname.slice(0, __dirname.length - 28) + '/tmp/' + newName + '.pdf');
+                filestream.on('data', function(chunk) {
+                    res.write(chunk);
+                });
+                filestream.on('end', function() {
+                    res.end();
+                });
+            })
+
+        })
+
+    },
     getTestPDF:function (req, res) {
         let rpt_id = req.params.id,
             pageSize = req.params.size,

+ 2 - 2
modules/reports/routes/report_router.js

@@ -25,9 +25,9 @@ module.exports =function (app) {
     rptRouter.get('/getTestPDF/:id/:size/:rptName', reportController.getTestPDF);
     //now is the real:
     rptRouter.post('/getReport', reportController.getReportAllPages);
-    // rptRouter.get('/getExcel/:id/:size/:rptName/:isOneSheet/:option', reportController.getExcel);
+    rptRouter.get('/getExcel/:prj_id/:rpt_id/:size/:rptName/:isOneSheet/:option', reportController.getExcel);
+    rptRouter.get('/getPDF/:prj_id/:rpt_id/:size/:rptName', reportController.getPDF);
     // rptRouter.get('/getExcelInOneBook/:ids/:size/:rptName/:option', reportController.getExcelInOneBook);
-    // rptRouter.get('/getPDF/:id/:size/:rptName', reportController.getPDF);
 
     app.use("/report_api", rptRouter);
 };

+ 40 - 18
modules/reports/rpt_component/jpc_bill_tab.js

@@ -1,8 +1,6 @@
 let JV = require('./jpc_value_define');
 let JpcFieldHelper = require('./helper/jpc_helper_field');
 let JpcBandHelper = require('./helper/jpc_helper_band');
-let JpcBand = require('./jpc_band');
-let JpcFlowTabHelper = require('./helper/jpc_helper_flow_tab');
 let JpcCommonHelper = require('./helper/jpc_helper_common');
 let JpcDiscreteHelper = require('./helper/jpc_helper_discrete');
 let JpcTextHelper = require('./helper/jpc_helper_text');
@@ -20,11 +18,24 @@ JpcBillTabSrv.prototype.createNew = function(){
         let me = this;
         JpcFieldHelper.findAndPutDataFieldIdx(rptTpl, rptTpl[JV.NODE_BILL_INFO][JV.NODE_BILL_CONTENT][JV.PROP_BILL_FIELDS], null, me.disp_fields_idx);
     };
-    JpcBillTabResult.paging = function(rptTpl) {
-        let me = this, rst = 0;
-        let detail_fields = rptTpl[JV.NODE_FIELD_MAP][JV.NODE_DETAIL_FIELDS];
-        if (detail_fields && detail_fields.length > 0) {
-            rst = detail_fields[0].length;
+    JpcBillTabResult.paging = function(rptTpl, dataObj) {
+        let rst = 0;
+        function getDataLength(fields_str) {
+            let dataFields = dataObj[fields_str];
+            if (dataFields && dataFields.length > 0) {
+                rst = dataFields[0].length;
+            }
+        }
+        if (rptTpl[JV.NODE_FIELD_MAP]) {
+            if (rptTpl[JV.NODE_FIELD_MAP][JV.NODE_DETAIL_FIELDS]) {
+                getDataLength(JV.DATA_DETAIL_DATA);
+            } else if (rptTpl[JV.NODE_FIELD_MAP][JV.NODE_MASTER_FIELDS]) {
+                getDataLength(JV.DATA_MASTER_DATA);
+            } else if (rptTpl[JV.NODE_FIELD_MAP][JV.NODE_DISCRETE_FIELDS]) {
+                getDataLength(JV.DATA_DISCRETE_DATA);
+            } else if (rptTpl[JV.NODE_FIELD_MAP][JV.NODE_DISCRETE_PARAMS]) {
+                rst = 1;
+            }
         }
         return rst;
     };
@@ -36,32 +47,43 @@ JpcBillTabSrv.prototype.createNew = function(){
         //2. start to output detail-part
         let unitFactor = JpcCommonHelper.getUnitFactor(rptTpl);
         //2.1 output content
-        tabRstLst.push(me.outputContent(rptTpl, dataObj, page, bands, unitFactor, controls, pageStatus));
+        tabRstLst.push(me.outputContent(rptTpl, dataObj, page, bands, unitFactor, controls, pageStatus, $CURRENT_RPT));
         //2.2 output discrete
         tabRstLst.push(JpcDiscreteHelper.outputDiscreteInfo(rptTpl[JV.NODE_BILL_INFO][JV.NODE_DISCRETE_INFO], bands, dataObj, unitFactor, pageStatus, page - 1, 1, 0, $CURRENT_RPT));
-    }
-    JpcBillTabResult.outputContent = function(rptTpl, dataObj, page, bands, unitFactor, controls, pageStatus) {
+        for (let i = 0; i < tabRstLst.length; i++) {
+            rst = rst.concat(tabRstLst[i]);
+            tabRstLst[i] = null;
+        }
+        return rst;
+    };
+    JpcBillTabResult.outputContent = function(rptTpl, dataObj, page, bands, unitFactor, controls, pageStatus, $CURRENT_RPT) {
         let me = this, rst = [];
         let tab = rptTpl[JV.NODE_BILL_INFO][JV.NODE_BILL_CONTENT];
         let band = bands[tab[JV.PROP_BAND_NAME]];
         if (band) {
-            if (pageStatus[band[JV.BAND_PROP_DISPLAY_TYPE]] == true) {
+            if (pageStatus[band[JV.BAND_PROP_DISPLAY_TYPE]]) {
                 let tab_fields = tab[JV.PROP_BILL_FIELDS];
-                let data_details = dataObj[JV.DATA_MASTER_DATA];
+                let data_details = null;
+                if (rptTpl[JV.NODE_FIELD_MAP][JV.NODE_DETAIL_FIELDS]) {
+                    data_details = dataObj[JV.DATA_DETAIL_DATA];
+                } else if (rptTpl[JV.NODE_FIELD_MAP][JV.NODE_MASTER_FIELDS]) {
+                    data_details = dataObj[JV.DATA_MASTER_DATA];
+                }
                 for (let i = 0; i < tab_fields.length; i++) {
                     let tab_field = tab_fields[i];
                     let data_field = null;
-                    if (me.disp_fields_idx[i] != JV.BLANK_FIELD_INDEX) {
+                    if (me.disp_fields_idx.length > i && me.disp_fields_idx[i] !== JV.BLANK_FIELD_INDEX) {
                         data_field = data_details[me.disp_fields_idx[i]];
                     } else {
-                        data_field = JE.F(tab_field[JV.PROP_FIELD_ID]);
+                        data_field = JE.F(tab_field[JV.PROP_FIELD_ID], $CURRENT_RPT);
                         if (data_field) {
                             data_field = data_field[JV.PROP_AD_HOC_DATA];
                         }
                     }
                     if (!(tab_field[JV.PROP_HIDDEN])) {
-                        let cellItem = JpcCommonOutputHelper.createCommonOutput(tab_field, JpcFieldHelper.getValue(data_field, page - 1), controls);
-                        cellItem[JV.PROP_AREA] = JpcAreaHelper.outputArea(tab_field[JV.PROP_AREA], band, unitFactor, 1, 0, 1, 0, 1, 0, true, false);
+                        let val = JpcFieldHelper.getValue(data_field, page - 1);
+                        let cellItem = JpcCommonOutputHelper.createCommonOutput(tab_field, val, controls);
+                        cellItem[JV.PROP_AREA] = JpcAreaHelper.outputArea(tab_field[JV.PROP_AREA], band, unitFactor, 1, 0, 1, 0, 1, 0, false, false);
                         rst.push(cellItem);
                     }
                 }
@@ -79,8 +101,8 @@ JpcBillTabSrv.prototype.createNew = function(){
             }
         }
         return rst;
-    }
+    };
     return JpcBillTabResult;
-}
+};
 
 module.exports = new JpcBillTabSrv();

+ 19 - 13
modules/reports/rpt_component/jpc_data.js

@@ -10,17 +10,21 @@ let JpcData = {
             let private_analyse = function(MASTER_FIELD_STR, DETAIL_FIELD_STR, MASTER_DATA_STR, DETAIL_DATA_STR, dataSeqArr) {
                 //1. get ID fields
                 let masterIDs = [];
-                for (let i = 0; i < rptTpl[JV.NODE_FIELD_MAP][MASTER_FIELD_STR].length; i++) {
-                    let mstFieldObj = rptTpl[JV.NODE_FIELD_MAP][MASTER_FIELD_STR][i];
-                    if (jpc_common_helper.getBoolean(mstFieldObj[JV.PROP_IS_ID])) {
-                        masterIDs.push({"idx": i, "seq": mstFieldObj[JV.PROP_ID_SEQ]});
+                if (rptTpl[JV.NODE_FIELD_MAP][MASTER_FIELD_STR]) {
+                    for (let i = 0; i < rptTpl[JV.NODE_FIELD_MAP][MASTER_FIELD_STR].length; i++) {
+                        let mstFieldObj = rptTpl[JV.NODE_FIELD_MAP][MASTER_FIELD_STR][i];
+                        if (jpc_common_helper.getBoolean(mstFieldObj[JV.PROP_IS_ID])) {
+                            masterIDs.push({"idx": i, "seq": mstFieldObj[JV.PROP_ID_SEQ]});
+                        }
                     }
                 }
                 let detailIDs = [];
-                for (let i = 0; i < rptTpl[JV.NODE_FIELD_MAP][DETAIL_FIELD_STR].length; i++) {
-                    let dtlFieldObj = rptTpl[JV.NODE_FIELD_MAP][DETAIL_FIELD_STR][i];
-                    if (jpc_common_helper.getBoolean(dtlFieldObj[JV.PROP_IS_ID])) {
-                        detailIDs.push({"idx": i, "seq": dtlFieldObj[JV.PROP_ID_SEQ]});
+                if (rptTpl[JV.NODE_FIELD_MAP][DETAIL_FIELD_STR]) {
+                    for (let i = 0; i < rptTpl[JV.NODE_FIELD_MAP][DETAIL_FIELD_STR].length; i++) {
+                        let dtlFieldObj = rptTpl[JV.NODE_FIELD_MAP][DETAIL_FIELD_STR][i];
+                        if (jpc_common_helper.getBoolean(dtlFieldObj[JV.PROP_IS_ID])) {
+                            detailIDs.push({"idx": i, "seq": dtlFieldObj[JV.PROP_ID_SEQ]});
+                        }
                     }
                 }
                 //2. sort the ID fields
@@ -84,11 +88,13 @@ let JpcData = {
                         }
                     }
                 } else { //if no master data
-                    let field = dataObj[DETAIL_DATA_STR][0];
-                    //dataSeqArr = [[]];
-                    dataSeqArr.push([]);
-                    for (let i = 0; i < field.length; i++) {
-                        dataSeqArr[0].push(i);
+                    if (dataObj && dataObj[DETAIL_DATA_STR] && dataObj[DETAIL_DATA_STR].length > 0) {
+                        //may be bill type report which may only have discrete fields!
+                        let field = dataObj[DETAIL_DATA_STR][0];
+                        dataSeqArr.push([]);
+                        for (let i = 0; i < field.length; i++) {
+                            dataSeqArr[0].push(i);
+                        }
                     }
                 }
             };

+ 26 - 5
modules/reports/rpt_component/jpc_ex.js

@@ -125,6 +125,9 @@ JpcExSrv.prototype.createNew = function(){
                 me.flowTabEx.sorting(rptTpl, dataObj, dataHelper.exDataSeq.slice(0));
             }
         }
+        if (me.billTab) {
+            me.billTab.sorting(rptTpl, dataObj, dataHelper.dataSeq.slice(0));
+        }
         if (me.crossTab) {
             me.crossTab.sorting(rptTpl, dataObj, dataHelper.dataSeq.slice(0));
         }
@@ -155,7 +158,7 @@ JpcExSrv.prototype.createNew = function(){
             //me.totalPages = me.crossTab.preSetupPages(rptTpl, defProperties, dftPagingOption);
             me.totalPages = me.crossTab.preSetupPages(rptTpl, defProperties, JV.PAGING_OPTION_NORMAL); //infinity对交叉表来说无意义
         } else if (me.billTab) {
-            me.totalPages = me.billTab.paging();
+            me.totalPages = me.billTab.paging(rptTpl, dataObj);
         }
     };
     JpcResult.executeFormulas = function(runType, $CURRENT_TEMPLATE, $CURRENT_DATA, $CURRENT_RPT) {
@@ -208,6 +211,18 @@ JpcExSrv.prototype.createNew = function(){
     };
     JpcResult.outputAsSimpleJSONPage = function(rptTpl, dataObj, bands, page, controls) {
         let me = this, rst = null;
+        function getPageMergeBorder() {
+            let rst = null;
+            if (bands[JV.BAND_PROP_MERGE_BAND]) {
+                let mergedBand = bands[JV.BAND_PROP_MERGE_BAND];
+                rst = {};
+                rst[JV.PROP_LEFT] = parseInt(mergedBand[JV.PROP_LEFT].toFixed(0));
+                rst[JV.PROP_RIGHT] = parseInt(mergedBand[JV.PROP_RIGHT].toFixed(0));
+                rst[JV.PROP_TOP] = parseInt(mergedBand[JV.PROP_TOP].toFixed(0));
+                rst[JV.PROP_BOTTOM] = parseInt(mergedBand[JV.PROP_BOTTOM].toFixed(0));
+            }
+            return rst;
+        }
         if (me.totalPages >= page) {
             rst = {};
             rst[JV.PROP_PAGE_SEQ] = page;
@@ -218,7 +233,7 @@ JpcExSrv.prototype.createNew = function(){
                     if (me.flowTab.paging_option === JV.PAGING_OPTION_INFINITY) {
                         adHocMergePos = {};
                     }
-                    rst.cells = me.flowTab.outputAsSimpleJSONPage(rptTpl, dataObj, page, bands, controls, adHocMergePos, me);
+                    rst[JV.PROP_CELLS] = me.flowTab.outputAsSimpleJSONPage(rptTpl, dataObj, page, bands, controls, adHocMergePos, me);
                     if (adHocMergePos) {
                         adHocMergePos[JV.NODE_PAGE_SIZE] = JpcCommonHelper.getPageSize(rptTpl);
                         rst[JV.PAGE_SPECIAL_MERGE_POS] = adHocMergePos;
@@ -226,13 +241,19 @@ JpcExSrv.prototype.createNew = function(){
 
                 } else {
                     if (!me.isFollowMode) {
-                        rst.cells = me.flowTabEx.outputAsSimpleJSONPage(rptTpl, dataObj, page - (me.totalPages - me.exTotalPages), bands, controls, adHocMergePos, me);
+                        rst[JV.PROP_CELLS] = me.flowTabEx.outputAsSimpleJSONPage(rptTpl, dataObj, page - (me.totalPages - me.exTotalPages), bands, controls, adHocMergePos, me);
                     }
                 }
             } else if (me.crossTab) {
-                rst.cells = me.crossTab.outputAsSimpleJSONPage(rptTpl, dataObj, page, bands, controls, me);
+                rst[JV.PROP_CELLS] = me.crossTab.outputAsSimpleJSONPage(rptTpl, dataObj, page, bands, controls, me);
             } else if (me.billTab) {
-                //
+                rst[JV.PROP_CELLS] = me.billTab.outputAsSimpleJSONPage(rptTpl, dataObj, page, bands, controls, me);
+            }
+            if (!(me.flowTab && me.flowTab.paging_option === JV.PAGING_OPTION_INFINITY)) {
+                let pageMergeBorder = getPageMergeBorder();
+                if (pageMergeBorder) {
+                    rst[JV.PROP_PAGE_MERGE_BORDER] = pageMergeBorder;
+                }
             }
         }
         return rst;

+ 2 - 1
modules/reports/rpt_component/jpc_flow_tab.js

@@ -370,7 +370,8 @@ JpcFlowTabSrv.prototype.createNew = function(){
                         }
                     }
                     //检测是否可退出
-                    if (currentRecAmt >= ttlSegRecAmt) {
+                    if ((currentRecAmt >= ttlSegRecAmt) && (pageIdx % me.multiCols === 0)) {
+                        //备注:这里必须得考虑多栏的情况,否则会造成pageStatus出界的问题
                         break;
                     }
                     //控制阀值,超过阀值则强制退出,防止死循环

+ 312 - 24
modules/reports/util/rpt_construct_data_util.js

@@ -12,6 +12,14 @@ let treeUtil = require('../../../public/treeUtil');
 let projectConst = consts.projectConst;
 let projectConstList = consts.projectConstList;
 
+const GLJ_TYPE = {
+    Labour: 1,
+    Material: 2,
+    Machine: 3,
+    Main_Material: 4,
+    Equipment: 5
+}
+
 class Rpt_Common{
     initialize(rpt_tpl, currentDataObj) {
         this.template = rpt_tpl;
@@ -135,6 +143,9 @@ class Rpt_Data_Extractor {
         pri_setup_filter(JV.NODE_DETAIL_FIELDS);
         pri_setup_filter(JV.NODE_MASTER_FIELDS_EX);
         pri_setup_filter(JV.NODE_DETAIL_FIELDS_EX);
+        if (rst.length === 0) {
+            rst.push(projectConst.RATION_ASS);
+        }
         return rst;
     };
 
@@ -152,7 +163,7 @@ class Rpt_Data_Extractor {
                 let srcData = getModuleDataByKey(rawDataObj.prjData, preHandle[JV.PROP_DATA_KEY]);
                 switch(preHandle[JV.PROP_HANDLE_TYPE]) {
                     case JV.PROP_HANDLE_TYPE_SORT:
-                        sortData(srcData, preHandle);
+                        sortData(srcData, preHandle, rawDataObj.prjData);
                         break;
                     case JV.PROP_HANDLE_TYPE_FILTER:
                         filterData(srcData, preHandle, rawDataObj.prjData);
@@ -160,6 +171,12 @@ class Rpt_Data_Extractor {
                     case JV.PROP_HANDLE_TYPE_SUM:
                         summaryData(srcData, preHandle, rawDataObj.prjData);
                         break;
+                    case JV.PROP_HANDLE_TYPE_ADD_DUMMY:
+                        addDummyData(srcData, preHandle);
+                        break;
+                    case JV.PROP_HANDLE_TYPE_ADJUST:
+                        adjustData(srcData, preHandle);
+                        break;
                     default:
                         break;
                 }
@@ -242,14 +259,15 @@ function summaryData(sourceData, handleCfg, prjData){
         } else {
             for (let sumKey of handleCfg[JV.PROP_SUM_SUM_KEYS]) {
                 if (dtl[sumKey]) {
-                    sumObj[grpKey][sumKey] += dtl[sumKey];
+                    // sumObj[grpKey][sumKey] += dtl[sumKey];
+                    sumObj[grpKey][sumKey] = parseFloat(sumObj[grpKey][sumKey]) + parseFloat(dtl[sumKey]);
                 }
             }
         }
     }
     delete sourceData.data;
     sourceData.data = rstArr;
-    // fsUtil.wirteObjToFile(sourceData.data, "D:/GitHome/ConstructionCost/tmp/sumRst.js");
+    // fsUtil.writeObjToFile(sourceData.data, "D:/GitHome/ConstructionCost/tmp/sumRst.js");
 }
 
 function filterData(sourceData, handleCfg, prjData) {
@@ -317,10 +335,136 @@ function filterData(sourceData, handleCfg, prjData) {
     }
     delete sourceData.data;
     sourceData.data = rstArr;
-    // fsUtil.wirteObjToFile(sourceData.data, "D:/GitHome/ConstructionCost/tmp/filteredRst.js");
+    // fsUtil.writeObjToFile(sourceData.data, "D:/GitHome/ConstructionCost/tmp/filteredRst.js");
+}
+
+function adjustData(sourceData, adjustCfg) {
+    let rstArr = [];
+    for (let item of sourceData.data) {
+        if (item._doc) {
+            rstArr.push(item._doc);
+        } else {
+            rstArr.push(item);
+        }
+    }
+    for (let item of adjustCfg[JV.PROP_ADJUST_COLLECTION]) {
+        for (let rec of rstArr) {
+            if (item[JV.PROP_ADJUST_ACTION] === "prefix") {
+                rec[item.key] = item[JV.PROP_ADJUST_ACTION_VAL] + rec[item.key];
+            } else if (item[JV.PROP_ADJUST_ACTION] === "suffix") {
+                rec[item.key] = rec[item.key] + item[JV.PROP_ADJUST_ACTION_VAL];
+            }
+        }
+    }
+    delete sourceData.data;
+    sourceData.data = rstArr;
+}
+
+function getDupGrpKeyVals(sourceData, segKeys) {
+    let rst = [];
+    function pushKeyVal(item) {
+        let tr = {};
+        for (let i = 0; i < segKeys.length; i++) {
+            tr[segKeys[i]] = item[segKeys[i]];
+        }
+        rst.push(tr);
+    }
+    for (let idx = 0; idx < sourceData.length; idx++) {
+        let itemRec = sourceData[idx];
+        if (idx === 0) {
+            pushKeyVal(itemRec);
+            continue;
+        }
+        let hasDiff = false;
+        for (let i = 0; i < segKeys.length; i++) {
+            if (itemRec[segKeys[i]] !== sourceData[idx - 1][segKeys[i]]) {
+                hasDiff = true;
+                break;
+            }
+        }
+        if (hasDiff) {
+            pushKeyVal(itemRec);
+        }
+    }
+    return rst;
+}
+
+function addDummyData(sourceData, addCfg) {
+    let rstArr = [], tempRstArr = [];
+    for (let item of sourceData.data) {
+        if (item._doc) {
+            tempRstArr.push(item._doc);
+        } else {
+            tempRstArr.push(item);
+        }
+    }
+    for (let item of addCfg[JV.PROP_DUMMY_COLLECTION]) {
+        let newRecStr = JSON.stringify(item[JV.PROP_DUMMY_VAL]), cacheGrpKeyRecs = null;
+        if (item[JV.PROP_FREQUENCY] === "OncePerGrp") {
+            if (!cacheGrpKeyRecs) {
+                cacheGrpKeyRecs = {};
+            }
+            let cacheKey = "";
+            for (let key of item[JV.PROP_GRP_KEYS]) {
+                cacheKey += "_" + key;
+            }
+            if (!cacheGrpKeyRecs[cacheKey]) {
+                cacheGrpKeyRecs[cacheKey] = getDupGrpKeyVals(tempRstArr, item[JV.PROP_GRP_KEYS]);
+            }
+            for (let kv of cacheGrpKeyRecs[cacheKey]) {
+                let rec = JSON.parse(newRecStr);
+                for (let key of item[JV.PROP_GRP_KEYS]) {
+                    rec[key] = kv[key];
+                }
+                rstArr.push(rec);
+            }
+
+        } else if (item[JV.PROP_FREQUENCY] === "Once") {
+            rstArr.push(JSON.parse(newRecStr));
+        }
+    }
+    rstArr = rstArr.concat(tempRstArr);
+    delete sourceData.data;
+    sourceData.data = rstArr;
+}
+
+function getGLJBizType(orgType, orgCode, orgName) {
+    let rst = orgType;
+    if (orgName.indexOf("其他材料费") >= 0) {
+        rst = 299;
+    } else if (orgType === GLJ_TYPE.Labour) {
+        rst = 11;
+        if (orgCode === "000000") rst = 10;
+    } else if (orgType === GLJ_TYPE.Main_Material || orgType === GLJ_TYPE.Equipment) {
+        //未计价材料(主材 + 设备)
+        rst = 30 + orgType;
+    } else if (orgType === GLJ_TYPE.Material || (orgType >= 200 && orgType < 300)) {
+        //材料
+        if (orgCode === "000000") {
+            rst = 20; //2.材料
+        } else if (orgCode === "000000_1") {
+            rst = 30; //(1) 未计价材料
+        } else if (orgCode === "000000_2") {
+            rst = 40; //(2) 辅助材料
+        } else if (orgCode === "000000_3") {
+            rst = 50; //(3) 其他材料费
+        } else {
+            rst = 45; //到这里就只有辅助材料没有预处理了
+        }
+    } else if (orgType === GLJ_TYPE.Machine || (orgType >= 300 && orgType < 400)) {
+        //机械
+        if (orgCode === "000000") {
+            rst = 300; //3.机械
+        } else if (orgType === GLJ_TYPE.Machine) {
+            rst = 300.5;
+        } else {
+            rst = orgType;
+        }
+    }
+    return rst;
 }
 
-function sortData(sourceData, sortCfg) {
+function sortData(sourceData, sortCfg, prjData) {
     let rst = sourceData.data, tempRstArr = [];
     let sortType = sortCfg[JV.PROP_SORT_TYPE];
     for (let item of sourceData.data) {
@@ -330,6 +474,56 @@ function sortData(sourceData, sortCfg) {
             tempRstArr.push(item);
         }
     }
+    function private_normal_sort(destArr, sortKeys) {
+        destArr.sort(function(a, b){
+            let compRst = 0;
+            for (let comp of sortKeys) {
+                let reverse = (comp.order === 'ascend')?1:(-1);
+                //
+                if (a[comp.key] > b[comp.key]) {
+                    compRst = reverse;
+                    break;
+                } else if (a[comp.key] < b[comp.key]) {
+                    compRst = -reverse;
+                    break;
+                }
+            }
+            return compRst;
+        });
+    }
+    function private_parent_sort(parentArr, parentKeys, childArr, childKeys) {
+        let tmpRst = {}, rst = [];
+        for (let pItem of parentArr) {
+            let pKey = "key";
+            for (let key of parentKeys) {
+                pKey += "_" + pItem[key];
+            }
+            tmpRst[pKey] = [];
+        }
+        for (let cItem of childArr) {
+            let cKey = "key";
+            for (let key of childKeys) {
+                cKey += "_" + cItem[key];
+            }
+            if (tmpRst[cKey]) {
+                tmpRst[cKey].push(cItem);
+            } else {
+                //unknown child value! should be filtered!
+            }
+        }
+        // childArr.splice(0);
+        for (let pItem of parentArr) {
+            let pKey = "key";
+            for (let key of parentKeys) {
+                pKey += "_" + pItem[key];
+            }
+            rst.push(tmpRst[pKey]);
+            // for (let rItem of tmpRst[pKey]) {
+            //     childArr.push(rItem);
+            // }
+        }
+        return rst;
+    }
     switch (sortType) {
         case "tree":
             rst = treeUtil.buildTreeNodeDirectly(tempRstArr);
@@ -337,26 +531,62 @@ function sortData(sourceData, sortCfg) {
             treeUtil.getFlatArray(rst, destArr);
             delete sourceData.data;
             sourceData.data = destArr;
-            // fsUtil.wirteObjToFile(sourceData.data, "D:/GitHome/ConstructionCost/tmp/sortedAndFlattedRst.js");
+            // fsUtil.writeObjToFile(sourceData.data, "D:/GitHome/ConstructionCost/tmp/sortedAndFlattedRst.js");
             break;
         case "normal":
-            tempRstArr.sort(function(a, b){
-                let compRst = 0;
-                for (let comp of sortCfg[JV.PROP_SORT_KEYS]) {
-                    let reverse = (comp.order === 'ascend')?1:(-1);
-                    if (a[comp.key] > b[comp.key]) {
-                        compRst = reverse;
-                        break;
-                    } else if (a[comp.key] < b[comp.key]) {
-                        compRst = -reverse;
-                        break;
+            private_normal_sort(tempRstArr, sortCfg[JV.PROP_SORT_KEYS]);
+            delete sourceData.data;
+            sourceData.data = tempRstArr;
+            // fsUtil.writeObjToFile(sourceData.data, "D:/GitHome/ConstructionCost/tmp/normalSortedRst.js");
+            break;
+        case "accord_to_parent":
+            let pcKey = sortCfg[JV.PROP_PARENT_CHILD_SORT_KEY];
+            let parentSrcData = getModuleDataByKey(prjData, pcKey[JV.PROP_PARENT_DATA_KEY]);
+            if (parentSrcData) {
+                let tempParentArr = [];
+                for (let item of parentSrcData.data) {
+                    if (item._doc) {
+                        tempParentArr.push(item._doc);
+                    } else {
+                        tempParentArr.push(item);
+                    }
+                }
+                let sortedRstArr = private_parent_sort(tempParentArr, pcKey[JV.PROP_PARENT_SORT_KEYS], tempRstArr, pcKey[JV.PROP_CHILD_SORT_KEYS]);
+                if (sortCfg[JV.PROP_OTHER_SUB_SORT] && sortCfg[JV.PROP_OTHER_SUB_SORT].length > 0) {
+                    for (let sort of sortCfg[JV.PROP_OTHER_SUB_SORT]) {
+                        if (sort[JV.PROP_SORT_TYPE] === 'normal') {
+                            for (let subArr of sortedRstArr) {
+                                private_normal_sort(subArr, sort[JV.PROP_SORT_KEYS]);
+                            }
+                        } else if (sort[JV.PROP_SORT_TYPE] === 'self_define') {
+                            for (let subArr of sortedRstArr) {
+                                // console.log(subArr);
+                                let selfDefFunc = null;
+                                eval('selfDefFunc = ' + sort[JV.PROP_SORT_TYPE_SELF_DEFINE_LOGIC]);
+                                subArr.sort(selfDefFunc);
+                                // console.log(subArr);
+                            }
+                        }
+                    }
+                }
+                tempRstArr.splice(0);
+                for (let item of sortedRstArr) {
+                    for (let subItem of item) {
+                        tempRstArr.push(subItem);
                     }
                 }
-                return compRst;
-            });
+            }
+            delete sourceData.data;
+            sourceData.data = tempRstArr;
+            break;
+        case "self_define":
+            if (sortCfg[JV.PROP_SORT_TYPE_SELF_DEFINE_LOGIC]) {
+                let selfDefFunc = null;
+                eval('selfDefFunc = ' + sortCfg[JV.PROP_SORT_TYPE_SELF_DEFINE_LOGIC]);
+                tempRstArr.sort(selfDefFunc);
+            }
             delete sourceData.data;
             sourceData.data = tempRstArr;
-            // fsUtil.wirteObjToFile(sourceData.data, "D:/GitHome/ConstructionCost/tmp/normalSortedRst.js");
             break;
         default:
             //
@@ -394,14 +624,63 @@ function shielded_exec_env($PROJECT, $ME, rptDataItemObj) {
     }
 }
 
+function getActPropertyVal(firstPropKey, secPropKey, orgObj) {
+    let rst = null;
+    if (orgObj[firstPropKey]) {
+        rst = orgObj[firstPropKey];
+    } else if (orgObj[secPropKey]){
+        rst = orgObj[secPropKey];
+    }
+    return rst;
+}
+
+function getDeepProperty(propKey, orgObj, destArr) {
+    let keys = propKey.split(".");
+    let dftPropKey = "key", dftPropVal = "value", secDftPropVal = "items";
+    let parent = orgObj, lastVal = null;
+    for (let key of keys) {
+        if (parent instanceof Array) {
+            for (let item of parent) {
+                if (item[dftPropKey] === key) {
+                    lastVal = getActPropertyVal(dftPropVal, secDftPropVal, item);
+                    break;
+                }
+            }
+        } else {
+            lastVal = null;
+            if (parent[key] !== undefined) {
+                lastVal = parent[key];
+            } else if (parent[secDftPropVal]){
+                for (let item of parent[secDftPropVal]) {
+                    if (item[dftPropKey] === key) {
+                        // lastVal = item[dftPropVal];
+                        lastVal = getActPropertyVal(dftPropVal, secDftPropVal, item);
+                        break;
+                    }
+                }
+            }
+        }
+        parent = lastVal;
+        if (parent === null) break;
+    }
+    if (destArr && destArr instanceof Array) {
+        destArr.push(lastVal);
+    }
+}
+
 function ext_mainGetPropety(propKey) {
     let rst = [], parentObj = this;
     let dtObj = parentObj["myOwnRawDataObj"];
     if (propKey && dtObj) {
         if (dtObj.hasOwnProperty("property")) {
-            rst.push(dtObj["property"][propKey]);
+            if (!dtObj["property"][propKey] && dtObj[propKey]) {
+                rst.push(dtObj[propKey]);
+            } else {
+                getDeepProperty(propKey, dtObj["property"], rst);
+            }
         } else  {
-            rst.push(dtObj[propKey]);
+            // rst.push(dtObj[propKey]);
+            getDeepProperty(propKey, dtObj, rst);
         }
     }
     return rst;
@@ -414,6 +693,11 @@ function ext_getPropety(propKey) {
         for (let dItem of dtObj.data) {
             let doc = (dItem._doc === null || dItem._doc === undefined)?dItem:dItem._doc;
             if (doc.hasOwnProperty("property")) {
+                // if (!doc["property"][propKey] && doc[propKey]) {
+                //     rst.push(doc[propKey]);
+                // } else {
+                //     getDeepProperty(propKey, doc["property"], rst);
+                // }
                 rst.push(doc["property"][propKey]);
             } else if (doc.hasOwnProperty(propKey)) {
                 rst.push(doc[propKey]);
@@ -583,7 +867,7 @@ function ext_getArrayItemByKey(arrayKey, itemKey, itemKeyValue, itemRstKey){
 
 }
 
-function ext_getPropertyByForeignId(foreignIdVal, adHocIdKey, propKey) {
+function ext_getPropertyByForeignId(foreignIdVal, adHocIdKey, propKey, dftValIfNotFound) {
     let rst = [], parentObj = this;
     let IdKey = (adHocIdKey)?adHocIdKey:"ID";
     let dtObj = parentObj["myOwnRawDataObj"];
@@ -618,7 +902,9 @@ function ext_getPropertyByForeignId(foreignIdVal, adHocIdKey, propKey) {
                         break;
                     }
                 }
-                // if (!isFound) rst.push[null];
+                if (!isFound) {
+                    rst.push(dftValIfNotFound);
+                }
             }
         } else {
             for (let item of dtObj.data) {
@@ -628,7 +914,9 @@ function ext_getPropertyByForeignId(foreignIdVal, adHocIdKey, propKey) {
                     break;
                 }
             }
-            // if (!isFound) rst.push[null];
+            if (!isFound) {
+                rst.push(dftValIfNotFound);
+            }
         }
     }
     return rst;

+ 42 - 12
modules/reports/util/rpt_excel_util.js

@@ -158,6 +158,9 @@ function writeStyles(stylesObj){
         if (strUtil.convertStrToBoolean(font[JV.FONT_PROPS[3]])) {
             rst.push('<b/>');
         }
+        if (strUtil.convertStrToBoolean(font[JV.FONT_PROPS[5]])) {
+            rst.push('<u/>');
+        }
         rst.push('<sz val="' + font.size + '"/>');
         rst.push('<color indexed="' + font.colorIdx + '"/>');
         rst.push('<name val="' + font[JV.FONT_PROPS[0]] + '"/>');
@@ -270,7 +273,13 @@ function writeSharedString(sharedStrList){
         for (let i = 0; i < sharedStrList.length; i++) {
             //rst.push('<si><t>' + sharedStrList[i] + '</t></si>');
             if (typeof sharedStrList[i] === 'string') {
-                rst.push('<si><t>' + sharedStrList[i].replace('|','\r\n') + '</t></si>');
+                if (sharedStrList[i].indexOf('|') >= 0) {
+                    //rst.push('<si><t>' + sharedStrList[i].split('|').join('\r\n') + '</t></si>');
+                    rst.push('<si><t>' + sharedStrList[i].split('|').join('\n') + '</t></si>');
+                } else {
+                    rst.push('<si><t>' + sharedStrList[i] + '</t></si>');
+                }
+                // rst.push('<si><t>' + sharedStrList[i].replace('|','\r\n') + '</t></si>');
             } else {
                 rst.push('<si><t>' + sharedStrList[i] + '</t></si>');
             }
@@ -332,7 +341,7 @@ function writeSheets(pageData, paperSize, sharedStrList, stylesObj, isSinglePage
 function writeSheet(pageData, sheetData, paperSize, sharedStrList, stylesObj){
     let rst = [], xPos = [], yPos = [], yMultiPos = [], headerStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
     let cacheBorderCell = {};
-    let currentPageMergePos = null;
+    let currentPageMergePos = null; //在 JV.PAGING_OPTION_INFINITY 场合应用
     let private_pre_analyze_pos = function(){
         let cell, pos;
         let self_analyze_sheet_pos = function (theShtData, theXPos, theYPos) {
@@ -416,11 +425,17 @@ function writeSheet(pageData, sheetData, paperSize, sharedStrList, stylesObj){
             stylesObj.fonts = [];
             //for (let i = 0; i < sheetData.font_collection)
         }
-        let sheetFont = pageData.font_collection[cell.font];
+        let sheetFont = null;
+        if (typeof cell[JV.PROP_FONT] === "string") {
+            sheetFont = pageData[JV.NODE_FONT_COLLECTION][cell[JV.PROP_FONT]];
+        } else {
+            sheetFont = cell[JV.PROP_FONT];
+        }
         for (let i = 0; i < stylesObj.fonts.length; i++) {
             let font = stylesObj.fonts[i];
             if (sheetFont) {
-                if (font[JV.FONT_PROPS[0]] === sheetFont[JV.FONT_PROPS[0]] && font.size === Math.round(sheetFont[JV.FONT_PROPS[1]] * 3 / 4) && font[JV.FONT_PROPS[3]] == sheetFont[JV.FONT_PROPS[3]]) {
+                if (font[JV.FONT_PROPS[0]] === sheetFont[JV.FONT_PROPS[0]] && font.size === Math.round(sheetFont[JV.FONT_PROPS[1]] * 3 / 4)
+                    && font[JV.FONT_PROPS[3]] === sheetFont[JV.FONT_PROPS[3]] && font[JV.FONT_PROPS[5]] === sheetFont[JV.FONT_PROPS[5]]) {
                     hasFont = true;
                     rst = i;
                     break;
@@ -436,6 +451,7 @@ function writeSheet(pageData, sheetData, paperSize, sharedStrList, stylesObj){
             font.charset = 134;
             font.colorIdx = "8";
             font[JV.FONT_PROPS[3]] = sheetFont[JV.FONT_PROPS[3]]; //font bold
+            font[JV.FONT_PROPS[5]] = sheetFont[JV.FONT_PROPS[5]]; //font underline
             stylesObj.fonts.push(font);
             rst = stylesObj.fonts.length - 1;
         }
@@ -476,7 +492,10 @@ function writeSheet(pageData, sheetData, paperSize, sharedStrList, stylesObj){
         return rst;
     };
     let private_chkAndGetMergeLine = function(cell, sheetBorder, borderStr, needFurtherChk) {
-        let rst = 0, mergeBorder = pageData[JV.BAND_PROP_MERGE_BAND];
+        let rst = 0,
+            mergeBorder = (sheetData[JV.PROP_PAGE_MERGE_BORDER])?sheetData[JV.PROP_PAGE_MERGE_BORDER]:pageData[JV.BAND_PROP_MERGE_BAND],
+            mergeBand = pageData[JV.BAND_PROP_MERGE_BAND]
+        ;
         if (sheetBorder[borderStr] && sheetBorder[borderStr][JV.PROP_LINE_WEIGHT] !== undefined) {
             rst = sheetBorder[borderStr][JV.PROP_LINE_WEIGHT];
         }
@@ -489,7 +508,7 @@ function writeSheet(pageData, sheetData, paperSize, sharedStrList, stylesObj){
                     for (let i = 0; i < topSide.length; i++) {
                         if (cell[JV.PROP_AREA][JV.PROP_TOP] >= topSide[i]) {
                             if (cell[JV.PROP_AREA][JV.PROP_BOTTOM] <= bottomSide[i]) {
-                                let destStyle = pageData[JV.NODE_STYLE_COLLECTION][mergeBorder[JV.PROP_STYLE][JV.PROP_ID]];
+                                let destStyle = pageData[JV.NODE_STYLE_COLLECTION][mergeBand[JV.PROP_STYLE][JV.PROP_ID]];
                                 rst = destStyle[borderStr][JV.PROP_LINE_WEIGHT];
                                 break;
                             }
@@ -498,13 +517,13 @@ function writeSheet(pageData, sheetData, paperSize, sharedStrList, stylesObj){
                         }
                     }
                 } else {
-                    let destStyle = pageData[JV.NODE_STYLE_COLLECTION][mergeBorder[JV.PROP_STYLE][JV.PROP_ID]];
+                    let destStyle = pageData[JV.NODE_STYLE_COLLECTION][mergeBand[JV.PROP_STYLE][JV.PROP_ID]];
                     rst = destStyle[borderStr][JV.PROP_LINE_WEIGHT];
                 }
             }
         } else {
             if (cell[JV.PROP_AREA][borderStr] === mergeBorder[borderStr]) {
-                let destStyle = pageData[JV.NODE_STYLE_COLLECTION][mergeBorder[JV.PROP_STYLE][JV.PROP_ID]];
+                let destStyle = pageData[JV.NODE_STYLE_COLLECTION][mergeBand[JV.PROP_STYLE][JV.PROP_ID]];
                 if (needFurtherChk) {
                     if (cell[JV.PROP_AREA][JV.PROP_TOP] >= mergeBorder[JV.PROP_TOP] &&
                         cell[JV.PROP_AREA][JV.PROP_BOTTOM] <= mergeBorder[JV.PROP_BOTTOM]) {
@@ -568,12 +587,23 @@ function writeSheet(pageData, sheetData, paperSize, sharedStrList, stylesObj){
         let rst = 1, hasStyle = false;
         if (!(stylesObj.cellXfs)) stylesObj.cellXfs = [];
         let fontId = private_getFontId(cell);
-        let fontAngle = parseInt(pageData.font_collection[cell.font].FontAngle);
+        let fontAngle = 0;
+        if (typeof cell[JV.PROP_FONT] === "string") {
+            fontAngle = parseInt(pageData[JV.NODE_FONT_COLLECTION][cell[JV.PROP_FONT]].FontAngle);
+        } else {
+            fontAngle = parseInt(cell[JV.PROP_FONT].FontAngle);
+        }
+
         let borderId = private_getBorderId(cell);
-        let cellControl = pageData[JV.NODE_CONTROL_COLLECTION][cell[JV.PROP_CONTROL]];
+        let cellControl = null;
+        if (typeof cell[JV.PROP_CONTROL] === "string") {
+            cellControl = pageData[JV.NODE_CONTROL_COLLECTION][cell[JV.PROP_CONTROL]];
+        } else {
+            cellControl = cell[JV.PROP_CONTROL];
+        }
         for (let i = 0; i < stylesObj.cellXfs.length; i++) {
             let sheetControl = stylesObj.cellXfs[i];
-            if (sheetControl.fontId == fontId && sheetControl.borderId == borderId) {
+            if (sheetControl.fontId === fontId && sheetControl.borderId === borderId) {
                 if (private_checkControl(cellControl, sheetControl)) {
                     rst = i;
                     hasStyle = true;
@@ -983,7 +1013,7 @@ module.exports = {
             }
             //3. everything is ok, then call me
             me.exportExcel(newPageData, paperSize, fName, 'false', sheetNames, callback);
-            fsUtil.wirteObjToFile(newPageData, 'D:/GitHome/ConstructionOperation/tmp/combinedHeader.js');
+            fsUtil.writeObjToFile(newPageData, 'D:/GitHome/ConstructionOperation/tmp/combinedHeader.js');
         } catch (e) {
             console.log(e);
         }

+ 132 - 50
modules/reports/util/rpt_pdf_util.js

@@ -6,8 +6,8 @@
  */
 
 const prf_cons = require('./pdf_base_files/rpt_pdf_consts');
-var pdf = require('pdfkit');
-var fs = require('fs');
+let pdf = require('pdfkit');
+let fs = require('fs');
 let jpcCmnHelper = require('../rpt_component/helper/jpc_helper_common');
 let DPI = jpcCmnHelper.getScreenDPI()[0];
 //let JV = prf_cons.JV;
@@ -21,7 +21,7 @@ module.exports ={
 function export_pdf_file (pageData, paperSize, fName, callback) {
     let offsetX= 10;
     let offsetY=10;
-    var doc = new pdf({autoFirstPage: false});
+    let doc = new pdf({autoFirstPage: false});
     let newName = '' + (new Date()).valueOf();
     let stream = doc.pipe(fs.createWriteStream(__dirname.slice(0, __dirname.length - 21) + '/tmp/'+newName+'.pdf'));
     let pageObj = pageData;
@@ -29,18 +29,38 @@ function export_pdf_file (pageData, paperSize, fName, callback) {
     let paperSizeIdx = JV.PAGES_SIZE_STR.indexOf(paperSize);
     let size = JV.PAGES_SIZE[paperSizeIdx];
 
+    function private_getIniPageMergeBorder(mergedBand) {
+        let rst = {};
+        rst[JV.PROP_LEFT] = mergedBand[JV.PROP_LEFT];
+        rst[JV.PROP_RIGHT] = mergedBand[JV.PROP_RIGHT];
+        rst[JV.PROP_TOP] = mergedBand[JV.PROP_TOP];
+        rst[JV.PROP_BOTTOM] = mergedBand[JV.PROP_BOTTOM];
+        rst[JV.PROP_STYLE] = mergedBand[JV.PROP_STYLE];
+        return rst;
+    }
+
+    let newPageMergeBand = private_getIniPageMergeBorder(pageObj[JV.BAND_PROP_MERGE_BAND]);
     if (pageObj && pageObj.items.length > 0 ) {
         for(let i=0;i<pageObj.items.length;i++){
-            doc.addPage({size:[size[1]*DPI,size[0]*DPI]});
-            var page = pageObj.items[i],
+            if (pageData[JV.NODE_PAGE_INFO][JV.NODE_PAGE_SIZE][0] > pageData[JV.NODE_PAGE_INFO][JV.NODE_PAGE_SIZE][1]) {
+                doc.addPage({size:[size[1]*DPI,size[0]*DPI]});
+            } else {
+                doc.addPage({size:[size[0]*DPI,size[1]*DPI]});
+            }
+            let page = pageObj.items[i],
                 fonts = pageObj[JV.NODE_FONT_COLLECTION],
                 styles = pageObj[JV.NODE_STYLE_COLLECTION],
-                controls = pageObj[JV.NODE_CONTROL_COLLECTION],
-                mergedBand = pageObj[JV.BAND_PROP_MERGE_BAND];
+                controls = pageObj[JV.NODE_CONTROL_COLLECTION];
 
-            for (var j = 0; j < page.cells.length; j++) {
-                var cell = page.cells[j];
-                private_drawCell(cell, fonts, styles, controls, mergedBand);
+            if (page[JV.PROP_PAGE_MERGE_BORDER]) {
+                newPageMergeBand[JV.PROP_LEFT] = page[JV.PROP_PAGE_MERGE_BORDER][JV.PROP_LEFT];
+                newPageMergeBand[JV.PROP_RIGHT] = page[JV.PROP_PAGE_MERGE_BORDER][JV.PROP_RIGHT];
+                newPageMergeBand[JV.PROP_TOP] = page[JV.PROP_PAGE_MERGE_BORDER][JV.PROP_TOP];
+                newPageMergeBand[JV.PROP_BOTTOM] = page[JV.PROP_PAGE_MERGE_BORDER][JV.PROP_BOTTOM];
+            }
+            for (let j = 0; j < page.cells.length; j++) {
+                let cell = page.cells[j];
+                private_drawCell(cell, fonts, styles, controls, newPageMergeBand);
             }
         }
     }
@@ -48,34 +68,43 @@ function export_pdf_file (pageData, paperSize, fName, callback) {
     stream.on('finish',function () {
         console.log(newName + ".pdf was written.");
         callback(newName);
-    })
+    });
 
+    function private_chkIfInMergedBand(mergedBand, cell) {
+        let rst = false;
+        if (mergedBand && cell) {
+            rst = mergedBand[JV.PROP_TOP] <= cell[JV.PROP_AREA][JV.PROP_TOP] && mergedBand[JV.PROP_BOTTOM] >= cell[JV.PROP_AREA][JV.PROP_BOTTOM] &&
+                mergedBand[JV.PROP_LEFT] <= cell[JV.PROP_AREA][JV.PROP_LEFT] && mergedBand[JV.PROP_RIGHT] >= cell[JV.PROP_AREA][JV.PROP_RIGHT];
+        }
+        return rst;
+    }
 
     function private_drawCell(cell, fonts, styles, controls, mergedBand) {
         doc.save();
         //doc.translate(0.5,0.5);
-        var style = styles[cell[JV.PROP_STYLE]];
+        let style = styles[cell[JV.PROP_STYLE]];
         if (style) {
-            private_drawLine(cell, doc, style, JV.PROP_TOP, [JV.PROP_LEFT, JV.PROP_TOP],[JV.PROP_RIGHT, JV.PROP_TOP], mergedBand, styles);
-            private_drawLine(cell, doc, style, JV.PROP_RIGHT, [JV.PROP_RIGHT, JV.PROP_TOP],[JV.PROP_RIGHT, JV.PROP_BOTTOM], mergedBand, styles);
-            private_drawLine(cell, doc, style, JV.PROP_BOTTOM, [JV.PROP_RIGHT, JV.PROP_BOTTOM],[JV.PROP_LEFT, JV.PROP_BOTTOM], mergedBand, styles);
-            private_drawLine(cell, doc, style, JV.PROP_LEFT, [JV.PROP_LEFT, JV.PROP_BOTTOM],[JV.PROP_LEFT, JV.PROP_TOP], mergedBand, styles);
+            let isNeedMergeBand = private_chkIfInMergedBand(mergedBand, cell);
+            private_drawLine(cell, doc, style, JV.PROP_TOP, [JV.PROP_LEFT, JV.PROP_TOP],[JV.PROP_RIGHT, JV.PROP_TOP], mergedBand, styles, isNeedMergeBand);
+            private_drawLine(cell, doc, style, JV.PROP_RIGHT, [JV.PROP_RIGHT, JV.PROP_TOP],[JV.PROP_RIGHT, JV.PROP_BOTTOM], mergedBand, styles, isNeedMergeBand);
+            private_drawLine(cell, doc, style, JV.PROP_BOTTOM, [JV.PROP_RIGHT, JV.PROP_BOTTOM],[JV.PROP_LEFT, JV.PROP_BOTTOM], mergedBand, styles, isNeedMergeBand);
+            private_drawLine(cell, doc, style, JV.PROP_LEFT, [JV.PROP_LEFT, JV.PROP_BOTTOM],[JV.PROP_LEFT, JV.PROP_TOP], mergedBand, styles, isNeedMergeBand);
         }
         private_drawCellText(cell, fonts, controls);
         doc.restore();
 
     }
 
-    function private_drawLine(cell, doc, style, styleBorderDest, startP, destP, mergedBand, styles) {
+    function private_drawLine(cell, doc, style, styleBorderDest, startP, destP, mergedBand, styles, isNeedMergeBand) {
         //doc.beginPath();
-        var destStyle = style;
+        let destStyle = style;
         if (mergedBand) {
-            if (mergedBand[styleBorderDest] == cell[JV.PROP_AREA][styleBorderDest]) {
+            if (isNeedMergeBand && parseFloat(mergedBand[styleBorderDest]) === parseFloat(cell[JV.PROP_AREA][styleBorderDest])) {
                 destStyle = styles[mergedBand[JV.PROP_STYLE][JV.PROP_ID]];
             }
         }
         doc.moveTo(cell[JV.PROP_AREA][startP[0]] + offsetX, cell[JV.PROP_AREA][startP[1]] + offsetY);
-        if (destStyle[styleBorderDest] && destStyle[styleBorderDest][JV.PROP_LINE_WEIGHT] != 0) {
+        if (destStyle[styleBorderDest] && parseFloat(destStyle[styleBorderDest][JV.PROP_LINE_WEIGHT]) !== 0) {
             doc.lineWidth(1.0 * destStyle[styleBorderDest][JV.PROP_LINE_WEIGHT]);
             doc.lineTo(cell[JV.PROP_AREA][destP[0]] + offsetX, cell[JV.PROP_AREA][destP[1]] + offsetY);
             doc.strokeColor(destStyle[styleBorderDest][JV.PROP_COLOR]);
@@ -84,12 +113,24 @@ function export_pdf_file (pageData, paperSize, fName, callback) {
     }
     function private_drawCellText(cell, fonts, controls) {
         if (cell[JV.PROP_VALUE]) {
-            var values = ("" + cell[JV.PROP_VALUE]).split('|');
-            var font = fonts[cell[JV.PROP_FONT]];
-            var control = controls[cell[JV.PROP_CONTROL]];
-            var height = cell[JV.PROP_AREA][JV.PROP_BOTTOM] - cell[JV.PROP_AREA][JV.PROP_TOP];
-            var area = [cell[JV.PROP_AREA][JV.PROP_LEFT] + offsetX, cell[JV.PROP_AREA][JV.PROP_TOP] + offsetY, cell[JV.PROP_AREA][JV.PROP_RIGHT] + offsetX, cell[JV.PROP_AREA][JV.PROP_BOTTOM] + offsetY];
-            for (var i = 0; i < values.length; i++) {
+            let values = ("" + cell[JV.PROP_VALUE]).split('|');
+            // let font = fonts[cell[JV.PROP_FONT]];
+            let font = null;
+            if (typeof cell[JV.PROP_FONT] === "string") {
+                font = fonts[cell[JV.PROP_FONT]];
+            } else {
+                font = cell[JV.PROP_FONT];
+            }
+            // let control = controls[cell[JV.PROP_CONTROL]];
+            let control = null;
+            if (typeof cell[JV.PROP_CONTROL] === "string") {
+                control = controls[cell[JV.PROP_CONTROL]];
+            } else {
+                control = cell[JV.PROP_CONTROL];
+            }
+            let height = cell[JV.PROP_AREA][JV.PROP_BOTTOM] - cell[JV.PROP_AREA][JV.PROP_TOP];
+            let area = [cell[JV.PROP_AREA][JV.PROP_LEFT] + offsetX, cell[JV.PROP_AREA][JV.PROP_TOP] + offsetY, cell[JV.PROP_AREA][JV.PROP_RIGHT] + offsetX, cell[JV.PROP_AREA][JV.PROP_BOTTOM] + offsetY];
+            for (let i = 0; i < values.length; i++) {
                 area[JV.IDX_TOP] = cell[JV.PROP_AREA][JV.PROP_TOP] + i * (height / values.length) + offsetY;
                 area[JV.IDX_BOTTOM] = cell[JV.PROP_AREA][JV.PROP_TOP] + (i + 1) * (height / values.length) + offsetY;
                 private_drawText(values[i], area, font, control);
@@ -98,22 +139,22 @@ function export_pdf_file (pageData, paperSize, fName, callback) {
     }
 
     function private_drawText(val, area, font, control) {
-        var dftFontHeight = 12;
-        var output = [];
+        let dftFontHeight = 12;
+        let output = [];
         if (font) {
             dftFontHeight = 1 * font[JV.FONT_PROPS[1]];
-            var dftFontBold = font[JV.FONT_PROPS[3]];
-            var dftFontItalic = font[JV.FONT_PROPS[4]];
-            if (dftFontBold && dftFontBold == 'T') {
+            let dftFontBold = font[JV.FONT_PROPS[3]];
+            let dftFontItalic = font[JV.FONT_PROPS[4]];
+            if (dftFontBold && dftFontBold === 'T') {
                 doc.font(__dirname+'/pdf_base_files/hwxsb.ttf');
-            }else if(dftFontItalic && dftFontItalic == 'T'){
+            }else if(dftFontItalic && dftFontItalic === 'T'){
                 doc.font(__dirname+'/pdf_base_files/Smart-italic.ttf');
             }else {
                 doc.font(__dirname+'/pdf_base_files/Smart.ttf');
             }
             doc.fontSize(dftFontHeight);
         }
-        var options={};
+        let options={};
         if (control) {
             private_setupAreaH(area, control.Horizon, font.FontAngle, dftFontHeight, output,options);
             private_setupAreaV(area, control.Vertical, font.FontAngle, dftFontHeight, output);
@@ -121,8 +162,8 @@ function export_pdf_file (pageData, paperSize, fName, callback) {
             private_setupAreaH(area, "left", font.FontAngle, dftFontHeight, output,options);
             private_setupAreaV(area, "bottom", font.FontAngle, dftFontHeight, output);
         }
-        var w = area[JV.IDX_RIGHT] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_RIGHT] - area[JV.IDX_LEFT] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_RIGHT];
-        if (font.FontAngle != "0") {
+        let w = area[JV.IDX_RIGHT] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_RIGHT] - area[JV.IDX_LEFT] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_RIGHT];
+        if (parseInt(font.FontAngle) !== 0) {
             w = area[JV.IDX_BOTTOM] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_BOTTOM] - area[JV.IDX_TOP] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP];
         }
         doc.save();
@@ -142,12 +183,54 @@ function export_pdf_file (pageData, paperSize, fName, callback) {
             }
         }
 
-        var rotateOptions;
-        if (font.FontAngle != "0") {
+        function private_drawUnderline() {
+            //A. 暂不支持角度; B. PDF输出时,坐标没有translate
+            let ctx = doc;
+            //1. 计算下划线的相关坐标
+            let width = ctx.widthOfString(val);
+            let height = dftFontHeight;
+            let startX = area[JV.IDX_LEFT], startY = area[JV.IDX_TOP], endX = area[JV.IDX_RIGHT], endY = area[JV.IDX_BOTTOM];
+            // let startX = 0, startY = 0, endX = width, endY = startY;
+            if (control.Horizon === "left") {
+                startX = Math.round(area[JV.IDX_LEFT] + JV.OUTPUT_OFFSET[JV.IDX_LEFT]);
+            } else if (control.Horizon === "right") {
+                startX = Math.round(area[JV.IDX_RIGHT] - width - JV.OUTPUT_OFFSET[JV.IDX_RIGHT]);
+            } else {
+                startX = Math.round( area[JV.IDX_LEFT] + (area[JV.IDX_RIGHT] - area[JV.IDX_LEFT] - width) / 2);
+            }
+            endX = Math.round(startX + width);
+
+            if (control.Vertical === "top") {
+                startY = Math.round(area[JV.IDX_TOP] + JV.OUTPUT_OFFSET[JV.IDX_TOP] + JV.OUTPUT_OFFSET[JV.IDX_BOTTOM] + height);
+            } else if (control.Vertical === "bottom") {
+                startY = Math.round(area[JV.IDX_BOTTOM] + JV.OUTPUT_OFFSET[JV.IDX_BOTTOM]);
+            } else {
+                startY = Math.round( area[JV.IDX_TOP] + (area[JV.IDX_BOTTOM] - area[JV.IDX_TOP] + height) / 2) + JV.OUTPUT_OFFSET[JV.IDX_TOP] + JV.OUTPUT_OFFSET[JV.IDX_BOTTOM];
+            }
+            endY = Math.round(startY);
+            //2. 画线
+            // ctx.save();
+            if ( output[1] !== Math.round(output[1])) {
+                ctx.translate(0,0.5);
+            }
+            // ctx.beginPath();
+            ctx.moveTo(startX, startY);
+            ctx.lineWidth(1);
+            ctx.strokeStyle = "BLACK";
+            ctx.lineTo(endX, endY);
+            ctx.stroke();
+            // ctx.restore();
+        }
+
+        let rotateOptions;
+        if (font[JV.FONT_PROPS[5]] === 'T' && parseInt(font.FontAngle) === 0) {
+            private_drawUnderline();
+        }
+        if (parseInt(font.FontAngle) !== 0) {
             if (control){
-                rotateOptions=private_setupAreaRotateOption(area,w,control.Vertical,dftFontHeight, output);
-            }else {
-                rotateOptions=private_setupAreaRotateOption(area,w,"bottom",dftFontHeight, output);
+                rotateOptions = private_setupAreaRotateOption(area,w,control.Vertical,dftFontHeight, output);
+            } else {
+                rotateOptions = private_setupAreaRotateOption(area,w,"bottom",dftFontHeight, output);
             }
             doc.rotate(font.FontAngle,rotateOptions);
         }
@@ -156,15 +239,15 @@ function export_pdf_file (pageData, paperSize, fName, callback) {
     }
 
     function private_setupAreaH(area, type, fontAngle, dftFontHeight, outputPoint,options) {
-        var lType = type;
-        if (type != "left" && type != "right" && type != "center") lType = "left";
+        let lType = type;
+        if (type !== "left" && type !== "right" && type !== "center") lType = "left";
         options.align=lType;
         outputPoint[0]=1 * area[JV.IDX_LEFT]+ JV.OUTPUT_OFFSET[JV.OFFSET_IDX_LEFT];
     }
 
     function private_setupAreaV(area, type, fontAngle, dftFontHeight, outputPoint) {
-        var lType = type;
-        if (type != "top" && type != "bottom" && type != "center") lType = "top";
+        let lType = type;
+        if (type !== "top" && type !== "bottom" && type !== "center") lType = "top";
         switch (lType) {
             case "top":
                 outputPoint[1] = 1 * area[JV.IDX_TOP] + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP];
@@ -179,13 +262,12 @@ function export_pdf_file (pageData, paperSize, fName, callback) {
     }
 
     function private_setupAreaRotateOption(area,w, type="top",dftFontHeight,outputPoint){
-        var x = (area[JV.IDX_RIGHT] - area[JV.IDX_LEFT])/2+area[JV.IDX_LEFT];
-        var y =(area[JV.IDX_BOTTOM] - area[JV.IDX_TOP])/2+ area[JV.IDX_TOP];
-        var rotateOptions = {origin:[x,y]};
-        var h = area[JV.IDX_RIGHT] - area[JV.IDX_LEFT];
+        let x = (area[JV.IDX_RIGHT] - area[JV.IDX_LEFT])/2+area[JV.IDX_LEFT];
+        let y =(area[JV.IDX_BOTTOM] - area[JV.IDX_TOP])/2+ area[JV.IDX_TOP];
+        let rotateOptions = {origin:[x,y]};
+        let h = area[JV.IDX_RIGHT] - area[JV.IDX_LEFT];
         outputPoint[0]=x-w/2+JV.OUTPUT_OFFSET[JV.OFFSET_IDX_LEFT];
-        var lType = type;
-        switch (lType) {
+        switch (type) {
             case "top":
                 outputPoint[1] = y- h/2+ JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP];
                 break;

+ 2 - 0
modules/volume_price/models/volume_price_model.js

@@ -30,6 +30,7 @@ class volumePriceModel extends baseModel {
             if(err){
                 callback(1, '', null);
             }else {
+                // console.log(JSON.stringify(datas));
                 callback(0, consts.projectConst.VOLUMEPRICE, datas);
             }
         })
@@ -39,6 +40,7 @@ class volumePriceModel extends baseModel {
         let funs = [];
 
         function saveOne(doc) {
+            // console.log('-----------------------------------------------------------------------------' + JSON.stringify(doc));
             return function (cb) {
                 switch (doc.updateType) {
                     case commonConsts.UT_UPDATE:

+ 6 - 0
modules/volume_price/models/volume_price_schema.js

@@ -20,12 +20,18 @@ let volumePriceSchema = new Schema({
     serialNo: Number,
     // 编号
     code: String,
+    // 量价类型:人工、材料、机械、主材、设备
+    type: String,
+    programID: Number,
     // 名称
     name: String,
     // 单位
     unit: String,
     // 数量
     quantity: Number,
+    marketUnitFee: String,
+    marketTotalFee: String,
+
     // 费用字段
     fees: [subSchema.feesSchema],
     // 是否删除

+ 0 - 474
public/calc_util.js

@@ -1,474 +0,0 @@
-/**
- * Created by Tony on 2017/6/21.
- * Modified by CSL, 2017-08-01 引入多套计算程序、费率同步、人工系数同步、改进基数计算、费字段映射等。
- * added by CSL, 2017-09-01 增加公式解析对象analyzer,用于解析用户修改公式、自定义表达式。
- */
-
-// 需求说小数位数固定为2位,这里预留缓冲接口,防止以后需求变卦。
-const Digit_Calc_Program = -2;              // 需求指定计算程序用到的小数位数。
-const Digit_Calc_Program_Default = -6;      // 需求末指定时默认用到的小数位数。
-function round(value, useDef = false) {
-    let digit = useDef ? Digit_Calc_Program_Default : Digit_Calc_Program;
-    return scMathUtil.roundTo(value, digit);
-};
-
-let executeObj = {
-    treeNode: null,
-    template: null,
-    calcBase: null,
-
-    at: function(ID) {
-        let me = executeObj,
-            rst = 0;
-        rst = me.template.compiledCalcItems[ID].unitFee;
-        rst = parseFloat(rst);
-        return rst;
-    },
-    base: function(calcBaseName) {
-        let me = executeObj, rst = 0,
-            //base = getRationCalcBase(calcBaseName);
-            base = me.calcBase[calcBaseName];
-
-        if (base != null) {
-            let price = 0, aprice = 0, mprice = 0, tmpSum = 0, mdSum = 0;
-
-            function isSubset(sub, arr){
-                // if(!(sub instanceof Array) || !(arr instanceof Array)) return false;
-                // if(sub.length > arr.length) return false;
-                for(var i = 0, len = sub.length; i < len; i++){
-                    if(arr.indexOf(sub[i]) == -1) return false;
-                }
-                return true;
-            }
-
-            // 机上人工费:多一层
-            if (isSubset(base.gljTypes, [gljType.MACHINE_LABOUR])) {
-                for (let glj of me.treeNode.data.gljList) {
-                       if (glj.type == gljType.GENERAL_MACHINE) {
-                            // 获取机械组成物
-                           let mds = projectObj.project.composition.getCompositionByCode(glj.code);
-                           if (!mds) mds = [];
-                           for (let md of mds){
-                               if (base.gljTypes.indexOf(md.glj_type) >= 0) {
-                                   price = md["base_price"];
-                                   if (!price) price = 0;
-                                   mdSum = mdSum +  round(md["consumption"] * price);
-                                   mdSum = round(mdSum, true);
-                               }
-                           };
-                           tmpSum = tmpSum + round(glj["quantity"] * mdSum, true);
-                           tmpSum = round(tmpSum, true);
-                       }
-                };
-            }else{
-                for (let glj of me.treeNode.data.gljList) {
-                    if (base.gljTypes.indexOf(glj.type) >= 0) {
-                        if (base.calcType == baseCalc){ price = glj["basePrice"];}
-                        else if (base.calcType == adjustCalc){price = glj["adjustPrice"];}
-                        else if (base.calcType == budgetCalc){price = glj["marketPrice"];}
-                        else if (base.calcType == diffCalc){
-                            aprice = glj["adjustPrice"];
-                            if (!aprice) aprice = 0;
-                            mprice = glj["marketPrice"];
-                            if (!mprice) mprice = 0;
-                            price = mprice - aprice;
-                        };
-                        if (!price) price = 0;
-                        tmpSum = round(tmpSum + round(glj["quantity"] * price, true), true);
-                    };
-                };
-            };
-
-            rst = round(tmpSum);
-        };
-
-        return rst;
-    },
-    HJ: function () {
-        let me = this;
-        return me.treeNode.data.baseTotalPrice;
-    }
-};
-
-let analyzer = {
-    calcTemplate: null,
-    success: true,
-
-    standard: function(expr){
-        let str = expr;
-        str = str.replace(/\s/g, "");               // 去空格、去中文空格
-        str = str.replace(/(/g, "(");              // 中文括号"("换成英文括号"("
-        str = str.replace(/)/g, ")");              // 中文括号")"换成英文括号")"
-        str = str.replace(/f/g, "F");               // f换成F
-        return str;
-    },
-
-    analyzeCalcBase: function(expr){
-        // 前提:必须无空格、无特殊符号
-        function getCalcBase(expr){
-            let base = '',
-                iPos1 = -1, iPos2 = -1;
-            for (let i = 0; i < expr.length; i++) {
-                if (expr[i] === '['){
-                    iPos1 = i;
-                }
-                else if (iPos1 != -1 && expr[i]===']'){
-                    iPos2 = i;
-                };
-
-                if (iPos1 != -1 && iPos2 != -1){
-                    base = expr.slice(iPos1, iPos2 + 1);
-                    break;
-                }
-            };
-            return base;
-        };
-        function calcBaseToIDExpr(base){
-            /*// for test. 公路模式,基数到ID
-            let id = -1;
-            if (base == '[人工费]'){
-                id = 111;
-            }
-            else if (base == '[材料费]'){
-                id = 222;
-            }
-            else if (base == '[机械费]'){
-                id = 333;
-            }
-            else id = "错误";
-
-            return "@('" + id + "')";*/
-            let baseValue = base.slice(1, -1);
-            return "base('" + baseValue + "')";
-        };
-
-        while (expr.indexOf('[') > 0) {
-            let base = getCalcBase(expr);
-            let id = calcBaseToIDExpr(base);
-            let baseValue = base.slice(1, -1);   // []会给下面的正则带来干扰,这里去掉
-            var pattBase =new RegExp(baseValue, "g");
-            expr = expr.replace(pattBase, id);
-            expr = expr.replace(/\[base\('/g, "base('");      // [@('       [base('
-            expr = expr.replace(/'\)\]/g, "')");        // ')]
-        };
-
-        return expr;
-    },
-
-    analyzeLineRef: function(expr){
-        let me = this;
-        function isOperator(char){
-            var operator = "+-*/()";
-            return operator.indexOf(char) > -1;
-        };
-        function lineNumToID(lineNum){
-            if (lineNum > me.calcTemplate.calcItems.length){
-                me.success = false;
-                return '越界';
-            }
-            else{
-                let id = me.calcTemplate.calcItems[lineNum - 1].ID;
-                return id;
-            }
-        };
-        // 前提:必须无空格、无特殊符号、标准大写F
-        function getSection(expr){
-            let section = '',
-                iPos1 = -1, iPos2 = -1;
-            for (let i = 0; i < expr.length; i++) {
-                if (expr[i] === 'F'){
-                    iPos1 = i;
-                }
-                else if (iPos1 != -1 && isOperator(expr[i])){
-                    iPos2 = i;
-                }
-                else if (iPos1 != -1 && i == expr.length - 1){
-                    iPos2 = i + 1;
-
-                };
-                if (iPos1 != -1 && iPos2 != -1){
-                    section = expr.slice(iPos1, iPos2);
-                    break;
-                }
-            };
-            return section;
-        };
-        function sectionToIDExpr(section){
-            if (section){
-                let lineNum = section.slice(1);
-                if (isNaN(lineNum)){
-                    me.success = false;
-                    return '错误';      // 这里的返回提示不能加上section,因为会无限循环
-                }
-                else
-                    return "@('" + lineNumToID(lineNum) + "')";
-            }
-            else return '';
-        };
-
-        while (expr.indexOf('F') > 0) {
-            let sec = getSection(expr);
-            let id = sectionToIDExpr(sec);
-            var pattSec =new RegExp(sec, "g");
-            expr = expr.replace(pattSec, id);
-        };
-        return expr;
-    },
-
-    analyzeUserExpr: function(calcTemplate, calcItem){
-        let me = this;
-        me.calcTemplate = calcTemplate;
-        let expr = calcItem.dispExpr;
-        // 标准化:处理特殊字符、中文符号、大小写
-        expr = me.standard(expr);
-        calcItem.dispExpr = expr;
-        // 先换掉计算基数
-        expr = me.analyzeCalcBase(expr);
-        // 再换掉行引用
-        expr = me.analyzeLineRef(expr);
-        calcItem.expression = expr;
-        return me.success;
-    }
-};
-
-class Calculation {
-    // 先编译公用的基础数据
-    compilePublics(feeRates, labourCoes, feeTypes, calcBases){
-        let me = this;
-        me.compiledFeeRates = {};
-        me.compiledLabourCoes = {};
-        me.compiledTemplates = {};
-        me.compiledFeeTypes = {};
-        me.compiledFeeTypeNames = [];
-        me.compiledCalcBases = {};
-        me.saveForReports = [];
-
-        let private_compile_feeRateFile = function() {
-            if (feeRates) {
-                for (let rate of feeRates) {
-                    me.compiledFeeRates["feeRate_" + rate.ID] = rate;
-                }
-            }
-        };
-        let private_compile_labourCoeFile = function() {
-            if (labourCoes) {
-                for (let coe of labourCoes) {
-                    me.compiledLabourCoes["LabourCoe_" + coe.ID] = coe;
-                }
-            }
-        };
-        let private_compile_feeType = function() {
-            if (feeTypes) {
-                for (let ft of feeTypes) {
-                    me.compiledFeeTypes[ft.type] = ft.name;
-                    me.compiledFeeTypes[ft.name] = ft.type;    // 中文预编译,可靠性有待验证
-                    me.compiledFeeTypeNames.push(ft.name);
-                }
-            }
-        };
-        let private_compile_calcBase = function() {
-            if (calcBases) {
-                for (let cb of calcBases) {
-                    me.compiledCalcBases[cb.dispName] = cb;         // 中文预编译,可靠性有待验证
-                }
-            }
-        };
-
-        private_compile_feeRateFile();
-        private_compile_labourCoeFile();
-        private_compile_feeType();
-        private_compile_calcBase();
-    };
-
-    compileTemplate(template){
-        let me = this;
-        me.compiledTemplates[template.ID] = template;
-        template.hasCompiled = false;
-        template.errs = [];
-
-        let private_extract_ID = function(str, idx){
-            let rst = '', lBracket = 0, rBracket = 0, firstIdx = idx, lastIdx = 0;
-            for (let i = idx; i < str.length; i++) {
-                if (str[i] === '(') {
-                    lBracket++;
-                    if (lBracket == 1) firstIdx = i + 1;
-                }
-                if (str[i] === ')') {
-                    rBracket++;
-                    if (lBracket == rBracket) {
-                        lastIdx = i - 1;
-                        if (lastIdx > firstIdx) {
-                            if (str[firstIdx] === "'") firstIdx++;
-                            if (str[lastIdx] !== "'") lastIdx++;
-                            if (lastIdx > firstIdx) {
-                                rst = str.slice(firstIdx, lastIdx);
-                            }
-                        }
-                        break;
-                    }
-                }
-            }
-            return rst;
-        };
-        let private_parse_ref = function(item, itemIdx){
-            let idx = item.expression.indexOf('@(', 0);
-            while (idx >= 0) {
-                let ID = private_extract_ID(item.expression, idx);
-                if (ID.length > 0) {
-                    let subItem = template.compiledCalcItems[ID];
-                    if (subItem) {
-                        if (subItem.ID !== item.ID) {
-                            private_parse_ref(subItem, template.compiledCalcItems[ID + "_idx"]);
-                        } else {
-                            template.errs.push("There exists the self refer ID: " + ID);
-                        }
-                    } else {
-                        template.errs.push("There exists the invalid ID by which could not find the item: " + ID);
-                        console.log('invalid ID: ' + ID);
-                    }
-                }
-                idx = item.expression.indexOf('@(', idx + ID.length + 3);
-            }
-            if (template.compiledSeq.indexOf(itemIdx) < 0) {
-                template.compiledSeq.push(itemIdx);
-            }
-        };
-        let private_setup_seq = function(item, itemIdx){
-            if (template.compiledSeq.indexOf(itemIdx) < 0) {
-                private_parse_ref(item, itemIdx);
-            }
-        };
-        let private_compile_items = function() {
-            for (let idx of template.compiledSeq) {
-                let item = template.calcItems[idx];
-                item.dispExprUser = item.dispExpr;    // 用于界面显示。disExpr是公式模板,不允许修改:人工系数占位符被修改后变成数值,第二次无法正确替换。
-                if (item.expression == 'HJ')
-                    item.compiledExpr = '$CE.HJ()'
-                else{
-                    item.compiledExpr = item.expression.split('@(').join('$CE.at(');
-                    item.compiledExpr = item.compiledExpr.split('base(').join('$CE.base(');
-                };
-
-                if (item.labourCoeID){
-                    let lc = me.compiledLabourCoes["LabourCoe_" + item.labourCoeID].coe;
-                    item.dispExprUser = item.dispExpr.replace(/L/gi, lc.toString());
-                    item.compiledExpr = item.compiledExpr.replace(/L/gi, lc.toString());
-                };
-
-                if (item.feeRateID) {
-                    let orgFeeRate = item.feeRate;
-                    let cmf = me.compiledFeeRates["feeRate_" + item.feeRateID];
-                    item.feeRate = cmf?cmf.rate:100;
-
-                    if (!orgFeeRate || (orgFeeRate && orgFeeRate != item.feeRate)){
-                        me.saveForReports.push({templatesID: template.ID, calcItem: item});
-                    }
-                };
-
-                // 字段名映射
-                item.displayFieldName = me.compiledFeeTypes[item.fieldName];
-            }
-        };
-
-        if (template && template.calcItems && template.calcItems.length > 0) {
-            template.compiledSeq = [];
-            template.compiledCalcItems = {};
-
-            for (let i = 0; i < template.calcItems.length; i++) {
-                let item = template.calcItems[i];
-                template.compiledCalcItems[item.ID] = item;
-                template.compiledCalcItems[item.ID + "_idx"] = i;
-            }
-
-            for (let i = 0; i < template.calcItems.length; i++) {
-                private_setup_seq(template.calcItems[i], i);
-            }
-            if (template.errs.length == 0) {
-                private_compile_items();
-                template.hasCompiled = true;
-            } else {
-                console.log('errors: ' + template.errs.toString());
-            }
-        };
-    };
-
-    calculate(treeNode){
-        let me = this;
-        let templateID = treeNode.data.programID;
-        if (!templateID) templateID = 1;
-        let template = me.compiledTemplates[templateID];
-        treeNode.data.calcTemplate = template;
-
-        let project = projectObj.project;
-
-        // 缺省计算程序需要提供总金额作为计算基数,然后每条按比例(费率)计算,不需要工料机明细。
-        if (treeNode.data.baseTotalPrice != undefined){
-            delete treeNode.data.gljList;
-        }
-        else {
-            if (treeNode.sourceType === project.Ration.getSourceType()) {
-                treeNode.data.gljList = project.ration_glj.getGljArrByRation(treeNode.data.ID);
-            }
-            else if (treeNode.sourceType === project.Bills.getSourceType()) {
-                let rations = project.Ration.getBillsSortRation(treeNode.source.getID());
-                treeNode.data.gljList = project.ration_glj.getGatherGljArrByRations(rations);
-            };
-        };
-
-        if (treeNode && template.hasCompiled) {
-            let $CE = executeObj;
-            $CE.treeNode = treeNode;
-            $CE.template = template;
-            $CE.calcBase = me.compiledCalcBases;
-
-            if (!treeNode.data.fees) {
-                treeNode.data.fees = [];
-                treeNode.data.feesIndex = {};
-                treeNode.changed = true;
-            };
-
-            for (let idx of template.compiledSeq) {
-                let calcItem = template.calcItems[idx];
-
-                let feeRate = calcItem.feeRate;
-                if (!feeRate) feeRate = 100;    // 100%
-                calcItem.unitFee = round(eval(calcItem.compiledExpr) * feeRate * 0.01);   // 如果eval()对清单树有影响,就换成小麦的Expression对象再试
-
-                let quantity = treeNode.data.quantity;
-                if (!quantity) quantity = 0;
-                calcItem.totalFee = round(calcItem.unitFee * quantity);
-
-                // 费用同步到定额
-                // 引入小麦的字段检测后,快速切换定额出现计算卡顿现象,过多的循环造成。这里把她的代码拆出来,减少微循环。
-                if (calcItem.fieldName != '') {
-                    if (!treeNode.data.feesIndex[calcItem.fieldName]){
-                        let fee = {
-                            'fieldName': calcItem.fieldName,
-                            'unitFee': calcItem.unitFee,
-                            'totalFee': calcItem.totalFee,
-                            'tenderUnitFee': 0,
-                            'tenderTotalFee': 0
-                        };
-                        treeNode.data.fees.push(fee);
-                        treeNode.data.feesIndex[calcItem.fieldName] = fee;
-                        treeNode.changed = true;
-                    }
-                    else{
-                        if (treeNode.data.feesIndex[calcItem.fieldName].unitFee != calcItem.unitFee){
-                            treeNode.data.feesIndex[calcItem.fieldName].unitFee = calcItem.unitFee;
-                            treeNode.changed = true;
-                        };
-
-                        if (treeNode.data.feesIndex[calcItem.fieldName].totalFee != calcItem.totalFee){
-                            treeNode.data.feesIndex[calcItem.fieldName].totalFee = calcItem.totalFee;
-                            treeNode.changed = true;
-                        };
-                    }
-                };
-            };
-        }
-    }
-};
-
-/*
-export default analyzer;*/

+ 0 - 20
public/common_util.js

@@ -1,20 +0,0 @@
-/**
- * Created by CSL on 2017-06-06.
- * public functions.
- */
-
-function deleteEmptyObject(arr) {
-    function isEmptyObject(e) {
-        var t;
-        for (t in e)
-            return !1;
-        return !0
-    };
-
-    for (var i = 0; i < arr.length; i++) {
-        if (isEmptyObject(arr[i])) {
-            arr.splice(i, 1);
-            i = i - 1;
-        };
-    };
-};

+ 0 - 28
public/debug.js

@@ -1,28 +0,0 @@
-/**
- * Created by CSL on 2017-05-19.
- * 用于展示未知对象的内容。如:debug.m(GC.Spread.Sheets.Events);
- */
-
-debug = {
-    m: function (flag, obj) {
-        alert(flag + this.objStr(obj));
-    },
-
-    d: function (flag, obj) {
-        alert(flag + JSON.stringify(obj));
-    },
-
-    objStr: function (obj) {
-        var str = "";
-        var spr = "";
-        for (var x in obj) {
-            if (obj.hasOwnProperty(x)) {
-                if (str == '') {
-                    spr = ''
-                } else { spr = ', ' } ;
-                str += spr + x + ':' + obj[x];
-            }
-        }
-        return str;
-    }
-}

+ 1 - 1
public/fsUtil.js

@@ -25,7 +25,7 @@ module.exports = {
             });
         }
     },
-    wirteObjToFile: function(obj, filePath) {
+    writeObjToFile: function(obj, filePath) {
         if (obj) {
             let arr = [];
             arr.push(JSON.stringify(obj));

+ 9 - 0
public/stringUtil.js

@@ -136,6 +136,15 @@ module.exports = {
         }
         return rst;
     },
+    trim: function(str) {
+        return str.replace(/(^\s*)|(\s*$)/g, "");
+    },
+    leftTrim: function(str) {
+        return str.replace(/(^\s*)/g,"");
+    },
+    rightTrim: function(rst) {
+        return str.replace(/(\s*$)/g,"");
+    },
     convertNumToChinese : function(num, isCurrency) {
         if (!/^\d*(\.\d*)?$/.test(num)) { return "Number is wrong!"; }
         let AA, BB;

+ 15 - 0
public/web/common_util.js

@@ -0,0 +1,15 @@
+/**
+ * Created by CSL on 2017-06-06.
+ * public functions for web.
+ */
+
+// 忽略大小写判断字符串是否和参数指定的字符串相同
+String.prototype.sameText = function (str) {
+    return this.toLowerCase() == str.toLowerCase();
+};
+
+// 忽略大小写判断字符串是否有参数指定的子串
+String.prototype.hasSubStr = function (str) {
+    return this.toLowerCase().indexOf(str.toLowerCase()) > -1;
+};
+

+ 21 - 1
public/web/rpt_value_define.js

@@ -47,6 +47,11 @@ const JV = {
 
     NODE_MAP_DATA_HANDLE_INFO: "映射数据预处理",
     PROP_DATA_KEY: "映射数据对象",
+    PROP_PARENT_DATA_KEY: "父映射数据对象",
+    PROP_PARENT_CHILD_SORT_KEY: "父子排序键",
+    PROP_PARENT_SORT_KEYS: "父排序键值集",
+    PROP_CHILD_SORT_KEYS: "子排序键值集",
+    PROP_OTHER_SUB_SORT: "其他子排序",
     PROP_HANDLE_TYPE: "预处理类型",
     PROP_FILTER_KEY: "过滤键值集",
     PROP_FILTER_COMPARE_OBJ: "compareObjKey",
@@ -56,7 +61,18 @@ const JV = {
     PROP_HANDLE_TYPE_FILTER: "过滤",
     PROP_HANDLE_TYPE_SUM: "合计",
     PROP_HANDLE_TYPE_SORT: "排序",
+    PROP_HANDLE_TYPE_ADD_DUMMY: "增加Dummy数据",
+    PROP_HANDLE_TYPE_ADJUST: "数据调整",
+
+    PROP_ADJUST_COLLECTION: "数据调整集",
+    PROP_ADJUST_ACTION: "action",
+    PROP_ADJUST_ACTION_VAL: "actionValue",
+    PROP_DUMMY_COLLECTION: "Dummy数据集",
+    PROP_DUMMY_VAL: "Dummy数据对象值",
+    PROP_FREQUENCY: "频率",
+    PROP_GRP_KEYS: "GrpKeyIds",
     PROP_SORT_TYPE: "排序方式",
+    PROP_SORT_TYPE_SELF_DEFINE_LOGIC: "自定义逻辑",
     PROP_SORT_KEYS: "排序键值集",
     PROP_SUM_GROUP_KEYS: "分组键值集",
     PROP_SUM_SUM_KEYS: "统计键值集",
@@ -120,6 +136,7 @@ const JV = {
     PROP_CALCULATION: "CalculationType",
     PROP_H_CALCULATION: "H_CalculationType",
     PROP_V_CALCULATION: "V_CalculationType",
+    PROP_FIT_AREA: "isFitArea",
 
     IDX_LEFT: 0,
     IDX_TOP: 1,
@@ -220,13 +237,14 @@ const JV = {
     SIZE_16K: [7.75, 10.75],
     SIZE_EXECUTIVE: [7.25, 10.5],
 
-    OUTPUT_OFFSET: [2,1,2,3],
+    OUTPUT_OFFSET: [2,2,1,3],
     OFFSET_IDX_LEFT: 0,
     OFFSET_IDX_RIGHT: 1,
     OFFSET_IDX_TOP: 2,
     OFFSET_IDX_BOTTOM: 3,
 
     PROP_PAGE_SEQ: "page_seq",
+    PROP_PAGE_MERGE_BORDER: "page_merge_border",
     PROP_CELLS: "cells",
 
     PAGING_OPTION_NORMAL: 'normal',
@@ -248,6 +266,8 @@ const JV = {
 
     VERTICAL_ANGLE: "90",
     ANTI_VERTICAL_ANGLE: "-90",
+    VERTICAL_ANGLE_INT: 90,
+    ANTI_VERTICAL_ANGLE_INT: -90,
 
     LAST_DEF: ""
 };

+ 20 - 0
public/web/scMathUtil.js

@@ -60,7 +60,14 @@ let scMathUtil = {
         let result = bin;
         let iDot = bin.indexOf('.');
         if (iDot < 0){return result};
+        // 二进制浮点数带小数位数最大长度
+        let floatLength = 53;
         let iLength = bin.length;
+        // 长度小于53说明该二进制数尾数长度小于Double类型限制,未被截断,无需进位
+        if (iLength < floatLength) {
+            return result;
+        }
+        iLength = bin.length;
         for (let i = iLength - 1; i > iDot; i--){
             let num = Number(bin[i]);
             if (num === 0){
@@ -77,6 +84,19 @@ let scMathUtil = {
     roundTo: function(num, digit){
         let me = this;
         return me.innerRoundTo(me.binToFloat(me.incMantissa(me.floatToBin(num))), digit);
+    },
+    isNumber : function (obj) {
+        return obj === +obj;
+    },
+    roundToString:function(obj,decimal){
+        let me = this;
+        let value;
+        if(me.isNumber(obj)){
+            value = me.roundTo(obj,-decimal)
+        }else {
+            value = me.roundTo(Number(obj),-decimal);
+        }
+        return value.toFixed(decimal);
     }
 };
 

+ 8 - 3
public/web/sheet/sheet_common.js

@@ -97,6 +97,9 @@ var sheetCommonObj = {
         //sheet.addRows(row, 1);
 
         sheet.clear(0, 0, sheet.getRowCount(), sheet.getColumnCount(), GC.Spread.Sheets.SheetArea.viewport, GC.Spread.Sheets.StorageType.data);
+        if(sheet.getRowCount()<data.length){
+            sheet.setRowCount(data.length);
+        }
         for (var col = 0; col < setting.header.length; col++) {
             var hAlign = "left", vAlign = "center";
             if (setting.header[col].hAlign) {
@@ -123,10 +126,12 @@ var sheetCommonObj = {
                 //var cell = sheet.getCell(row, col, GC.Spread.Sheets.SheetArea.viewport);
                 var val = data[row][setting.header[col].dataCode];
                 if(val&&setting.header[col].dataType === "Number"){
-                    if(setting.header[col].hasOwnProperty('tofix')){
-                        val =parseFloat(val).toFixed(setting.header[col].tofix);
+                    if(setting.header[col].hasOwnProperty('decimalField')){
+                        var decimal = getDecimal(setting.header[col].decimalField);
+                        val =scMathUtil.roundToString(val,decimal);
+                        sheet.setFormatter(-1, col,getFormatter(decimal), GC.Spread.Sheets.SheetArea.viewport);
                     }else {
-                        val =parseFloat(val).toFixed(2);
+                        val =scMathUtil.roundToString(val,2);
                     }
                 }
                 if(val!=null&&setting.header[col].cellType === "checkBox"){

+ 1 - 1
public/web/tree_sheet/tree_sheet_helper.js

@@ -137,7 +137,7 @@ var TREE_SHEET_HELPER = {
                     cell.value(getFieldText2());
                 }
                 if (colSetting.data.cellType && Object.prototype.toString.apply(colSetting.data.cellType) !== "[object String]") {
-                    cell.cellType(colSetting.data.cellType);
+                    cell.cellType(colSetting.data.cellType(node));
                 }
                 if (colSetting.readOnly) {
                     if (Object.prototype.toString.apply(colSetting.readOnly) === "[object Function]") {

+ 16 - 0
public/web/tree_table/tree_table.js

@@ -198,6 +198,17 @@
  			return treenode;
  		};
 
+		Tree.prototype.findNodeByNid = function (nid) {
+			let treenode = null,
+				callback = function (node) {
+					if(node.data && node.data[node.setting.tree.nid] === nid){
+						treenode = node;
+					}
+				};
+			this.traverseDF.call(this, callback);
+			return treenode;
+		}
+
  		Tree.prototype.findNodeByDomId = function (domId) {
  			var treenode = null,
  				callback = function (node) {
@@ -274,6 +285,11 @@
 					parent.children.splice(parent.childIndex(node), 1);
 					parent.children.splice(parent.childIndex(next), 0, node);
 				};
+				let pre = that.findNodeByNid(node.data.ID) || null;
+				if(pre && parent.childIndex(pre) !== parent.childIndex(node) - 1){
+					parent.children.splice(parent.childIndex(pre), 1);
+					parent.children.splice(parent.childIndex(node), 0, pre);
+				}
 			};
 
  			for (i = 0; i < arrData.length; i++){

+ 3 - 82
test/tmp_data/test_ration_calc/ration_calc_base.js

@@ -3,89 +3,10 @@
  */
 "use strict";
 
-const baseCalc = 0, adjustCalc = 1, budgetCalc = 2, diffCalc = 3,  offerCalc = 4;
-
-const gljType = {
-    // 人工
-    LABOUR: 1,
-    // ==============材料类型=================
-    // 普通材料
-    GENERAL_MATERIAL: 201,
-    // 混凝土
-    CONCRETE: 202,
-    // 砂浆
-    MORTAR: 203,
-    // 配合比
-    MIX_RATIO: 204,
-    // 商品混凝土
-    COMMERCIAL_CONCRETE: 205,
-    // 商品砂浆
-    COMMERCIAL_MORTAR: 206,
-    // ==============材料类型=================
-    // ==============机械类型=================
-    // 普通机械
-    GENERAL_MACHINE: 301,
-    // 机械组成物
-    MACHINE_COMPOSITION: 302,
-    // 机上人工
-    MACHINE_LABOUR: 303,
-    // ==============机械类型=================
-    // 主材
-    MAIN_MATERIAL: 4,
-    // 设备
-    EQUIPMENT: 5
-};
-
-let rationCalcBase = [
-    {
-        'dispName': '定额基价人工费',
-        'calcFun': 'base',
-        'calcType': baseCalc,
-        'gljTypes': [gljType.LABOUR]
-    },{
-        'dispName': '定额基价材料费',
-        'calcFun': 'base',
-        'calcType': baseCalc,
-        'gljTypes': [gljType.GENERAL_MATERIAL, gljType.CONCRETE, gljType.MORTAR, gljType.MIX_RATIO, gljType.COMMERCIAL_CONCRETE, gljType.COMMERCIAL_MORTAR]
-    },{
-        'dispName': '定额基价机械费',
-        'calcFun': 'base',
-        'calcType': baseCalc,
-        'gljTypes': [gljType.GENERAL_MACHINE]
-    },{
-        'dispName': '定额基价机上人工费',
-        'calcFun': 'base',
-        'calcType': baseCalc,
-        'gljTypes': [gljType.MACHINE_LABOUR]
-    },{
-        'dispName': '人工费价差',
-        'calcFun': 'diff',
-        'calcType': diffCalc,
-        'gljTypes': [gljType.LABOUR]
-    },{
-        'dispName': '材料费价差',
-        'calcFun': 'diff',
-        'calcType': diffCalc,
-        'gljTypes': [gljType.GENERAL_MATERIAL, gljType.CONCRETE, gljType.MORTAR, gljType.MIX_RATIO, gljType.COMMERCIAL_CONCRETE, gljType.COMMERCIAL_MORTAR]
-    },{
-        'dispName': '机械费价差',
-        'calcFun': 'diff',
-        'calcType': diffCalc,
-        'gljTypes': [gljType.GENERAL_MACHINE]
-    },{
-        'dispName': '主材费',
-        'calcFun': 'budget',
-        'calcType': budgetCalc,
-        'gljTypes': [gljType.MAIN_MATERIAL]
-    },{
-        'dispName': '设备费',
-        'calcFun': 'budget',
-        'calcType': budgetCalc,
-        'gljTypes': [gljType.EQUIPMENT]
-    }
-];
+// 这里的代码分别移到main_costs.js中和calc_program.js中,目的是相关内容集中放置,减少重复声明,减少单元引用。
 
 // 该方法暂时不用,已使用中文预编译的方式代替,但可靠性有待验证,若有问题再用回来。
+/*
 function getRationCalcBase(dispName){
     for (let base of rationCalcBase) {
        if (base.dispName == dispName) {
@@ -93,4 +14,4 @@ function getRationCalcBase(dispName){
        };
     }
     return null;
-};
+};*/

+ 115 - 0
test/unit/reports/test_cover_01.js

@@ -0,0 +1,115 @@
+/**
+ * Created by Tony on 2017/11/21.
+ */
+
+let test = require('tape');
+import JpcEx from "../../../modules/reports/rpt_component/jpc_ex";
+import JV from "../../../modules/reports/rpt_component/jpc_value_define";
+let mongoose = require("mongoose");
+let fileUtils = require("../../../modules/common/fileUtils");
+let path = require('path');
+let dbm = require("../../../config/db/db_manager");
+let rpt_cfg = require('./rpt_cfg');
+dbm.connect();
+let consts = require('../../../modules/main/models/project_consts');
+let projectConsts = consts.projectConst;
+fileUtils.getGlobbedFiles('../../../modules/complementary_glj_lib/models/*.js').forEach(function(modelPath) {
+    require(path.resolve(modelPath));
+});
+
+fileUtils.getGlobbedFiles('../../../modules/ration_glj/models/*.js').forEach(function(modelPath) {
+    require(path.resolve(modelPath));
+});
+
+//引入报表模块
+fileUtils.getGlobbedFiles('../../../modules/reports/models/*.js').forEach(function(modelPath) {
+    require(path.resolve(modelPath));
+})
+
+//暂时引入其它模块的model
+require('../../../modules/fee_rates/models/fee_rates');
+// 引入人工系数模块
+require('../../../modules/main/models/labour_coe_model');
+require('../../../modules/main/models/calc_program_model');
+
+let fsUtil = require("../../../public/fsUtil");
+
+let prjMdl = require('../../../modules/pm/models/project_model');
+let projectDataMdl = require('../../../modules/main/models/project');
+let demoPrjId = - 1;
+let demoRptId = 223, pagesize = "A4";
+
+let userId_Leng = 1142; //小冷User Id
+demoPrjId = 1220; //QA:
+/*/
+ let userId_Dft = userId_Leng;
+ /*/
+let userId_Dft = 76075;
+//*/
+
+let rptTplFacade = require("../../../modules/reports/facade/rpt_template_facade");
+let rptTplDataFacade = require("../../../modules/reports/facade/rpt_tpl_data_facade");
+
+import rptDataExtractor from "../../../modules/reports/util/rpt_construct_data_util";
+
+let fs = require('fs');
+//设置Date Format函数
+fs.readFile(__dirname.slice(0, __dirname.length - 18) + '/public/web/date_util.js', 'utf8', 'r', function (err, data) {
+    eval(data);
+});
+
+//*
+test('测试 - 打开模板: 封-1 ', function (t) {
+    rptTplFacade.getRptTemplate(demoRptId).then(function(rptTpl) {
+        let rptDataUtil = new rptDataExtractor();
+        rptDataUtil.initialize(rptTpl._doc);
+        let filter = rptDataUtil.getDataRequestFilter();
+        // console.log(filter);
+        //正常应该根据报表模板定义的数据类型来请求数据
+        rptTplDataFacade.prepareProjectData(userId_Dft, demoPrjId, filter, function (err, msg, rawDataObj) {
+            if (!err) {
+                try {
+                    // fsUtil.writeObjToFile(rawDataObj, "D:/GitHome/ConstructionCost/tmp/rptTplRawDataObject.js");
+                    let tplData = rptDataUtil.assembleData(rawDataObj);
+                    //build the report
+                    let printCom = JpcEx.createNew();
+                    rptTpl[JV.NODE_MAIN_INFO][JV.NODE_PAGE_INFO][JV.PROP_PAGE_SIZE] = pagesize;
+                    let defProperties = rpt_cfg;
+                    let dftOption = JV.PAGING_OPTION_NORMAL;
+                    printCom.initialize(rptTpl);
+                    printCom.analyzeData(rptTpl, tplData, defProperties, dftOption);
+                    let maxPages = printCom.totalPages;
+                    let pageRst = printCom.outputAsSimpleJSONPageArray(rptTpl, tplData, 1, maxPages, defProperties);
+                    if (pageRst) {
+                        // fsUtil.writeObjToFile(pageRst, "D:/GitHome/ConstructionCost/tmp/testBuiltPageResult.js");
+                    } else {
+                        console.log("oh! no pages were created!");
+                    }
+                } catch (ex) {
+                    console.log(ex);
+                    t.pass('pass with exception!');
+                    t.end();
+                }
+
+                t.pass('pass succeeded!');
+                t.end();
+            } else {
+                console.log(msg);
+                t.pass('pass with error!');
+                t.end();
+            }
+        })
+    });
+});
+//*/
+
+test('close the connection', function (t) {
+    setTimeout(function () {
+        mongoose.disconnect();
+        t.pass('closing db connection');
+        t.end();
+    }, 8000);
+    // mongoose.disconnect();
+    // t.pass('closing db connection');
+    // t.end();
+});

+ 9 - 8
test/unit/reports/test_tpl_09_1.js

@@ -39,7 +39,8 @@ let demoPrjId = - 1;
 let demoRptId = 226, pagesize = "A4";
 
 let userId_Leng = 1142; //小冷User Id
-demoPrjId = 720; //QA: DW3
+// demoPrjId = 720; //QA: DW3
+demoPrjId = 838; //QA:
 /*/
 let userId_Dft = userId_Leng;
 /*/
@@ -61,7 +62,7 @@ fs.readFile(__dirname.slice(0, __dirname.length - 18) + '/public/web/date_util.j
 test('测试 - 获取project数据: ', function (t) {
     projectDataMdl.getData(demoPrjId, function (err, message, result) {
         if (!err) {
-            fsUtil.wirteObjToFile(result, "D:/GitHome/ConstructionCost/tmp/ProjectDataFullObject.js");
+            fsUtil.writeObjToFile(result, "D:/GitHome/ConstructionCost/tmp/ProjectDataFullObject.js");
             t.pass('pass succeeded!');
             t.end();
         } else {
@@ -90,8 +91,8 @@ test('测试 - 获取project部分数据: ', function (t) {
                     //     newData.push(JSON.stringify(item));
                     // }
                     // fsUtil.writeArrayToFile(newData, "D:/GitHome/ConstructionCost/tmp/getProjectData_partial.js");
-                    fsUtil.wirteObjToFile(prjObj, "D:/GitHome/ConstructionCost/tmp/getProjectObjectNew.js");
-                    fsUtil.wirteObjToFile(results, "D:/GitHome/ConstructionCost/tmp/getProjectData_partialNew.js");
+                    // fsUtil.writeObjToFile(prjObj, "D:/GitHome/ConstructionCost/tmp/getProjectObjectNew.js");
+                    fsUtil.writeObjToFile(results, "D:/GitHome/ConstructionCost/tmp/getProjectData_partialNew.js");
                     t.pass('pass succeeded!');
                     t.end();
                 } else {
@@ -120,7 +121,7 @@ test('测试 - 测试模板啦: ', function (t) {
             if (!err) {
                 try {
                     let tplData = rptDataUtil.assembleData(rawDataObj);
-                    // fsUtil.wirteObjToFile(rawDataObj, "D:/GitHome/ConstructionCost/tmp/rptTplRawDataObject.js");
+                    // fsUtil.writeObjToFile(rawDataObj, "D:/GitHome/ConstructionCost/tmp/rptTplRawDataObject.js");
                     //it's time to build the report!!!
                     let printCom = JpcEx.createNew();
                     rptTpl[JV.NODE_MAIN_INFO][JV.NODE_PAGE_INFO][JV.PROP_PAGE_SIZE] = pagesize;
@@ -131,9 +132,9 @@ test('测试 - 测试模板啦: ', function (t) {
                     let maxPages = printCom.totalPages;
                     let pageRst = printCom.outputAsSimpleJSONPageArray(rptTpl, tplData, 1, maxPages, defProperties);
                     if (pageRst) {
-                        fsUtil.wirteObjToFile(pageRst, "D:/GitHome/ConstructionCost/tmp/testBuiltPageResult.js");
+                        // fsUtil.wirteObjToFile(pageRst, "D:/GitHome/ConstructionCost/tmp/testBuiltPageResult.js");
                     } else {
-                        console.log("oh! no pages were created!")
+                        console.log("oh! no pages were created!");
                     }
                 } catch (ex) {
                     console.log(ex);
@@ -179,7 +180,7 @@ test('测试 - 显示保存小数位数问题: ', function (t) {
     rpt_decimal_mdl.find({}).then(function (rst) {
         //console.log(rst);
         if (rst.length > 0) {
-            fsUtil.wirteObjToFile(rst, "D:/GitHome/ConstructionCost/tmp/testDecimalResult.js");
+            fsUtil.writeObjToFile(rst, "D:/GitHome/ConstructionCost/tmp/testDecimalResult.js");
         }
         t.pass('pass get decimal ok!');
         t.end();

+ 3 - 0
tmp/forDeployment.js

@@ -0,0 +1,3 @@
+/**
+ * Created by Tony on 2017/11/28.
+ */

+ 4 - 4
web/building_saas/complementary_glj_lib/js/gljComponent.js

@@ -309,7 +309,7 @@ let gljComponentOprObj = {
          if(args.col === 4 && me.currentEditingComponent.code && args.editingText && args.editingText.trim().length > 0){//消耗量
             let consumeAmt = parseFloat(args.editingText);
             if(!isNaN(consumeAmt) && consumeAmt !== me.currentEditingComponent.consumeAmt){
-                let roundCons = scMathUtil.roundTo(consumeAmt, -3);
+                let roundCons = scMathUtil.roundTo(parseFloat(consumeAmt), -3);
                 let component = that.currentGlj.component;
                 for(let i = 0; i < component.length; i++){
                     if(component[i].ID === that.currentComponent[args.row].ID){
@@ -430,7 +430,7 @@ let gljComponentOprObj = {
                 if(row + i < that.currentComponent.length){
                     let currentObj = that.currentComponent[row + i];
                     if(items[i].consumeAmt.trim().length > 0 && items[i].consumeAmt !== currentObj.consumeAmt){
-                        let roundCons = scMathUtil.roundTo(items[i].consumeAmt, 3);
+                        let roundCons = scMathUtil.roundTo(parseFloat(items[i].consumeAmt), 3);
                         isChange = true;
                         currentObj.consumeAmt = roundCons;
                         for(let j = 0; j < component.length; j++){
@@ -496,8 +496,8 @@ let gljComponentOprObj = {
     reCalGljBasePrc: function (component) {
         let me = gljComponentOprObj, gljBasePrc = 0;
         for(let i = 0; i < component.length; i++){
-            let roundBasePrc = scMathUtil.roundTo(component[i].basePrice, -2);
-            gljBasePrc += scMathUtil.roundTo(roundBasePrc * component[i].consumeAmt, -2);
+            let roundBasePrc = scMathUtil.roundTo(parseFloat(component[i].basePrice), -2);
+            gljBasePrc += scMathUtil.roundTo(roundBasePrc * parseFloat(component[i].consumeAmt), -2);
         }
         return gljBasePrc;
     }

+ 51 - 45
web/building_saas/css/main.css

@@ -8,10 +8,10 @@ body {
     font-size: 0.9rem
 }
 .btn.disabled, .btn:disabled {
-  color:#999
+    color:#999
 }
 .btn-link:focus, .btn-link:hover{
-  text-decoration: none
+    text-decoration: none
 }
 /*自定义css*/
 .header {
@@ -28,10 +28,10 @@ body {
     line-height: inherit
 }
 .top-msg{
-  position: fixed;
-  top:0;
-  width:100%;
-  z-index: 999
+    position: fixed;
+    top:0;
+    width:100%;
+    z-index: 999
 }
 .in-1{padding-left:0px!important}
 .in-2{padding-left:21px!important}
@@ -70,7 +70,7 @@ body {
     color: #333
 }
 .main-nav .nav-tabs{
-  border-bottom: none
+    border-bottom: none
 }
 .content {
     border-left: 1px solid #ccc;
@@ -78,7 +78,7 @@ body {
     background: #fff
 }
 .toolsbar,.toolsbar-f {
-  border-bottom: 1px solid #ccc
+    border-bottom: 1px solid #ccc
 }
 .tools-btn {
     height: 30px;
@@ -92,23 +92,23 @@ body {
     overflow-y: hidden;
 }
 .main-data-top,.main-data-full{
-  overflow: hidden;
-  width:100%
+    overflow: hidden;
+    width:100%
 }
 .main-content.col-lg-8{
-  width:66.666667%
+    width:66.666667%
 }
 .main-content.col-lg-12{
-  width:100%
+    width:100%
 }
 .main-side.col-lg-4{
-  width: 33.333333%;
+    width: 33.333333%;
 }
 .main-side.col-lg-0{
-  width:0%;
+    width:0%;
 }
 .sidebar-bottom,.sidebar-bottom .col-lg-6,.sidebar-bottom .col-lg-12 {
-  height:300px
+    height:300px
 }
 .top-content, .fluid-content {
     overflow: auto;
@@ -124,7 +124,7 @@ body {
     padding: 0.2em 0.5em
 }
 .side-tabs .nav-tabs .nav-item {
-  z-index: 999
+    z-index: 999
 }
 .side-tabs .nav-tabs {
     border-bottom: none;
@@ -251,63 +251,69 @@ body {
     overflow: auto;
 }
 .poj-list span.poj-icon {
-  padding-right:7px;
-  color:#ccc
+    padding-right:7px;
+    color:#ccc
 }
 .poj-list a.tree-open,.poj-list a.tree-close{
-  width:15px;
-  display: inline-block;
+    width:15px;
+    display: inline-block;
 }
 .print-toolsbar{
-  padding:5px
+    padding:5px
 }
 .print-toolsbar .panel {
-  display:inline-block;
-  vertical-align:top;
-  background:#f7f7f9
+    display:inline-block;
+    vertical-align:top;
+    background:#f7f7f9
 }
 .print-toolsbar .panel .panel-foot{
-  text-align: center;
-  font-size: 12px
+    text-align: center;
+    font-size: 12px
 }
 .print-list {
-  border-right:1px solid #ccc
+    border-right:1px solid #ccc
 }
 .print-list .form-list {
-  overflow: auto
+    overflow: auto
 }
 .print-list .list-tools{
-  height:50px;
-  padding:10px 0;
-  border-bottom:1px solid #f2f2f2
+    height:50px;
+    padding:10px 0;
+    border-bottom:1px solid #f2f2f2
 }
 .pageContainer {
-  background: #ededed;
-  text-align: center
+    background: #ededed;
+    text-align: center
 }
 .pageContainer .page{
-  border:9px solid transparent;
-  display: inline-block;
+    border:9px solid transparent;
+    display: inline-block;
 }
 .pageContainer .page img{
-  width:inherit;
-  height: inherit;
+    width:inherit;
+    height: inherit;
 }
 .modal-auto-height {
-  height: 400px;
-  overflow-y: auto;
+    height: 400px;
+    overflow-y: auto;
 }
 .modal-fixed-height {
-  height: 400px;
-  overflow-y: hidden;
+    height: 400px;
+    overflow-y: hidden;
 }
 .sidebar-tools-bar {
-  background:#fff
+    background:#fff
 }
 .side-search-box{
-  background:#fff;
-  border-bottom:1px solid #ddd
+    background:#fff;
+    border-bottom:1px solid #ddd
 }
 .navbar-crumb span{
-  max-width: 200px
+    max-width: 200px
+}
+.dropdown-item{
+    color:#007bff
+}
+.dropdown-item.disabled, .dropdown-item:disabled{
+    pointer-events:none
 }

+ 1 - 1
web/building_saas/glj/html/glj_index.html

@@ -12,7 +12,7 @@
 </div>
 <div class="container-fluid">
     <div class="row">
-        <div class="main-content col-lg-12 p-0">
+        <div class="col-lg-12 p-0">
             <div class="top-content">
                 <div class="main-data-top" id="project-glj">
                     <p style="text-align: center; margin-top: 30px;">正在加载数据</p>

+ 0 - 1
web/building_saas/glj/js/composition_spread.js

@@ -133,7 +133,6 @@ CompositionSpread.prototype.getRatioData = function(projectGLJid) {
         success: function(response) {
             if (response.err === 0) {
                 response.data = JSON.parse(response.data);
-                console.log(response.data);
                 // 设置数据
                 self.sheetObj.setData(response.data);
                 self.specialColumn(response.data);

+ 35 - 17
web/building_saas/glj/js/project_glj.js

@@ -22,6 +22,7 @@ let mixRatioConnectData = [];
 let mixRatioMap={};
 // 单价文件相关
 let usedUnitPriceInfo = {};
+let usedTenderList = [];
 let otherFileData = {};
 let currentTag = '';
 let isChanging = false;
@@ -162,12 +163,15 @@ $(document).ready(function () {
             // 从其他项目中复制
             changeUnitPriceId = $("#other-file").val();
         }
+        $('#change-dj').modal("hide");
+        $.bootstrapLoading.start();
         $.ajax({
             url: '/glj/change-file',
             type: 'post',
             data: {project_id: scUrlUtil.GetQueryString('project'), change_id: changeUnitPriceId, type: type},
             error: function() {
                 isChanging = false;
+                $.bootstrapLoading.end();
             },
             beforeSend: function() {
                 isChanging = true;
@@ -177,9 +181,16 @@ $(document).ready(function () {
                 if (response.err === 1) {
                     let msg = response.msg !== undefined ? response.msg : '未知错误';
                     alert(msg);
+                    $.bootstrapLoading.end();
                     return false;
                 }
-                $('#change-dj').modal("hide");
+                projectObj.project.projectGLJ.loadData(function () {
+                    let projectGLJ = projectObj.project.projectGLJ;
+                    projectGLJ.loadCacheData();
+                    unitPriceFileInit();
+                    gljOprObj.refreshView();
+                    $.bootstrapLoading.end();
+                });
             }
         });
     });
@@ -193,6 +204,14 @@ $(document).ready(function () {
             $("#notify").slideUp('fast');
         });
     });
+
+    $('#pop-dj').popover({
+            placement:"bottom",
+            html:true,
+            trigger:"hover | focus",
+            content: getUsedTenderInfo
+        }
+    );
 });
 
 /**
@@ -215,14 +234,10 @@ function init() {
                 data.constData.ownCompositionTypes : canNotChangeTypeId;
             GLJTypeConst = data.constData.GLJTypeConst !== undefined ? JSON.parse(data.constData.GLJTypeConst) : GLJTypeConst;
 
-            let usedTenderList = data.usedTenderList !== undefined ? data.usedTenderList : [];
-            usedUnitPriceInfo = data.constData.usedUnitPriceInfo !== undefined ?
-                data.constData.usedUnitPriceInfo : {};
-
             // 连接socket服务器
             socketInit();
 
-            unitPriceFileInit(usedUnitPriceInfo.name, usedTenderList);
+            unitPriceFileInit();
 
             setTimeout(spreadInit, 1);
         } else {
@@ -279,17 +294,19 @@ function spreadInit() {
  * @param {Array} data
  * @return {void}
  */
-function unitPriceFileInit(name, data) {
-    $("#used-name").text(name);
-    let usedCount = data.length <= 0 ? 1 : data.length;
+function unitPriceFileInit() {
+    let projectGLJ = projectObj.project.projectGLJ;
+    let data = projectGLJ.datas;
+     usedTenderList = data.usedTenderList !== undefined ? data.usedTenderList : [];
+    usedUnitPriceInfo = data.constData.usedUnitPriceInfo !== undefined ?
+        data.constData.usedUnitPriceInfo : {};
+    $("#used-name").text(usedUnitPriceInfo.name);
+    let usedCount = usedTenderList.length <= 0 ? 1 : usedTenderList.length;
     $("#used-count").text(usedCount);
-    $('#pop-dj').popover({
-            placement:"bottom",
-            html:true,
-            trigger:"hover | focus",
-            content: data.join('<br>')
-        }
-    );
+}
+
+function getUsedTenderInfo() {
+   return usedTenderList.join("<br>");
 }
 
 /**
@@ -360,4 +377,5 @@ function filterProjectGLJ(jsonData) {
         jsonData = tmpData;
     }
     return jsonData;
-}
+}
+

+ 3 - 1
web/building_saas/glj/js/project_glj_spread.js

@@ -246,6 +246,7 @@ ProjectGLJSpread.prototype.specialColumn = function (sourceData) {
     // 获取列号
     let isEvaluateColumn = this.sheetObj.getFieldColumn('is_evaluate');
     let marketPriceColumn = this.sheetObj.getFieldColumn('unit_price.market_price');
+    let adjustPriceColumn = this.sheetObj.getFieldColumn("adjust_price");
     let connectCodeColumn = this.sheetObj.getFieldColumn('connect_code');
     let consumptionColumn = this.sheetObj.getFieldColumn('consumption');
     let supplyColumn = this.sheetObj.getFieldColumn('supply');
@@ -306,7 +307,8 @@ ProjectGLJSpread.prototype.specialColumn = function (sourceData) {
             activeSheet.setValue(rowCounter, connectCodeColumn, connectCodeString);
             activeSheet.setValue(rowCounter, consumptionColumn, consumptionString);
         }
-
+        data.adjust_price = projectObj.project.projectGLJ.getAdjustPrice(data);
+        activeSheet.setValue(rowCounter,adjustPriceColumn,data.adjust_price);
         rowCounter++;
     }
 };

二進制
web/building_saas/img/FirstPageSimple.cur


二進制
web/building_saas/img/LastPageSimple.cur


二進制
web/building_saas/img/NextPageSimple.cur


二進制
web/building_saas/img/PreviousPageSimple.cur


二進制
web/building_saas/img/baobiao.png


+ 58 - 23
web/building_saas/main/html/main.html

@@ -13,6 +13,7 @@
     <!--zTree-->
     <link rel="stylesheet" href="/lib/ztree/css/zTreeStyle.css" type="text/css">
     <!--SpreadJs-->
+    <link rel="stylesheet" href="/lib/jquery-ui/jquery-ui.css" type="text/css">
     <link rel="stylesheet" href="/lib/spreadjs/sheets/css/gc.spread.sheets.excel2013lightGray.10.0.1.css" type="text/css">
     <link rel="stylesheet" href="/lib/spreadjs/views/gc.spread.views.dataview.10.0.0.css">
     <!-- jquery.contextmenu -->
@@ -260,11 +261,11 @@
                     <div class="row">
                         <div class="col-3">
                             <ul class="nav flex-column nav-pills" role="tablist">
-                                <li class="nav-item"><a class="nav-link active" data-toggle="pill" href="#poj-settings-1" role="tab">基本信息</a></li>
-                                <li class="nav-item"><a class="nav-link" data-toggle="pill" href="#poj-settings-2" role="tab">工程特征</a></li>
+                                <li class="nav-item"><a class="nav-link active" data-toggle="pill" href="#poj-settings-basicInfo" role="tab" id="tab_poj-settings-basicInfo">基本信息</a></li>
+                                <li class="nav-item"><a class="nav-link" data-toggle="pill" href="#poj-settings-projFeature" id="tab_poj-settings-projFeature" role="tab">工程特征</a></li>
                                 <li class="nav-item"><a class="nav-link" data-toggle="pill" href="#poj-settings-3" role="tab">指标信息</a></li>
                                 <li class="nav-item"><a class="nav-link" data-toggle="pill" href="#poj-settings-4" role="tab">关于计算</a></li>
-                                <li class="nav-item"><a class="nav-link" data-toggle="pill" href="#poj-settings-5" role="tab">清单工程精度</a></li>
+                                <li class="nav-item"><a class="nav-link" data-toggle="pill" href="#poj-settings-billsQuanDecimal" id="tab_poj-settings-bqDecimal" role="tab">清单工程量精度</a></li>
                                 <li class="nav-item"><a class="nav-link" data-toggle="pill" href="#poj-settings-decimal" role="tab" id="tab_poj-settings-decimal">小数位数</a></li>
                                 <li class="nav-item"><a class="nav-link" data-toggle="pill" href="#poj-settings-6" role="tab" id="tab_poj-settings-6">人工单价调整</a></li>
                             </ul>
@@ -272,14 +273,13 @@
                         <div class="col-9">
                             <div class="tab-content">
                                 <!--基本信息-->
-                                <div class="tab-pane fade show active" id="poj-settings-1" role="tabpanel">
-                                    <div class="modal-auto-height">
-                                        基本信息
+                                <div class="tab-pane fade show active" id="poj-settings-basicInfo" role="tabpanel">
+                                    <div class="modal-auto-height" style="overflow: hidden;" id="basicInfoSpread">
                                     </div>
                                 </div>
                                 <!--工程特征-->
-                                <div class="tab-pane fade" id="poj-settings-2" role="tabpanel">
-                                    <div class="modal-auto-height">
+                                <div class="tab-pane fade" id="poj-settings-projFeature" role="tabpanel">
+                                    <div class="modal-auto-height" style="overflow: hidden" id="projFeatureSpread">
                                         工程特征
                                     </div>
                                 </div>
@@ -337,8 +337,8 @@
                                     </div>
                                 </div>
                                 <!--清单工程精度-->
-                                <div class="tab-pane fade" id="poj-settings-5" role="tabpanel">
-                                    <div class="modal-auto-height" id="quantityPrecision">
+                                <div class="tab-pane fade" id="poj-settings-billsQuanDecimal" role="tabpanel">
+                                    <div class="modal-auto-height" style="overflow: hidden;" id="billsQuanDecimal">
                                     </div>
                                 </div>
                                 <!--小数位数-->
@@ -385,7 +385,7 @@
                                             <div class="row m-0">
                                                 <div class="col-sm-3">
                                                     <div class="input-group input-group-sm mb-2">
-                                                        <div class="input-group-addon">工程量</div>
+                                                        <div class="input-group-addon">消耗量</div>
                                                         <input type="number" name="glj-quantity" class="form-control" value="2" step="1" max="6" min="0">
                                                     </div>
                                                 </div>
@@ -553,6 +553,37 @@
             </div>
         </div>
     </div>
+    <!--工料机类型选择-->
+    <div class="modal fade" id="glj_class_div" data-backdrop="static">
+        <div class="modal-dialog modal-m" role="document" id="class_con">
+            <div class="modal-content" >
+                <div class="modal-header">
+                    <h5 class="modal-title">请选择分类</h5>
+                    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                        <span aria-hidden="true">&times;</span>
+                    </button>
+                    <input type="hidden" name="" id="selected_class">
+                </div>
+                <div class="modal-body">
+                    <div class="row">
+                        <div class="col-12">
+                            <div  class="modal-auto-height" id="classTreeDiv" style="overflow: hidden">
+                                <!--<div class="print-list">-->
+                                <div style="width: 100%; height: 100%; overflow: auto">
+                                    <ul id="classTree" class="ztree"></ul>
+                                </div>
+                                <!--</div>-->
+                            </div>
+                        </div>
+                    </div>
+                </div>
+                <div class="modal-footer">
+                    <button type="button" id="classCacnel" class="btn btn-secondary" data-dismiss="modal">取消</button>
+                    <button type="button" href="javascript:void(0);" id="class_selected_conf" class="btn btn-primary" disabled >确定</button>
+                </div>
+            </div>
+        </div>
+    </div>
     <!--费率选择窗口-->
     <div class="modal fade" id="fee_rate_tree" data-backdrop="static">
         <div class="modal-dialog modal-feeRate" role="document" id="fee_rate_dialog">
@@ -566,17 +597,16 @@
                 <div class="modal-body">
                     <input type="hidden" id="edit_from">
                     <div class="row">
-                        <div class="modal-auto-height col-12" style="overflow: hidden" id="fee_rate_sheet">
+                        <div class="modal-auto-height col-12" style="overflow: hidden" id="fee_rate_sheet"></div>
+                    </div>
+                    <div class="modal-footer">
+                        <button type="button" id="frCacnel" class="btn btn-secondary" data-dismiss="modal">取消</button>
+                        <a href="javascript:void(0);" id="fee_selected_conf" class="btn btn-primary">确定</a>
                     </div>
-                </div>
-                <div class="modal-footer">
-                    <button type="button" id="frCacnel" class="btn btn-secondary" data-dismiss="modal">取消</button>
-                    <a href="javascript:void(0);" id="fee_selected_conf" class="btn btn-primary">确定</a>
                 </div>
             </div>
         </div>
     </div>
-
         <!-- JS. -->
         <script type="text/javascript" src="/lib/spreadjs/sheets/gc.spread.sheets.all.10.0.1.min.js"></script>
 
@@ -591,6 +621,9 @@
 
 
         <!-- inject:js -->
+        <!--<script type="text/javascript" src="/test/tmp_data/test_ration_calc/ration_calc_base.js"></script>-->
+        <script type="text/javascript" src="/web/building_saas/main/js/models/main_consts.js"></script>
+        <script type="text/javascript" src="/public/web/common_util.js"></script>
 
         <script type="text/javascript" src="/web/building_saas/glj/js/project_glj.js"></script>
         <script type="text/javascript" src="/web/building_saas/glj/js/composition.js"></script>
@@ -616,6 +649,8 @@
         <script src="/lib/JSExpressionEval_src/Tokanizer.js"></script>
         <script src="/lib/JSExpressionEval_src/Evaluator.js"></script>
         <!--end expression calculate-->
+        <script type="text/javascript" src="/lib/jquery-ui/jquery-ui.min.js"></script>
+        <script type="text/javascript" src="/lib/jquery-ui/jquery-ui-datepickerCN.js"></script>
         <script type="text/javascript" src="/lib/jquery-contextmenu/jquery.contextMenu.js"></script>
         <script type="text/javascript" src="/lib/jquery-contextmenu/jquery.ui.position.js"></script>
         <script type="text/javascript" src="/lib/lodash/lodash.js"></script>
@@ -630,7 +665,7 @@
 
         <!--<script src="/lib/spreadjs/views/locale/gc.spread.views.dataview.locale.zh-CN.10.0.0.min.js" type="text/javascript"></script>-->
         <!-- Model -->
-        <script type="text/javascript" src="/web/building_saas/main/js/models/main_consts.js"></script>
+
         <script type="text/javascript" src="/web/building_saas/main/js/models/project.js"></script>
         <script type="text/javascript" src="/web/building_saas/main/js/models/bills.js"></script>
         <script type="text/javascript" src="/web/building_saas/main/js/models/ration.js"></script>
@@ -641,18 +676,17 @@
         <script type="text/javascript" src="/web/building_saas/main/js/models/ration_glj.js"></script>
         <script type="text/javascript" src="/web/building_saas/main/js/models/ration_coe.js"></script>
         <script type="text/javascript" src="/web/building_saas/main/js/models/ration_ass.js"></script>
-        <script type="text/javascript" src="/web/building_saas/main/js/models/volume_price.js"></script>
+        <!--<script type="text/javascript" src="/web/building_saas/main/js/models/volume_price.js"></script>-->
         <script type="text/javascript" src="/web/building_saas/main/js/models/labour_coe.js"></script>
         <script type="text/javascript" src="/web/building_saas/main/js/models/calc_program.js"></script>
         <script type="text/javascript" src="/web/building_saas/main/js/views/calc_program_manage.js"></script>
 
         <script type="text/javascript" src="/public/web/id_tree.js"></script>
-        <script type="text/javascript" src="/test/tmp_data/test_ration_calc/ration_calc_base.js"></script>
         <script type="text/javascript" src="/web/building_saas/main/js/models/cache_tree.js"></script>
         <script type="text/javascript" src="/web/building_saas/main/js/calc/calc_fees.js"></script>
         <script type="text/javascript" src="/web/building_saas/main/js/calc/ration_calc.js"></script>
         <script type="text/javascript" src="/web/building_saas/main/js/calc/bills_calc.js"></script>
-        <script type="text/javascript" src="/public/calc_util.js"></script>
+        <!--<script type="text/javascript" src="/public/calc_util.js"></script>-->
         <!-- Controller -->
         <script type="text/javascript" src="/public/web/tree_sheet/tree_sheet_controller.js"></script>
         <script type="text/javascript" src="/public/web/tree_sheet/tree_sheet_helper.js"></script>
@@ -663,7 +697,10 @@
         <script type="text/javascript" src="/web/building_saas/main/js/views/project_info.js"></script>
         <script type="text/javascript" src="/web/building_saas/main/js/views/project_view.js"></script>
         <script type="text/javascript" src="/web/building_saas/main/js/views/options_view.js"></script>
+        <script type="text/javascript" src="/web/building_saas/main/js/views/project_property_bills_quantity_decimal.js"></script>
         <script type="text/javascript" src="/web/building_saas/main/js/views/project_property_decimal_view.js"></script>
+        <script type="text/javascript" src="/web/building_saas/main/js/views/project_property_basicInfo.js"></script>
+        <script type="text/javascript" src="/web/building_saas/main/js/views/project_property_projFeature.js"></script>
         <script type="text/javascript" src="/web/building_saas/main/js/main_ajax.js"></script>
         <script type="text/javascript" src="/web/building_saas/main/js/main.js"></script>
         <script type="text/javascript" src="/web/building_saas/main/js/controllers/project_controller.js"></script>
@@ -694,8 +731,6 @@
             autoFlashHeight();
         </script>
 
-        <script src="/public/debug.js"></script>
-
         <SCRIPT type="text/javascript">
             <!--
 //            var zNodes =[

+ 1 - 1
web/building_saas/main/js/calc/bills_calc.js

@@ -2,7 +2,7 @@
  * Created by Mai on 2017/7/5.
  */
 
-const rationContent = 0, rationPrice = 1, rationPriceConverse = 2, billsPrice = 3;
+// const rationContent = 0, rationPrice = 1, rationPriceConverse = 2, billsPrice = 3;
 
 // sumTotalFeeFlag: sum(child.totalFee), totalFeeFlag: bills.quantity × bills.unitFee
 const sumTotalFeeFlag = 0, totalFeeFlag = 1;

+ 2 - 1
web/building_saas/main/js/calc/calc_fees.js

@@ -18,6 +18,7 @@ let feeType = [
     {type: 'material', name: '材料费'},
     {type: 'machine', name: '机械费'},
     {type: 'mainMaterial', name: '主材费'},
+    {type: 'equipment', name: '设备费'},
     {type: 'manage', name: '企业管理费'},
     {type: 'profit', name: '利润'},
     {type: 'risk', name: '风险费'},
@@ -70,7 +71,7 @@ let calcFees = {
     getFee: function (data, fullField) {
         let fields = fullField.split('.'), value = data;
         for (let field of fields) {
-            if (value[field]) {
+            if (value[field]!=undefined||value[field]!=null) {
                 value = value[field];
             } else {
                 return 0;

+ 16 - 21
web/building_saas/main/js/controllers/project_controller.js

@@ -47,7 +47,7 @@ ProjectController = {
             this.syncDisplayNewNode(sheetController, newNode);
         }
     },
-    addRation: function (project, sheetController, std) {
+    addRation: function (project, sheetController, rationType, std) {
         if (!project || !sheetController) { return; }
 
         let selected = project.mainTree.selected, newSource = null, newNode = null;
@@ -59,30 +59,25 @@ ProjectController = {
             } else if (false) {
                 alert('当前清单已有公式计算,不能套用定额。');
             } else {
-                let firstChild = selected.firstChild();
-                if (firstChild && firstChild.sourceType === project.VolumePrice.getSourceType()) {
-                    alert('当前位置已有量价,不能套用定额。');
+                if (std) {
+                    newSource = project.Ration.insertStdRation(selected.source.getID(), null, std);
+                    project.ration_glj.addRationGLJ(newSource,std);
                 } else {
-                    if (std) {
-                        newSource = project.Ration.insertStdRation(selected.source.getID(), null, std);
-                        project.ration_glj.addRationGLJ(newSource,std);
-                    } else {
-                        newSource = project.Ration.insertRation(selected.source.getID());
-                    }
+                    newSource = project.Ration.insertRation(selected.source.getID());
                 }
+
                 newNode = project.mainTree.insert(selected.getID(), selected.tree.rootID());
             }
-        } else if (selected.sourceType === project.Ration.getSourceType()) {
+        }
+        else if (selected.sourceType === project.Ration.getSourceType()) {
             if (std) {
                 newSource = project.Ration.insertStdRation(selected.source[project.masterField.ration], selected.source, std);
                 project.ration_glj.addRationGLJ(newSource,std);
             } else {
-                newSource = project.Ration.insertRation(selected.source[project.masterField.ration], selected.source);
+                newSource = project.Ration.insertRation(selected.source[project.masterField.ration], selected.source, rationType);
             }
             newNode = project.mainTree.insert(selected.getParentID(), selected.getNextSiblingID());
-        } else if (selected.sourceType === project.VolumePrice.getSourceType()) {
-            alert('当前位置已有量价,不能套用定额。');
-        }
+        };
         if (newNode) {
             newNode.source = newSource;
             newNode.sourceType = project.Ration.getSourceType();
@@ -104,8 +99,8 @@ ProjectController = {
         } else {
             alert('当前焦点行不是定额,无法替换。');
         }
-    },
-    addVolumePrice: function (project, sheetController) {
+    }
+/*    addVolumePrice: function (project, sheetController) {
         if (!project || !sheetController) { return null; }
 
         var selected = project.mainTree.selected;
@@ -117,7 +112,7 @@ ProjectController = {
         if (selected.sourceType === project.Bills.getSourceType() && selected.source.children.length === 0) {
             newSource = project.VolumePrice.insertVolumePrice(selected.source.getID());
             newNode = project.mainTree.insert(selected.getID(), selected.tree.rootID());
-        } else if (selected.sourceType === project.VolumePrice.getSourceType()) {
+        } else if (selected.sourceType === project.Ration.getSourceType() || selected.sourceType === project.VolumePrice.getSourceType()) {
             newSource = project.VolumePrice.insertVolumePrice(selected.source[project.masterField.volumePrice], selected.source);
             newNode = project.mainTree.insert(selected.getParentID(), selected.getNextSiblingID());
         }
@@ -128,13 +123,13 @@ ProjectController = {
 
             this.syncDisplayNewNode(sheetController, newNode);
         }
-    },
-    calculateAll: function (project, sheetController, CalcType) {
+    },*/
+/*    calculateAll: function (project, sheetController, CalcType) {
         this.project.setCalcFlag(CalcType);
         let calc = new BillsCalcHelper(project);
         calc.calcAll();
         sheetController.showTreeData();
         project.Bills.updateAll();
         calc = null;
-    }
+    }*/
 }

+ 6 - 3
web/building_saas/main/js/models/bills.js

@@ -182,6 +182,7 @@ var Bills = {
                     data.data.itemCharacter = stdBillsData.itemCharacter;
                     data.data.jobContentText = stdBillsData.jobContentText;
                     data.data.itemCharacterText = stdBillsData.itemCharacterText;
+                    data.data.programID = stdBillsData.engineering;
                     //zhong
                     newData = data.data;
                 }
@@ -195,15 +196,16 @@ var Bills = {
         bills.prototype.deleteBills = function (node) {
             let deleteNode = function (node) {
                 this.project.Ration.deleteByBills([node]);
-                this.project.VolumePrice.deleteByBills([node]);
+                // this.project.VolumePrice.deleteByBills([node]);
                 return this.tree.delete(node);
             }
             var deleteData = this.tree.getDeleteData(node);
             var ration_glj =projectObj.project.ration_glj;
-            let modules =[ModuleNames.bills, ModuleNames.ration, ModuleNames.ration_glj, ModuleNames.volume_price];
+            // let modules =[ModuleNames.bills, ModuleNames.ration, ModuleNames.ration_glj, ModuleNames.volume_price];
+            let modules =[ModuleNames.bills, ModuleNames.ration, ModuleNames.ration_glj];
             let deleteDatas=[tools.coverseTreeUpdateData(deleteData, this.project.ID()),
                 this.project.Ration.getDeleteDataByBill([node]), ration_glj.getDeleteDataByBills(deleteData),
-                this.project.VolumePrice.getDeleteDataByBills([node])
+                // this.project.VolumePrice.getDeleteDataByBills([node])
             ];
             project.ration_glj.deleteByBills(deleteData);
             project.quantity_detail.deleteByBills(deleteData);
@@ -332,6 +334,7 @@ var Bills = {
                 // 特征
                 node.data.itemCharacter = stdBillsData.itemCharacter;
                 node.data.itemCharacterText = stdBillsData.itemCharacterText;
+                node.data.programID = stdBillsData.engineering;
             }
             updateData.push({'updateType': 'ut_update', 'updateData': tools.formatBillsUpdateData(node.data)});
 

+ 718 - 141
web/building_saas/main/js/models/calc_program.js

@@ -1,5 +1,6 @@
 /**
  * Created by CSL on 2017-07-19.
+ * 计算程序。所有定额、清单、父清单的计算都从此入。
  *  dispExpr: F8*(L-1); expression: "@('8') * (L-1)";
  *  说明:F后跟行号,L替换人工系数值,@后跟ID。用到L的规则必须有labourCoeID属性(反过来不要求),
  *  用到费率的规则必须有feeRateID属性,当有该属性时,会自动显示费率值。
@@ -7,7 +8,7 @@
 
 let defaultBillTemplate = {
     ID: 15,
-    name: "清单缺省",
+    name: "清单公式",
     calcItems: [
         {
             ID: 1,
@@ -99,14 +100,305 @@ let defaultBillTemplate = {
     ]
 };
 
+const baseCalcType = {baseCalc: 0, adjustCalc: 1, budgetCalc: 2, diffCalc: 3,  offerCalc: 4};
+
+let rationCalcBase = [
+    {
+        'dispName': '定额基价人工费',
+        'calcType': baseCalcType.baseCalc,
+        'gljTypes': [gljType.LABOUR]
+    },
+    {
+        'dispName': '定额基价材料费',
+        'calcType': baseCalcType.baseCalc,
+        'gljTypes': [gljType.GENERAL_MATERIAL, gljType.CONCRETE, gljType.MORTAR, gljType.MIX_RATIO, gljType.COMMERCIAL_CONCRETE, gljType.COMMERCIAL_MORTAR]
+    },
+    {
+        'dispName': '定额基价机械费',
+        'calcType': baseCalcType.baseCalc,
+        'gljTypes': [gljType.GENERAL_MACHINE]
+    },
+    {
+        'dispName': '定额基价机上人工费',
+        'calcType': baseCalcType.baseCalc,
+        'gljTypes': [gljType.MACHINE_LABOUR]
+    },
+    {
+        'dispName': '人工费价差',
+        'calcType': baseCalcType.diffCalc,
+        'gljTypes': [gljType.LABOUR]
+    },
+    {
+        'dispName': '材料费价差',
+        'calcType': baseCalcType.diffCalc,
+        'gljTypes': [gljType.GENERAL_MATERIAL, gljType.CONCRETE, gljType.MORTAR, gljType.MIX_RATIO, gljType.COMMERCIAL_CONCRETE, gljType.COMMERCIAL_MORTAR]
+    },
+    {
+        'dispName': '机械费价差',
+        'calcType': baseCalcType.diffCalc,
+        'gljTypes': [gljType.GENERAL_MACHINE]
+    },
+    {
+        'dispName': '主材费',
+        'calcType': baseCalcType.budgetCalc,
+        'gljTypes': [gljType.MAIN_MATERIAL]
+    },
+    {
+        'dispName': '设备费',
+        'calcType': baseCalcType.budgetCalc,
+        'gljTypes': [gljType.EQUIPMENT]
+    }
+];
+
+let analyzer = {
+    calcTemplate: null,
+    success: true,
+
+    standard: function(expr){
+        let str = expr;
+        str = str.replace(/\s/g, "");               // 去空格、去中文空格
+        str = str.replace(/(/g, "(");              // 中文括号"("换成英文括号"("
+        str = str.replace(/)/g, ")");              // 中文括号")"换成英文括号")"
+        str = str.replace(/f/g, "F");               // f换成F
+        return str;
+    },
+
+    analyzeCalcBase: function(expr){
+        // 前提:必须无空格、无特殊符号
+        function getCalcBase(expr){
+            let base = '',
+                iPos1 = -1, iPos2 = -1;
+            for (let i = 0; i < expr.length; i++) {
+                if (expr[i] === '['){
+                    iPos1 = i;
+                }
+                else if (iPos1 != -1 && expr[i]===']'){
+                    iPos2 = i;
+                };
+
+                if (iPos1 != -1 && iPos2 != -1){
+                    base = expr.slice(iPos1, iPos2 + 1);
+                    break;
+                }
+            };
+            return base;
+        };
+        function calcBaseToIDExpr(base){
+            /*// for test. 公路模式,基数到ID
+            let id = -1;
+            if (base == '[人工费]'){
+                id = 111;
+            }
+            else if (base == '[材料费]'){
+                id = 222;
+            }
+            else if (base == '[机械费]'){
+                id = 333;
+            }
+            else id = "错误";
+
+            return "@('" + id + "')";*/
+            let baseValue = base.slice(1, -1);
+            return "base('" + baseValue + "')";
+        };
+
+        while (expr.indexOf('[') > 0) {
+            let base = getCalcBase(expr);
+            let id = calcBaseToIDExpr(base);
+            let baseValue = base.slice(1, -1);   // []会给下面的正则带来干扰,这里去掉
+            var pattBase =new RegExp(baseValue, "g");
+            expr = expr.replace(pattBase, id);
+            expr = expr.replace(/\[base\('/g, "base('");      // [@('       [base('
+            expr = expr.replace(/'\)\]/g, "')");        // ')]
+        };
+
+        return expr;
+    },
+
+    analyzeLineRef: function(expr){
+        let me = this;
+        function isOperator(char){
+            var operator = "+-*/()";
+            return operator.indexOf(char) > -1;
+        };
+        function lineNumToID(lineNum){
+            if (lineNum > me.calcTemplate.calcItems.length){
+                me.success = false;
+                return '越界';
+            }
+            else{
+                let id = me.calcTemplate.calcItems[lineNum - 1].ID;
+                return id;
+            }
+        };
+        // 前提:必须无空格、无特殊符号、标准大写F
+        function getSection(expr){
+            let section = '',
+                iPos1 = -1, iPos2 = -1;
+            for (let i = 0; i < expr.length; i++) {
+                if (expr[i] === 'F'){
+                    iPos1 = i;
+                }
+                else if (iPos1 != -1 && isOperator(expr[i])){
+                    iPos2 = i;
+                }
+                else if (iPos1 != -1 && i == expr.length - 1){
+                    iPos2 = i + 1;
+
+                };
+                if (iPos1 != -1 && iPos2 != -1){
+                    section = expr.slice(iPos1, iPos2);
+                    break;
+                }
+            };
+            return section;
+        };
+        function sectionToIDExpr(section){
+            if (section){
+                let lineNum = section.slice(1);
+                if (isNaN(lineNum)){
+                    me.success = false;
+                    return '错误';      // 这里的返回提示不能加上section,因为会无限循环
+                }
+                else
+                    return "@('" + lineNumToID(lineNum) + "')";
+            }
+            else return '';
+        };
+
+        while (expr.indexOf('F') > 0) {
+            let sec = getSection(expr);
+            let id = sectionToIDExpr(sec);
+            var pattSec =new RegExp(sec, "g");
+            expr = expr.replace(pattSec, id);
+        };
+        return expr;
+    },
+
+    analyzeUserExpr: function(calcTemplate, calcItem){
+        let me = this;
+        me.calcTemplate = calcTemplate;
+        let expr = calcItem.dispExpr;
+        // 标准化:处理特殊字符、中文符号、大小写
+        expr = me.standard(expr);
+        calcItem.dispExpr = expr;
+        // 先换掉计算基数
+        expr = me.analyzeCalcBase(expr);
+        // 再换掉行引用
+        expr = me.analyzeLineRef(expr);
+        calcItem.expression = expr;
+        return me.success;
+    }
+};
+
+let executeObj = {
+    treeNode: null,
+    template: null,
+    calcBase: null,
+
+    at: function(ID) {
+        let me = executeObj,
+            rst = 0;
+        rst = me.template.compiledCalcItems[ID].unitFee;
+        rst = parseFloat(rst);
+        return rst;
+    },
+    base: function(calcBaseName) {
+        let me = executeObj, rst = 0,
+            base = me.calcBase[calcBaseName];
+
+        if (base != null) {
+            function isSubset(sub, arr){
+                for(var i = 0, len = sub.length; i < len; i++){
+                    if(arr.indexOf(sub[i]) == -1) return false;
+                }
+                return true;
+            };
+            // 机上人工费:多一层
+            function machineLabourFee() {
+                if (!me.treeNode.data.gljList) return 0;
+                let result = 0, mdSum = 0;
+                for (let glj of me.treeNode.data.gljList) {
+                    if (glj.type == gljType.GENERAL_MACHINE) {
+                        // 获取机械组成物
+                        let mds = projectObj.project.composition.getCompositionByCode(glj.code);
+                        if (!mds) mds = [];
+                        for (let md of mds){
+                            if (base.gljTypes.indexOf(md.glj_type) >= 0) {
+                                let q = md["consumption"] ? md["consumption"] : 0;
+                                let p = md["base_price"] ? md["base_price"] : 0;
+                                mdSum = mdSum + (q * p).toDecimal(decimalObj.process);
+                                mdSum = (mdSum).toDecimal(decimalObj.process);
+                            }
+                        };
+                        result = result + (glj["quantity"] * mdSum).toDecimal(decimalObj.process);
+                        result = (result).toDecimal(decimalObj.process);
+                    };
+                };
+                return result;
+            };
+            function commonGLJFee(){
+                if (!me.treeNode.data.gljList) return 0;
+                let result = 0;
+                for (let glj of me.treeNode.data.gljList) {
+                    let price = 0;
+                    if (base.gljTypes.indexOf(glj.type) >= 0) {
+                        if (base.calcType == baseCalcType.baseCalc){ price = parseFloat(glj["basePrice"]);}
+                        else if (base.calcType == baseCalcType.adjustCalc){price = parseFloat(glj["adjustPrice"]);}
+                        else if (base.calcType == baseCalcType.budgetCalc){price = parseFloat(glj["marketPrice"]);}
+                        else if (base.calcType == baseCalcType.diffCalc){
+                            let aprice = glj["adjustPrice"] ? glj["adjustPrice"] : 0;
+                            let mprice = glj["marketPrice"] ? glj["marketPrice"] : 0;
+                            price = (parseFloat(mprice) - parseFloat(aprice)).toDecimal(decimalObj.process);
+                        };
+                        result = result + (glj["quantity"] * price).toDecimal(decimalObj.process);
+                        result = (result).toDecimal(decimalObj.process);
+                    };
+                };
+                return result;
+            };
+            // 量价没有具体的工料机类型,但仍然要用定额的计算程序,所以要给计算基数直接指定。
+            function volumePriceFee() {
+                let result = 0;
+                if (
+                    ( me.treeNode.data.subType === gljType.LABOUR && base.dispName === '定额基价人工费') ||
+                    ( me.treeNode.data.subType === gljType.GENERAL_MATERIAL && base.dispName === '定额基价材料费') ||
+                    ( me.treeNode.data.subType === gljType.GENERAL_MACHINE && base.dispName === '定额基价机械费') ||
+                    ( me.treeNode.data.subType === gljType.MAIN_MATERIAL && base.dispName === '主材费') ||
+                    ( me.treeNode.data.subType === gljType.EQUIPMENT && base.dispName === '设备费')
+                ) result = me.treeNode.data.marketUnitFee ? me.treeNode.data.marketUnitFee : 0;
+
+                return result;
+            };
+
+            if (me.treeNode.data.type == rationType.volumePrice || me.treeNode.data.type == rationType.gljRation){
+                rst = volumePriceFee();
+            }
+            else{
+                if (isSubset(base.gljTypes, [gljType.MACHINE_LABOUR]))
+                    rst = machineLabourFee()
+                else
+                    rst = commonGLJFee();
+            }
+        };
+
+        return rst;
+    },
+    HJ: function () {
+        let me = this;
+        let p = me.treeNode.data.calcBase ? me.treeNode.data.calcBase : 0;
+        let q = me.treeNode.data.quantity ? me.treeNode.data.quantity : 1;
+        let u = (p / q).toDecimal(decimalObj.decimal('unitPrice', me.treeNode));
+        return u;
+    }
+};
+
 class CalcProgram {
     constructor(project){
-        this.project = project;
-        this.datas = [];
-        this.digit = 2;
-        this.digitDefault = 6;
-        this.calc = new Calculation();
-        project.registerModule(ModuleNames.calc_program, this);
+        let me = this;
+        me.project = project;
+        me.datas = [];
+        project.registerModule(ModuleNames.calc_program, me);
     };
 
     getSourceType () {
@@ -125,201 +417,486 @@ class CalcProgram {
 
     // 经测试,全部编译一次耗时0.003~0.004秒。耗时基本忽略不计。
     compileAllTemps(){
-        let calcFeeRates = this.project.FeeRate.datas.rates;
-        let calcLabourCoes = this.project.labourCoe.datas.coes;
-        let calcTemplates = this.project.calcProgram.datas.templates;
-        calcTemplates.push(defaultBillTemplate);
-
-        this.calc.compilePublics(calcFeeRates, calcLabourCoes, feeType, rationCalcBase);
-        for (let ct of calcTemplates){
-            this.calc.compileTemplate(ct);
+        let me = this;
+        me.compiledFeeRates = {};
+        me.compiledLabourCoes = {};
+        me.compiledTemplates = {};
+        me.compiledTemplateMaps = {};
+        me.compiledTemplateNames = [];
+        me.compiledFeeTypeMaps = {};
+        me.compiledFeeTypeNames = [];
+        me.compiledCalcBases = {};
+        me.saveForReports = [];
+
+        me.feeRates = this.project.FeeRate.datas.rates;
+        me.labourCoes = this.project.labourCoe.datas.coes;
+        me.feeTypes = feeType;
+        me.calcBases = rationCalcBase;
+        me.templates = this.project.calcProgram.datas.templates;
+
+        me.templates.push(defaultBillTemplate);
+        // 先编译公用的基础数据
+        me.compilePublics();
+        for (let t of me.templates){
+            me.compileTemplate(t);
         };
 
         // 存储费率临时数据,报表用。
-        if (this.calc.saveForReports.length > 0){
+        if (me.saveForReports.length > 0){
             let saveDatas = {};
             saveDatas.projectID = projectInfoObj.projectInfo.ID;
-            saveDatas.calcItems = this.calc.saveForReports;
+            saveDatas.calcItems = me.saveForReports;
             CommonAjax.post('/calcProgram/saveCalcItems', saveDatas, function (data) {
-                this.calc.saveForReports = [];
+                me.saveForReports = [];
             });
         };
     };
 
-    calculate(treeNode){
+    compilePublics(){
         let me = this;
-        me.calc.calculate(treeNode);
-        // 存储、刷新本结点、所有父结点
-        if (treeNode.changed) {
-            me.saveAndCalcParents(treeNode);
-            delete treeNode.changed;
-        };
+        for (let rate of me.feeRates) {
+            me.compiledFeeRates[rate.ID] = rate;
+        }
+
+        for (let coe of me.labourCoes) {
+            me.compiledLabourCoes[coe.ID] = coe;
+        }
+
+        for (let ft of me.feeTypes) {
+            me.compiledFeeTypeMaps[ft.type] = ft.name;
+            me.compiledFeeTypeMaps[ft.name] = ft.type;    // 中文预编译,可靠性有待验证
+            me.compiledFeeTypeNames.push(ft.name);
+        }
+
+        for (let cb of me.calcBases) {
+            me.compiledCalcBases[cb.dispName] = cb;         // 中文预编译,可靠性有待验证
+        }
     };
 
-    saveAndCalcParents(treeNode) {
-        if (treeNode.parent) {
-            projectObj.converseCalculateBills(treeNode.parent);
+    compileTemplate(template){
+        let me = this;
+        me.compiledTemplates[template.ID] = template;
+        me.compiledTemplateMaps[template.ID] = template.name;
+        me.compiledTemplateMaps[template.name] = template.ID;
+        me.compiledTemplateNames.push(template.name);
+        template.hasCompiled = false;
+        template.errs = [];
+
+        let private_extract_ID = function(str, idx){
+            let rst = '', lBracket = 0, rBracket = 0, firstIdx = idx, lastIdx = 0;
+            for (let i = idx; i < str.length; i++) {
+                if (str[i] === '(') {
+                    lBracket++;
+                    if (lBracket == 1) firstIdx = i + 1;
+                }
+                if (str[i] === ')') {
+                    rBracket++;
+                    if (lBracket == rBracket) {
+                        lastIdx = i - 1;
+                        if (lastIdx > firstIdx) {
+                            if (str[firstIdx] === "'") firstIdx++;
+                            if (str[lastIdx] !== "'") lastIdx++;
+                            if (lastIdx > firstIdx) {
+                                rst = str.slice(firstIdx, lastIdx);
+                            }
+                        }
+                        break;
+                    }
+                }
+            }
+            return rst;
+        };
+        let private_parse_ref = function(item, itemIdx){
+            let idx = item.expression.indexOf('@(', 0);
+            while (idx >= 0) {
+                let ID = private_extract_ID(item.expression, idx);
+                if (ID.length > 0) {
+                    let subItem = template.compiledCalcItems[ID];
+                    if (subItem) {
+                        if (subItem.ID !== item.ID) {
+                            private_parse_ref(subItem, template.compiledCalcItems[ID + "_idx"]);
+                        } else {
+                            template.errs.push("There exists the self refer ID: " + ID);
+                        }
+                    } else {
+                        template.errs.push("There exists the invalid ID by which could not find the item: " + ID);
+                        console.log('invalid ID: ' + ID);
+                    }
+                }
+                idx = item.expression.indexOf('@(', idx + ID.length + 3);
+            }
+            if (template.compiledSeq.indexOf(itemIdx) < 0) {
+                template.compiledSeq.push(itemIdx);
+            }
+        };
+        let private_setup_seq = function(item, itemIdx){
+            if (template.compiledSeq.indexOf(itemIdx) < 0) {
+                private_parse_ref(item, itemIdx);
+            }
         };
+        let private_compile_items = function() {
+            for (let idx of template.compiledSeq) {
+                let item = template.calcItems[idx];
+                item.dispExprUser = item.dispExpr;    // 用于界面显示。disExpr是公式模板,不允许修改:人工系数占位符被修改后变成数值,第二次无法正确替换。
+                if (item.expression == 'HJ')
+                    item.compiledExpr = '$CE.HJ()'
+                else{
+                    item.compiledExpr = item.expression.split('@(').join('$CE.at(');
+                    item.compiledExpr = item.compiledExpr.split('base(').join('$CE.base(');
+                };
 
-        let data = {ID: treeNode.data.ID, projectID: projectObj.project.ID(), fees: treeNode.data.fees};
-        let newDta = {'updateType': 'ut_update', 'updateData': data};
-        let newDataArr = [];
-        newDataArr.push(newDta);
-        projectObj.project.pushNow('', treeNode.sourceType, newDataArr);
-        projectObj.mainController.refreshTreeNode([treeNode]);
-    };
+                if (item.labourCoeID){
+                    let lc = me.compiledLabourCoes[item.labourCoeID].coe;
+                    item.dispExprUser = item.dispExpr.replace(/L/gi, lc.toString());
+                    item.compiledExpr = item.compiledExpr.replace(/L/gi, lc.toString());
+                };
+
+                if (item.feeRateID) {
+                    let orgFeeRate = item.feeRate;
+                    let cmf = me.compiledFeeRates[item.feeRateID];
+                    item.feeRate = cmf?cmf.rate:100;
+
+                    if (!orgFeeRate || (orgFeeRate && orgFeeRate != item.feeRate)){
+                        me.saveForReports.push({templatesID: template.ID, calcItem: item});
+                    }
+                };
 
-    initFees(treeNode){
-        if (!treeNode.data.fees) {
-            treeNode.data.fees = [];
-            treeNode.data.feesIndex = {};
-            treeNode.changed = true;
+                // 字段名映射
+                item.displayFieldName = me.compiledFeeTypeMaps[item.fieldName];
+            }
+        };
+
+        if (template && template.calcItems && template.calcItems.length > 0) {
+            template.compiledSeq = [];
+            template.compiledCalcItems = {};
+
+            for (let i = 0; i < template.calcItems.length; i++) {
+                let item = template.calcItems[i];
+                template.compiledCalcItems[item.ID] = item;
+                template.compiledCalcItems[item.ID + "_idx"] = i;
+            }
+
+            for (let i = 0; i < template.calcItems.length; i++) {
+                private_setup_seq(template.calcItems[i], i);
+            }
+            if (template.errs.length == 0) {
+                private_compile_items();
+                template.hasCompiled = true;
+            } else {
+                console.log('errors: ' + template.errs.toString());
+            }
         };
     };
 
-    checkFee(treeNode, ftObj){
-        if (!treeNode.data.feesIndex[ftObj.fieldName]){
-            let fee = {
-                'fieldName': ftObj.fieldName,
-                'unitFee': ftObj.unitFee,
-                'totalFee': ftObj.totalFee,
-                'tenderUnitFee': 0,
-                'tenderTotalFee': 0
-            };
-            treeNode.data.fees.push(fee);
-            treeNode.data.feesIndex[ftObj.fieldName] = fee;
-            treeNode.changed = true;
-        }
-        else{
-            if (treeNode.data.feesIndex[ftObj.fieldName].unitFee != ftObj.unitFee){
-                treeNode.data.feesIndex[ftObj.fieldName].unitFee = ftObj.unitFee;
-                treeNode.changed = true;
-            };
+    isLeafBill(treeNode){
+        let me = this;
+        return treeNode.sourceType === me.project.Bills.getSourceType() &&
+               treeNode.source.children &&
+               treeNode.source.children.length === 0;
+    };
 
-            if (treeNode.data.feesIndex[ftObj.fieldName].totalFee != ftObj.totalFee){
-                treeNode.data.feesIndex[ftObj.fieldName].totalFee = ftObj.totalFee;
+    // 仅内部调用。注意:外部不能直接使用,因为这里传入的树节点必须有一定的初始化。
+    InnerCalc(treeNode){
+        let me = this;
+        let project = me.project;
+
+        function initFees(treeNode){
+            if (!treeNode.data.fees) {
+                treeNode.data.fees = [];
+                treeNode.data.feesIndex = {};
                 treeNode.changed = true;
             };
         };
-    };
 
-    gatherFeeTypes(treeNode, gatherType){
-        let me = this;
-        let rst = [];
+        function checkFee(treeNode, feeObj){
+            if (feeObj.fieldName == '') return;
 
-        if (treeNode.sourceType === this.project.Bills.getSourceType()) {
-            me.initFees(treeNode);
+            if (!treeNode.data.feesIndex[feeObj.fieldName]){
+                let fee = {
+                    'fieldName': feeObj.fieldName,
+                    'unitFee': feeObj.unitFee,
+                    'totalFee': feeObj.totalFee,
+                    'tenderUnitFee': 0,
+                    'tenderTotalFee': 0
+                };
+                treeNode.data.fees.push(fee);
+                treeNode.data.feesIndex[feeObj.fieldName] = fee;
+                treeNode.changed = true;
+            }
+            else{
+                if (treeNode.data.feesIndex[feeObj.fieldName].unitFee != feeObj.unitFee){
+                    treeNode.data.feesIndex[feeObj.fieldName].unitFee = feeObj.unitFee;
+                    treeNode.changed = true;
+                };
 
-            let objsArr = [];
-            if (gatherType == CP_GatherType.rations){
-                objsArr = this.project.Ration.getRationsByNode(treeNode);
-            }else if (gatherType == CP_GatherType.bills){
-                objsArr = treeNode.children;
+                if (treeNode.data.feesIndex[feeObj.fieldName].totalFee != feeObj.totalFee){
+                    treeNode.data.feesIndex[feeObj.fieldName].totalFee = feeObj.totalFee;
+                    treeNode.changed = true;
+                };
             };
+        };
 
+        function isBaseFeeType(type){
+            return ['labour', 'material', 'machine', 'mainMaterial', 'equipment'].indexOf(type) > -1;
+        };
+
+        // 汇总定额或子清单的费用类别
+        if (treeNode.calcType == treeNodeCalcType.ctGatherRationsFees || treeNode.calcType == treeNodeCalcType.ctGatherBillsFees){
+            treeNode.data.programID = null;
+            initFees(treeNode);
+
+            let objsArr = (treeNode.calcType == treeNodeCalcType.ctGatherRationsFees) ? project.Ration.getRationsByNode(treeNode) : treeNode.children;
+            let rst = [];
             for (let ft of feeType) {
                 let ftObj = {};
                 ftObj.fieldName = ft.type;
                 ftObj.name = ft.name;
-                let uf = 0, tf = 0, tuf = 0, ttf = 0;
-                for (let item of objsArr) {
-                    let data = {};
-                    if (gatherType == CP_GatherType.rations){
-                        data = item;
-                    }else if (gatherType == CP_GatherType.bills){
-                        data = item.data;
+                let buf = 0, btf = 0, btuf = 0, bttf = 0;
+
+                if (treeNode.calcType == treeNodeCalcType.ctGatherBillsFees){
+                    for (let item of objsArr) {
+                        let data = item.data;
+                        if (data.feesIndex && data.feesIndex[ft.type]) {
+                            buf = (buf + parseFloat(data.feesIndex[ft.type].unitFee)).toDecimal(decimalObj.process);
+                            btf = (btf + parseFloat(data.feesIndex[ft.type].totalFee)).toDecimal(decimalObj.process);
+                            btuf = (btuf + parseFloat(data.feesIndex[ft.type].tenderUnitFee)).toDecimal(decimalObj.process);
+                            bttf = (bttf + parseFloat(data.feesIndex[ft.type].tenderTotalFee)).toDecimal(decimalObj.process);
+                        };
+                    };
+                }
+                else if (treeNode.calcType == treeNodeCalcType.ctGatherRationsFees){     // 这里的算法要配合冷姐姐的神图才能看懂^_^
+                    let sum_rtf = 0, sum_rttf = 0;
+                    let bq = parseFloat(treeNode.data.quantity ? treeNode.data.quantity : 1);
+
+                    for (let data of objsArr) {
+                        let rq = parseFloat(data.quantity ? data.quantity : 0);
+
+                        let ruf = 0, rtuf = 0, rtf = 0, rttf = 0;
+                        if (data.feesIndex && data.feesIndex[ft.type]) {
+                            ruf = parseFloat(data.feesIndex[ft.type].unitFee);
+                            rtuf = parseFloat(data.feesIndex[ft.type].tenderUnitFee);
+                            rtf = parseFloat(data.feesIndex[ft.type].totalFee);
+                            rttf = parseFloat(data.feesIndex[ft.type].tenderTotalFee);
+                        };
+
+                        if (me.project.projSetting.billsCalcMode === leafBillGetFeeType.rationContent) {
+                            buf = (buf + (ruf * rq / bq).toDecimal(decimalObj.process)).toDecimal(decimalObj.process);
+                            btuf = (btuf + (rtuf * rq / bq).toDecimal(decimalObj.process)).toDecimal(decimalObj.process);
+                        };
+
+                        sum_rtf = (sum_rtf + rtf).toDecimal(decimalObj.process);
+                        sum_rttf = (sum_rttf + rttf).toDecimal(decimalObj.process);
                     };
-                    if (data.feesIndex && data.feesIndex[ft.type]) {
-                        uf = (uf + parseFloat(data.feesIndex[ft.type].unitFee)).toDecimal(me.digitDefault);
-                        tf = (tf + parseFloat(data.feesIndex[ft.type].totalFee)).toDecimal(me.digitDefault);
-                        tuf = (tuf + parseFloat(data.feesIndex[ft.type].tenderUnitFee)).toDecimal(me.digitDefault);
-                        ttf = (ttf + parseFloat(data.feesIndex[ft.type].tenderTotalFee)).toDecimal(me.digitDefault);
+
+                    if (me.project.projSetting.billsCalcMode === leafBillGetFeeType.rationPrice || me.project.projSetting.billsCalcMode === leafBillGetFeeType.rationPriceConverse) {
+                        buf = (sum_rtf / bq).toDecimal(decimalObj.process);
+                        btuf = (sum_rttf / bq).toDecimal(decimalObj.process);
+                    };
+                    if (isBaseFeeType(ft.type) || (me.project.projSetting.billsCalcMode === leafBillGetFeeType.rationPriceConverse && ft.type == "common")){
+                        btf = sum_rtf;
+                        bttf = sum_rttf;
+                    }
+                    else{
+                        btf = (buf * bq).toDecimal(decimalObj.process);
+                        bttf = (btuf * bq).toDecimal(decimalObj.process);
                     };
                 };
-                ftObj.unitFee = uf.toDecimal(me.digit);
-                ftObj.totalFee = tf.toDecimal(me.digit);
-                ftObj.tenderUnitFee = tuf.toDecimal(me.digit);
-                ftObj.tenderTotalFee = ttf.toDecimal(me.digit);
 
-                me.checkFee(treeNode, ftObj);
+                ftObj.unitFee = buf.toDecimal(decimalObj.bills.unitPrice);
+                ftObj.totalFee = btf.toDecimal(decimalObj.bills.totalPrice);
+                ftObj.tenderUnitFee = btuf.toDecimal(decimalObj.bills.unitPrice);
+                ftObj.tenderTotalFee = bttf.toDecimal(decimalObj.bills.totalPrice);
+                checkFee(treeNode, ftObj);
 
                 rst.push(ftObj);
             };
+            treeNode.data.calcTemplate = {"calcItems": rst};
+        }
+        else{
+            // 叶子清单的公式计算:使用缺省清单计算程序。需要提供总金额作为计算基数(不需要工料机),然后每条按比例(费率)计算,不需要工料机明细。
+            if (treeNode.calcType == treeNodeCalcType.ctCalcBaseValue){
+                delete treeNode.data.gljList;
+
+                if (treeNode.data.programID == undefined){
+                    treeNode.data.programID = defaultBillTemplate.ID;
+                };
+            }
+            else if (treeNode.calcType == treeNodeCalcType.ctRationCalcProgram) {
+                if (treeNode.data.type == rationType.volumePrice){
+                    delete treeNode.data.gljList;
+                    let muf = treeNode.data.marketUnitFee ? treeNode.data.marketUnitFee : 0;
+                    let q = treeNode.data.quantity ? treeNode.data.quantity : 0;
+                    treeNode.data.marketTotalFee = (muf * q).toDecimal(decimalObj.ration.totalPrice);
+                }
+                else if (treeNode.data.type == rationType.gljRation){
+
+                }
+                else{
+                    treeNode.data.gljList = me.project.ration_glj.getGljArrByRation(treeNode.data.ID);
+                };
+
+                if (treeNode.data.programID == undefined){
+                    treeNode.data.programID = projectInfoObj.projectInfo.property.engineering;
+                };
+            }
+            else if (treeNode.calcType == treeNodeCalcType.ctBillCalcProgram) {
+                let rations = project.Ration.getBillsSortRation(treeNode.source.getID());
+                treeNode.data.gljList = project.ration_glj.getGatherGljArrByRations(rations);
 
-            if (treeNode.changed) {
-                me.saveAndCalcParents(treeNode);
-                delete treeNode.changed;
+                if (treeNode.data.programID == undefined || treeNode.data.programID == defaultBillTemplate.ID){
+                    treeNode.data.programID = projectInfoObj.projectInfo.property.engineering;
+                }
             };
-        };
 
-        return rst;
-    };
+            let template = me.compiledTemplates[treeNode.data.programID];
+            treeNode.data.calcTemplate = template;
 
-    calcDefaultBillTemp(treeNode, totalPrice){
-        let me = this;
-        let rst = [];
-        if (treeNode.sourceType != me.project.Bills.getSourceType()){return rst};
+            if (treeNode && template.hasCompiled) {
+                let $CE = executeObj;
+                $CE.treeNode = treeNode;
+                $CE.template = template;
+                $CE.calcBase = me.compiledCalcBases;
 
-        treeNode.data.baseTotalPrice = totalPrice;
-        treeNode.data.programID = defaultBillTemplate.ID;
-        me.calc.calculate(treeNode);
+                initFees(treeNode);
 
-        if (treeNode.changed) {
-            me.saveAndCalcParents(treeNode);
-            delete treeNode.changed;
-        };
+                for (let idx of template.compiledSeq) {
+                    let calcItem = template.calcItems[idx];
 
-        rst = treeNode.data.calcTemplate.calcItems;
-        return rst;
+                    let feeRate = calcItem.feeRate;
+                    if (!feeRate) feeRate = 100;    // 100%
+                    calcItem.unitFee = (eval(calcItem.compiledExpr) * feeRate * 0.01).toDecimal(decimalObj.decimal('unitPrice', treeNode));   // 如果eval()对清单树有影响,就换成小麦的Expression对象再试
+
+                    let quantity = treeNode.data.quantity;
+                    if (!quantity) quantity = 0;
+                    calcItem.totalFee = (calcItem.unitFee * quantity).toDecimal(decimalObj.decimal('totalPrice', treeNode));
+
+                    checkFee(treeNode, calcItem);
+                };
+            }
+        };
     };
 
-    getCalcDatas(treeNode){
+    // 计算本节点(默认同时递归计算所有父节点,可选)
+    calculate(treeNode, calcParents = true){
         let me = this;
-        let rst = [];
         let isRation = treeNode.sourceType === me.project.Ration.getSourceType();
         let isBill = treeNode.sourceType === me.project.Bills.getSourceType();
-        let isLeafBill = isBill && treeNode.source.children && treeNode.source.children.length === 0;
-        let isBillPriceCalc = me.project.projSetting.billsCalcMode === billsPrice;
+        let isBillPriceCalc = me.project.projSetting.billsCalcMode === leafBillGetFeeType.billsPrice;
+        let isLeafBill = me.isLeafBill(treeNode);
 
-        if (isRation) {                 // 清单单价计算模式下的叶子清单:取自己的计算程序ID,找到自己的计算程序计算。
-            me.calculate(treeNode);
-            rst = treeNode.data.calcTemplate.calcItems;
+        if (isRation){
+            treeNode.calcType = treeNodeCalcType.ctRationCalcProgram;
         }
         else if (isLeafBill) {
-            let ct = '';
             if (treeNode.children && treeNode.children.length > 0){
-                if (treeNode.children[0].sourceType == me.project.Ration.getSourceType()){
-                    ct = childrenType.ration;
-                }
-                else if (treeNode.children[0].sourceType == me.project.VolumePrice.getSourceType()){
-                    ct = childrenType.volumePrice;
-                };
+                // me.calcLeafBillChildren(treeNode);
+
+                if (isBillPriceCalc)                        // 清单单价计算模式下的叶子清单:取自己的计算程序ID,找到自己的计算程序计算。(汇总清单所有定额的工料机)
+                    treeNode.calcType = treeNodeCalcType.ctBillCalcProgram;
+                else                                        // 前三种计算模式下的叶子清单:汇总定额的计算程序的费用类别
+                    treeNode.calcType = treeNodeCalcType.ctGatherRationsFees;
             }
-            else{
-                ct = childrenType.formula;
+            else{                                          // 公式计算
+                treeNode.calcType = treeNodeCalcType.ctCalcBaseValue;
             };
+        }
+        else if (isBill)                                 // 父清单:汇总子清单的费用类别
+            treeNode.calcType = treeNodeCalcType.ctGatherBillsFees;
+
+        me.InnerCalc(treeNode);
+
+        // 计算所有父结点
+        if (treeNode.changed && calcParents && treeNode.parent) {
+            me.calculate(treeNode.parent);
+        };
+    };
+
+    // 存储、刷新本节点(默认存储刷新所有父节点,可选)
+    saveNode(treeNode, saveParents = true) {
+        if (!treeNode.changed) return;
+        let me = this;
+        let nodesArr = [];
+        let curNode = treeNode;
+        while (curNode) {
+            if (curNode.changed){nodesArr.push(curNode)};
+            if (saveParents) curNode = curNode.parent
+            else break;
+        };
+        me.saveNodes(nodesArr);
+    };
 
-            if (ct == childrenType.ration){
-                if (isBillPriceCalc){   // 清单单价计算模式下的叶子清单:取自己的计算程序ID,找到自己的计算程序计算。
-                    me.calculate(treeNode);
-                    rst = treeNode.data.calcTemplate.calcItems;
-                }else{                  // 前三种计算模式下的叶子清单:汇总定额的计算程序的费用类别
-                    rst = me.gatherFeeTypes(treeNode, CP_GatherType.rations);
+    // 多个树结点入库存储,刷新界面显示。
+    saveNodes(treeNodes){
+        if (treeNodes.length < 1) return;
+
+        let me = this;
+
+        me.project.beginUpdate('');
+        for (let node of treeNodes){
+            if (node.changed){
+                let data = {
+                    ID: node.data.ID,
+                    projectID: me.project.ID(),
+                    subType: node.data.subType,
+                    quantity: node.data.quantity,
+                    calcBase: node.data.calcBase,
+                    programID: node.data.programID,
+                    marketUnitFee: node.data.marketUnitFee,
+                    marketTotalFee: node.data.marketTotalFee,
+                    fees: node.data.fees,
+                    isFromDetail:node.data.isFromDetail
                 };
+                let newData = {'updateType': 'ut_update', 'updateData': data};
+                me.project.push(node.sourceType, [newData]);
             }
-            else if (ct == childrenType.volumePrice){
-                let totalPrice = 10000;
-                rst = me.calcDefaultBillTemp(treeNode, totalPrice);
+        };
+        me.project.endUpdate();
+
+        for (let node of treeNodes){delete node.changed};
+        projectObj.mainController.refreshTreeNode(treeNodes);
+
+        if (activeSubSheetIs(subSheetIndex.ssiCalcProgram)) {
+            calcProgramObj.showData(me.project.mainTree.selected, false);
+        };
+    };
+
+/*    计算所有树结点(分3种情况),并将发生计算改动的结点入库存储。
+    参数取值如下:
+    calcAllType.catAll       计算所有树结点 (不指定参数时的默认值)
+    calcAllType.catBills     计算所有清单 (改变项目属性中清单取费算法时会用到)
+    calcAllType.catRations   计算所有定额、工料机形式的定额、量价,因为它们都走自己的计算程序 (改变人工系数、费率值、工料机单价时会用到) */
+    calcAllNodes(calcType = calcAllType.catAll){
+        let me = this;
+        let needSaveNodes = [];
+
+        function calcNodes(nodes) {
+            for (let node of nodes) {
+                if (node.children.length > 0) {
+                    calcNodes(node.children);
+                };
+
+                if ((calcType == calcAllType.catAll) || (calcType == node.sourceType)) {
+                    me.calculate(node, false);
+                    if (node.changed) needSaveNodes.push(node);
+                };
             }
-            else if (ct == childrenType.formula){
-                let totalPrice = 20000;
-                rst = me.calcDefaultBillTemp(treeNode, totalPrice);
-            };
-        }
-        else if (isBill){    // 父清单:汇总子清单的费用类别
-            rst = me.gatherFeeTypes(treeNode, CP_GatherType.bills);
         };
 
-        return rst;
-    }
+        calcNodes(me.project.mainTree.roots);
+        me.saveNodes(needSaveNodes);
+    };
+
+    // 重新计算叶子清单下的所有子结点:如定额、工料机定额等(calculate算法基于定额、工料机定额的计算结果是正确的,实际上有时它们的计算结果并不是最新的)
+    calcLeafBillChildren(treeNode){
+        let me = this;
+        if(!me.isLeafBill(treeNode)) return;
+        if (treeNode.children && treeNode.children.length > 0) {
+            let needSaveNodes = [];
+            for (let child of treeNode.children){
+                me.calculate(child, false);
+                if (child.changed) needSaveNodes.push(child);
+            };
+            me.saveNodes(needSaveNodes);
+        };
+    };
 }

+ 87 - 10
web/building_saas/main/js/models/main_consts.js

@@ -11,12 +11,43 @@ const ModuleNames = {
     ration_coe:'ration_coe',
     ration_ass:'ration_ass',
     quantity_detail:'quantity_detail',
-    volume_price: 'volume_price',
+    // volume_price: 'volume_price',
     projectGLJ: 'project_glj',
     labour_coe: 'labour_coe',
     calc_program: 'calc_program'
 };
 
+const gljType = {
+    // 人工
+    LABOUR: 1,
+    // ==============材料类型=================
+    // 普通材料
+    GENERAL_MATERIAL: 201,
+    // 混凝土
+    CONCRETE: 202,
+    // 砂浆
+    MORTAR: 203,
+    // 配合比
+    MIX_RATIO: 204,
+    // 商品混凝土
+    COMMERCIAL_CONCRETE: 205,
+    // 商品砂浆
+    COMMERCIAL_MORTAR: 206,
+    // ==============材料类型=================
+    // ==============机械类型=================
+    // 普通机械
+    GENERAL_MACHINE: 301,
+    // 机械组成物
+    MACHINE_COMPOSITION: 302,
+    // 机上人工
+    MACHINE_LABOUR: 303,
+    // ==============机械类型=================
+    // 主材
+    MAIN_MATERIAL: 4,
+    // 设备
+    EQUIPMENT: 5
+};
+
 const feeRate_consts={
     decimal:3
 };
@@ -36,14 +67,60 @@ const CP_Col_Width = {          // 多处计算程序界面的列宽统一设置
     totalFee: 90
 };
 
-const CP_GatherType = {
-    rations: 'rations',
-    bills: 'bills'
+const treeNodeCalcType = {
+    ctRationCalcProgram: 1,
+    ctBillCalcProgram: 2,       // 汇总清单下所有定额的工料机
+    ctGatherRationsFees: 3,     // 汇总定额的各个费
+    ctGatherBillsFees: 4,       // 汇总清单的各个费
+    ctCalcBaseValue: 5
+};
+
+const calcAllType = {
+    catAll: 'all',
+    catBills: 'bills',
+    catRations: 'ration'
+};
+
+const subSheetIndex = {
+    ssiRationGLJ: 0,
+    ssiRationCoe: 1,
+    ssiRationAssistant: 2,
+    ssiQuantityDetail: 3,
+    ssiCalcProgram: 4,
+    ssiMemo: 5,
+    ssiFeature: 6
+};
+
+const volumePriceMaps = {
+    "量人": gljType.LABOUR,
+    "量材": gljType.GENERAL_MATERIAL,
+    "量机": gljType.GENERAL_MACHINE,
+    "量主": gljType.MAIN_MATERIAL,
+    "量设": gljType.EQUIPMENT,
+
+    "人工": gljType.LABOUR,
+    "材料": gljType.GENERAL_MATERIAL,
+    "机械": gljType.GENERAL_MACHINE,
+    "主材": gljType.MAIN_MATERIAL,
+    "设备": gljType.EQUIPMENT,
+
+    1: "量人",
+    201: "量材",
+    301: "量机",
+    4: "量主",
+    5: "量设"
+};
+
+const rationType = {
+    ration: 1,
+    volumePrice: 2,
+    gljRation: 3
+};
+
+const leafBillGetFeeType = {
+    rationContent: 0,
+    rationPrice: 1,
+    rationPriceConverse: 2,
+    billsPrice: 3
 };
 
-const childrenType = {
-    ration: 'ration',
-    bill: 'bill',
-    volumePrice: 'volumePrice',
-    formula: 'formula'
-};

+ 6 - 5
web/building_saas/main/js/models/project.js

@@ -77,7 +77,7 @@ var PROJECT = {
             this.ration_ass = ration_ass.createNew(this);
             this.quantity_detail = quantity_detail.createNew(this);
             this.FeeRate = FeeRate.createNew(this);
-            this.VolumePrice = VolumePrice.createNew(this);
+            // this.VolumePrice = VolumePrice.createNew(this);
             this.projectGLJ = new ProjectGLJ();
             this.projectGLJ.loadData();
             this.composition = new Composition();
@@ -92,7 +92,8 @@ var PROJECT = {
             this.labourCoe = new LabourCoe(this);
             this.calcProgram = new CalcProgram(this);
 
-            this.masterField = {ration: 'billsItemID', volumePrice: 'billsItemID'};
+            // this.masterField = {ration: 'billsItemID', volumePrice: 'billsItemID'};
+            this.masterField = {ration: 'billsItemID'};
         };
 
         // prototype用于定义public方法
@@ -135,7 +136,7 @@ var PROJECT = {
                     newNode.data = br[i];
                 }
             };
-            let loadVolumePriceNode = function (cacheNode) {
+/*            let loadVolumePriceNode = function (cacheNode) {
                 let newNode = null, bv = that.VolumePrice.getBillsSortVolumePrice(cacheNode.source.getID());
                 for (let v of bv) {
                     newNode = that.mainTree.addNode(cacheNode);
@@ -143,7 +144,7 @@ var PROJECT = {
                     newNode.sourceType = that.VolumePrice.getSourceType();
                     newNode.data = v;
                 }
-            };
+            };*/
             var loadIdTreeNode = function (nodes, parent) {
                 var newNode, i;
                 for (i = 0; i < nodes.length; i++) {
@@ -154,7 +155,7 @@ var PROJECT = {
                     that.FeeRate.loadFeeRateToBill(newNode);
                     if (nodes[i].children.length === 0) {
                         loadRationNode(that.Ration.datas, newNode);
-                        loadVolumePriceNode(newNode);
+                        // loadVolumePriceNode(newNode);
                     } else {
                         loadIdTreeNode(nodes[i].children, newNode);
                     }

+ 15 - 1
web/building_saas/main/js/models/project_glj.js

@@ -212,7 +212,8 @@ ProjectGLJ.prototype.setAdjustPrice=function(glj){
     switch (glj.unit_price.type + '') {
         // 人工: 调整基价=基价单价*调整系数
         case GLJTypeConst.LABOUR:
-            glj.adjust_price = scMathUtil.roundTo(parseFloat(glj.adjustment * glj.unit_price.base_price), -2);
+        case GLJTypeConst.MACHINE_LABOUR:
+            glj.adjust_price = this.getAdjustPrice(glj);
             break;
         // 机械类型的算法
         case GLJTypeConst.MACHINE:
@@ -222,4 +223,17 @@ ProjectGLJ.prototype.setAdjustPrice=function(glj){
         default:
             glj.adjust_price = glj.unit_price.base_price;
     }
+}
+
+ProjectGLJ.prototype.getAdjustPrice = function (glj) {
+    GLJTypeConst = this.datas.constData.GLJTypeConst !== undefined ? JSON.parse(this.datas.constData.GLJTypeConst) : GLJTypeConst;
+    if(glj.unit_price.type==GLJTypeConst.LABOUR||glj.unit_price.type==GLJTypeConst.MACHINE_LABOUR){
+        let labour = projectObj.project.calcProgram.compiledLabourCoes[glj.adjCoe];
+        let coe = labour&&labour.coe?labour.coe:1;
+        let decimal = getDecimal("glj.unitPrice");
+        return scMathUtil.roundTo(parseFloat(coe*glj.unit_price.base_price),-decimal);
+    }else {
+        return glj.unit_price.base_price
+    }
+
 }

+ 84 - 62
web/building_saas/main/js/models/quantity_detail.js

@@ -48,13 +48,10 @@ var quantity_detail = {
                 _.forEach(data.update_task,function (item) {
                     me.refreshEachItme(item.query,item.doc);
                 })
-                gljOprObj.detailData.push(data.doc);
                 this.datas.push(data.doc);
             }else {
                 this.datas.push(data);
-                gljOprObj.detailData.push(data);
             }
-            gljOprObj.detailData=_.sortBy(gljOprObj.detailData,'seq');
             this.refreshSheetData();
         };
         quantity_detail.prototype.resortData=function(data,req){
@@ -106,28 +103,13 @@ var quantity_detail = {
                 me.refreshEachItme(item.query,item.doc);
             });
             _.remove(this.datas,{ID:data.doc.ID});
-            _.remove(gljOprObj.detailData,{ID:data.doc.ID});
             this.refreshSheetData();
         };
         quantity_detail.prototype.refreshSheetData=function () {
-            sheetCommonObj.showData(gljOprObj.detailSheet,gljOprObj.detailSetting,gljOprObj.detailData);
-        };
-        quantity_detail.prototype.getUpdateData=function(type,query,doc,callfunction){
-            var updateData = [];
-            var newobj = {
-                'updateType': type,
-                'query': query,
-            }
-            if(doc){
-                newobj['doc']=doc;
-            }
-            if(callfunction){
-                newobj['updateFunction']=callfunction;
-            }
-            updateData.push(newobj);
-            return updateData;
+            gljOprObj.showQuantityDetailData();
         };
         quantity_detail.prototype.saveQuantityDetail=function (args,dataCode) {
+            var me = this;
             var doc={};
             var selected = projectObj.project.mainTree.selected;
             if(selected.sourceType==ModuleNames.ration){
@@ -152,13 +134,25 @@ var quantity_detail = {
                 }
 
             }
-            var updateData
-            if(args.hasOwnProperty("insertRecode")){
-                updateData = this.getUpdateData('ut_update',null,doc,'insertRecode');
+            var url="";
+            $.bootstrapLoading.start();
+            if(args.hasOwnProperty("insertRecode")){//右键插入或者是通过直接编辑保存
+                url = "/quantity_detail/insertRecode";
             }else{
-                updateData = this.getUpdateData('ut_create',null,doc);
+                url = "/quantity_detail/save";
+            }
+            var callback = function (data) {
+                if(doc.refreshQuantity==false){//清空数据
+                    me.cleanQuantityDetail();
+                }else {
+                    data.newRecord?me.refreshAfterSave(data.newRecord):me.refreshAfterSave(data);
+                    data.node?gljOprObj.refreshTreeNode(data.node):"";
+                }
+                $.bootstrapLoading.end();
             }
-            project.pushNow('saveQuantityDetail',[this.getSourceType()],updateData);
+            CommonAjax.post(url,doc,callback,function () {
+                $.bootstrapLoading.end();
+            });
         };
         quantity_detail.prototype.insertQuantityDetail = function (row) {
             var args = {
@@ -169,20 +163,27 @@ var quantity_detail = {
                 args.insertRecode = true;
             }
             this.saveQuantityDetail(args,'isSummation');
-
         };
 
         quantity_detail.prototype.deleteQuantityDetail = function (row) {
+           var me = this;
            var deleteable = this.checkReference(row);
            if(deleteable){
                var recode = gljOprObj.detailData[row];
-               var updateData = this.getUpdateData('ut_delete',null,recode);
-               project.pushNow('deleteQuantityDetail',[this.getSourceType()],updateData);
+               $.bootstrapLoading.start();
+               var callback=function (result) {
+                   me.refreshAfterDelete(result.data);
+                   result.node?gljOprObj.refreshTreeNode(result.node):"";
+                   $.bootstrapLoading.end();
+               }
+               CommonAjax.post("/quantity_detail/deleteRecode",recode,callback,function () {
+                   $.bootstrapLoading.end();
+               });
            }else {
                alert("当前行已被引用,不可删除。");
            }
-
         };
+
         quantity_detail.prototype.checkReference = function (row) {
             var deleteable = true;
            for(var i =0;i<gljOprObj.detailData.length;i++){
@@ -231,13 +232,7 @@ var quantity_detail = {
                     update_task.push({query:{ID:item.ID,projectID:item.projectID},doc:{regex:regex,referenceIndexs:item.referenceIndexs}});
                 }
             })
-
-            var updateData=[];
-            update_task.forEach(function (task) {
-                updateData.push({'updateType': 'ut_update', 'query': task.query,'doc':task.doc});
-            })
-            project.pushNow('updateQuantityDetail',[this.getSourceType()],[updateData]);
-
+            me.commonUpdate("/quantity_detail/swapRow",update_task);
         };
         quantity_detail.prototype.replaceAll=function(FindText, RepText,str) {
             let regExp = new RegExp(FindText, "g");
@@ -281,8 +276,7 @@ var quantity_detail = {
                 }
             }
             if(needupdate){
-                var updateData = this.getUpdateData('ut_update',query,doc,'updateQuantityRegex');
-                project.pushNow('updateQuantityDetail',[this.getSourceType()],updateData);
+                this.commonUpdate("/quantity_detail/updateRegex",{query:query,doc:doc});
             }else {
                 var sheet = subSpread.getActiveSheet();
                 sheet.getCell(args.row,args.col).value(gljOprObj.detailData[args.row].regex);
@@ -312,9 +306,21 @@ var quantity_detail = {
             };
             this.normalUpdate(query,doc);
         };
+        quantity_detail.prototype.commonUpdate = function (url,postData) {
+            var me = this;
+            $.bootstrapLoading.start();
+            var callback = function (data) {
+                me.refreshAfterUpdate(data);
+                data.node?gljOprObj.refreshTreeNode(data.node):"";
+                $.bootstrapLoading.end();
+            }
+            CommonAjax.post(url,postData,callback,function () {
+                $.bootstrapLoading.end();
+            });
+        };
         quantity_detail.prototype.normalUpdate=function(query,doc){
-            var updateData = this.getUpdateData('ut_update',query,doc);
-            project.pushNow('updateQuantityDetail',[this.getSourceType()],updateData);
+            var url = "/quantity_detail/update";
+            this.commonUpdate(url,{query:query,doc:doc});
         };
         quantity_detail.prototype.regexChecking=function(text){
             var regex=/^[0-9Cc\+\-\*\^/\(\)\.]*$/g;
@@ -447,32 +453,48 @@ var quantity_detail = {
                 this.datas = newList;
             }
         };
+        quantity_detail.prototype.cleanQuantityDetail = function (node,needSave) {
+           node =node?node:projectObj.project.mainTree.selected;
+           var query={projectID:node.data.projectID};
+            if(node.sourceType === project.Bills.getSourceType()){
+                query.billID = node.data.ID;
+                this.deleteByBills([{type:'delete',data:node.data}]);
+            }else if(node.sourceType === project.Ration.getSourceType()){
+                this.deleteByRation(node.data);
+                query.rationID = node.data.ID;
+            }
+            if(needSave===true){
+                query.refreshQuantity=false;
+                CommonAjax.post("/quantity_detail/save",query);
+            }
+            gljOprObj.detailData=[];
+            sheetCommonObj.showData(gljOprObj.detailSheet,gljOprObj.detailSetting,[]);
+
+        };
+
         quantity_detail.prototype.quantityEditChecking = function(value,node,fieldName){
             var validate = true;
-            if (value && value != calcFees.getFee(node.data, fieldName)){
-                if(fieldName=='quantity'){
-                   if(node.data.hasOwnProperty('isFromDetail')&&node.data.isFromDetail==1){
-                       var c = confirm('已有工程量明细,是否清空明细表,采用手工输入的表达式?')
-                        if(c){
-                            node.data.isFromDetail=0;
-                            if(node.sourceType === project.Bills.getSourceType()){
-                                this.deleteByBills([{type:'delete',data:node.data}]);
-                            }else if(node.sourceType === project.Ration.getSourceType()){
-                                this.deleteByRation(node.data);
-                            }
-                            gljOprObj.detailData=[];
-                            sheetCommonObj.showData(gljOprObj.detailSheet,gljOprObj.detailSetting,[]);
-
-                            validate = true;
-                        }else {
-                            validate = false;
-                        }
-                   }
-                }
+            if(fieldName=='quantity'){
+               if(node.data.hasOwnProperty('isFromDetail')&&node.data.isFromDetail==1){
+                   var c = confirm('已有工程量明细,是否清空明细表,采用手工输入的表达式?')
+                    if(c){
+                        validate = true;
+                    }else {
+                        validate = false;
+                    }
+               }
             }
             return validate;
-
-        }
+        };
+        quantity_detail.prototype.getDecimal=function (node) {
+            var decimal = 3;
+            if(node.sourceType === project.Bills.getSourceType()){
+                decimal = billsQuanDecimal.decimal(node.data.unit);
+            }else {
+                decimal = decimalObj.ration.quantity
+            }
+            return;
+        };
         return new quantity_detail(project);
     }
 

+ 15 - 17
web/building_saas/main/js/models/ration.js

@@ -101,9 +101,14 @@ var Ration = {
             });
             controller.sheet.getCell(selected[0].row,col).value(data[fieldName]);
         };
-        ration.prototype.getTempRationData = function (id, billsID, serialNo) {
+        ration.prototype.getTempRationData = function (id, billsID, serialNo, rType) {
             var newData = {'ID': id, 'serialNo': serialNo, projectID: this.project.ID()};
             newData[project.masterField.ration] = billsID;
+            newData['type'] = rType;
+            if (rType == rationType.volumePrice){
+                newData['subType'] = gljType.GENERAL_MATERIAL;   // 默认的量价类型为材料
+                newData['programID'] = projectInfoObj.projectInfo.property.engineering;
+            };
             return newData;
         };
 
@@ -151,17 +156,17 @@ var Ration = {
             return rations;
         };
 
-        ration.prototype.getInsertRationData = function (billsID, preRation) {
+        ration.prototype.getInsertRationData = function (billsID, preRation, rationType) {
             var br = this.getBillsSortRation(billsID);
             var updateData = [];
             if (preRation) {
                 var preIndex = br.indexOf(preRation), i;
-                updateData.push({updateType: 'ut_create', updateData: this.getTempRationData(this.maxRationID() + 1, billsID, preIndex < br.length - 1 ? br[preIndex + 1].serialNo : br[preIndex].serialNo + 1)});
+                updateData.push({updateType: 'ut_create', updateData: this.getTempRationData(this.maxRationID() + 1, billsID, preIndex < br.length - 1 ? br[preIndex + 1].serialNo : br[preIndex].serialNo + 1, rationType)});
                 for (i = preIndex + 1; i < br.length; i++) {
-                    updateData.push({updateType: 'ut_update', updateData: this.getTempRationData(br[i].ID, billsID, i < br.length - 1 ? br[i+1].serialNo : br[i].serialNo + 1)});
+                    updateData.push({updateType: 'ut_update', updateData: this.getTempRationData(br[i].ID, billsID, i < br.length - 1 ? br[i+1].serialNo : br[i].serialNo + 1, rationType)});
                 }
             } else {
-                updateData.push({updateType: 'ut_create', updateData: this.getTempRationData(this.maxRationID() + 1, billsID, br.length > 0 ? br[br.length - 1].serialNo + 1 : 1)});
+                updateData.push({updateType: 'ut_create', updateData: this.getTempRationData(this.maxRationID() + 1, billsID, br.length > 0 ? br[br.length - 1].serialNo + 1 : 1, rationType)});
             }
             return updateData;
         };
@@ -174,27 +179,27 @@ var Ration = {
             }
             return updateData;
         };
-        ration.prototype.insertRation = function (billsID, preRation) {
+        ration.prototype.insertRation = function (billsID, preRation, rationType) {
             var br = this.getBillsSortRation(billsID);
             this.project.pushNow('insertRation', [this.getSourceType(), this.project.projCounter()],
-                [this.getInsertRationData(billsID, preRation), this.getCounterData()]);
+                [this.getInsertRationData(billsID, preRation, rationType), this.getCounterData()]);
 
             var newRation = null;
             if (preRation) {
                 var preIndex = br.indexOf(preRation), i;
-                newRation = this.getTempRationData(this.getNewRationID(), billsID, preIndex < br.length - 1 ? br[preIndex + 1].serialNo : br[preIndex].serialNo + 1);
+                newRation = this.getTempRationData(this.getNewRationID(), billsID, preIndex < br.length - 1 ? br[preIndex + 1].serialNo : br[preIndex].serialNo + 1, rationType);
                 this.datas.push(newRation);
                 for (i = preIndex + 1; i < br.length; i++) {
                     br[i].serialNo = i < br.length - 1 ? br [i + 1].serialNo : br[i].serialNo + 1;
                 }
             } else {
-                newRation = this.getTempRationData(this.getNewRationID(), billsID, br.length > 0 ? br[br.length - 1].serialNo + 1 : 1);
+                newRation = this.getTempRationData(this.getNewRationID(), billsID, br.length > 0 ? br[br.length - 1].serialNo + 1 : 1, rationType);
                 this.datas.push(newRation);
             }
             return newRation;
         };
         ration.prototype.insertStdRation = function (billsID, preRation, std) {
-            var br = this.getBillsSortRation(billsID), updateData = this.getInsertRationData(billsID, preRation), newRation = null, that = this;
+            var br = this.getBillsSortRation(billsID), updateData = this.getInsertRationData(billsID, preRation, rationType.ration), newRation = null, that = this;
             updateData.forEach(function (data) {
 
                 if (data.updateType === 'ut_create') {
@@ -373,13 +378,6 @@ var Ration = {
 
             this.project.endUpdate();
         };
-
-        ration.prototype.calcAll = function (){
-            for (let ration of this.datas){
-                let node = this.project.mainTree.findNode(ration.ID);
-                 this.project.calcProgram.calculate(node);
-            };
-        };
         
         return new ration(project);
     }

+ 55 - 12
web/building_saas/main/js/models/ration_glj.js

@@ -41,9 +41,11 @@ var ration_glj = {
         };
 
         ration_glj.prototype.getGljArrByRation = function (rationID) {
-            return this.datas.filter(function (data) {
+            let result =  this.datas.filter(function (data) {
                 return data.rationID === rationID;
             })
+            result = gljOprObj.combineWithProjectGlj(result);
+            return result;
         };
         ration_glj.prototype.getGatherGljArrByRations = function (rations) {
             let result = [];
@@ -98,8 +100,9 @@ var ration_glj = {
         ration_glj.prototype.refreshAfterSave=function(data){
             let neRecodes=[];
             if(data){
-                neRecodes=data.newRecords;
-                gljOprObj.sheetData=data.showDatas;
+               // neRecodes=data.newRecords;//原来是显示和缓存分开的,后来发现会导致数据不一致的问题所以改成统一的了,这里也只是会作为显示。
+                neRecodes = data.showDatas;
+                gljOprObj.sheetData=neRecodes;
             }
             if(projectObj.project.ration_glj.datas&&Array.isArray(projectObj.project.ration_glj.datas)){
                 if(data){
@@ -109,6 +112,12 @@ var ration_glj = {
                 projectObj.project.ration_glj.datas = neRecodes;
             }
             gljOprObj.showRationGLJSheetData(true);
+            let node = project.mainTree.selected;
+            project.calcProgram.calculate(node);
+            project.calcProgram.saveNode(node);
+            if (activeSubSheetIs(subSheetIndex.ssiCalcProgram)) {
+                calcProgramObj.showData(node, false);
+            };
         };
         ration_glj.prototype.refreshAfterUpdate=function(data){
             var me = this;
@@ -131,7 +140,6 @@ var ration_glj = {
             })
             _.forEach(doc, function(n, key) {
                 glj_list[glj_index][key] = n;
-                gljOprObj.sheetData[sheet_index][key]=n;
             });
             return glj_list[glj_index].rationID;
         };
@@ -181,7 +189,7 @@ var ration_glj = {
                 for(let i=0;i<data.rationGljList.length;i++){
                     let temdata = data.rationGljList[i];
                     let newGLJ = {};
-                    newGLJ.projectID = newRation.projectID;
+                    newGLJ.projectID = parseInt(newRation.projectID);
                     newGLJ.GLJID = temdata.gljId;
                     newGLJ.rationID = newRation.ID;
                     newGLJ.billsItemID=newRation.billsItemID,
@@ -274,6 +282,7 @@ var ration_glj = {
             var me=this;
             $.bootstrapLoading.start();
             var callback=function (data) {
+                let initShow = false;//是否需要表格初始化显示
                 if(updateField=='customQuantity'){
                     me.refreshAfterQuantityUpdate(data);
                 }else {
@@ -284,9 +293,17 @@ var ration_glj = {
                     if(data.hasOwnProperty('adjustState')){//更新定额调整状态
                         me.updateRationAdjustState(data.adjustState);
                     }
+                    if(recode.subList&&recode.subList.length>0){
+                        initShow = true;
+                    }
+                }
+                if(initShow==false){//不需要初始化,只需耍新当前显示就可以了
+                    gljOprObj.showRationGLJSheetData();
                 }
-                gljOprObj.showRationGLJSheetData();
                 projectObj.project.projectGLJ.loadData(function () {//等项目工料机加载完成后再给用户编辑
+                    if(initShow==true){
+                        gljOprObj.refreshView();
+                    }
                     $.bootstrapLoading.end();
                 });
             }
@@ -337,20 +354,29 @@ var ration_glj = {
                   quantity:0,
                   name:glj.name,
                   code:glj.code,
+                  original_code:glj.code,
                   unit:glj.unit,
                   specs:glj.specs,
                   basePrice:glj.basePrice,
                   shortName:glj.shortName,
                   type:glj.gljType,
+                  adjCoe:glj.adjCoe,
                   createType:'add',
                   repositoryId:glj.repositoryId
               }
               if(glj.hasOwnProperty("compilationId")){
                   ration_glj.from="cpt";
+                  if(glj.code.indexOf('-')!=-1){//这条工料机是用户通过修改包称、规格、型号等保存到补充工料机库的
+                      ration_glj.original_code = glj.code.split('-')[0];//取-前的编号作为原始编号
+                  }
               }
+
               gljList.push(ration_glj);
           });
-            CommonAjax.post("/rationGlj/addGLJ",gljList,callback);
+          $.bootstrapLoading.start();
+            CommonAjax.post("/rationGlj/addGLJ",gljList,callback,function () {
+                $.bootstrapLoading.end();
+            });
         };
         ration_glj.prototype.replaceGLJ=function (selectCode,oldData,callback) {
             var allGLJ=gljOprObj.AllRecode;
@@ -366,16 +392,23 @@ var ration_glj = {
             oldData.rationItemQuantity=0;
             oldData.name=glj.name;
             oldData.code=glj.code;
+            oldData.original_code=glj.code;
             oldData.unit=glj.unit;
             oldData.specs=glj.specs;
             oldData.basePrice=glj.basePrice;
             oldData.repositoryId=glj.repositoryId;
             if(glj.hasOwnProperty("compilationId")){
                 oldData.from="cpt";
+                if(glj.code.indexOf('-')!=-1){//这条工料机是用户通过修改包称、规格、型号等保存到补充工料机库的
+                    oldData.original_code = glj.code.split('-')[0];//取-前的编号作为原始编号
+                }
             }else {
                 oldData.from="std";
             }
-            CommonAjax.post("/rationGlj/replaceGLJ",oldData,callback);
+            $.bootstrapLoading.start();
+            CommonAjax.post("/rationGlj/replaceGLJ",oldData,callback,function () {
+                $.bootstrapLoading.end();
+            });
         };
 
         ration_glj.prototype.mReplaceGLJ=function (selectCode,oldData,callback) {
@@ -387,7 +420,12 @@ var ration_glj = {
             var query={
                 projectID:oldData.projectID,
                 code:oldData.code,
-                name:oldData.name
+                name:oldData.name,
+                unit:oldData.unit,
+                type:oldData.type
+            }
+            if(oldData.specs&&oldData.specs!=''){
+                query.specs=oldData.specs;
             }
             var doc={
                 GLJID:glj.ID,
@@ -395,6 +433,7 @@ var ration_glj = {
                 rationItemQuantity:0,
                 name:glj.name,
                 code:glj.code,
+                original_code:glj.code,
                 unit:glj.unit,
                 specs:glj.specs,
                 type:glj.gljType,
@@ -409,12 +448,16 @@ var ration_glj = {
             }
             if(glj.hasOwnProperty("compilationId")){
                 doc.from="cpt";
+                if(glj.code.indexOf('-')!=-1){//这条工料机是用户通过修改包称、规格、型号等保存到补充工料机库的
+                    doc.original_code = glj.code.split('-')[0];//取-前的编号作为原始编号
+                }
             }else {
                 doc.from="std";
             }
-            CommonAjax.post("/rationGlj/mReplaceGLJ",{query:query,doc:doc},callback);
-
-
+            $.bootstrapLoading.start();
+            CommonAjax.post("/rationGlj/mReplaceGLJ",{query:query,doc:doc},callback,function () {
+                $.bootstrapLoading.end();
+            });
         };
         return new ration_glj(project);
     }

+ 4 - 2
web/building_saas/main/js/models/volume_price.js

@@ -51,6 +51,8 @@ var VolumePrice = {
             getTempVolumePrice (newID, billsID, serialNo) {
                 var newData = {'ID': newID, 'serialNo': serialNo, projectID: tools.owner.ID()};
                 newData[project.masterField.volumePrice] = billsID;
+                newData.type = '材料';
+                newData.programID = projectInfoObj.projectInfo.property.engineering;
                 return newData;
             };
             getBillsSortVolumePrice (billsID) {
@@ -75,7 +77,7 @@ var VolumePrice = {
             getInsertVolumePriceData (billsID, pre) {
                 let bv = this.getBillsSortVolumePrice(billsID);
                 let updateData = [];
-                if (pre) {
+                if (pre && bv.indexOf(pre) > -1) {
                     let preIndex = bv.indexOf(pre), i;
                     updateData.push({updateType: 'ut_create', updateData: this.getTempVolumePrice(this.maxID() + 1, billsID, preIndex < bv.length - 1 ? bv[preIndex + 1].serialNo : bv[preIndex].serialNo + 1)});
                     for (i = preIndex + 1; i < bv.length; i++) {
@@ -90,7 +92,7 @@ var VolumePrice = {
                 tools.owner.pushNow('insertVolumePrice', [this.getSourceType(), this.getProject().projCounter()], [this.getInsertVolumePriceData(billsID, pre), this.getCounterData()]);
 
                 let bv = this.getBillsSortVolumePrice(billsID), newVP = null;
-                if (pre) {
+                if (pre && bv.indexOf(pre) > -1) {
                     let preIndex = bv.indexOf(pre);
                     newVP = this.getTempVolumePrice(this.getNewID(), billsID, preIndex < bv.length - 1 ? bv[preIndex + 1].serialNo : bv[preIndex].serialNo + 1);
                     this.datas.push(newVP);

+ 2 - 2
web/building_saas/main/js/views/calc_program_manage.js

@@ -33,7 +33,7 @@ let rationPM = {
         ],
         view:{
             comboBox:[],
-            lockColumns:[0,1,2,4,5,6],
+            lockColumns:[0,1,2,5,6],
             colHeaderHeight: CP_Col_Width.colHeader,
             rowHeaderWidth: CP_Col_Width.rowHeader
         }
@@ -53,7 +53,7 @@ let rationPM = {
         me.mainSpread = sheetCommonObj.buildSheet($('#mainSpread')[0], me.mainSetting, me.datas.length);
         me.detailSpread = sheetCommonObj.buildSheet($('#detailSpread')[0], me.detailSetting, me.datas[0].calcItems.length);
         var fieldName = new GC.Spread.Sheets.CellTypes.ComboBox();
-        fieldName.items(projectObj.project.calcProgram.calc.compiledFeeTypeNames);
+        fieldName.items(projectObj.project.calcProgram.compiledFeeTypeNames);
         me.detailSpread.getSheet(0).getRange(-1, 4, -1, 1).cellType(fieldName);
 
         me.mainSpread.getSheet(0).bind(GC.Spread.Sheets.Events.EnterCell, me.onMainEnterCell);

+ 6 - 2
web/building_saas/main/js/views/calc_program_view.js

@@ -225,10 +225,14 @@ let calcProgramObj = {
         sheetCommonObj.initSheet(me.sheet, me.setting, 1);
     },
 
-    showData: function (treeNode) {
+    showData: function (treeNode, needCalc = true) {
         var me = this;
         me.treeNode = treeNode;
-        me.datas = projectObj.project.calcProgram.getCalcDatas(treeNode);
+        if (needCalc){
+            projectObj.project.calcProgram.calculate(treeNode);
+            projectObj.project.calcProgram.saveNode(treeNode);
+        };
+        me.datas = treeNode.data.calcTemplate ? treeNode.data.calcTemplate.calcItems : [];
         sheetCommonObj.initSheet(me.sheet, me.setting, me.datas.length);
         sheetCommonObj.showData(me.sheet, me.setting, me.datas);
 

+ 178 - 37
web/building_saas/main/js/views/glj_view.js

@@ -15,6 +15,7 @@ var gljOprObj = {
     detailSheet:null,
     detailData:[],
     GLJSelection:[],
+    selectedGLJClass:null,
     parentNodeIds:{},
     activeTab:'#linkGLJ',
     decimalSetting:{
@@ -28,12 +29,12 @@ var gljOprObj = {
             {headerName: "规格型号", headerWidth: 120, dataCode: "specs", dataType: "String", hAlign: "left"},
             {headerName: "单位", headerWidth: 45, dataCode: "unit", dataType: "String", hAlign: "center"},
             {headerName: "类型", headerWidth: 45, dataCode: "shortName", dataType: "String", hAlign: "center"},
-            {headerName: "定额消耗量", headerWidth: 80, dataCode: "rationItemQuantity", dataType: "Number", hAlign: "right",formatter:"0.000",tofix:3},    // dataType: "Number", formatter: "0.00"
-            {headerName: "自定义消耗量", headerWidth: 80, dataCode: "customQuantity", dataType: "Number", hAlign: "right",formatter:"0.000",tofix:3},
-            {headerName: "消耗量", headerWidth: 80, dataCode: "quantity", dataType: "Number", hAlign: "right",formatter:"0.000",tofix:3},
-            {headerName: "基价单价", headerWidth: 80, dataCode: "basePrice", dataType: "Number", hAlign: "right",formatter:"0.00"},
-            {headerName: "调整基价", headerWidth: 80, dataCode: "adjustPrice", dataType: "Number", hAlign: "right",formatter:"0.00"},
-            {headerName: "市场单价", headerWidth: 80, dataCode: "marketPrice", dataType: "Number", hAlign: "right",formatter:"0.00"},
+            {headerName: "定额消耗量", headerWidth: 80, dataCode: "rationItemQuantity", dataType: "Number", hAlign: "right",decimalField:"glj.quantity"},    // dataType: "Number", formatter: "0.00"
+            {headerName: "自定义消耗量", headerWidth: 80, dataCode: "customQuantity", dataType: "Number", hAlign: "right",decimalField:"glj.quantity"},
+            {headerName: "消耗量", headerWidth: 80, dataCode: "quantity", dataType: "Number", hAlign: "right",decimalField:"glj.quantity"},
+            {headerName: "基价单价", headerWidth: 80, dataCode: "basePrice", dataType: "Number", hAlign: "right",decimalField:"glj.unitPrice"},
+            {headerName: "调整基价", headerWidth: 80, dataCode: "adjustPrice", dataType: "Number", hAlign: "right",decimalField:"glj.unitPrice"},
+            {headerName: "市场单价", headerWidth: 80, dataCode: "marketPrice", dataType: "Number", hAlign: "right",decimalField:"glj.unitPrice"},
             {headerName: "是否暂估", headerWidth: 65, dataCode: "isEstimate", dataType: "String", hAlign: "center",vAlign:"center",cellType:"checkBox"}
         ],
         view: {
@@ -107,12 +108,23 @@ var gljOprObj = {
         },
         callback:{
             onClick: function(event,treeId,treeNode) {
-                let me = gljOprObj, gljTypeId = treeNode.ID;
-                if(me.gljCurTypeId !== treeNode.ID){
-                    me.gljCurTypeId = treeNode.ID;
-                    me.filterLibGLJSheetData();
-                    me.showLibGLJSheetData();
+                if(treeId=='gljTree'){
+                    let me = gljOprObj, gljTypeId = treeNode.ID;
+                    if(me.gljCurTypeId !== treeNode.ID){
+                        me.gljCurTypeId = treeNode.ID;
+                        me.filterLibGLJSheetData();
+                        me.showLibGLJSheetData();
+                    }
+                }else {
+                    if(treeNode.isParent){
+                        $('#class_selected_conf').attr("disabled","disabled");
+                        $('#selected_class').val("");
+                    }else {
+                        $('#class_selected_conf').removeAttr("disabled");
+                        $('#selected_class').val(treeNode.ID);
+                    }
                 }
+
             }
         }
     },
@@ -418,6 +430,11 @@ var gljOprObj = {
         var me = gljOprObj;
         var header = me.setting.header;
         var disable = null;
+        if(me.sheetData[args.row]!=undefined){
+            if(me.sheetData[args.row].isMixRatio){
+                disable = true;
+            }
+        }
         if(header[args.col]&&header[args.col].dataCode=='marketPrice'){
             var type = me.sheetData[args.row].shortName;
             var index= _.indexOf(me.setting.notEditedType,type);
@@ -534,22 +551,25 @@ var gljOprObj = {
         }
     },
     showRationGLJSheetData:function (init) {
-        if(init){
-            this.sheet.getRange(0,-1,this.sheet.getRowCount(),-1).visible(true);
-            this.sheetData=_.sortBy(this.sheetData,'type');
-            this.addMixRatioToShow();
-            this.initRationTree();
-        }
+        this.sheet.getRange(0,-1,this.sheet.getRowCount(),-1).visible(true);
+        this.sheetData=_.sortBy(this.sheetData,'type');
+        this.addMixRatioToShow();
+        this.initRationTree(init);
         sheetCommonObj.showData(this.sheet,this.setting,this.sheetData);
 
     },
-    initRationTree:function () {
+    initRationTree:function (init) {
         this.sheet.getRange(-1, 0, -1, 1).cellType(this.getTreeNodeCellType(this.sheetData));
         for(var i =0;i<this.sheetData.length;i++){
             if(this.sheetData[i].hasOwnProperty('subList')){
-                this.sheet.setTag(i,0,true);
-                this.sheet.getRange(i+1, -1, this.sheetData[i].subList.length, -1).visible(false);
-                this.sheet.getRange(i+1, -1, this.sheetData[i].subList.length, -1).locked(true);
+                var collapsed = false;
+                if(init){
+                    this.sheetData[i].collapsed=true;
+                    collapsed = true;
+                }else {
+                    collapsed = this.sheetData[i].collapsed==undefined?true:this.sheetData[i].collapsed;
+                }
+                this.sheet.getRange(i+1, -1, this.sheetData[i].subList.length, -1).visible(!collapsed);// this.sheet.getRange(i+1, -1, this.sheetData[i].subList.length, -1).locked(true);
             }
         }
     },
@@ -585,9 +605,10 @@ var gljOprObj = {
                 if(glj){
                     ration_gljs[i].basePrice=glj.unit_price.base_price;
                     ration_gljs[i].marketPrice=glj.unit_price.market_price;
-                    ration_gljs[i].adjustPrice=glj.adjust_price;
+                    //ration_gljs[i].adjustPrice=glj.adjust_price;
                     ration_gljs[i].isEstimate=glj.is_evaluate;
                     ration_gljs[i].isAdd=glj.unit_price.is_add;
+                    ration_gljs[i].adjustPrice=projectObj.project.projectGLJ.getAdjustPrice(glj);
                     var connect_index = this.getIndex(glj,['code','name','specs','unit','type'])
                     if(mixRatioMap.hasOwnProperty(connect_index)){
                         var mixRatios = this.getMixRationShowDatas(mixRatioMap[connect_index],projectGljs);
@@ -626,7 +647,8 @@ var gljOprObj = {
                 adjustPrice:pg.adjust_price,
                 isEstimate:pg.is_evaluate,
                 isMixRatio:true,
-                isAdd:pg.unit_price.is_add
+                isAdd:pg.unit_price.is_add,
+                GLJID:pg.glj_id
             }
             temRationGLJs.push(tem);
         }
@@ -650,6 +672,7 @@ var gljOprObj = {
     },
     showQuantityDetailData:function (node) {
         var details=[];
+        node =node?node:projectObj.project.mainTree.selected;
         var quantity_detail =projectObj.project.quantity_detail;
         if(node.sourceType==ModuleNames.ration){
             details=_.filter(quantity_detail.datas,{'rationID':node.data.ID});
@@ -696,10 +719,14 @@ var gljOprObj = {
         var recode = me.sheetData[args.row];
         var newval;
         if(updateField=='marketPrice'||updateField=='customQuantity'||updateField=='basePrice'){
-            newval = number_util.checkNumberValue(args.editingText,this.decimalSetting[updateField]);
-            if(!newval){
-                me.sheet.getCell(args.row, args.col).value(recode[updateField]);
-                return;
+            if(args.editingText==null){
+                newval="";
+            }else {
+                newval = number_util.checkNumberValue(args.editingText,this.decimalSetting[updateField]);
+                if(newval==null){
+                    me.sheet.getCell(args.row, args.col).value(recode[updateField]);
+                    return;
+                }
             }
         }else {
              if(updateField=='name'||updateField=='unit'){
@@ -711,6 +738,9 @@ var gljOprObj = {
              }
             newval=args.editingText==null?"":args.editingText;
         }
+        if(newval === recode[updateField]){//如果值完全相等,则不需要更新
+            return
+        }
         if(updateField=='marketPrice'||updateField=='basePrice'){
             projectObj.project.projectGLJ.updatePriceFromRG(recode,updateField,newval);
         } else {
@@ -886,15 +916,17 @@ var gljOprObj = {
             return _.find(gljOprObj.sheetData,{'code':n})?false:true;
         })
         if(gljOprObj.GLJSelection.length>0&&selected&&selected.sourceType==ModuleNames.ration){
+            $("#glj_tree_div").modal('hide');
             project.ration_glj.addGLJByLib(gljOprObj.GLJSelection,selected.data,function (result) {
                 if(result){
                     selected.data.adjustState=result.adjustState;
-                    project.ration_glj.datas = project.ration_glj.datas.concat(result.newRecodes);
+                    //project.ration_glj.datas = project.ration_glj.datas.concat(result.newRecodes);//显示和缓存统一,这样的话就不用更新两个位置了
+                    project.ration_glj.datas = project.ration_glj.datas.concat(result.showData);
                     gljOprObj.sheetData = gljOprObj.sheetData.concat(result.showData)
                     gljOprObj.showRationGLJSheetData();
                     project.projectGLJ.loadData();
                     projectObj.mainController.refreshTreeNode([selected]);
-                    $("#glj_tree_div").modal('hide');
+                    $.bootstrapLoading.end();
                 }
             });//doc.rationID=selected.data.ID;
         }else {
@@ -908,6 +940,7 @@ var gljOprObj = {
         var project= projectObj.project;
         var selectCode=gljOprObj.GLJSelection[0];
         var selected = projectObj.project.mainTree.selected;
+        $("#glj_tree_div").modal('hide');
         project.ration_glj.replaceGLJ(selectCode,oldData,function (result) {
             if(result){
                 //result.adjustState;
@@ -919,7 +952,7 @@ var gljOprObj = {
                 selected.data.adjustState=result.adjustState;
                 projectObj.mainController.refreshTreeNode([selected]);
             }
-            $("#glj_tree_div").modal('hide');
+            $.bootstrapLoading.end();
         })
     },
     doMReplaceGLJ:function () {
@@ -927,18 +960,21 @@ var gljOprObj = {
         var oldData=me.sheetData[gljContextMenu.selectedRow];
         var project= projectObj.project;
         var selectCode=me.GLJSelection[0];
+        $("#glj_tree_div").modal('hide');
         project.ration_glj.mReplaceGLJ(selectCode,oldData,function (result) {
             var data=result.data;
             var stateList= result.stateList;
+            var n_index = me.getIndex(data.query,['code','name','specs','unit','type']);
             _.forEach(project.ration_glj.datas,function (t) {
-                if(t.code==data.query.code&&t.name==data.query.name){
+                var t_index =me.getIndex(t,['code','name','specs','unit','type']);
+                if(n_index==t_index){
                     me.updateProperty(t,data.doc);
                 }
             })
             me.showRationGLJSheetData();
             project.projectGLJ.loadData();
             me.refreshStateAfterMreplace(stateList);
-            $("#glj_tree_div").modal('hide');
+            $.bootstrapLoading.end();
         })
     },
     updateProperty:function (obj,doc) {
@@ -962,6 +998,33 @@ var gljOprObj = {
     refreshView:function () {
         this.showRationGLJData();
     },
+    //
+    refreshTreeNode:function (obj) {
+    if(!obj){
+        return;
+    }
+    var objectArray =[];
+    var nodes =[];
+    if(obj instanceof Array){
+        objectArray.concat(obj);
+    }else {
+        objectArray.push(obj);
+    }
+    for(let o of objectArray ){
+        var node = _.find(projectObj.project.mainTree.items,function (n) {
+            return n.sourceType==o.type&&n.data.ID==o.ID;
+        })
+        if(node){
+            for (var k in o.data){
+                node.data[k] = o.data[k];
+            }
+            nodes.push(node);
+        }
+    }
+
+    projectObj.mainController.refreshTreeNode(nodes);
+
+    },
     getTreeNodeCellType:function (data) {
         var ns = GC.Spread.Sheets;
         var rectW = 10;
@@ -1030,7 +1093,7 @@ var gljOprObj = {
                 var recode = data[options.row];
                 if(recode&&recode.hasOwnProperty('subList')){
                     drowRect(ctx,x,y,w,h);
-                    var collapsed = options.sheet.getTag(options.row,options.col);
+                    var collapsed = recode.collapsed==undefined?true:recode.collapsed;//options.sheet.getTag(options.row,options.col);
                     drowSymbol(ctx,x,y,w,h,collapsed);
                 }else if(recode&&recode.isMixRatio){
                     offset= drowSubItem(ctx,x,y,w,h,offset,data[options.row+1]);
@@ -1056,9 +1119,10 @@ var gljOprObj = {
             if(recode&&recode.hasOwnProperty('subList')){
                var hoffset= hitinfo.cellRect.x+3;
                 if (hitinfo.x > hoffset && hitinfo.x < hoffset + 10){
-                    var collapsed =  hitinfo.sheet.getTag(hitinfo.row,hitinfo.col,hitinfo.sheetArea);
-                    collapsed = !collapsed;
-                    hitinfo.sheet.setTag(hitinfo.row,hitinfo.col,collapsed);
+                    var collapsed = recode.collapsed==undefined?true:recode.collapsed;
+                    collapsed = !collapsed
+                    recode.collapsed=collapsed;
+                    //hitinfo.sheet.setTag(hitinfo.row,hitinfo.col,collapsed);
                     hitinfo.sheet.getRange(hitinfo.row+1, -1, recode.subList.length, -1).visible(!collapsed);
                     hitinfo.sheet.invalidateLayout();
                     hitinfo.sheet.repaint();
@@ -1119,7 +1183,84 @@ $(function(){
             gljOprObj.doMReplaceGLJ();
         }
     })
-})
 
+    $('#class_selected_conf').click(function () {
+        var gljClass =  $('#selected_class').val();
+        var glj = gljOprObj.selectedGLJClass;
+        if(glj&&gljClass&&gljClass!=""){
+            //保存到我的工料机库
+            /*1 检查是否有组成物
+            * 2 如果有,则检查组成物中是否有新增的记录,如果有,查看是否已经保存了,没有的话,要先添加组成物到补充工料机库
+            * 3 保存
+
+            * */
+            var newItem={
+                code:glj.code,
+                name:glj.name,
+                specs:glj.specs,
+                unit:glj.unit,
+                basePrice:glj.basePrice,
+                gljType:glj.type,
+                shortName:glj.shortName,
+                component:[],
+                gljClass:gljClass
+            };
+            if(glj.hasOwnProperty("subList")&&glj.subList.length>0){//有组成物,检查组成物信息,目前组成物不允许修改,所以暂时不用考虑组成物是新增的情况
+                for(var i=0;i<glj.subList.length;i++ ){
+                    let tem={
+                        ID:glj.subList[i].GLJID,
+                        consumeAmt:glj.rationItemQuantity,
+                        isStd:true
+                    }
+                    newItem.component.push(tem);
+                }
+            }
+            var data = getcmpUpdateData([newItem]);
+            $.bootstrapLoading.start();
+            var callback = function (data) {
+                $("#glj_class_div").modal('hide');
+                $.bootstrapLoading.end();
+            }
+            CommonAjax.post("complementartGlj/api/mixUpdateGljItems", data, callback, function () {
+                $.bootstrapLoading.end();
+            });
+        }
+    })
+
+    $('#glj_class_div').on('hidden.bs.modal', function (e){
+        gljOprObj.selectedGLJClass=null;
+        $('#class_selected_conf').attr("disabled","disabled");
+        $('#selected_class').val("");
+    })
 
+    function getcmpUpdateData(items) {
+        var data ={
+            "updateItems": [],
+            "removeIds": []
+        }
+        data.addItems = items;
+        return data;
+    }
+})
 
+function getDecimal(fieldID,node) {
+    if(node){
+        return decimalObj.decimal(fieldID,node);
+    }else if(fieldID.indexOf(".")){
+        var keyArray = fieldID.split(".");
+        return decimalObj[keyArray[0]][keyArray[1]];
+    }else {
+        return decimalObj.decimal(fieldID);
+    }
+}
+
+function getFormatter(decimal) {
+    var pre = "0.";
+    if(decimal<=0){
+        return "0";
+    }
+    for(i=0;i<decimal;i++){
+        pre += "0"
+    }
+    return pre;
+}

+ 15 - 1
web/building_saas/main/js/views/glj_view_contextMenu.js

@@ -90,7 +90,9 @@ var gljContextMenu = {
                         return disable;
                     },
                     callback: function () {
-                        getGLJData('m_replace');
+                        var sheetData = gljOprObj.sheetData;
+                        var recode = sheetData[gljContextMenu.selectedRow];
+                        showGLJClassTree(recode);
                     }
                 }
             }
@@ -205,4 +207,16 @@ function getGLJData(actionType) {
         $("input[name='glj']").get(0).checked=true;
         $("#glj_tree_div").modal({show:true});
     })
+}
+
+function showGLJClassTree(record) {
+    CommonAjax.post('/rationGlj/getGLJClass',record, function (data) {
+        if(data.exist==true){
+            alert("当前工料机已存在。");
+        }else {
+            gljOprObj.selectedGLJClass = record;
+            $("#glj_class_div").modal({show:true});
+            zTreeHelper.createTree(data.items, gljOprObj.gljTreeSetting, "classTree", gljOprObj);
+        }
+    })
 }

+ 94 - 23
web/building_saas/main/js/views/main_tree_col.js

@@ -4,19 +4,44 @@
 
 let MainTreeCol = {
     getText: {
-        type: function (node) {
+        subType: function (node) {
             if (node.sourceType === projectObj.project.Bills.getSourceType()) {
                 return '';
+            // CSL, 2017-11-29
             } else if (node.sourceType === projectObj.project.Ration.getSourceType()) {
-                return '定';
-            } else if (node.sourceType === projectObj.project.VolumePrice.getSourceType()) {
-                return '量';
+                if (node.data.type == 1 || node.data.type == undefined)    // 兼容旧定额
+                    return '定'
+                else if (node.data.type == 2){    // 量价
+                    return volumePriceMaps[node.data.subType];
+                }
+                else if (node.data.type == 3){    // 工料机定额
+                    return '工料机';     // 这里明细值等张伟城确定
+                }
             } else if (node.sourceType === projectObj.project.ration_glj.getSourceType()) {
                 return '主';
             }
+        },
+
+        // CSL, 2017-11-28
+        calcProgramName: function (node) {
+            let programID = node.data.programID;
+            if (!programID) return
+            else return projectObj.project.calcProgram.compiledTemplateMaps[programID];
         }
     },
     readOnly: {
+        subType: function (node){
+            return (node.data.type != 2 && node.data.type != 3);
+        },
+        // CSL, 2017-11-28
+        calcProgramName: function (node) {
+            if (
+                node.sourceType === projectObj.project.Ration.getSourceType() ||
+                (projectObj.project.calcProgram.isLeafBill(node) && projectObj.project.projSetting.billsCalcMode === leafBillGetFeeType.billsPrice)
+            ) return false
+            else return true;
+        },
+
         bills: function (node) {
             return node.sourceType === projectObj.project.Bills.getSourceType();
         },
@@ -24,7 +49,7 @@ let MainTreeCol = {
             return node.sourceType === projectObj.project.Ration.getSourceType();
         },
         volumePrice: function (node) {
-            return node.sourceType === projectObj.project.VolumePrice.getSourceType();
+            return (node.data.type == rationType.volumePrice || node.data.type == rationType.gljRation);
         },
         non_bills: function (node) {
             return node.sourceType !== projectObj.project.Bills.getSourceType();
@@ -33,7 +58,7 @@ let MainTreeCol = {
             return node.sourceType !== projectObj.project.Ration.getSourceType();
         },
         non_volumePrice: function (node) {
-            return node.sourceType !== projectObj.project.Ration.getSourceType();
+            return !(node.data.type == rationType.volumePrice || node.data.type == rationType.gljRation);
         },
         billsParent: function (node) {
             return node.sourceType === projectObj.project.Bills.getSourceType() && node.source.children.length > 0;
@@ -69,7 +94,33 @@ let MainTreeCol = {
 
         feeRate: function () {
             return feeRateObject.getFeeRateEditCellType();
-        }
+        },
+
+        // CSL, 2017-11-28
+        calcProgramName: function (node) {
+            if (
+                node.sourceType === projectObj.project.Ration.getSourceType() ||
+                (projectObj.project.calcProgram.isLeafBill(node) && projectObj.project.projSetting.billsCalcMode === leafBillGetFeeType.billsPrice)
+            ) {
+                var names = new GC.Spread.Sheets.CellTypes.ComboBox();
+                names.items(projectObj.project.calcProgram.compiledTemplateNames);
+                return names;
+            }
+        },
+
+        // CSL, 2017-11-28
+        subType: function (node) {
+            if (node.data.type == rationType.volumePrice || node.data.type == rationType.gljRation){
+                let VPType = new GC.Spread.Sheets.CellTypes.ComboBox();
+
+                if (node.data.type == rationType.volumePrice)
+                    VPType.items(["人工","材料","机械","主材","设备"])
+                else if (node.data.type == rationType.gljRation)
+                    VPType.items(["材料","主材","设备"]);
+
+                return VPType;
+            };
+        },
      },
     getEvent: function (eventName) {
         let names = eventName.split('.');
@@ -87,22 +138,42 @@ let MainTreeCol = {
             return event;
         }
     },
-    getNumberFormatter: function (digit) {
-        switch (digit) {
-            case 1:
-                return '0.#';
-            case 2:
-                return '0.##';
-            case 3:
-                return '0.###';
-            case 4:
-                return '0.####';
-            case 5:
-                return '0.#####';
-            case 6:
-                return '0.######';
-            default:
-                return '0.##';
+    getNumberFormatter: function (digit, align) {    // CSL, 2017-11-30 扩展:小数点是否对齐。
+        if (align) {
+            switch (digit) {
+                case 1:
+                    return '0.0';
+                case 2:
+                    return '0.00';
+                case 3:
+                    return '0.000';
+                case 4:
+                    return '0.0000';
+                case 5:
+                    return '0.00000';
+                case 6:
+                    return '0.000000';
+                default:
+                    return '0.00';
+            };
+        }
+        else{
+            switch (digit) {
+                case 1:
+                    return '0.#';
+                case 2:
+                    return '0.##';
+                case 3:
+                    return '0.###';
+                case 4:
+                    return '0.####';
+                case 5:
+                    return '0.#####';
+                case 6:
+                    return '0.######';
+                default:
+                    return '0.##';
+            };
         }
     }
 };

+ 3 - 0
web/building_saas/main/js/views/project_info.js

@@ -32,6 +32,9 @@ var projectInfoObj = {
                 that.projectInfo = data;
                 //init decimal
                 setDecimal(decimalObj, data.property.decimal);
+                billsQuanDecimal.datas = data.property.billsQuantityDecimal || [billsDecimalView.angleDecimal];
+                basicInfoView.orgDatas = data.property.basicInformation ? basicInfoView.toViewDatas(data.property.basicInformation) : [];
+                projFeatureView.orgDatas = data.property.projectFeature ? projFeatureView.toViewDatas(data.property.projectFeature) : [];
                 $('#fullpath').html(that.getFullPathHtml(that.projectInfo));
             }
         });

+ 463 - 0
web/building_saas/main/js/views/project_property_basicInfo.js

@@ -0,0 +1,463 @@
+/**
+ * Created by Zhong on 2017/11/23.
+ */
+let basicInfoView = {
+    orgDatas: [],//for compare
+    datas: [],//just for view
+    workBook: null,
+    setting:{
+        header: [
+            {name: '属性', dataCode: 'dispName', width: 200, vAlign: 'center', hAlign: 'left'},
+            {name: '值', dataCode: 'value', width: 120, vAlign: 'center', hAlign: 'left'}
+        ],
+        options: {
+            tabStripVisible:  false,
+            allowCopyPasteExcelStyle : false,
+            allowExtendPasteRange: false,
+            allowUserDragDrop : false,
+            allowUserDragFill: false,
+            scrollbarMaxAlign : true
+        },
+        dateRows: [6, 18],
+        locked: {
+            rows: [0, 2, 19, 23, 27],
+            cols: [0]
+        }
+    },
+
+    renderSheetFuc: function (sheet, fuc) {
+        sheet.suspendPaint();
+        sheet.suspendEvent();
+        fuc();
+        sheet.resumePaint();
+        sheet.resumeEvent();
+    },
+
+    setOptions: function (workbook, opts) {
+        for(let opt in opts){
+            workbook.options[opt] = opts[opt];
+        }
+    },
+
+    setDatePicker: function (sheet, dateRows) {
+        let me = this;
+        this.renderSheetFuc(sheet, function () {
+            for(let i = 0, len = dateRows.length; i < len; i++){
+                sheet.getCell(dateRows[i], 1).cellType(me.getDatePickerCellType()).width(100).formatter('yyyy-mm-dd');
+            }
+        });
+    },
+
+    buildHeader: function (sheet, headers) {
+        let me = basicInfoView;
+        let fuc = function () {
+            sheet.options.clipBoardOptions = GC.Spread.Sheets.ClipboardPasteOptions.values;
+            sheet.setColumnCount(headers.length);
+            sheet.setRowHeight(0, 40, GC.Spread.Sheets.SheetArea.colHeader);
+            for(let i = 0, len = headers.length; i < len; i++){
+                sheet.setValue(0, i, headers[i].name, GC.Spread.Sheets.SheetArea.colHeader);
+                sheet.setColumnWidth(i, headers[i].width, GC.Spread.Sheets.SheetArea.colHeader);
+            }
+        };
+        me.renderSheetFuc(sheet, fuc);
+    },
+
+    buildSheet: function () {
+        if(!this.workBook){
+            this.workBook = new GC.Spread.Sheets.Workbook($('#basicInfoSpread')[0], {sheetCount: 1});
+            this.setOptions(this.workBook, this.setting.options);
+            this.buildHeader(this.workBook.getActiveSheet(), this.setting.header);
+            this.bindEvent(this.workBook);
+        }
+    },
+
+    bindEvent: function (workBook) {
+        const _events = GC.Spread.Sheets.Events;
+        let sheet = workBook.getActiveSheet();
+        sheet.bind(_events.EditStarting, this.onEditStarting);
+        sheet.bind(_events.EditEnded, this.onEditEnded);
+        sheet.bind(_events.ClipboardPasting, this.onClipboardPasting);
+        sheet.bind(_events.ClipboardPasted, this.onClipboardPasted);
+    },
+
+    showData(datas){
+        let me = basicInfoView;
+        let sheet = this.workBook.getActiveSheet();
+        let cols = this.setting.header;
+        let fuc = function () {
+            sheet.setRowCount(datas.length);
+            me.initTree(sheet, true, datas);
+            me.setDatePicker(sheet, me.setting.dateRows);
+            sheet.setFormatter(-1, 1, '@');
+            for(let col = 0, cLen = cols.length; col < cLen; col++){
+                sheet.getRange(-1, col, -1, 1).hAlign(GC.Spread.Sheets.HorizontalAlign[cols[col]['hAlign']]);
+                sheet.getRange(-1, col, -1, 1).hAlign(GC.Spread.Sheets.VerticalAlign[cols[col]['vAlign']]);
+                for(let row = 0, rLen = datas.length; row < rLen; row++){
+                    sheet.setValue(row, col, datas[row][cols[col]['dataCode']]);
+                }
+            }
+        };
+        this.renderSheetFuc(sheet, fuc);
+    },
+
+    onEditStarting: function (sender, args) {
+        let me = basicInfoView;
+        if(me.setting.locked.cols.indexOf(args.col) !== -1){
+            args.cancel = true;
+        }
+        //工程专业
+        if(args.col === 1 && me.setting.locked.rows.indexOf(args.row) !== -1){
+            args.cancel = true;
+        }
+    },
+
+    onEditEnded: function (sender, args) {
+        let me = basicInfoView;
+        let v =  args.editingText ? args.editingText.toString().trim() : '';
+        if(args.row < me.datas.length){
+            //date
+            if(me.setting.dateRows.indexOf(args.row) !== -1){
+                if(v.length > 0){
+                    v = formatDate(new Date(v), 'yyyy-MM-dd')
+                    v = me.filtDate(v);
+                    if(!v){
+                        alert('请输入正确的日期格式yyyy-mm-dd');
+                        args.sheet.setValue(args.row, args.col, me.datas[args.row].value ? me.datas[args.row].value : '');
+                        return;
+                    }
+                }
+            }
+            me.datas[args.row].value = v;
+        }
+    },
+
+    onClipboardPasting: function (sender, args) {
+        let me = basicInfoView;
+        if(me.setting.locked.cols.indexOf(args.cellRange.col) !== -1){
+            args.cancel = true;
+        }
+    },
+
+    onClipboardPasted: function (sender, args) {
+        let me = basicInfoView;
+        let items = sheetCommonObj.analyzePasteData(me.setting, args);
+        let recRows = [];
+        for(let i = 0, len = items.length; i < len; i++){
+            let row = i + args.cellRange.row;
+            if(me.setting.locked.rows.indexOf(row) !== -1){
+                recRows.push(row);
+            }
+            else if(me.setting.dateRows.indexOf(row) !== -1){
+                items[i].value = me.filtDate(items[i].value);
+                if(!me.isDef(items[i].value)){
+                    recRows.push(row);
+                }
+                else {
+                    me.datas[row].value = items[i].value;
+                }
+            }
+            else {
+                me.datas[row].value = items[i].value;
+            }
+        }
+        if(recRows.length > 0){
+            me.renderSheetFuc(args.sheet, function () {
+                for(let i = 0, len = recRows.length; i < len; i++){
+                    let staticV = me.datas[recRows[i]].value || '';
+                    args.sheet.setValue(recRows[i], args.cellRange.col, staticV);
+                }
+            })
+        }
+    },
+
+    isDef: function (v) {
+        return v !== undefined && v !== null;
+    },
+
+    filtDate: function (v) {
+        let re = /([0-9]{3}[1-9]|[0-9]{2}[1-9][0-9]{1}|[0-9]{1}[1-9][0-9]{2}|[1-9][0-9]{3})-(((0[13578]|1[02])-(0[1-9]|[12][0-9]|3[01]))|((0[469]|11)-(0[1-9]|[12][0-9]|30))|(02-(0[1-9]|[1][0-9]|2[0-8])))/
+        let reDate = re.exec(v);
+        let rst = reDate ? reDate[0] : null;
+        return rst;
+    },
+
+    copyObj: function(obj){
+        let newObj = {};
+        for(let attr in obj){
+            newObj[attr] = obj[attr];
+        }
+        return newObj;
+    },
+
+    initDatas: function (datas) {
+        this.datas = [];
+        for(let i = 0, len = datas.length; i < len; i++){
+            this.datas.push(this.copyObj(datas[i]));
+        }
+    },
+
+    //数据库读到的数据转换为展示的结构
+    toViewDatas: function (datas) {
+        let rst = [];
+        for(let i = 0, len = datas.length; i < len; i++){
+            let items = datas[i].items || null;
+            if(items){
+                rst.push(datas[i]);
+                for(let j = 0, jLen = items.length; j < jLen; j++){
+                    rst.push(items[j]);
+                }
+            }
+        }
+        return rst;
+    },
+
+    //展示的结构转换为入库的结构
+    toSaveDatas: function (datas) {
+        let rst = [];
+        let index = -1;
+        for(let i = 0, len = datas.length; i < len; i++){
+            let items = datas[i].items || null;
+            if(items){
+                delete datas[i].collapsed;
+                datas[i].items = [];
+                rst.push(datas[i]);
+                index++;
+            }
+            else {
+                rst[index]['items'].push(datas[i]);
+            }
+        }
+        return rst;
+    },
+
+    toUpdate: function (orgDatas, newDatas) {
+        if(orgDatas.length !== newDatas.length){
+            return true;
+        }
+        for(let i = 0, len = orgDatas.length; i < len; i++){
+            let orgObj = orgDatas[i], newObj = newDatas[i];
+            if(orgObj.value !== newObj.value){
+                return true;
+            }
+        }
+        return false;
+    },
+
+
+    a_updateInfo: function (datas) {
+        let me = this;
+        let url = '/pm/api/updateProjects',
+            updateData = {
+                updateType: 'update',
+                updateData: {ID: parseInt(scUrlUtil.GetQueryString('project')), 'property.basicInformation': datas}
+            },
+            postData = {
+                user_id: userID,
+                updateData: [updateData]
+            };
+        CommonAjax.post(url, postData, function (rstData) {
+            me.orgDatas = me.toViewDatas(datas);
+        });
+    },
+
+    initTree:function (sheet, init, datas) {
+        sheet.getRange(-1, 0, -1, 1).cellType(this.getTreeNodeCellType(datas));
+        for(let i =0, len = datas.length; i < len; i++){
+            if(datas[i].hasOwnProperty('items')){
+                let collapsed = false;
+                if(init){
+                    datas[i].collapsed=true;
+                    collapsed = true;
+                }else {
+                    collapsed = datas[i].collapsed == undefined ? true : datas[i].collapsed;
+                }
+                sheet.getRange(i+1, -1, datas[i].items.length, -1).visible(!collapsed);
+            }
+        }
+    },
+
+    getTreeNodeCellType:function (data) {
+        var ns = GC.Spread.Sheets;
+        var rectW = 10;
+        var rectH = 10;
+        var margin = 3;
+        function TreeNodeCellType() {
+        }
+
+        function drowRect(ctx,x,y,w,h) {
+            ctx.save();
+            ctx.strokeStyle = "gray";
+            ctx.translate(0.5,0.5);
+            ctx.beginPath();
+            var rectX = x+margin;
+            var rectY =  y+ Math.round(h/2)-rectH/2;
+            ctx.moveTo(rectX, rectY);
+            ctx.lineTo(rectX, rectY+rectH);
+            ctx.lineTo(rectX+rectW, rectY+rectH);
+            ctx.lineTo(rectX+rectW, rectY);
+            ctx.lineTo(rectX, rectY);
+            ctx.moveTo(rectX+rectW, y+Math.round(h/2));
+            ctx.lineTo(rectX+rectW+5, y+Math.round(h/2));
+            ctx.stroke();
+            ctx.restore();
+        }
+
+        function drowSymbol(ctx,x,y,w,h,collapsed) {
+            ctx.save();
+            ctx.strokeStyle = "#000000";
+            ctx.translate(0.5, 0.5);
+            ctx.beginPath();
+            ctx.moveTo(x+margin+2, y+Math.round(h/2));
+            ctx.lineTo(x+margin+8, y+Math.round(h/2));
+            var rectY =  y+ Math.round(h/2)-rectH/2;
+            if(collapsed){
+                ctx.moveTo(x+margin+rectW/2,rectY+2);
+                ctx.lineTo(x+margin+rectW/2,rectY+2+6);
+            }
+            ctx.stroke();
+            ctx.restore();
+        }
+
+        function drowSubItem(ctx,x,y,w,h,offset,nextItem) {
+            offset+=6;
+            ctx.save();
+            ctx.strokeStyle = "gray";
+            ctx.translate(0.5, 0.5);
+            ctx.beginPath();
+            ctx.moveTo(x+offset, y);
+            ctx.lineTo(x+offset, y+Math.round(h/2));
+            offset+=9;
+            ctx.lineTo(x+offset, y+Math.round(h/2));
+            if(nextItem&&!nextItem.hasOwnProperty('items')){
+                ctx.moveTo(x+offset-9, y+Math.round(h/2));
+                ctx.lineTo(x+offset-9, y+h);
+            }
+            ctx.stroke();
+            ctx.restore();
+            return offset;
+        }
+
+        TreeNodeCellType.prototype = new ns.CellTypes.Text();
+        TreeNodeCellType.prototype.paint = function (ctx, value, x, y, w, h, style, options) {
+            if(value!=null){
+                var offset = margin+rectW+6;
+                var recode = data[options.row];
+                if(recode&&recode.hasOwnProperty('items')){
+                    drowRect(ctx,x,y,w,h);
+                    var collapsed = recode.collapsed==undefined?true:recode.collapsed;//options.sheet.getTag(options.row,options.col);
+                    drowSymbol(ctx,x,y,w,h,collapsed);
+                }else if(recode&&!recode.hasOwnProperty('items')){
+                    offset= drowSubItem(ctx,x,y,w,h,offset,data[options.row+1]);
+                    offset+=1;
+                }
+                ctx.fillText(value,x+offset+ctx.measureText(value).width,y+h-5);
+            }
+        };
+        // override getHitInfo to allow cell type get mouse messages
+        TreeNodeCellType.prototype.getHitInfo = function (x, y, cellStyle, cellRect, context) {
+            return {
+                x: x,
+                y: y,
+                row: context.row,
+                col: context.col,
+                cellStyle: cellStyle,
+                cellRect: cellRect,
+                sheetArea: context.sheetArea
+            };
+        }
+        TreeNodeCellType.prototype.processMouseDown = function (hitinfo) {
+            var recode = data[hitinfo.row];
+            if(recode&&recode.hasOwnProperty('items')){
+                var hoffset= hitinfo.cellRect.x+3;
+                if (hitinfo.x > hoffset && hitinfo.x < hoffset + 10){
+                    var collapsed = recode.collapsed==undefined?true:recode.collapsed;
+                    collapsed = !collapsed
+                    recode.collapsed=collapsed;
+                    //hitinfo.sheet.setTag(hitinfo.row,hitinfo.col,collapsed);
+                    hitinfo.sheet.getRange(hitinfo.row+1, -1, recode.items.length, -1).visible(!collapsed);
+                    hitinfo.sheet.invalidateLayout();
+                    hitinfo.sheet.repaint();
+                }
+            }
+        };
+        return new TreeNodeCellType()
+    },
+
+    getDatePickerCellType: function () {
+        let ns = GC.Spread.Sheets;
+
+        function DatePickerCellType() {
+        }
+        DatePickerCellType.prototype = new GC.Spread.Sheets.CellTypes.Base();
+
+        DatePickerCellType.prototype.createEditorElement = function (context) {
+            //Create input presenter.
+            return document.createElement("input");
+        };
+        DatePickerCellType.prototype.activateEditor = function (editorContext, cellStyle, cellRect, context) {
+            //Initialize input editor.
+            if (editorContext) {
+                $editor = $(editorContext);
+                //DatePickerCellType.prototype.activateEditor.apply(this, arguments);
+                $editor.datepicker({dateFormat: 'yy-mm-dd'});
+                $editor.css("position", "absolute");
+                $editor.attr("gcUIElement", "gcEditingInput");
+                $(".ui-datepicker").attr("gcUIElement", "gcEditingInput");
+            }
+        }
+        DatePickerCellType.prototype.deactivateEditor = function (editorContext, context) {
+            //Remove input editor when end editor status.
+            if (editorContext) {
+                var element = editorContext;
+                $(element).datepicker("hide");
+                $(element).datepicker("destroy");
+            }
+           // DatePickerCellType.prototype.deactivateEditor.apply(this, arguments)
+        };
+        DatePickerCellType.prototype.setEditorValue = function (editor, value, context) {
+            //Sync value from Cell value to editor value.
+            $(editor).datepicker("setDate", value);
+        };
+        DatePickerCellType.prototype.getEditorValue = function (editor, context) {
+            //Sync value from editor value to cell value.
+            return $(editor).datepicker("getDate");
+        };
+        DatePickerCellType.prototype.updateEditor = function (editorContext, cellStyle, cellRect, context) {
+            if (editorContext) {
+                $editor = $(editorContext);
+                $editor.css("width", cellRect.width - 1);
+                $editor.css("height", cellRect.height - 3);
+            }
+        };
+        return new DatePickerCellType();
+    }
+
+};
+
+$(document).ready(function () {
+    $('#poj-set').on('shown.bs.modal', function (e) {
+        //init Spread
+        basicInfoView.initDatas(basicInfoView.orgDatas);
+        basicInfoView.buildSheet();
+        basicInfoView.showData(basicInfoView.datas);
+    });
+
+    $('#poj-set').on('hidden.bs.modal', function (e) {
+        //destroy Spread
+        if(basicInfoView.workBook){
+            basicInfoView.workBook.destroy();
+            basicInfoView.workBook = null;
+        }
+        basicInfoView.datas = [];
+    });
+
+    $('#tab_poj-settings-basicInfo').on('shown.bs.tab', function () {
+        basicInfoView.workBook.refresh();
+    });
+
+    $('#property_ok').bind('click', function () {
+        if(basicInfoView.toUpdate(basicInfoView.orgDatas, basicInfoView.datas)){
+            basicInfoView.a_updateInfo(basicInfoView.toSaveDatas(basicInfoView.datas));
+        }
+    });
+});

+ 348 - 0
web/building_saas/main/js/views/project_property_bills_quantity_decimal.js

@@ -0,0 +1,348 @@
+/**
+ * Created by Zhong on 2017/11/15.
+ */
+/*
+* 清单工程量精度
+* */
+let billsQuanDecimal = {
+    datas: [],
+    //根据单位获取精度, 需要用清单工程量精度的调用此接口
+    decimal: function (unit) {
+        for(let i = 0, len = this.datas.length; i < len; i++){
+            if(unit === this.datas[i].unit){
+                return this.datas[i].decimal;
+            }
+        }
+        return this.datas[0].decimal;
+    }
+};
+
+//点了确定才更新数据
+let billsDecimalView = {
+    default: {min: 0, max: 4, decimal: 3},//以防后面修改,小数数位的取值范围, default.decimal为新增时默认的小数位
+    angleDecimal: {unit: '其他未列单位', decimal: 2},//防止出现工程没有初始默认的清单工程量精度模板(正常不会出现)
+    cache: [],//temp
+    workBook: null,
+    setting:{
+        header: [
+            {name: '计量单位', dataCode: 'unit', width: 120, vAlign: 'center', hAlign: 'left'},
+            {name: '小数位数', dataCode: 'decimal', width: 80, vAlign: 'center', hAlign: 'center'}
+        ],
+        options: {
+            tabStripVisible:  false,
+            allowCopyPasteExcelStyle : false,
+            allowUserDragDrop : false,
+            allowUserDragFill: false,
+            scrollbarMaxAlign : true
+        }
+    },
+
+    renderSheetFuc: function (sheet, fuc) {
+        sheet.suspendPaint();
+        sheet.suspendEvent();
+        fuc();
+        sheet.resumePaint();
+        sheet.resumeEvent();
+    },
+
+    setOptions: function (workbook, opts) {
+        for(let opt in opts){
+            workbook.options[opt] = opts[opt];
+        }
+    },
+
+    getComboItems: function (min, max) {
+        let rst = [];
+        while (min <= max){
+            rst.push(min);
+            min ++;
+        }
+        return rst;
+    },
+    
+    setComboBox: function (sheet, items) {
+        let combo = new GC.Spread.Sheets.CellTypes.ComboBox();
+        combo.items(items);
+        combo.editable(true);
+        sheet.getRange(-1, 1, -1, 1).cellType(combo);
+    },
+
+    buildHeader: function (sheet, headers) {
+        let me = billsDecimalView;
+        let fuc = function () {
+            sheet.setColumnCount(headers.length);
+            sheet.setRowHeight(0, 40, GC.Spread.Sheets.SheetArea.colHeader);
+            me.setComboBox(sheet, me.getComboItems(me.default.min, me.default.max));
+            for(let i = 0, len = headers.length; i < len; i++){
+                sheet.setValue(0, i, headers[i].name, GC.Spread.Sheets.SheetArea.colHeader);
+                sheet.setColumnWidth(i, headers[i].width, GC.Spread.Sheets.SheetArea.colHeader);
+            }
+        };
+        me.renderSheetFuc(sheet, fuc);
+    },
+
+    buildSheet: function () {
+        if(!this.workBook){
+            this.workBook = new GC.Spread.Sheets.Workbook($('#billsQuanDecimal')[0], {sheetCount: 1});
+            this.setOptions(this.workBook, this.setting.options);
+            this.buildHeader(this.workBook.getActiveSheet(), this.setting.header);
+            this.bindEvent(this.workBook);
+            this.onContextmenuOpr();
+        }
+    },
+
+    bindEvent: function (workBook) {
+        const _events = GC.Spread.Sheets.Events;
+        let sheet = workBook.getActiveSheet();
+        sheet.bind(_events.EditStarting, this.onEditStarting);
+        sheet.bind(_events.EditEnded, this.onEditEnded);
+        sheet.bind(_events.ClipboardPasting, this.onClipboardPasting);
+        sheet.bind(_events.ClipboardPasted, this.onClipboardPasted);
+    },
+
+    showData(datas){
+        let sheet = this.workBook.getActiveSheet();
+        let cols = this.setting.header;
+        let fuc = function () {
+            sheet.setRowCount(datas.length);
+            for(let col = 0, cLen = cols.length; col < cLen; col++){
+                sheet.getRange(-1, col, -1, 1).hAlign(GC.Spread.Sheets.HorizontalAlign[cols[col]['hAlign']]);
+                sheet.getRange(-1, col, -1, 1).hAlign(GC.Spread.Sheets.VerticalAlign[cols[col]['vAlign']]);
+                for(let row = 0, rLen = datas.length; row < rLen; row++){
+                    sheet.setValue(row, col, datas[row][cols[col]['dataCode']]);
+                }
+            }
+        };
+        this.renderSheetFuc(sheet, fuc);
+    },
+
+    onEditStarting: function (sender, args) {
+        if(args.col === 0 && args.row === 0){//其他未列单位不可编辑
+            args.cancel = true;
+        }
+    },
+
+    onEditEnded: function (sender, args) {
+        let me = billsDecimalView;
+        let v =  args.editingText ? args.editingText.toString().trim() : '';
+        if(v.length === 0){
+            return;
+        }
+        if(args.col === 0){
+            if(me.hasUnit(me.cache, v)){
+                alert('已存在此计量单位');
+                args.sheet.setValue(args.row, args.col, me.isDef(me.cache[args.row]) ? me.cache[args.row].unit : '');
+            }
+            else {
+                me.cache[args.row].unit = v;
+                if(!me.isValidDecimal(me.cache[args.row].decimal)){
+                    me.cache[args.row].decimal = me.default.decimal;
+                    args.sheet.setValue(args.row, 1, me.default.decimal);
+                }
+            }
+        }
+        else if(args.col === 1){
+            if(!me.isValidDecimal(v)){
+                alert('小数位数只能是'+ me.default.min + '-' + me.default.max + '的整数');
+                args.sheet.setValue(args.row, args.col, me.isDef(me.cache[args.row]) ? me.cache[args.row].decimal : '');
+            }
+            else {
+                me.cache[args.row].decimal = v;
+            }
+        }
+    },
+
+    onClipboardPasting: function (sender, args) {
+        if(args.cellRange.row === 0 && args.cellRange.col === 0){
+            args.cancel = true;
+            alert('不可更改其他未列计量单位');
+        }
+    },
+
+    onClipboardPasted: function (sender, args) {
+        let me = billsDecimalView;
+        let items = sheetCommonObj.analyzePasteData(me.setting, args);
+        for(let i = 0, len = items.length; i < len; i++){
+            let row = args.cellRange.row + i;
+            if(me.isDef(me.cache[row])){
+                if(me.isDef(items[i].unit) && !me.hasUnit(me.cache, items[i].unit)){
+                    me.cache[row].unit = items[i].unit;
+                }
+                if(me.isValidDecimal(items[i].decimal)){
+                    me.cache[row].decimal = items[i].decimal;
+                }
+            }
+        }
+        me.showData(me.cache);
+    },
+
+    onContextmenuOpr: function () {//右键菜单
+        let me = billsDecimalView;
+        $.contextMenu({
+            selector: '#billsQuanDecimal',
+            build: function($triggerElement, e){
+                //控制允许右键菜单在哪个位置出现
+                let target = SheetDataHelper.safeRightClickSelection($triggerElement, e, me.workBook);
+                let sheet = me.workBook.getSheet(0);
+                if(target.hitTestType === 3 && typeof target.row !== 'undefined'){//在表格内
+                    let delDis = false;
+                    let insertDis = false;
+                    //控制按钮是否可用
+                    sheet.setActiveCell(target.row, target.col);
+                    if(target.row === 0){//不可删除其他未列单位行
+                        delDis = true;
+                    }
+                    if(!me.isDef(me.cache) || target.row >= me.cache.length){//有数据才能删除
+                        delDis = true;
+                    }
+                    return {
+                        callback: function(){},
+                        items: {
+                            "insert": {name: "添加", disabled: insertDis, icon: "fa-sign-in", callback: function (key, opt) {
+                                //插入空行
+                                me.addRow(me.cache, sheet, target.row);
+                            }},
+                            "delete": {name: "删除", disabled: delDis, icon: "fa-remove", callback: function (key, opt) {
+                               me.deleteRow(me.cache, sheet, target.row);
+                            }}
+                        }
+                    };
+                }
+                else{
+                    return false;
+                }
+            }
+        });
+    },
+    //新增一空白行
+    addRow: function (all, sheet, row) {
+        let func = function () {
+            let data = Object.create(null);
+            data.unit = '';
+            data.decimal = '';
+            //新增空行
+            if(row === 0){//保证其他未列单位在第一行
+                row = 1;
+            }
+            //新增空白数据
+            all.splice(row, 0, data);
+            sheet.addRows(row, 1);
+        };
+        this.renderSheetFuc(sheet, func);
+    },
+
+    deleteRow: function (all, sheet, row) {
+        let func = function () {
+            all.splice(row, 1);
+            sheet.deleteRows(row, 1);
+        };
+        this.renderSheetFuc(sheet, func);
+    },
+
+    isDef: function (v) {
+        return v !== undefined && v !== null;
+    },
+
+    isData: function (v) {
+        return this.isDef(v) && v.toString().trim().length > 0;
+    },
+
+    isValidDecimal: function (v) {
+        return this.isData(v) && !isNaN(v) && parseInt(v) % 1 === 0 && parseInt(v) >= this.default.min && parseInt(v) <= this.default.max;
+    },
+
+    hasUnit: function (all, v) {
+        for(let i = 0, len = all.length; i < len; i++){
+            if(all[i].unit === v){
+                return true;
+            }
+        }
+        return false;
+    },
+
+    rowHasData: function (sheet, row) {
+        let v = sheet.getValue(row, 0);
+        if(v && v.toString().trim().length > 0) {
+            return true;
+        }
+        return false;
+    },
+
+    toUpdate: function (orgV, newV) {//org: billsDecimal.datas, newV: toBillsDecimalDatas(cache)
+        for(let i = 0, len = orgV.length; i < len; i++){
+            if(!this.isDef(newV[i])){
+                return true;
+            }
+            else {
+                if(orgV[i].unit !== newV[i].unit || orgV[i].decimal !== newV[i].decimal){
+                    return true;
+                }
+            }
+        }
+        return false;
+    },
+
+    //将tempDatas提取出新的请单工程量精度数据
+    toBillsDecimalDatas: function (datas) {
+        let rst = [];
+        for(let i = 0, len = datas.length; i < len; i++){
+            if(this.isData(datas[i].unit) && this.isValidDecimal(datas[i].decimal)){
+                datas[i].decimal = parseInt(datas[i].decimal);
+                rst.push(datas[i]);
+            }
+        }
+        return rst;
+    },
+    
+    a_update: function (datas) {
+        let url = '/pm/api/updateProjects';
+        let updateData = {
+            updateType: 'update',
+            updateData: {
+                ID: parseInt(scUrlUtil.GetQueryString('project')),
+                'property.billsQuantityDecimal': datas
+            }
+        };
+        let postData = {
+            user_id: userID,
+            updateData: [updateData]
+        };
+        CommonAjax.post(url, postData, function () {
+            billsQuanDecimal.datas = datas;
+        });
+    }
+};
+
+$(document).ready(function () {
+    $('#poj-set').on('shown.bs.modal', function (e) {
+        //init Spread
+        if(billsDecimalView.isDef(billsQuanDecimal.datas)){
+            billsDecimalView.cache = billsDecimalView.cache.concat(billsQuanDecimal.datas);
+        }
+        if(billsDecimalView.cache.length === 0){
+            billsDecimalView.cache.push(billsDecimalView.angleDecimal);
+        }
+        billsDecimalView.buildSheet();
+        billsDecimalView.showData(billsDecimalView.cache);
+    });
+
+    $('#poj-set').on('hidden.bs.modal', function (e) {
+        //destroy Spread
+        if(billsDecimalView.workBook){
+            billsDecimalView.workBook.destroy();
+            billsDecimalView.workBook = null;
+        }
+        billsDecimalView.cache = [];
+    });
+
+    $('#tab_poj-settings-bqDecimal').on('shown.bs.tab', function () {
+        billsDecimalView.workBook.refresh();
+    });
+
+    $('#property_ok').bind('click', function () {
+        let newBillsDecimalDatas = billsDecimalView.toBillsDecimalDatas(billsDecimalView.cache);
+        if(billsDecimalView.toUpdate(billsQuanDecimal.datas, newBillsDecimalDatas)){
+            billsDecimalView.a_update(newBillsDecimalDatas);
+        }
+    });
+});

+ 42 - 7
web/building_saas/main/js/views/project_property_decimal_view.js

@@ -5,7 +5,7 @@
 let defaultDecimal = {
     min: 0,
     max: 6,
-    _def: {//定义往这加, editable: 开放给用户编辑的(入库),定义editable为false的字段时,只需在此定义便可,定义editable为true的字段时,要在后端project_model.js defaultDecimal中添加定义,html添加input
+    _def: {//editable: 开放给用户编辑的
         bills: {editable: true, data: {unitPrice: 2, totalPrice: 2}},
         ration: {editable: true, data: {quantity: 3, unitPrice: 2, totalPrice: 2}},
         glj: {editable: true, data: {quantity: 3, unitPrice: 2}},
@@ -17,6 +17,41 @@ let defaultDecimal = {
 
 let decimalObj = Object.create(null);
 
+decimalObj.decimal = function (field, node) {
+    if(isDef(field)){
+        if(field === 'feeRate'){
+            return this[field];
+        }
+        else if (field.sameText('unitFee')) field = 'unitPrice'
+        else if (field.sameText('totalFee')) field = 'totalPrice';
+
+        if(isDef(node)){
+            if(node.sourceType === projectObj.project.Bills.getSourceType()){
+                if(field === 'quantity'){
+                    return billsQuanDecimal.decimal(node.data.unit);
+                }
+                else {
+                    returnV(this['bills'][field], this.process);
+                }
+            }
+            else if(node.sourceType === projectObj.project.Ration.getSourceType()){
+                returnV(this['ration'][field], this.process);
+            }
+            else if(node.sourceType === projectObj.project.GLJ.getSourceType()){
+                returnV(this['glj'][field], this.process);
+            }
+        }
+    }
+    return this.process;
+};
+
+function returnV(v, r){
+    if(isDef(v)){
+        return v;
+    }
+    return r;
+}
+
 function isUndef(v) {
     return v === undefined || v === null;
 }
@@ -70,14 +105,9 @@ function toUpdateDecimal(orgV, newV){
 
 function setDecimal(_digits, data){
     if(isDef(data)){
-        for(let attr in data){//设置入库的数据
+        for(let attr in data){
             _digits[attr] = data[attr] || defaultDecimal['_def'][attr]['data'];
         }
-        for(let attr in defaultDecimal['_def']){//设置不入库的数据
-            if(!defaultDecimal['_def'][attr]['editable']){
-                _digits[attr] = defaultDecimal['_def'][attr]['data'];
-            }
-        }
     }
     else {
         for(let attr in defaultDecimal['_def']){
@@ -114,6 +144,11 @@ function m_getDecimalData(inputs){
             rst[attrs[0]] = parseInt($(inputs[i]).val());
         }
     }
+    for(let attr in defaultDecimal['_def']){
+        if(!defaultDecimal['_def'][attr]['editable']){
+            rst[attr] = defaultDecimal['_def'][attr]['data'];
+        }
+    }
     return rst;
 }
 

+ 409 - 0
web/building_saas/main/js/views/project_property_projFeature.js

@@ -0,0 +1,409 @@
+/**
+ * Created by Zhong on 2017/11/24.
+ */
+let projFeatureView = {
+    orgDatas: [],//for compare
+    datas: [],//just for view
+    workBook: null,
+    firstData: {dispName: '工程特征', key: 'projFeature', items: []},//for show
+    setting:{
+        header: [
+            {name: '属性', dataCode: 'dispName', width: 200, vAlign: 'center', hAlign: 'left'},
+            {name: '值', dataCode: 'value', width: 120, vAlign: 'center', hAlign: 'left'}
+        ],
+        options: {
+            tabStripVisible:  false,
+            allowCopyPasteExcelStyle : false,
+            allowExtendPasteRange: false,
+            allowUserDragDrop : false,
+            allowUserDragFill: false,
+            scrollbarMaxAlign : true
+        },
+        combos: [
+            {row: 1, key: 'projType', items: ['住宅', '公共建筑', '厂房', '办公楼']},
+            {row: 2, key: 'structureType', items: ['排架结构', '框架结构', '现浇、框架结构', '预制、砖混结构', '外砖内模', '内浇外挂', '钢结构']},
+            {row: 3, key: 'baseType', items: ['带基', '框排架柱距6m以内', '框排架柱距6m以外', '满基筏式', '满基板式', '满基箱式', '独立基础']},
+            {row: 4, key: 'buildingFeature', items: ['点式', '凹式', '凸式', 'Y式', '其他']}
+        ],
+        numRows: [5, 6, 7, 8, 9, 10, 11, 12, 13, 14],
+        dateRows: [],
+        locked: {
+            rows: [0],
+            cols: [0]
+        }
+    },
+
+    renderSheetFuc: function (sheet, fuc) {
+        sheet.suspendPaint();
+        sheet.suspendEvent();
+        fuc();
+        sheet.resumePaint();
+        sheet.resumeEvent();
+    },
+
+    setOptions: function (workbook, opts) {
+        for(let opt in opts){
+            workbook.options[opt] = opts[opt];
+        }
+    },
+
+    setCombo: function (sheet, row, items) {
+        let combo = new GC.Spread.Sheets.CellTypes.ComboBox();
+        combo.items(items);
+        combo.editable(false);
+        sheet.getCell(row, 1).cellType(combo);
+    },
+
+    getComboItemsByRow: function (row) {
+        for(let i = 0, len = this.setting.combos.length; i < len; i++){
+            if(this.setting.combos[i].row === row){
+                return this.setting.combos[i].items;
+            }
+        }
+        return null;
+    },
+
+    buildHeader: function (sheet, headers) {
+        let me = projFeatureView;
+        let fuc = function () {
+            sheet.options.clipBoardOptions = GC.Spread.Sheets.ClipboardPasteOptions.values;
+            sheet.setColumnCount(headers.length);
+            sheet.setRowHeight(0, 40, GC.Spread.Sheets.SheetArea.colHeader);
+            for(let i = 0, len = headers.length; i < len; i++){
+                sheet.setValue(0, i, headers[i].name, GC.Spread.Sheets.SheetArea.colHeader);
+                sheet.setColumnWidth(i, headers[i].width, GC.Spread.Sheets.SheetArea.colHeader);
+            }
+        };
+        me.renderSheetFuc(sheet, fuc);
+    },
+
+    buildSheet: function () {
+        if(!this.workBook){
+            this.workBook = new GC.Spread.Sheets.Workbook($('#projFeatureSpread')[0], {sheetCount: 1});
+            this.setOptions(this.workBook, this.setting.options);
+            this.buildHeader(this.workBook.getActiveSheet(), this.setting.header);
+            this.bindEvent(this.workBook);
+        }
+    },
+
+    bindEvent: function (workBook) {
+        const _events = GC.Spread.Sheets.Events;
+        let sheet = workBook.getActiveSheet();
+        sheet.bind(_events.EditStarting, this.onEditStarting);
+        sheet.bind(_events.EditEnded, this.onEditEnded);
+        sheet.bind(_events.ClipboardPasting, this.onClipboardPasting);
+        sheet.bind(_events.ClipboardPasted, this.onClipboardPasted);
+    },
+
+    showData(datas){
+        let me = projFeatureView;
+        let sheet = this.workBook.getActiveSheet();
+        let cols = this.setting.header;
+        let fuc = function () {
+            sheet.setRowCount(datas.length);
+            me.initTree(sheet, true, datas);
+            sheet.setFormatter(-1, 1, '@');
+            //setCombo
+            for(let i = 0, len = me.setting.combos.length; i < len; i++){
+                me.setCombo(sheet, me.setting.combos[i].row, me.setting.combos[i].items);
+            }
+            for(let col = 0, cLen = cols.length; col < cLen; col++){
+                sheet.getRange(-1, col, -1, 1).hAlign(GC.Spread.Sheets.HorizontalAlign[cols[col]['hAlign']]);
+                sheet.getRange(-1, col, -1, 1).hAlign(GC.Spread.Sheets.VerticalAlign[cols[col]['vAlign']]);
+                for(let row = 0, rLen = datas.length; row < rLen; row++){
+                    sheet.setValue(row, col, datas[row][cols[col]['dataCode']]);
+                }
+            }
+        };
+        this.renderSheetFuc(sheet, fuc);
+    },
+
+    onEditStarting: function (sender, args) {
+        let me = projFeatureView;
+        if(me.setting.locked.cols.indexOf(args.col) !== -1){
+            args.cancel = true;
+        }
+        if(args.col === 1 && me.setting.locked.rows.indexOf(args.row) !== -1){
+            args.cancel = true;
+        }
+    },
+
+    onEditEnded: function (sender, args) {
+        let me = projFeatureView;
+        let v =  args.editingText ? args.editingText.toString().trim() : '';
+        if(args.row < me.datas.length){
+            if(me.setting.numRows.indexOf(args.row) !== -1){//控制数值
+                if(!me.isNum(v)){
+                    alert('只能输入数值');
+                    args.sheet.setValue(args.row, args.col, me.datas[args.row].value ? me.datas[args.row].value : '');
+                }
+            }
+            me.datas[args.row].value = v;
+        }
+    },
+
+    onClipboardPasting: function (sender, args) {
+        let me = projFeatureView;
+        if(me.setting.locked.cols.indexOf(args.cellRange.col) !== -1){
+            args.cancel = true;
+        }
+    },
+
+    onClipboardPasted: function (sender, args) {
+        let me = projFeatureView;
+        let items = sheetCommonObj.analyzePasteData(me.setting, args);
+        let recRows = [];
+        console.log(args);
+        console.log(items);
+        console.log('enter');
+        if(items.length === 0){
+            return;
+        }
+        for(let i = 0, len = items.length; i < len; i++){
+            let row = i + args.cellRange.row;
+            let comboItems = me.getComboItemsByRow(row);
+            if(me.setting.locked.rows.indexOf(row) !== -1){
+                recRows.push(row);
+            }
+            //粘贴下拉框数据过滤
+            else if(comboItems && comboItems.indexOf(items[i].value) !== -1){
+                recRows.push(row);
+            }
+            else if(me.setting.numRows.indexOf(row) !== -1 && !me.isNum(items[i].value)){
+                recRows.push(row);
+            }
+            else {
+                me.datas[row].value = items[i].value;
+            }
+        }
+        if(recRows.length > 0){
+            me.renderSheetFuc(args.sheet, function () {
+                for(let i = 0, len = recRows.length; i < len; i++){
+                    let staticV = me.datas[recRows[i]].value || '';
+                    args.sheet.setValue(recRows[i], args.cellRange.col, staticV);
+                }
+            })
+        }
+    },
+
+    isDef: function (v) {
+        return v !== undefined && v !== null;
+    },
+    isNum: function(v){
+        return this.isDef(v) && !isNaN(v);
+    },
+
+    copyObj: function(obj){
+        let newObj = {};
+        for(let attr in obj){
+            newObj[attr] = obj[attr];
+        }
+        return newObj;
+    },
+
+    initDatas: function (datas) {
+        this.datas = [];
+        for(let i = 0, len = datas.length; i < len; i++){
+            this.datas.push(this.copyObj(datas[i]));
+        }
+    },
+
+//数据库读到的数据转换为展示的结构
+    toViewDatas: function (datas) {
+        let rst = [];
+        this.firstData.items = datas;
+        rst.push(this.firstData);
+        rst = rst.concat(datas);
+       return rst;
+    },
+
+//展示的结构转换为入库的结构
+    toSaveDatas: function (datas) {
+        let rst = [].concat(datas);
+        rst.splice(0, 1);
+        return rst;
+    },
+
+    toUpdate: function (orgDatas, newDatas) {
+        if(orgDatas.length !== newDatas.length){
+            return true;
+        }
+        for(let i = 0, len = orgDatas.length; i < len; i++){
+            let orgObj = orgDatas[i], newObj = newDatas[i];
+            if(orgObj.value !== newObj.value){
+                return true;
+            }
+        }
+        return false;
+    },
+
+
+    a_updateInfo: function (datas) {
+        let me = this;
+        let url = '/pm/api/updateProjects',
+            updateData = {
+                updateType: 'update',
+                updateData: {ID: parseInt(scUrlUtil.GetQueryString('project')), 'property.projectFeature': datas}
+            },
+            postData = {
+                user_id: userID,
+                updateData: [updateData]
+            };
+        CommonAjax.post(url, postData, function (rstData) {
+            me.orgDatas = me.toViewDatas(datas);
+        });
+    },
+
+    initTree:function (sheet, init, datas) {
+        sheet.getRange(-1, 0, -1, 1).cellType(this.getTreeNodeCellType(datas));
+        for(let i =0, len = datas.length; i < len; i++){
+            if(datas[i].hasOwnProperty('items')){
+                let collapsed = false;
+                if(init){
+                    datas[i].collapsed=true;
+                    collapsed = true;
+                }else {
+                    collapsed = datas[i].collapsed == undefined ? true : datas[i].collapsed;
+                }
+                sheet.getRange(i+1, -1, datas[i].items.length, -1).visible(!collapsed);
+            }
+        }
+    },
+
+    getTreeNodeCellType:function (data) {
+        var ns = GC.Spread.Sheets;
+        var rectW = 10;
+        var rectH = 10;
+        var margin = 3;
+        function TreeNodeCellType() {
+        }
+
+        function drowRect(ctx,x,y,w,h) {
+            ctx.save();
+            ctx.strokeStyle = "gray";
+            ctx.translate(0.5,0.5);
+            ctx.beginPath();
+            var rectX = x+margin;
+            var rectY =  y+ Math.round(h/2)-rectH/2;
+            ctx.moveTo(rectX, rectY);
+            ctx.lineTo(rectX, rectY+rectH);
+            ctx.lineTo(rectX+rectW, rectY+rectH);
+            ctx.lineTo(rectX+rectW, rectY);
+            ctx.lineTo(rectX, rectY);
+            ctx.moveTo(rectX+rectW, y+Math.round(h/2));
+            ctx.lineTo(rectX+rectW+5, y+Math.round(h/2));
+            ctx.stroke();
+            ctx.restore();
+        }
+
+        function drowSymbol(ctx,x,y,w,h,collapsed) {
+            ctx.save();
+            ctx.strokeStyle = "#000000";
+            ctx.translate(0.5, 0.5);
+            ctx.beginPath();
+            ctx.moveTo(x+margin+2, y+Math.round(h/2));
+            ctx.lineTo(x+margin+8, y+Math.round(h/2));
+            var rectY =  y+ Math.round(h/2)-rectH/2;
+            if(collapsed){
+                ctx.moveTo(x+margin+rectW/2,rectY+2);
+                ctx.lineTo(x+margin+rectW/2,rectY+2+6);
+            }
+            ctx.stroke();
+            ctx.restore();
+        }
+
+        function drowSubItem(ctx,x,y,w,h,offset,nextItem) {
+            offset+=6;
+            ctx.save();
+            ctx.strokeStyle = "gray";
+            ctx.translate(0.5, 0.5);
+            ctx.beginPath();
+            ctx.moveTo(x+offset, y);
+            ctx.lineTo(x+offset, y+Math.round(h/2));
+            offset+=9;
+            ctx.lineTo(x+offset, y+Math.round(h/2));
+            if(nextItem&&!nextItem.hasOwnProperty('items')){
+                ctx.moveTo(x+offset-9, y+Math.round(h/2));
+                ctx.lineTo(x+offset-9, y+h);
+            }
+            ctx.stroke();
+            ctx.restore();
+            return offset;
+        }
+
+        TreeNodeCellType.prototype = new ns.CellTypes.Text();
+        TreeNodeCellType.prototype.paint = function (ctx, value, x, y, w, h, style, options) {
+            if(value!=null){
+                var offset = margin+rectW+6;
+                var recode = data[options.row];
+                if(recode&&recode.hasOwnProperty('items')){
+                    drowRect(ctx,x,y,w,h);
+                    var collapsed = recode.collapsed==undefined?true:recode.collapsed;//options.sheet.getTag(options.row,options.col);
+                    drowSymbol(ctx,x,y,w,h,collapsed);
+                }else if(recode&&!recode.hasOwnProperty('items')){
+                    offset= drowSubItem(ctx,x,y,w,h,offset,data[options.row+1]);
+                    offset+=1;
+                }
+                ctx.fillText(value,x+offset+ctx.measureText(value).width,y+h-5);
+            }
+        };
+        // override getHitInfo to allow cell type get mouse messages
+        TreeNodeCellType.prototype.getHitInfo = function (x, y, cellStyle, cellRect, context) {
+            return {
+                x: x,
+                y: y,
+                row: context.row,
+                col: context.col,
+                cellStyle: cellStyle,
+                cellRect: cellRect,
+                sheetArea: context.sheetArea
+            };
+        }
+        TreeNodeCellType.prototype.processMouseDown = function (hitinfo) {
+            var recode = data[hitinfo.row];
+            if(recode&&recode.hasOwnProperty('items')){
+                var hoffset= hitinfo.cellRect.x+3;
+                if (hitinfo.x > hoffset && hitinfo.x < hoffset + 10){
+                    var collapsed = recode.collapsed==undefined?true:recode.collapsed;
+                    collapsed = !collapsed
+                    recode.collapsed=collapsed;
+                    //hitinfo.sheet.setTag(hitinfo.row,hitinfo.col,collapsed);
+                    hitinfo.sheet.getRange(hitinfo.row+1, -1, recode.items.length, -1).visible(!collapsed);
+                    hitinfo.sheet.invalidateLayout();
+                    hitinfo.sheet.repaint();
+                }
+            }
+        };
+        return new TreeNodeCellType()
+    },
+
+};
+
+$(document).ready(function () {
+
+    $('#poj-set').on('shown.bs.modal', function (e) {
+        //init Spread
+        projFeatureView.initDatas(projFeatureView.orgDatas);
+        projFeatureView.buildSheet();
+        projFeatureView.showData(projFeatureView.datas);
+    });
+
+    $('#poj-set').on('hidden.bs.modal', function (e) {
+        //destroy Spread
+        if(projFeatureView.workBook){
+            projFeatureView.workBook.destroy();
+            projFeatureView.workBook = null;
+        }
+        projFeatureView.datas = [];
+    });
+
+    $('#tab_poj-settings-projFeature').on('shown.bs.tab', function () {
+        projFeatureView.workBook.refresh();
+    });
+
+    $('#property_ok').bind('click', function () {
+        if(projFeatureView.toUpdate(projFeatureView.orgDatas, projFeatureView.datas)){
+           projFeatureView.a_updateInfo(projFeatureView.toSaveDatas(projFeatureView.datas));
+        }
+    });
+});

+ 111 - 85
web/building_saas/main/js/views/project_view.js

@@ -18,12 +18,8 @@ var projectObj = {
         gljOprObj.showDataIfRationSelect(node);
 
         // CSL.2017.07.25
-        if (SubActiveSheetNameIs('JSCX')) {
-            if (node.sourceType === project.Bills.getSourceType() || node.sourceType === project.Ration.getSourceType()) {
-                calcProgramObj.showData(node);
-            } else {
-                calcProgramObj.clearData();
-            };
+        if (activeSubSheetIs(subSheetIndex.ssiCalcProgram)) {
+            calcProgramObj.showData(node);
         };
         //zhong 2017-9-1 特征及内容
         if(pageCCOprObj.active){
@@ -112,7 +108,7 @@ var projectObj = {
                 value = null;
             }
         }
-        if (value) {
+        if (value!=undefined||value!=null) {
             if (colSetting.data.decimal) {
                 value = value.toDecimal(colSetting.data.decimal);   
             }
@@ -125,11 +121,21 @@ var projectObj = {
     checkSpreadEditingText: function (editingText, colSetting) {
         if (colSetting.data.field === 'quantity' || colSetting.data.field === 'feesIndex.common.unitFee') {
             return this.checkFormulaValidField(editingText, colSetting);
-        } else {
+        }
+        else if (colSetting.data.field === 'programID') {
+            return this.project.calcProgram.compiledTemplateMaps[editingText];
+        }
+        else if (colSetting.data.field === 'subType') {
+            if (typeof(editingText) !== "number")
+                return volumePriceMaps[editingText]
+            else
+                return editingText;
+        }
+        else {
             return this.checkCommonField(editingText, colSetting);
         }
     },
-    updateAndReCalculate: function (node, fieldName, value) {
+    /*updateAndReCalculate: function (node, fieldName, value) {
         let project = projectObj.project, calc = new BillsCalcHelper(project), nodes = [];
         let getNodes = function (node) {
             let cur = node, nodes = [];
@@ -172,7 +178,7 @@ var projectObj = {
         }
         this.mainController.refreshTreeNode(nodes, false);
         calc = null;
-    },
+    },*/
     updateBillsCode: function (node, value) {
         let project = projectObj.project;
         let stdMatchCode, formatCode, matchs;
@@ -247,9 +253,15 @@ var projectObj = {
     updateCode: function (node, value) {
         let project = projectObj.project;
         if (node.sourceType === project.Bills.getSourceType()) {
-            this.updateBillsCode(node, value);
+            this.updateBillsCode(node, value);   // 新清单不适合实时计算,下面套什么还不能确定,无数量计算也无意义
         } else if (node.sourceType === project.Ration.getSourceType()) {
-            this.updateRationCode(node, value);
+            this.updateRationCode(node, value);  // 新套定额适合实时计算
+            // 这里因异步问题暂时缺少工料机价格。该过程移到:ration_glj.js的refreshAfterSave方法中。
+            /*project.calcProgram.calculate(node);
+            project.calcProgram.saveNode(node);
+            if (activeSubSheetIs(subSheetIndex.ssiCalcProgram)) {
+                calcProgramObj.showData(node, false);
+            };*/
         }
     },
     updateCellValue: function (node, value, colSetting) {
@@ -257,20 +269,39 @@ var projectObj = {
         if (value !== calcFees.getFee(node.data, fieldName)) {
             if (fieldName === 'code') {
                 projectObj.updateCode(node, value);
-            } else if (fieldName === 'quantity' && project.quantity_detail.quantityEditChecking(value,node,fieldName)) {
-                projectObj.updateAndReCalculate(node, fieldName, value);
-            } else if (fieldName === 'feesIndex.common.unitFee') {
-                projectObj.updateAndReCalculate(node, fieldName, value);
-            } else if(fieldName ==='feeRate'){
+            }
+            else if(fieldName ==='feeRate'){
                 project.FeeRate.updateFeeRateFromBills(value,node,fieldName);
-            }else {
+            }
+            else if (fieldName === 'quantity' || fieldName === 'marketUnitFee' || fieldName === 'programID' || fieldName === 'subType' || fieldName === 'calcBase'){
+                if (fieldName === 'quantity') {
+                    if (value) {value = value.toDecimal(decimalObj.decimal(fieldName,node))};
+                   if(project.quantity_detail.quantityEditChecking(value,node,fieldName)){
+                       node.data.isFromDetail=0;
+                       project.quantity_detail.cleanQuantityDetail(node,true);
+                   }else {
+                       projectObj.mainController.refreshTreeNode([node]);
+                       return;
+                   }
+                } else if (fieldName === 'marketUnitFee') {
+                    if (value) {value = parseFloat(value).toDecimal(decimalObj.decimal("unitPrice", node))};
+                } else if (fieldName === 'calcBase') {
+                    if (value) {value = parseFloat(value).toDecimal(decimalObj.decimal("totalPrice", node))};
+                };
+
+                node.changed = true;
+                node.data[fieldName] = value;
+                project.calcProgram.calculate(node);
+                project.calcProgram.saveNode(node);
+            }
+            else {
                 if (node.sourceType === project.Bills.getSourceType()) {
                     project.Bills.updateField(node.source, fieldName, value, true);
-                } else if (node.sourceType === project.Ration.getSourceType()) {
-                    project.Ration.updateField(node.source, fieldName, value, true);
-                } else if (node.sourceType === project.VolumePrice.getSourceType()) {
-                    project.VolumePrice.updateField(node.source, fieldName, value, true);
                 }
+                else if (node.sourceType === project.Ration.getSourceType()) {
+                    project.Ration.updateField(node.source, fieldName, value, true);
+                };
+
                 if (colSetting.data.wordWrap) {
                     info.sheet.autoFitRow(node.serialNo());
                 }
@@ -321,6 +352,7 @@ var projectObj = {
         this.project = PROJECT.createNew(scUrlUtil.GetQueryString('project'), userID);
         this.project.loadDatas(function (err) {
             if (!err) {
+                that.project.calcProgram.compileAllTemps();
                 that.project.calcFields = JSON.parse(JSON.stringify(feeType));
                 that.project.initCalcFields();
                 let str = JSON.stringify(that.project.projSetting.main_tree_col);
@@ -328,6 +360,13 @@ var projectObj = {
                 that.project.projSetting.mainGridSetting.frozenCols = 4;
                 TREE_SHEET_HELPER.initSetting($('#billsSpread')[0], that.project.projSetting.mainGridSetting);
                 that.project.projSetting.mainGridSetting.cols.forEach(function (col) {
+                    // for test.  后端没有绑定,暂时写死用于测试。
+/*                    if (col.data.field == '' && col.head.titleNames[0] == "取费专业") {
+                        col.data.field = 'programID';
+                        col.data.getText = 'getText.calcProgramName';
+                        col.data.cellType = 'cellType.calcProgramName';
+                    };*/
+
                     col.data.splitFields = col.data.field.split('.');
                     if (col.data.getText && Object.prototype.toString.apply(col.data.getText) === "[object String]") {
                         col.data.getText = MainTreeCol.getEvent(col.data.getText);
@@ -337,18 +376,24 @@ var projectObj = {
                     }
                     if (col.data.cellType && Object.prototype.toString.apply(col.data.cellType) === "[object String]") {
                         let getCellType = MainTreeCol.getEvent(col.data.cellType);
-                        col.data.cellType = getCellType();
-                    }
-                    if (col.data.digit && Object.prototype.toString.apply(col.data.digit) === "[object String]") {
-                        col.data.decimal = that.project.getDecimal(col.data.digit);
-                        col.data.formatter = MainTreeCol.getNumberFormatter(col.data.decimal);
+                        col.data.cellType = getCellType;
                     }
+                    // if (col.data.digit && Object.prototype.toString.apply(col.data.digit) === "[object String]") {
+                    //     col.data.decimal = that.project.getDecimal(col.data.digit);
+                    //     col.data.formatter = MainTreeCol.getNumberFormatter(col.data.decimal);
+                    // }
                     if (col.data.field === 'code') {
                         col.data.formatter = '@';
                     }
-                });
 
-                that.project.calcProgram.compileAllTemps();
+                    // for test digit. CSLAAAAA
+                    if (col.data.field.hasSubStr("totalFee"))
+                       col.data.formatter = MainTreeCol.getNumberFormatter(decimalObj.ration.totalPrice, true)
+                    else if (col.data.field.hasSubStr("unitFee"))
+                        col.data.formatter = MainTreeCol.getNumberFormatter(decimalObj.ration.unitPrice, true)
+                    else if (col.data.field == "quantity")
+                        col.data.formatter = MainTreeCol.getNumberFormatter(decimalObj.ration.quantity, true);
+                });
 
                 that.mainController = TREE_SHEET_CONTROLLER.createNew(that.project.mainTree, that.mainSpread.getActiveSheet(), that.project.projSetting.mainGridSetting);
                 that.mainController.showTreeData();
@@ -392,23 +437,15 @@ var projectObj = {
                     disabled: function () {
                         var selected = project.mainTree.selected;
                         if (selected) {
-                            if (selected.sourceType === project.Ration.getSourceType()) {
-                                return false;
-                            } else if (selected.sourceType === project.Bills.getSourceType()) {
-                                if (selected.source.children.length === 0) {
-                                    return selected.children.length !== 0 ? selected.firstChild().sourceType !== project.Ration.getSourceType() : false;
-                                } else {
-                                    return true;
-                                }
-                            } else if (selected.sourceType === project.VolumePrice.getSourceType()) {
-                                return true;
-                            };
-                        } else {
-                            return true;
-                        }
+                            if (            // CSL, 2017-11-28
+                                selected.sourceType === project.Ration.getSourceType() ||
+                                (selected.sourceType === project.Bills.getSourceType() && selected.source.children.length === 0)
+                               ) return false
+                            else return true
+                        } else return true
                     },
                     callback: function (key, opt) {
-                        ProjectController.addRation(project, controller);
+                        ProjectController.addRation(project, controller, rationType.ration);
                     }
                 },
                 "insertLJ": {
@@ -417,23 +454,15 @@ var projectObj = {
                     disabled: function () {
                         var selected = project.mainTree.selected;
                         if (selected) {
-                            if (selected.sourceType === project.Ration.getSourceType()) {
-                                return true;
-                            } else if (selected.sourceType === project.Bills.getSourceType()) {
-                                if (selected.source.children.length === 0) {
-                                    return selected.children.length !== 0 ? selected.firstChild().sourceType !== project.VolumePrice.getSourceType() : false;
-                                } else {
-                                    return true;
-                                }
-                            } else if (selected.sourceType === project.VolumePrice.getSourceType()) {
-                                return false;
-                            };
-                        } else {
-                            return true;
-                        }
+                            if (            // CSL, 2017-11-28
+                            selected.sourceType === project.Ration.getSourceType() ||
+                            (selected.sourceType === project.Bills.getSourceType() && selected.source.children.length === 0)
+                            ) return false
+                            else return true
+                        } else return true
                     },
                     callback: function (key, opt) {
-                        ProjectController.addVolumePrice(project, controller);
+                        ProjectController.addRation(project, controller, rationType.volumePrice);
                     }
                 },
                 "spr1": '--------',
@@ -453,9 +482,6 @@ var projectObj = {
                             } else if (selected.sourceType === project.Ration.getSourceType()) {
                                 project.Ration.delete(selected.source);
                                 controller.delete();
-                            } else if (selected.sourceType === project.VolumePrice.getSourceType()) {
-                                project.VolumePrice.delete(selected.source);
-                                controller.delete();
                             };
                             projectObj.converseCalculateBills(parent);
                         }
@@ -465,7 +491,8 @@ var projectObj = {
                 "calculateAll_RationContent": {
                     name: '造价计算',
                     callback: function () {
-                        projectObj.calculateAll();
+                        // projectObj.calculateAll();
+                        project.calcProgram.calcAllNodes();
                     }
                 }
             }
@@ -473,7 +500,7 @@ var projectObj = {
     },
     // 计算node及node的所有父项
     converseCalculateBills: function (node) {
-        if (node) {
+/*        if (node) {
             let calc = new BillsCalcHelper(this.project);
             calc.calcNode(node, true);
             let cur = node, nodes = [];
@@ -484,16 +511,18 @@ var projectObj = {
             this.mainController.refreshTreeNode(nodes, false);
             this.project.Bills.updateNodes(nodes, true);
             calc = null;
-        }
+        }*/
+        projectObj.project.calcProgram.calculate(node);
+        projectObj.project.calcProgram.saveNode(node);
     },
     // 计算全部清单
-    calculateAll: function () {
+/*    calculateAll: function () {
         let calc = new BillsCalcHelper(this.project);
         calc.calcAll();
         this.mainController.showTreeData();
         this.project.Bills.updateAll();
         calc = null;
-    }
+    }*/
 };
 
 $('#insert').click(function () {
@@ -503,9 +532,7 @@ $('#insert').click(function () {
     if (!selected || selected.sourceType === project.Bills.getSourceType()) {
         ProjectController.addBills(project, controller);
     } else if (selected.sourceType === project.Ration.getSourceType()) {
-        ProjectController.addRation(project, controller);
-    } else if (selected.sourceType === project.VolumePrice.getSourceType()) {
-        ProjectController.addVolumePrice(project, controller);
+        ProjectController.addRation(project, controller, selected.data.type);
     }
 });
 $('#delete').click(function () {
@@ -519,9 +546,6 @@ $('#delete').click(function () {
         } else if (selected.sourceType === project.Ration.getSourceType()) {
             project.Ration.delete(selected.source);
             controller.delete();
-        } else if (selected.sourceType === project.VolumePrice.getSourceType()) {
-            project.VolumePrice.delete(selected.source);
-            controller.delete();
         };
         projectObj.converseCalculateBills(parent);
     }
@@ -555,10 +579,7 @@ $('#upMove').click(function () {
     } else if (selected.sourceType === project.Ration.getSourceType()) {
         project.Ration.changePos(selected.source, selected.preSibling.source);
         controller.upMove();
-    } else if (selected.sourceType === project.VolumePrice.getSourceType()) {
-        project.VolumePrice.changePos(selected.source, selected.preSibling.source);
-        controller.upMove();
-    }
+    };
 });
 $('#downMove').click(function () {
     var controller = projectObj.mainController, project = projectObj.project;
@@ -571,10 +592,7 @@ $('#downMove').click(function () {
         } else if (selected.sourceType === project.Ration.getSourceType()) {
             project.Ration.changePos(selected.source, selected.nextSibling.source);
             controller.downMove();
-        } else if (selected.sourceType === project.VolumePrice.getSourceType()) {
-            project.VolumePrice.changePos(selected.source, selected.nextSibling.source);
-            controller.downMove();
-        }
+        };
     }
 });
 
@@ -617,10 +635,18 @@ $('#property_ok').click(function () {
         reCalc = true;
     }
     if (reCalc) {
-        projectObj.calculateAll();
-        project.pushNow('editBillsCalcMode',
+        // projectObj.calculateAll();
+/*        project.pushNow('editBillsCalcMode',
             [project.projSetting.moduleName, project.Bills.getSourceType()],
-            [{projectID: project.ID(), billsCalcMode: project.projSetting.billsCalcMode}, project.Bills.getUpdateAllData()]
-        );
+            [{
+                projectID: project.ID(),
+                billsCalcMode: project.projSetting.billsCalcMode
+            }, project.Bills.getUpdateAllData()]
+        );*/
+        project.pushNow('', [project.projSetting.moduleName], [{
+            projectID: project.ID(),
+            billsCalcMode: project.projSetting.billsCalcMode
+        }]);
+        project.calcProgram.calcAllNodes(calcAllType.catBills);
     }
 });

+ 2 - 2
web/building_saas/main/js/views/std_ration_lib.js

@@ -82,7 +82,7 @@ var rationLibObj = {
         var select = $('#stdRationLibSelect'), rationCode = args.sheet.getText(args.row, 0);
         if (rationCode !== '') {
             CommonAjax.postRationLib('/rationRepository/api/getRationItem', {user_id: userID, rationLibId: select.val(), code: rationCode}, function (data) {
-                ProjectController.addRation(projectObj.project, projectObj.mainController, data);
+                ProjectController.addRation(projectObj.project, projectObj.mainController, rationType.ration, data);
             });
         }
     },
@@ -103,7 +103,7 @@ var rationLibObj = {
                         let rationCode = rationSelect.length > 0 ? rationSheet.getText(rationSelect[0].row, 0) : '';
                         if (rationCode !== '') {
                             CommonAjax.postRationLib('/rationRepository/api/getRationItem', {user_id: userID, rationLibId: select.val(), code: rationCode}, function (data) {
-                                ProjectController.addRation(projectObj.project, projectObj.mainController, data);
+                                ProjectController.addRation(projectObj.project, projectObj.mainController, rationType.ration, data);
                             });
                         }
                     }

+ 5 - 7
web/building_saas/main/js/views/sub_view.js

@@ -92,11 +92,7 @@ $("#linkJSCX").click(function(){        // 计算程序
     if (!projectObj.mainController.tree.selected)
         projectObj.mainController.tree.selected = projectObj.mainController.tree.firstNode();
     let sel = projectObj.mainController.tree.selected;
-    if (sel.sourceType === projectObj.project.Bills.getSourceType() || sel.sourceType === projectObj.project.Ration.getSourceType()) {
-        calcProgramObj.showData(sel);
-    } else {
-        calcProgramObj.clearData();
-    };
+    calcProgramObj.showData(sel);
     gljOprObj.activeTab='#linkJSCX';
 });
 
@@ -116,10 +112,12 @@ $("#linkTZJNR").click(function () {
     }
     gljOprObj.activeTab='#linkTZJNR';
 });
-function SubActiveSheetNameIs(sheetName){
-    let rst = subSpread.getActiveSheet().name() == sheetName;
+
+function activeSubSheetIs(idx){
+    let rst = subSpread.getActiveSheetIndex() == idx;
     return rst;
 }
+
 //弹出清单规则或定额库后导致subSpread和特征及内容spread显示出问题
 function refreshSubSpread(){
     if(pageCCOprObj.active){

+ 51 - 37
web/building_saas/pm/html/project-management.html

@@ -74,10 +74,15 @@
                         <div class="tab-pane active" id="pm_all" role="tabpanel">
                         <div class="toolsbar">
                             <div class="tools-btn btn-group align-top">
-                                <a href="javascript:void(0);" class="btn btn-sm" id="add-project-btn"><i class="fa fa-cubes"></i>&nbsp;新建建设项目</a>
-                                <a href="javascript:void(0);" class="btn btn-sm" id="add-engineering-btn"><i class="fa fa-cube"></i>&nbsp;新建单项工程</a>
-                                <a href="javascript:void(0);" class="btn btn-sm" id="add-tender-btn"><i class="fa fa-sticky-note-o"></i>&nbsp;新建单位工程</a>
-                                <a href="javascript:void(0);" class="btn btn-sm" id="add-folder-btn"><i class="fa fa-folder-o"></i>&nbsp;新建文件夹</a>
+                                <a href="javascript:void(0);"  id="add-tender-btn" class="btn btn-sm"><i class="fa fa-sticky-note-o"></i>新建单位工程</a>
+                                <div class="btn-group" role="group">
+                                    <a href="javascript:void(0);" class="btn btn-sm dropdown-toggle" data-toggle="dropdown"></a>
+                                    <div class="dropdown-menu" aria-labelledby="btnGroupDrop1">
+                                        <a href="javascript:void(0);"  id="add-project-btn" class="dropdown-item"><i class="fa fa-cubes"></i>新建建设项目</a>
+                                        <a href="javascript:void(0);"  id="add-engineering-btn" class="dropdown-item disabled"><i class="fa fa-cube"></i> 新建单项工程</a>
+                                        <a href="javascript:void(0);"  id="add-folder-btn" class="dropdown-item"><i class="fa fa-folder-o"></i>新建文件夹</a>
+                                    </div>
+                                </div>
                                 <a href="javascript:void(0);" class="btn btn-sm" id="rename-btn">重命名</a>
                                 <a href="javascript:void(0);" class="btn btn-sm" id="del-btn">删除</a>
                                 <a href="javascript:void(0);" class="btn btn-sm" id="move-to-btn">移动到...</a>
@@ -219,47 +224,28 @@
         </div>
     </div>
 </div>
+
 <!--弹出新建建设项目-->
 <div class="modal fade" id="add-project-dialog" data-backdrop="static">
     <div class="modal-dialog" role="document">
         <div class="modal-content">
             <div class="modal-header">
-                <h5 class="modal-title"><i class="fa fa-cubes"></i>&nbsp;新建建设项目</h5>
+                <h5 class="modal-title">新建 <i class="fa fa-cubes"></i> 建设项目</h5>
                 <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                     <span aria-hidden="true">&times;</span>
                 </button>
             </div>
             <div class="modal-body">
-                <!--没有点击任何节点-->
                 <form>
                     <div class="form-group">
                         <label>建设项目</label>
                         <input type="text" class="form-control" placeholder="输入建设项目名称" id="project-name">
                     </div>
-                    <div class="form-group">
-                        <label>计价方式</label>
-                        <div>
-                            <label class="custom-control custom-radio">
-                                <input name="valuation_type" type="radio" class="custom-control-input" value="bill">
-                                <span class="custom-control-indicator"></span>
-                                <span class="custom-control-description">清单计价</span>
-                            </label>
-                            <label class="custom-control custom-radio">
-                                <input name="valuation_type" type="radio" class="custom-control-input" value="ration">
-                                <span class="custom-control-indicator"></span>
-                                <span class="custom-control-description">定额计价</span>
-                            </label>
-                        </div>
-                    </div>
-                    <div class="form-group">
-                        <label>计价规则</label>
-                        <select class="form-control" id="valuation"><option value="">请选择计价规则</option></select>
-                    </div>
                 </form>
             </div>
             <div class="modal-footer">
                 <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
-                <a href="javacript:void(0);" class="btn btn-primary" id='addProjOk'>确定</a>
+                <a href="javascript:void(0);" class="btn btn-primary" id="addProjOk">确定</a>
             </div>
         </div>
     </div>
@@ -295,27 +281,51 @@
     <div class="modal-dialog" role="document">
         <div class="modal-content">
             <div class="modal-header">
-                <h5 class="modal-title"><i class="fa fa-sticky-note-o"></i>&nbsp;新建单位工程</h5>
+                <h5 class="modal-title">新建 <i class="fa fa-sticky-note-o"></i> 单位工程</h5>
                 <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                     <span aria-hidden="true">&times;</span>
                 </button>
             </div>
             <div class="modal-body">
-                <!--没有点击任何节点-->
                 <form>
                     <div class="form-group">
-                        <label>单位工程</label>
-                        <input type="text" class="form-control" placeholder="输入单位工程名称" id="tender-name">
+                        <label>建设项目</label>
+                        <div class="input-group">
+                            <input type="text" class="form-control" placeholder="输入建设项目名称" id="poj-name" autocomplete="off">
+                              <span class="input-group-btn">
+                                <button class="btn btn-secondary dropdown-toggle" type="button" data-toggle="dropdown"></button>
+                                <div class="dropdown-menu dropdown-menu-right" style="width:468px" id="poj-name-list">
+                                    <button class="dropdown-item" type="button">汽车生产车间1</button>
+                                    <button class="dropdown-item" type="button">汽车生产车间2</button>
+                                    <button class="dropdown-item" type="button">汽车生产车间3</button>
+                                </div>
+                              </span>
+                        </div>
+                        <span class="form-text text-info" id="poj-name-info" style="display: none;">新建 “汽车生产车间5”</span>
                     </div>
                     <div class="form-group">
-                        <label>工程专业</label>
-                        <select class="form-control" id="tender-engineering"><option>建筑工程</option></select>
+                        <label>单项工程</label>
+                        <div class="input-group">
+                            <input type="text" class="form-control" placeholder="输入单项工程名称" id="eng-name" autocomplete="off">
+                              <span class="input-group-btn">
+                                <button class="btn btn-secondary dropdown-toggle" type="button" data-toggle="dropdown"></button>
+                                <div class="dropdown-menu dropdown-menu-right" style="width:468px" id="eng-name-list">
+                                    <button class="dropdown-item" type="button">左3号生产车间</button>
+                                    <button class="dropdown-item" type="button">左4号生产车间</button>
+                                </div>                              </span>
+                        </div>
+                        <span class="form-text text-info" id="eng-name-info" style="display: none;">新建 “左2号生产车间2”</span>
                     </div>
-                    <div class="form-group hidden-area">
+                    <div class="form-group">
+                        <label>单位工程</label>
+                        <input type="text" class="form-control" placeholder="输入单位工程名称" id="tender-name" autocomplete="off">
+                        <span class="form-text text-danger" id="tender-name-info" style="display: none;">已存在 “建筑工程1”</span>
+                    </div>
+                    <div class="form-group">
                         <label>单价文件</label>
                         <select class="form-control" id="unit-price"><option value="">新建单价文件</option></select>
                     </div>
-                    <div class="form-group hidden-area">
+                    <div class="form-group">
                         <label>费率文件</label>
                         <select class="form-control" id="tender-fee-rate"><option value="">新建费率文件</option></select>
                     </div>
@@ -323,12 +333,12 @@
                         <label>计价方式</label>
                         <div>
                             <label class="custom-control custom-radio">
-                                <input name="tender_valuation_type" type="radio" class="custom-control-input" value="bill" checked="checked">
+                                <input name="valuation_type" value="bill" type="radio" class="custom-control-input">
                                 <span class="custom-control-indicator"></span>
                                 <span class="custom-control-description">清单计价</span>
                             </label>
                             <label class="custom-control custom-radio">
-                                <input name="tender_valuation_type" type="radio" class="custom-control-input" value="ration" disabled="disabled">
+                                <input name="valuation_type" value="ration" type="radio" class="custom-control-input">
                                 <span class="custom-control-indicator"></span>
                                 <span class="custom-control-description">定额计价</span>
                             </label>
@@ -336,7 +346,11 @@
                     </div>
                     <div class="form-group">
                         <label>计价规则</label>
-                        <select class="form-control" id="tender-valuation" disabled><option>重庆[2008]调整人工单价</option></select>
+                        <select class="form-control" id="valuation"><option value="">请选择计价规则</option></select>
+                    </div>
+                    <div class="form-group">
+                        <label>工程专业</label>
+                        <select class="form-control" id="tender-engineering"><option value="">请选择对应的工程专业</option></select>
                     </div>
                 </form>
             </div>

+ 7 - 25
web/building_saas/pm/js/pm_gc.js

@@ -551,37 +551,19 @@ function e_recProj(btn){
 }
 
 function a_getGC(callback){
-    $.ajax({
-        type: 'post',
-        url: '/pm/api/getGCDatas',
-        data: {data: JSON.stringify({user_id: userID})},
-        dataType: 'json',
-        timeout: 5000,
-        success: function (result) {
-            if(!result.error){
-                if(callback){
-                    callback(result.data);
-                }
-            }
+    CommonAjax.post('/pm/api/getGCDatas', {user_id: userID}, function (rstData) {
+        if(callback){
+            callback(rstData);
         }
     });
 }
 
 function a_rec(nodes, callback){
-    $.ajax({
-        type: 'post',
-        url: '/pm/api/recGC',
-        data: {data: JSON.stringify({user_id: userID, nodes: nodes})},
-        dataType: 'json',
-        timeout: 5000,
-        success: function (result) {
-            if(!result.error){
-                if(callback){
-                    callback();
-                }
-            }
+    CommonAjax.post('/pm/api/recGC', {user_id: userID, nodes: nodes}, function (rstData) {
+        if(callback){
+            callback();
         }
-    })
+    });
 }
 
 //去除重名,回收站不处理重名,只保证恢复到项目管理中不出现重名

文件差異過大導致無法顯示
+ 597 - 193
web/building_saas/pm/js/pm_main.js


+ 45 - 4
web/building_saas/report/html/rpt_main.html

@@ -16,9 +16,11 @@
                 <div class="print-toolsbar">
                     <div class="panel">
                         <div class="panel-body">
-                            <button class="btn btn-secondary btn-sm" type="button">
-                                <i class="fa fa-print"></i><br>
-                                打印 <span id="checkCount" class="badge badge-primary">5</span>
+                            <button class="btn btn-secondary btn-sm" type="button" onclick="">
+                                <i class="fa fa-print"></i><br>打印
+                            </button>
+                            <button class="btn btn-secondary btn-sm" type="button"  href="#export" data-toggle="modal" data-target="#export">
+                                <i class="fa fa-share-square-o"></i><br>导出
                             </button>
                         </div>
                     </div>
@@ -51,7 +53,7 @@
                         <div class="panel-body">
                             <div class="btn-group" role="group">
                                 <button type="button" class="btn btn-secondary btn-sm">-</button>
-                                <a class="btn btn-secondary btn-sm" data-toggle="tooltip" data-placement="bottom" title="重置默认大小">1000%</a>
+                                <a class="btn btn-secondary btn-sm" data-toggle="tooltip" data-placement="bottom" title="重置默认大小">100%</a>
                                 <button type="button" class="btn btn-secondary btn-sm">+</button>
                             </div>
                         </div>
@@ -139,6 +141,45 @@
         </div>
     </div>
 </div>
+<!--弹出导出-->
+<div class="modal fade" id="export" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">选择导出格式</h5>
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">&times;</span>
+                </button>
+            </div>
+            <div class="modal-body">
+                <div class="row">
+                    <div class="col-6">
+                        <a class="btn btn-block btn-outline-secondary" id="PDF_TYPE" onclick="rptControlObj.changeType('PDF')">
+                            <!-- <i class="fa fa-check-square pull-right"></i> -->
+                            <div class="card-body text-center">
+                                <h1 class="display-3"><i class="fa fa-file-pdf-o"></i></h1>
+                                <h1>PDF</h1>
+                            </div>
+                        </a>
+                    </div>
+                    <div class="col-6">
+                        <a class="btn btn-block btn-primary" id="EXCEL_TYPE" onclick="rptControlObj.changeType('Excel')">
+                            <!-- <i class="fa fa-check-square pull-right"></i> -->
+                            <div class="card-body text-center">
+                                <h1 class="display-3"><i class="fa fa-file-excel-o"></i></h1>
+                                <h1>Excel</h1>
+                            </div>
+                        </a>
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
+                <a onclick="rptControlObj.outputRpt();" class="btn btn-primary">确定</a>
+            </div>
+        </div>
+    </div>
+</div>
 <script>
     const SCREEN_DPI = [];
     function getScreenDPI() {

+ 105 - 29
web/building_saas/report/js/jpc_output.js

@@ -17,26 +17,26 @@ let JpcCanvasOutput = {
 
         function private_setupAreaH(area, type, fontAngle, dftFontHeight, outputPoint) {
             let lType = type;
-            if (type != "left" && type != "right" && type != "center") lType = "left";
+            if (type !== "left" && type !== "right" && type !== "center") lType = "left";
             switch (lType) {
                 case "left":
-                    if (fontAngle == JV.VERTICAL_ANGLE) {
+                    if (fontAngle === JV.VERTICAL_ANGLE_INT) {
                         outputPoint[1] = 1 * area[JV.IDX_TOP] + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_LEFT];
-                    } else if (fontAngle == JV.ANTI_VERTICAL_ANGLE) {
+                    } else if (fontAngle === JV.ANTI_VERTICAL_ANGLE_INT) {
                         outputPoint[1] = 1 * area[JV.IDX_BOTTOM] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_LEFT];
                     } else outputPoint[0] = 1 * area[JV.IDX_LEFT] + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_LEFT];
                     ctx.textAlign="start";
                     break;
                 case "right":
-                    if (fontAngle == JV.VERTICAL_ANGLE) {
+                    if (fontAngle === JV.VERTICAL_ANGLE_INT) {
                         outputPoint[1] = 1 * area[JV.IDX_BOTTOM] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_RIGHT];
-                    } else if (fontAngle == JV.ANTI_VERTICAL_ANGLE) {
+                    } else if (fontAngle === JV.ANTI_VERTICAL_ANGLE_INT) {
                         outputPoint[1] = 1 * area[JV.IDX_TOP] + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_RIGHT];
                     } else outputPoint[0] = 1 * area[JV.IDX_RIGHT] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_RIGHT];
                     ctx.textAlign="end";
                     break;
                 case "center":
-                    if (fontAngle == JV.VERTICAL_ANGLE || fontAngle == JV.ANTI_VERTICAL_ANGLE) {
+                    if (fontAngle === JV.VERTICAL_ANGLE_INT || fontAngle === JV.ANTI_VERTICAL_ANGLE_INT) {
                         outputPoint[1] = (1 * area[JV.IDX_TOP] + 1 * area[JV.IDX_BOTTOM]) / 2;
                     } else outputPoint[0] = (1 * area[JV.IDX_LEFT] + 1 * area[JV.IDX_RIGHT]) / 2;
                     ctx.textAlign="center";
@@ -45,26 +45,26 @@ let JpcCanvasOutput = {
         }
         function private_setupAreaV(area, type, fontAngle, dftFontHeight, outputPoint) {
             let lType = type;
-            if (type != "top" && type != "bottom" && type != "center") lType = "top";
+            if (type !== "top" && type !== "bottom" && type !== "center") lType = "top";
             switch (lType) {
                 case "top":
-                    if (fontAngle == JV.VERTICAL_ANGLE) {
+                    if (fontAngle === JV.VERTICAL_ANGLE_INT) {
                         outputPoint[0] = 1 * area[JV.IDX_RIGHT] - dftFontHeight - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP];
-                    } else if (fontAngle == JV.ANTI_VERTICAL_ANGLE) {
+                    } else if (fontAngle === JV.ANTI_VERTICAL_ANGLE_INT) {
                         outputPoint[0] = 1 * area[JV.IDX_LEFT] + dftFontHeight + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP];
                     } else outputPoint[1] = 1 * area[JV.IDX_TOP] + dftFontHeight + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP];
                     break;
                 case "bottom":
-                    if (fontAngle == JV.VERTICAL_ANGLE) {
+                    if (fontAngle === JV.VERTICAL_ANGLE_INT) {
                         outputPoint[0] = 1 * area[JV.IDX_LEFT] + JV.OUTPUT_OFFSET[JV.OFFSET_IDX_BOTTOM];
-                    } else if (fontAngle == JV.ANTI_VERTICAL_ANGLE) {
+                    } else if (fontAngle === JV.ANTI_VERTICAL_ANGLE_INT) {
                         outputPoint[0] = 1 * area[JV.IDX_RIGHT] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_BOTTOM];
                     } else outputPoint[1] = 1 * area[JV.IDX_BOTTOM] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_BOTTOM];
                     break;
                 case "center":
-                    if (fontAngle == JV.VERTICAL_ANGLE) {
+                    if (fontAngle === JV.VERTICAL_ANGLE_INT) {
                         outputPoint[0] = (1 * area[JV.IDX_LEFT] + 1 * area[JV.IDX_RIGHT] - dftFontHeight) / 2;
-                    } else if (fontAngle == JV.ANTI_VERTICAL_ANGLE) {
+                    } else if (fontAngle === JV.ANTI_VERTICAL_ANGLE_INT) {
                         outputPoint[0] = (1 * area[JV.IDX_LEFT] + 1 * area[JV.IDX_RIGHT] + dftFontHeight) / 2;
                     } else outputPoint[1] = (1 * area[JV.IDX_TOP] + 1 * area[JV.IDX_BOTTOM] + dftFontHeight) / 2;
                     break;
@@ -74,31 +74,75 @@ let JpcCanvasOutput = {
             let dftFontHeight = 12;
             let output = [];
             if (font) {
-                dftFontHeight = 1 * font[JV.FONT_PROPS[1]];
+                dftFontHeight = parseFloat(font[JV.FONT_PROPS[1]]);
                 let dftOthers = "";
                 let dftFontBold = font[JV.FONT_PROPS[3]];
-                if (dftFontBold && dftFontBold == 'T') {
+                if (dftFontBold && dftFontBold === 'T') {
                     dftOthers = "bold " + dftOthers ;
                 }
                 let dftFontItalic = font[JV.FONT_PROPS[4]];
-                if (dftFontItalic && dftFontItalic == 'T') {
+                if (dftFontItalic && dftFontItalic === 'T') {
                     dftOthers = dftOthers + "italic ";
                 }
                 ctx.font = dftOthers + dftFontHeight + "px " + font[JV.PROP_NAME];
             }
             if (control) {
-                private_setupAreaH(area, control.Horizon, font.FontAngle, dftFontHeight, output);
-                private_setupAreaV(area, control.Vertical, font.FontAngle, dftFontHeight, output);
+                private_setupAreaH(area, control.Horizon, parseInt(font.FontAngle), dftFontHeight, output);
+                private_setupAreaV(area, control.Vertical, parseInt(font.FontAngle), dftFontHeight, output);
             } else {
-                private_setupAreaH(area, "left", font.FontAngle, dftFontHeight, output);
-                private_setupAreaV(area, "bottom", font.FontAngle, dftFontHeight, output);
+                private_setupAreaH(area, "left", parseInt(font.FontAngle), dftFontHeight, output);
+                private_setupAreaV(area, "bottom", parseInt(font.FontAngle), dftFontHeight, output);
             }
             let w = area[JV.IDX_RIGHT] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_RIGHT] - area[JV.IDX_LEFT] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_RIGHT];
-            if (font.FontAngle != "0") {
+            if ( parseInt(font.FontAngle) !== 0) {
                 w = area[JV.IDX_BOTTOM] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_BOTTOM] - area[JV.IDX_TOP] - JV.OUTPUT_OFFSET[JV.OFFSET_IDX_TOP];
             }
+
+            function private_drawUnderline() {
+                //A. 暂不支持角度; B. 坐标已经translate
+                //1. 计算下划线的相关坐标
+                let width = ctx.measureText(val).width;
+                let height = dftFontHeight;
+                // let startX = area[JV.IDX_LEFT], startY = area[JV.IDX_TOP], endX = area[JV.IDX_RIGHT], endY = area[JV.IDX_BOTTOM];
+                let startX = 0, startY = 0, endX = width, endY = startY;
+                if (control.Horizon === "left") {
+                    // 无变化;
+                } else if (control.Horizon === "right") {
+                    startX = Math.round(startX - width);
+                } else {
+                    startX = Math.round(startX - width / 2);
+                }
+                endX = Math.round(startX + width);
+
+                if (control.Vertical === "top") {
+                    startY += JV.OUTPUT_OFFSET[JV.IDX_TOP] + JV.OUTPUT_OFFSET[JV.IDX_BOTTOM];
+                } else if (control.Vertical === "bottom") {
+                    // startY = Math.round(startY);
+                    startY += JV.OUTPUT_OFFSET[JV.IDX_TOP] + JV.OUTPUT_OFFSET[JV.IDX_BOTTOM];
+                } else {
+                    startY = Math.round(height / 2) - JV.OUTPUT_OFFSET[JV.IDX_TOP] - JV.OUTPUT_OFFSET[JV.IDX_BOTTOM];
+                    // startY += JV.OUTPUT_OFFSET[JV.IDX_TOP] + JV.OUTPUT_OFFSET[JV.IDX_BOTTOM];
+                }
+                endY = Math.round(startY);
+                //2. 画线
+                // ctx.save();
+                if ( output[1] !== Math.round(output[1])) {
+                    ctx.translate(0,0.5);
+                }
+                ctx.beginPath();
+                ctx.moveTo(startX, startY);
+                ctx.lineWidth = 1;
+                ctx.strokeStyle = "BLACK";
+                ctx.lineTo(endX, endY);
+                ctx.stroke();
+                // ctx.restore();
+            }
+
             ctx.save();
             ctx.translate(output[0], output[1]);
+            if (font[JV.FONT_PROPS[5]] === 'T' && parseInt(font.FontAngle) === 0) {
+                private_drawUnderline();
+            }
             if (font.FontAngle === JV.VERTICAL_ANGLE) {
                 ctx.rotate(Math.PI/2);
             } else if (font.FontAngle === JV.ANTI_VERTICAL_ANGLE) {
@@ -121,8 +165,19 @@ let JpcCanvasOutput = {
         function private_drawCellText(cell, fonts, controls) {
             if (cell[JV.PROP_VALUE]) {
                 let values = ("" + cell[JV.PROP_VALUE]).split('|');
-                let font = fonts[cell[JV.PROP_FONT]];
-                let control = controls[cell[JV.PROP_CONTROL]];
+                let font = null;
+                if (typeof cell[JV.PROP_FONT] === "string") {
+                    font = fonts[cell[JV.PROP_FONT]];
+                } else {
+                    font = cell[JV.PROP_FONT];
+                }
+                //let control = controls[cell[JV.PROP_CONTROL]];
+                let control = null;
+                if (typeof cell[JV.PROP_CONTROL] === "string") {
+                    control = controls[cell[JV.PROP_CONTROL]];
+                } else {
+                    control = cell[JV.PROP_CONTROL];
+                }
                 if (control.ShowZero === "F") {
                     if (parseFloat(cell[JV.PROP_VALUE]) === 0.0) {
                         values = [""];
@@ -141,12 +196,12 @@ let JpcCanvasOutput = {
             ctx.beginPath();
             let destStyle = style;
             if (mergedBand) {
-                if (isNeedMergeBand && mergedBand[styleBorderDest] == cell[JV.PROP_AREA][styleBorderDest]) {
+                if (isNeedMergeBand && parseFloat(mergedBand[styleBorderDest]) === parseFloat(cell[JV.PROP_AREA][styleBorderDest])) {
                     destStyle = styles[mergedBand[JV.PROP_STYLE][JV.PROP_ID]];
                 }
             }
             ctx.moveTo(cell[JV.PROP_AREA][startP[0]] + me.offsetX, cell[JV.PROP_AREA][startP[1]] + me.offsetY);
-            if (destStyle[styleBorderDest] && destStyle[styleBorderDest][JV.PROP_LINE_WEIGHT] != 0) {
+            if (destStyle[styleBorderDest] && parseFloat(destStyle[styleBorderDest][JV.PROP_LINE_WEIGHT]) !== 0.0) {
                 ctx.lineWidth = 1.0 * destStyle[styleBorderDest][JV.PROP_LINE_WEIGHT];
                 ctx.strokeStyle = destStyle[styleBorderDest][JV.PROP_COLOR];
                 ctx.lineTo(cell[JV.PROP_AREA][destP[0]] + me.offsetX, cell[JV.PROP_AREA][destP[1]] + me.offsetY);
@@ -175,6 +230,15 @@ let JpcCanvasOutput = {
             private_drawCellText(cell, fonts, controls);
             ctx.restore();
         }
+        function getIniPageMergeBorder(mergedBand) {
+            let rst = {};
+            rst[JV.PROP_LEFT] = mergedBand[JV.PROP_LEFT];
+            rst[JV.PROP_RIGHT] = mergedBand[JV.PROP_RIGHT];
+            rst[JV.PROP_TOP] = mergedBand[JV.PROP_TOP];
+            rst[JV.PROP_BOTTOM] = mergedBand[JV.PROP_BOTTOM];
+            rst[JV.PROP_STYLE] = mergedBand[JV.PROP_STYLE];
+            return rst;
+        }
 
         if (pageObj && pageObj.items.length > 0 && canvas && pageObj.items.length >= pageIdx) {
             let page = pageObj.items[pageIdx - 1],
@@ -182,17 +246,23 @@ let JpcCanvasOutput = {
                 styles = pageObj[JV.NODE_STYLE_COLLECTION],
                 controls = pageObj[JV.NODE_CONTROL_COLLECTION],
                 mergedBand = pageObj[JV.BAND_PROP_MERGE_BAND];
+            let newPageMergeBand = getIniPageMergeBorder(mergedBand);
+            if (page[JV.PROP_PAGE_MERGE_BORDER]) {
+                newPageMergeBand[JV.PROP_LEFT] = page[JV.PROP_PAGE_MERGE_BORDER][JV.PROP_LEFT];
+                newPageMergeBand[JV.PROP_RIGHT] = page[JV.PROP_PAGE_MERGE_BORDER][JV.PROP_RIGHT];
+                newPageMergeBand[JV.PROP_TOP] = page[JV.PROP_PAGE_MERGE_BORDER][JV.PROP_TOP];
+                newPageMergeBand[JV.PROP_BOTTOM] = page[JV.PROP_PAGE_MERGE_BORDER][JV.PROP_BOTTOM];
+            }
             for (let j = 0; j < page.cells.length; j++) {
                 let cell = page.cells[j];
-                private_drawCell(cell, fonts, styles, controls, mergedBand);
+                // private_drawCell(cell, fonts, styles, controls, mergedBand);
+                private_drawCell(cell, fonts, styles, controls, newPageMergeBand);
             }
         }
     },
     drawPageBorder: function(rptTpl, canvas, resolution) {
         let me = this;
-        let size = rptTpl[JV.NODE_PAGE_INFO][JV.NODE_PAGE_SIZE].slice(0);
-        size[0] = Math.round(resolution[0] * size[0]);
-        size[1] = Math.round(resolution[0] * size[1]);
+        let size = me.getReportSizeInPixel(rptTpl, resolution);
 
         let ctx = canvas.getContext("2d");
         ctx.save();
@@ -210,5 +280,11 @@ let JpcCanvasOutput = {
         ctx.fillStyle="black";
         ctx.fillRect(size[0] + me.offsetX,10 + me.offsetY,10,size[1]);
         ctx.fillRect(10 + me.offsetX,size[1] + me.offsetY,size[0],10);
+    },
+    getReportSizeInPixel: function(rptTpl, resolution) {
+        let rst = rptTpl[JV.NODE_PAGE_INFO][JV.NODE_PAGE_SIZE].slice(0);
+        rst[0] = Math.round(resolution[0] * rst[0]);
+        rst[1] = Math.round(resolution[0] * rst[1]);
+        return rst;
     }
 };

+ 4 - 1
web/building_saas/report/js/jpc_output_value_define.js

@@ -13,6 +13,7 @@ let JV = {
     NODE_CONTROL_COLLECTION: "control_collection",
 
     BAND_PROP_MERGE_BAND: "MergeBand",
+    PROP_PAGE_MERGE_BORDER: "page_merge_border",
 
     PROP_NAME: "Name",
     PROP_VALUE: "Value",
@@ -38,12 +39,14 @@ let JV = {
     PROP_COLOR: "Color",
     FONT_PROPS: ["Name", "FontHeight", "FontColor", "FontBold", "FontItalic", "FontUnderline", "FontStrikeOut", "FontAngle"],
 
-    OUTPUT_OFFSET: [2,1,2,3],
+    OUTPUT_OFFSET: [2,2,1,3],
     OFFSET_IDX_LEFT: 0,
     OFFSET_IDX_RIGHT: 1,
     OFFSET_IDX_TOP: 2,
     OFFSET_IDX_BOTTOM: 3,
 
+    VERTICAL_ANGLE_INT: 90,
+    ANTI_VERTICAL_ANGLE_INT: -90,
     VERTICAL_ANGLE: "90",
     ANTI_VERTICAL_ANGLE: "-90"
 };

+ 0 - 0
web/building_saas/report/js/rpt_main.js


部分文件因文件數量過多而無法顯示