Просмотр исходного кода

Merge branch '1.0.0_online' of http://192.168.1.12:3000/SmartCost/ConstructionCost into 1.0.0_online

TonyKang 6 лет назад
Родитель
Сommit
0a7fd392e7
72 измененных файлов с 3849 добавлено и 926 удалено
  1. 1 1
      config/config.js
  2. 2 2
      config/db/db_manager.js
  3. 1 0
      config/gulpConfig.js
  4. 1 0
      lib/x2js/xml2json.min.js
  5. 43 0
      logs/online_logs.js
  6. 2 1
      modules/all_models/engineering_lib.js
  7. 17 0
      modules/all_models/online_logs.js
  8. 18 3
      modules/all_models/project_glj.js
  9. 13 0
      modules/all_models/user.js
  10. 5 1
      modules/common/const/bills_fixed.js
  11. 8 6
      modules/complementary_ration_lib/models/compleRationModel.js
  12. 57 24
      modules/complementary_ration_lib/models/searchModel.js
  13. 1 1
      modules/glj/controllers/glj_controller.js
  14. 6 11
      modules/main/facade/ration_facade.js
  15. 2 0
      modules/pm/controllers/pm_controller.js
  16. 62 16
      modules/pm/facade/pm_facade.js
  17. 3 1
      modules/ration_glj/facade/glj_calculate_facade.js
  18. 26 25
      modules/ration_glj/facade/ration_glj_facade.js
  19. 14 1
      modules/users/controllers/cld_controller.js
  20. 256 92
      modules/users/controllers/login_controller.js
  21. 36 1
      modules/users/controllers/sms_controller.js
  22. 41 1
      modules/users/controllers/user_controller.js
  23. 2 0
      modules/users/models/engineering_lib_model.js
  24. 3 2
      modules/users/models/log_model.js
  25. 41 2
      modules/users/models/sms.js
  26. 71 1
      modules/users/models/user_model.js
  27. 3 1
      modules/users/routes/cld_route.js
  28. 1 1
      modules/users/routes/login_route.js
  29. 2 1
      modules/users/routes/sms_route.js
  30. 3 1
      modules/users/routes/user_route.js
  31. 6 1
      public/gljUtil.js
  32. 2 0
      public/web/common_util.js
  33. 5 3
      public/web/gljUtil.js
  34. 22 6
      public/web/slideResize.js
  35. 0 6
      public/web/tree_sheet/tree_sheet_helper.js
  36. 23 13
      server.js
  37. 27 15
      web/building_saas/complementary_glj_lib/js/glj.js
  38. 3 1
      web/building_saas/css/custom.css
  39. 24 0
      web/building_saas/css/main.css
  40. 73 1
      web/building_saas/glj/html/project_glj.html
  41. 1 0
      web/building_saas/js/global.js
  42. 9 7
      web/building_saas/main/html/main.html
  43. 1 1
      web/building_saas/main/js/controllers/material_controller.js
  44. 53 97
      web/building_saas/main/js/models/calc_program.js
  45. 391 92
      web/building_saas/main/js/models/exportStandardInterface.js
  46. 103 0
      web/building_saas/main/js/models/importStandardInterface.js
  47. 43 20
      web/building_saas/main/js/models/main_consts.js
  48. 13 8
      web/building_saas/main/js/models/ration.js
  49. 1 0
      web/building_saas/main/js/models/ration_glj.js
  50. 8 4
      web/building_saas/main/js/views/fee_rate_view.js
  51. 149 1
      web/building_saas/main/js/views/glj_col.js
  52. 85 6
      web/building_saas/main/js/views/glj_view.js
  53. 7 0
      web/building_saas/main/js/views/glj_view_contextMenu.js
  54. 228 13
      web/building_saas/main/js/views/locate_view.js
  55. 387 0
      web/building_saas/main/js/views/material_adjust_view.js
  56. 57 27
      web/building_saas/main/js/views/project_glj_view.js
  57. 5 30
      web/building_saas/main/js/views/project_view.js
  58. 4 2
      web/building_saas/main/js/views/std_ration_lib.js
  59. 42 15
      web/building_saas/main/js/views/sub_view.js
  60. 1 0
      web/building_saas/main/js/views/zmhs_view.js
  61. 195 4
      web/building_saas/pm/html/project-management.html
  62. 166 0
      web/building_saas/pm/js/pm_import.js
  63. 258 159
      web/building_saas/pm/js/pm_newMain.js
  64. 13 4
      web/building_saas/pm/js/pm_share.js
  65. 78 66
      web/over_write/js/chongqing_2018.js
  66. 189 6
      web/over_write/js/neimenggu_2017.js
  67. 90 0
      web/users/html/login-sms.html
  68. 53 0
      web/users/html/login-ver.html
  69. 54 21
      web/users/html/login.html
  70. 37 26
      web/users/html/user-safe.html
  71. 171 74
      web/users/js/login.js
  72. 31 1
      web/users/js/user.js

+ 1 - 1
config/config.js

@@ -11,7 +11,7 @@ module.exports = {
             auth: {
                 "authdb": "admin"
             },
-            connectTimeoutMS: 50000,
+            connectTimeoutMS: 100000,
             useMongoClient: true
         }
     },

+ 2 - 2
config/db/db_manager.js

@@ -50,11 +50,11 @@ module.exports = {
         var config = require("../config.js");
         var dbURL = 'mongodb://' + config[env].server + ":" + config[env].port + '/scConstruct';
         if(config[env].dbURL){
-            mg.connect(config[env].dbURL,{connectTimeoutMS: 20000,useMongoClient: true});
+            mg.connect(config[env].dbURL,{connectTimeoutMS: 100000,useMongoClient: true});
         } else if(config[env].options){
             mg.connect(dbURL,config[env].options);
         }else {
-            mg.connect(dbURL,{connectTimeoutMS: 20000,useMongoClient: true});//useMongoClient': true*! //报 DeprecationWarning: `open()` is deprecated in mongoose这个错
+            mg.connect(dbURL,{connectTimeoutMS: 100000,useMongoClient: true});//useMongoClient': true*! //报 DeprecationWarning: `open()` is deprecated in mongoose这个错
         }
         var db = mg.connection;
         db.on("error",function (err) {

+ 1 - 0
config/gulpConfig.js

@@ -143,6 +143,7 @@ module.exports = {
         'web/building_saas/main/js/views/confirm_modal.js',
         'web/building_saas/main/js/views/zlfb_view.js',
         'web/building_saas/main/js/views/installation_fee_view.js',
+        'web/building_saas/main/js/views/material_adjust_view.js',
         'web/building_saas/main/js/views/project_glj_view.js',
         'web/building_saas/main/js/views/importBills.js',
         'public/web/rpt_tpl_def.js',

Разница между файлами не показана из-за своего большого размера
+ 1 - 0
lib/x2js/xml2json.min.js


+ 43 - 0
logs/online_logs.js

@@ -0,0 +1,43 @@
+/**
+ * Created by zhang on 2019/4/12.
+ */
+module.exports = {
+    saveOnlineTime:saveOnlineTime
+};
+
+let mongoose = require("mongoose");
+import moment from "moment";
+let logs_model = mongoose.model("online_logs");
+
+async function saveOnlineTime(req) {
+    let online_times = 0;
+    let end = + new Date();
+    try {
+        let interval_time = 10 * 60 *1000;
+        let start = req.session.online_start_time;
+        if(start === undefined) return req.session.online_start_time ==end;
+        let online_times =  end - start;
+        //1秒内只记一次就好
+        if(online_times < 500) return;//如果间隔太短,则忽略
+        if(online_times > interval_time ){//如果间隔超过有效时长,则不累加这次时长,从头开始算
+            req.session.online_start_time = end;
+            return
+        }
+        if(!req.session.sessionUser||!req.session.sessionCompilation) return;
+        let dataString = moment(end).format('YYYY-MM-DD');
+        let condition = {userID:req.session.sessionUser.id,compilationID:req.session.sessionCompilation._id,dateString:dataString};
+        let record = await logs_model.findOne(condition);
+        if(record){ //如果找到,则累加
+            await logs_model.update(condition,{$inc:{'online_times' : online_times }});
+        }else {//如果没找到,则新增一条记录
+            condition["online_times"] = online_times;
+            let today = moment(dataString).toDate();
+            condition["dateTime"] = +today;
+            await logs_model.create(condition);
+        }
+    }catch (e){
+        console.log("统计登录时间错误,online_times值:"+online_times);
+        console.log(e);
+    }
+    req.session.online_start_time = end;
+}

+ 2 - 1
modules/all_models/engineering_lib.js

@@ -77,7 +77,8 @@ let modelSchema = {
     //取费专业
     engineering:Number,
     //是否计算安装增加费
-    isInstall:{type: Boolean, default: false}
+    isInstall:{type: Boolean, default: false},
+    seq:Number//序列号
 };
 mongoose.model(collectionName, new Schema(modelSchema, {versionKey: false, collection: collectionName}));
 

+ 17 - 0
modules/all_models/online_logs.js

@@ -0,0 +1,17 @@
+/**
+ * Created by zhang on 2019/4/12.
+ */
+//用户在线时长统计
+
+let mongoose = require("mongoose");
+let Schema = mongoose.Schema;
+
+// 表结构
+let schema = {
+    userID: String,
+    compilationID: String,
+    dateString: String,
+    dateTime:Number,//dateString转换回毫秒数对应的数值,方便查询
+    online_times: Number
+};
+mongoose.model("online_logs", new Schema(schema, {versionKey: false}),"online_logs");

+ 18 - 3
modules/all_models/project_glj.js

@@ -65,6 +65,16 @@ let modelSchema = {
         type: Number,
         default: 0
     },
+    //不计税设备
+    no_tax_eqp:{
+        type: Number,
+        default: 0
+    },
+    //评标材料
+    is_eval_material:{
+        type: Number,
+        default: 0
+    },
     // 调整系数ID
     adjCoe: Number,
 
@@ -87,7 +97,6 @@ let modelSchema = {
     quantity: String,
     techQuantity:String,//技术措施项目消耗量
     subdivisionQuantity:String,//分部分项消耗量
-    // 不调价
     tenderPrice: String,//调整后价格
     // 显示组成物的消耗量
     consumption: String,
@@ -95,13 +104,19 @@ let modelSchema = {
     mix_ratio_id: Number,
     // 显示关联父级工料机code(组合物用)
     connect_code: String,
-    materialType: Number,   //三材类别
-    materialCoe: Number,    //三材系数
+    materialType: Number,//三材类别
+    materialCoe: Number,//三材系数
     // 是否主要材料 (0为否 1为是)
     is_main_material: {
         type: Number,
         default: 0
     },
+    is_info_adjust:{type: Number, default: 0},//是否造价信息差额调整
+    is_coe_adjust:{type: Number, default: 0},//是否价格指数调整
+    riskCoe:String,//风险系数
+    standardPrice:String,//standardPrice
+    FO:String,//基本价格指数
+    FI:String,//现行价格指数
     ratio_data: Schema.Types.Mixed,
     remark:String,
     originPlace:String,//产地

+ 13 - 0
modules/all_models/user.js

@@ -83,6 +83,19 @@ let schema = {
     used_list: {
         type: [userdList],
         default: []
+    },
+    // 是否邮箱已通过验证
+    isUserActive: Number,
+    // 是否只允许短信登录
+    isSmsLogin: {
+        type: Number,
+        default: 0
+    },
+    // 登录异常短信通知
+    isLoginValid: {
+        type: Number,
+        default: 0
     }
+
 };
 mongoose.model(collectionName, new Schema(schema, {versionKey: false}));

+ 5 - 1
modules/common/const/bills_fixed.js

@@ -58,7 +58,11 @@ const fixedFlag = {
     VISA: 27,
     ADDITIONAL_TAX: 28,
     //环境保护税
-    ENVIRONMENTAL_PROTECTION_TAX: 29
+    ENVIRONMENTAL_PROTECTION_TAX: 29,
+    //建设工程竣工档案编制费
+    PROJECT_COMPLETE_ARCH_FEE:30,
+    //住宅工程质量分户验收费
+    HOUSE_QUALITY_ACCEPT_FEE:31
 };
 
 export default fixedFlag;

+ 8 - 6
modules/complementary_ration_lib/models/compleRationModel.js

@@ -12,6 +12,7 @@ import async from 'async';
 let stdRationModel = require ('../../ration_repository/models/ration_item').Model;
 let counter = require('../../../public/counter/counter');
 const scMathUtil = require('../../../public/scMathUtil').getUtil();
+let gljUtil = require('../../../public/gljUtil');
 
 class CompleRatoinDao {
     async updateRation(userID, compilationId, updateData, callback){
@@ -157,8 +158,8 @@ class CompleRatoinDao {
             if(comGljIds.length > 0) {
                 comGljs = await complementaryGljModel.find({userId: userId, ID: {$in: comGljIds}});
             }
-            let gljDatas = stdGljs.concat(comGljs);
-            gljDatas.sort(function (a, b) {
+            let gljDatas = gljUtil.sortRationGLJ(stdGljs.concat(comGljs),true);
+          /*  gljDatas.sort(function (a, b) {
                 let aV = a.gljType + a.code,
                     bV = b.gljType + b.code;
                 if(aV > bV) {
@@ -167,7 +168,7 @@ class CompleRatoinDao {
                     return -1;
                 }
                 return 0;
-            });
+            });*/
             if(ration.jobContent && ration.jobContent.toString().trim() !== ''){
                 hintsArr.push(`<label class="nomargin font_blue">工作内容:`);
                 hintsArr = hintsArr.concat(ration.jobContent.split('\n'));
@@ -245,8 +246,8 @@ class CompleRatoinDao {
             if(stdGljIds.length > 0) {
                 stdGljs = await stdGljModel.find({ID: {$in: stdGljIds}});
             }
-            let gljDatas = stdGljs;
-            gljDatas.sort(function (a, b) {
+            let gljDatas =  gljUtil.sortRationGLJ(stdGljs,true);
+        /*    gljDatas.sort(function (a, b) {
                 let aV = a.gljType + a.code,
                     bV = b.gljType + b.code;
                 if(aV > bV) {
@@ -255,7 +256,8 @@ class CompleRatoinDao {
                     return -1;
                 }
                 return 0;
-            });
+            });*/
+            gljDatas = gljUtil.sortRationGLJ(gljDatas,true);
             if(ration.jobContent && ration.jobContent.toString().trim() !== ''){
                 hintsArr.push(`<label class="nomargin font_blue">工作内容:`);
                 hintsArr = hintsArr.concat(ration.jobContent.split('\n'));

+ 57 - 24
modules/complementary_ration_lib/models/searchModel.js

@@ -8,6 +8,7 @@ const stdGljModel = mongoose.model('std_glj_lib_gljList');
 const compleRationSectionTreeModel = mongoose.model('complementary_ration_section_tree');
 let stdSectionTreeModel = require ('../../ration_repository/models/ration_section_tree').Model;
 let stdRationModel = require ('../../ration_repository/models/ration_item').Model;
+let gljUtil = require('../../../public/gljUtil');
 
 const compleRationLib = 'compleRationLib';
 
@@ -15,27 +16,30 @@ class SearchDao{
     async getRationItem(userId, compilationId, rationRepIds, code, ID, callback){
         let ration = null;
         try{
+            let firstLib = rationRepIds.shift();//优先取第一个
+            if(rationRepIds.includes(firstLib)) {//去掉重复的库ID
+                rationRepIds.splice(rationRepIds.indexOf(firstLib), 1);
+            }
             if(rationRepIds.includes(compleRationLib)) {
                 rationRepIds.splice(rationRepIds.indexOf(compleRationLib), 1);
             }
-            let stdQuery = {rationRepId: {$in: rationRepIds}, code: code, $or: [{isDeleted: null}, {isDeleted: false}]};
-            if(ID){
-                stdQuery = {ID: ID, $or: [{isDeleted: null}, {isDeleted: false}]};
-            }
-            let stdRation = await stdRationModel.findOne(stdQuery);
-            if(isDef(stdRation)){
-                ration = stdRation._doc;
-                ration.type = 'std';
-            } else{
-                let compleQuery = {userId: userId, compilationId: compilationId, code: code, $or: [{deleteInfo: null}, {'deleteInfo.deleted': false}]};
+            if(firstLib == compleRationLib){//说明选中的是补充定额库
+                ration = await this.getCompleRation(userId,compilationId,code,ID);
+            }else {
+                firstLib = parseInt(firstLib);
+                let firstQuery = {rationRepId: firstLib, code: code, $or: [{isDeleted: null}, {isDeleted: false}]};
                 if(ID){
-                    compleQuery.ID = ID;
+                    firstQuery = {ID: ID, $or: [{isDeleted: null}, {isDeleted: false}]};
                 }
-                let compleRation = await compleRationModel.findOne(compleQuery);
-                if(isDef(compleRation)){
-                    ration = compleRation._doc;
-                    ration.type = 'complementary';
+                ration = await this.getStdRation(firstQuery);
+            }
+            if(ration == null){//选中的定额库或者默认的定额库中没有找到定额,才走常规的流程查找其它定额库
+                let stdQuery = {rationRepId: {$in: rationRepIds}, code: code, $or: [{isDeleted: null}, {isDeleted: false}]};
+                if(ID){
+                    stdQuery = {ID: ID, $or: [{isDeleted: null}, {isDeleted: false}]};
                 }
+                ration = await this.getStdRation(stdQuery);
+                if(ration == null) ration = await this.getCompleRation(userId,compilationId,code,ID);
             }
             if(isDef(ration)){
                 if (ration.type === 'std') {
@@ -61,7 +65,28 @@ class SearchDao{
         }
         return ration;
     }
-
+    async getCompleRation(userId,compilationId,code,ID){
+        let ration = null;
+        let compleQuery = {userId: userId, compilationId: compilationId, code: code, $or: [{deleteInfo: null}, {'deleteInfo.deleted': false}]};
+        if(ID){
+            compleQuery.ID = ID;
+        }
+        let compleRation = await compleRationModel.findOne(compleQuery);
+        if(isDef(compleRation)){
+            ration = compleRation._doc;
+            ration.type = 'complementary';
+        }
+        return ration
+    }
+    async getStdRation(query){
+        let ration = null;
+        let stdRation = await stdRationModel.findOne(query);
+        if(isDef(stdRation)){
+            ration = stdRation._doc;
+            ration.type = 'std';
+        }
+        return ration;
+    }
     //@param {Object}skip({std: Number, comple: Number})
     async findRation(userId, compilationId, rationRepId, keyword, skip, callback){
         //每次限制结果数
@@ -147,10 +172,13 @@ class SearchDao{
                 //对人材机进行排序
                 ration.rationGljList.sort(function (a, b) {
                     let gljA = gljIDMapping[a.gljId],
-                        gljB = gljIDMapping[b.gljId];
+                        gljB = gljIDMapping[b.gljId],
+                        seqs = gljUtil.getGljTypeSeq() ;
                     if(gljA && gljB){
-                        let aV = gljA.gljType + gljA.code,
-                            bV = gljB.gljType + gljB.code;
+                        let indA = seqs.indexOf(gljA.gljType)+"",
+                            indB = seqs.indexOf(gljB.gljType)+"";
+                        let aV = indA+gljA.gljType + gljA.code,
+                            bV = indB+gljB.gljType + gljB.code;
                         if(aV > bV) {
                             return 1;
                         } else if(aV < bV) {
@@ -159,17 +187,22 @@ class SearchDao{
                     }
                     return 0;
                 });
+
+                if(ration.jobContent && ration.jobContent.toString().trim() !== ''){
+                    hintsArr.push(`<label class="nomargin font_blue">工作内容:`);
+                    hintsArr = hintsArr.concat(ration.jobContent.split('\n'));
+                    hintsArr.push("</label>");
+                    hintsArr.push("");
+                }
+
                 for(let rationGlj of ration.rationGljList){
                     let glj = gljIDMapping[rationGlj.gljId];
                     if(glj){
-                        hintsArr.push(` ${glj.code} ${glj.name}${glj.specs ? ' ' + glj.specs : ''} ${glj.unit} ${rationGlj.consumeAmt}`);
+                        hintsArr.push(`<label class="nomargin ${glj.gljType==4?"font_blue":""}"> ${glj.code} ${glj.name}${glj.specs ? '&nbsp;&nbsp;&nbsp;' + glj.specs : ''}&nbsp;&nbsp&nbsp;${glj.unit}&nbsp;&nbsp;&nbsp;${rationGlj.consumeAmt}</label>`)
+                        //hintsArr.push(` ${glj.code} ${glj.name}${glj.specs ? ' ' + glj.specs : ''} ${glj.unit} ${rationGlj.consumeAmt}`);
                     }
                 }
                 hintsArr.push(`基价 元 ${ration.basePrice}`);
-                if(ration.jobContent && ration.jobContent.toString().trim() !== ''){
-                    hintsArr.push(`工作内容:`);
-                    hintsArr = hintsArr.concat(ration.jobContent.split('\n'));
-                }
                 if(ration.annotation && ration.annotation.toString().trim() !== ''){
                     hintsArr.push(`附注:`);
                     hintsArr = hintsArr.concat(ration.annotation.split('\n'));

+ 1 - 1
modules/glj/controllers/glj_controller.js

@@ -85,7 +85,7 @@ class GLJController extends BaseController {
         };
         try {
             // 可编辑的字段
-            let editableField = ['is_evaluate', 'unit_price.market_price', 'is_adjust_price', 'mix_ratio.consumption',
+            let editableField = ['is_evaluate', 'unit_price.market_price', 'is_adjust_price', 'mix_ratio.consumption','is_eval_material','no_tax_eqp','is_coe_adjust','is_info_adjust','FI','FO','standardPrice','riskCoe',
                 'supply', 'supply_quantity','delivery_address','delivery','materialType','materialCoe','is_main_material','originPlace','vender','qualityGrace','brand','remark'];
             if (editableField.indexOf(field) < 0) {
                 throw '对应字段不能编辑';

+ 6 - 11
modules/main/facade/ration_facade.js

@@ -146,11 +146,9 @@ async function insertNewRation(newData,defaultLibID,std,calQuantity) {//插入
         }
         newData.prefix = '';
         newData.from = std.type === 'complementary' ? 'cpt' : 'std';
-        if(defaultLibID !== std.rationRepId){//借
-            newData.prefix = '借';
-        }
-        else if(std.rationRepId === defaultLibID && newData.from === 'cpt') {
-            newData.prefix = '补';
+        if(defaultLibID !== std.rationRepId){//定额是默认定额库中的时,只显示编号;
+            newData.prefix = '借';//定额不是默认定额库中的、且不是补充定额库中的时, 在定额编号前显示“借”。
+            if(newData.from === 'cpt') newData.prefix = '补';//定额是补充定额库中的时,在定额编号前显示“补”;
         }
         if(std.feeType == undefined || std.feeType == null || std.feeType ==''){//定额取费专业为空的情况下,取项目属性中的定额取费专业ID
             newData.programID = await getProgramForProject(newData.projectID);
@@ -554,12 +552,9 @@ async function  updateRation(std,defaultLibID,rationID,billsItemID,projectID,cal
     ration.from = std.type === 'complementary' ? 'cpt' : 'std';
     //定额前缀 none:0, complementary:1, borrow: 2
     ration.prefix = '';
-    //借用优先级比补充高
-    if(std.rationRepId !== parseInt(defaultLibID)){//借用
-        ration.prefix = '借';
-    }
-    else if(std.rationRepId === defaultLibID && ration.from === 'cpt') {
-        ration.prefix = '补';
+    if(parseInt(defaultLibID) !== std.rationRepId){//定额是默认定额库中的时,只显示编号;
+        ration.prefix = '借';//定额不是默认定额库中的、且不是补充定额库中的时, 在定额编号前显示“借”。
+        if(ration.from === 'cpt') ration.prefix = '补';//定额是补充定额库中的时,在定额编号前显示“补”;
     }
     ration.rationAssList = createRationAss(std);//生成辅助定额
     if(cleanzmh==false){//如果是清空子目换算,即cleanzmh==true 保留定额工程量、工程量表达式、含量(分解系数)、取费专业(取费类别)

+ 2 - 0
modules/pm/controllers/pm_controller.js

@@ -611,6 +611,8 @@ module.exports = {
                     if (proj.projType === projType.tender) {
                         //设置工程专业
                         proj._doc.feeStandardName = proj.property.feeStandardName || '';
+                        //设置计税方法
+                        proj._doc.taxType = proj.property.taxType;
                     }
                     delete proj._doc.property;
                 }

+ 62 - 16
modules/pm/facade/pm_facade.js

@@ -615,10 +615,12 @@ function getTotalFee(bills, feeName) {
     return 0;
 }
 
-function summarizeToParent(parent, child) {
+function summarizeToParent(parent, child, fields = null) {
     const decimal = -2;
-    let costFields = ['engineeringCost', 'subEngineering', 'measure', 'safetyConstruction', 'other', 'charge', 'tax', 'estimate'];
-    for (let field of costFields) {
+    if (!fields) {
+        fields = ['engineeringCost', 'subEngineering', 'measure', 'safetyConstruction', 'other', 'charge', 'tax', 'estimate'];
+    }
+    for (let field of fields) {
         parent[field] = scMathUtil.roundTo(parseFloat(parent[field]) + parseFloat(child[field]), decimal);
     }
    /* const decimal = -2;
@@ -731,18 +733,37 @@ async function getTendersFeeInfo(tenders) {
     return IDMapping;
 }
 
-async function getSummaryInfo(projectIDs){
+async function getSummaryInfo(projectIDs, feeFields = null){
+    function initFees(obj, feeFields) {
+        for (let data of feeFields) {
+            obj[data.v] = 0;
+        }
+    }
+    if (!feeFields) {
+        feeFields = [
+            {k: billsFlags.ENGINEERINGCOST, v: 'engineeringCost'},
+            {k: billsFlags.SUB_ENGINERRING, v: 'subEngineering'},
+            {k: billsFlags.MEASURE, v: 'measure'},
+            {k: billsFlags.SAFETY_CONSTRUCTION, v: 'safetyConstruction'},
+            {k: billsFlags.OTHER, v: 'other'},
+            {k: billsFlags.CHARGE, v: 'charge'},
+            {k: billsFlags.TAX, v: 'tax'}
+        ];
+    }
     //ID与汇总信息映射
     let IDMapping = {};
     //固定清单类别与汇总金额字段映射
     let flagFieldMapping = {};
-    flagFieldMapping[billsFlags.ENGINEERINGCOST] = 'engineeringCost';
+    for (let data of feeFields) {
+        flagFieldMapping[data.k] = data.v;
+    }
+    /*flagFieldMapping[billsFlags.ENGINEERINGCOST] = 'engineeringCost';
     flagFieldMapping[billsFlags.SUB_ENGINERRING] = 'subEngineering';
     flagFieldMapping[billsFlags.MEASURE] = 'measure';
     flagFieldMapping[billsFlags.SAFETY_CONSTRUCTION] = 'safetyConstruction';
     flagFieldMapping[billsFlags.OTHER] = 'other';
     flagFieldMapping[billsFlags.CHARGE] = 'charge';
-    flagFieldMapping[billsFlags.TAX] = 'tax';
+    flagFieldMapping[billsFlags.TAX] = 'tax';*/
     let projects = await projectModel.find({ID: {$in : projectIDs}, projType: projectType.project, $or: [{deleteInfo: null}, {'deleteInfo.deleted': false}]});
     //设置建设项目的总建筑面积
     for(let project of projects){
@@ -758,7 +779,10 @@ async function getSummaryInfo(projectIDs){
                 }
             }
         }
-        IDMapping[project.ID] = {engineeringCost: 0, subEngineering: 0, measure: 0, safetyConstruction: 0, other: 0, charge: 0, tax: 0, estimate: 0, rate: 0, buildingArea: grossArea, perCost: ''};
+        //IDMapping[project.ID] = {engineeringCost: 0, subEngineering: 0, measure: 0, safetyConstruction: 0, other: 0, charge: 0, tax: 0, estimate: 0, rate: 0, buildingArea: grossArea, perCost: ''};
+        IDMapping[project.ID] = {estimate: 0, rate: 0, buildingArea: grossArea, perCost: ''};
+        initFees(IDMapping[project.ID], feeFields);
+        console.log(IDMapping[project.ID]);
     }
 
     //单项工程
@@ -768,7 +792,9 @@ async function getSummaryInfo(projectIDs){
     let engIDs = [];
     for(let eng of engineerings){
         engIDs.push(eng.ID);
-        IDMapping[eng.ID] = {engineeringCost: 0, subEngineering: 0, measure: 0, safetyConstruction: 0, other: 0, charge: 0, tax: 0, estimate: 0, rate: 0, buildingArea: '', perCost: ''};
+        //IDMapping[eng.ID] = {engineeringCost: 0, subEngineering: 0, measure: 0, safetyConstruction: 0, other: 0, charge: 0, tax: 0, estimate: 0, rate: 0, buildingArea: '', perCost: ''};
+        IDMapping[eng.ID] = {estimate: 0, rate: 0, buildingArea: '', perCost: ''};
+        initFees(IDMapping[eng.ID], feeFields);
     }
     if(engIDs.length > 0){
         tenders = await projectModel.find({ParentID: {$in : engIDs}, projType: projectType.tender, $or: [{deleteInfo: null}, {'deleteInfo.deleted': false}]});
@@ -777,16 +803,19 @@ async function getSummaryInfo(projectIDs){
     if(tenders.length > 0){
         for(let tender of tenders){
             tenderIDs.push(tender.ID);
-            IDMapping[tender.ID] = {engineeringCost: 0, subEngineering: 0, measure: 0, safetyConstruction: 0, other: 0, charge: 0, tax: 0, rate: 0, estimate: 0,
-                buildingArea: '', perCost: '',changeMark:tender.changeMark,property:tender.property};
+            /*IDMapping[tender.ID] = {engineeringCost: 0, subEngineering: 0, measure: 0, safetyConstruction: 0, other: 0, charge: 0, tax: 0, rate: 0, estimate: 0,
+                buildingArea: '', perCost: '',changeMark:tender.changeMark,property:tender.property};*/
+            IDMapping[tender.ID] = {rate: 0, estimate: 0, buildingArea: '', perCost: '', changeMark:tender.changeMark,property:tender.property};
+            initFees(IDMapping[tender.ID], feeFields);
             let buildingArea = getBuildingArea(tender.property.projectFeature);
             if(buildingArea){
                 IDMapping[tender.ID]['buildingArea'] = buildingArea;
             }
         }
-        //需要获取的清单固定类别综合合价:工程造价、分部分项、措施项目、安全文明施工专项、规费、其他项目、税金
-        let needFlags = [billsFlags.ENGINEERINGCOST, billsFlags.SUB_ENGINERRING, billsFlags.MEASURE,
-            billsFlags.SAFETY_CONSTRUCTION, billsFlags.CHARGE, billsFlags.OTHER, billsFlags.TAX];
+        //需要获取的清单固定类别综合合价:工程造价、分部分项、措施项目、安全文明施工专项、规费、其他项目、税金...
+        /*let needFlags = [billsFlags.ENGINEERINGCOST, billsFlags.SUB_ENGINERRING, billsFlags.MEASURE,
+            billsFlags.SAFETY_CONSTRUCTION, billsFlags.CHARGE, billsFlags.OTHER, billsFlags.TAX];*/
+        let needFlags = feeFields.map(data => data.k);
         //获取单位工程汇总金额需要用到的所有清单
         let allBills = await billsModel.find({projectID: {$in: tenderIDs}, 'flags.flag': {$in: needFlags}, $or: [{deleteInfo: null}, {'deleteInfo.deleted': false}]},
                                             '-_id projectID fees flags');
@@ -800,13 +829,15 @@ async function getSummaryInfo(projectIDs){
                 IDMapping[bills.projectID]['estimate'] = getTotalFee(bills, 'estimate');
             }
         }
+        let summaryFields = feeFields.map(data => data.v);
+        summaryFields.push('estimate');
         //进行单项工程级别的汇总
         for(let tender of tenders){
-            summarizeToParent(IDMapping[tender.ParentID], IDMapping[tender.ID]);
+            summarizeToParent(IDMapping[tender.ParentID], IDMapping[tender.ID], summaryFields);
         }
         //进行建设项目级别的汇总
         for(let eng of engineerings){
-            summarizeToParent(IDMapping[eng.ParentID], IDMapping[eng.ID]);
+            summarizeToParent(IDMapping[eng.ParentID], IDMapping[eng.ID], summaryFields);
         }
         //占造价比例、单方造价
         const rateDecimal = -2;
@@ -1090,7 +1121,22 @@ async function getProjectByGranularity(tenderID, granularity, userID, versionNam
     }
     constructionProject._doc.children = engineerings;
     //获取汇总信息
-    constructionProject._doc.summaryInfo = await getSummaryInfo([constructionProject.ID]);
+    let feeFields = [
+        {k: billsFlags.ENGINEERINGCOST, v: 'engineeringCost'},
+        {k: billsFlags.SUB_ENGINERRING, v: 'subEngineering'},
+        {k: billsFlags.MEASURE, v: 'measure'},
+        {k: billsFlags.SAFETY_CONSTRUCTION, v: 'safetyConstruction'},
+        {k: billsFlags.OTHER, v: 'other'},
+        {k: billsFlags.CHARGE, v: 'charge'},
+        {k: billsFlags.PROVISIONAL, v: 'provisional'},
+        {k: billsFlags.MATERIAL_PROVISIONAL, v: 'materialProvisional'},
+        {k: billsFlags.ENGINEERING_ESITIMATE, v: 'engineeringEstimate'},
+        {k: billsFlags.DAYWORK, v: 'daywork'},
+        {k: billsFlags.TURN_KEY_CONTRACT, v: 'turnKeyContract'},
+        {k: billsFlags.CLAIM_VISA, v: 'claimVisa'},
+        {k: billsFlags.TAX, v: 'tax'}
+    ];
+    constructionProject._doc.summaryInfo = await getSummaryInfo([constructionProject.ID], feeFields);
     //获取编制软件信息: 软件公司;软件名;版本号;授权信息; base64
     let product = await productModel.findOne({});
     let company = product.company || '珠海纵横创新软件有限公司',

+ 3 - 1
modules/ration_glj/facade/glj_calculate_facade.js

@@ -96,8 +96,10 @@ function generateRationName(ration,gljList) {
     let caption = ration.caption ? ration.caption:ration.name;
     if(ration.rationAssList && ration.rationAssList.length > 0){
         let ass = ration.rationAssList[0];
-        if(ass.actualValue != null && ass.actualValue != undefined ){
+        if( ass.isAdjust == 1 && ass.actualValue != null && ass.actualValue != undefined ){
             caption =  caption.replace('%s',ass.actualValue);
+        }else {
+            caption =  caption.replace('%s',ass.stdValue);//没勾选的时候要恢复成标准值
         }
     }
     let reNameList = [];

+ 26 - 25
modules/ration_glj/facade/ration_glj_facade.js

@@ -1,6 +1,31 @@
 /**
  * Created by chen on 2017/6/29.
  */
+module.exports = {
+    save: save,
+    getData: getData,
+    deleteByRation: deleteByRation,
+    getQuantityByProjectGLJ: getQuantityByProjectGLJ,
+    getLibInfo: getLibInfo,
+    getGLJData: getGLJData,
+    getGLJDataByCodes:getGLJDataByCodes,
+    addGLJ: addGLJ,
+    deleteGLJ:deleteGLJ,
+    insertAddTypeGLJ:insertAddTypeGLJ,
+    replaceGLJ: replaceGLJ,
+    replaceGLJByData:replaceGLJByData,
+    mReplaceGLJ: mReplaceGLJ,
+    updateRationGLJByEdit: updateRationGLJByEdit,
+    getGLJClass: getGLJClass,
+    insertGLJAsRation: insertGLJAsRation,
+    getRationTypeGLJQuantity:getRationTypeGLJQuantity,
+    getInfoFromProjectGLJ:getInfoFromProjectGLJ,
+    createNewRecord:createNewRecord,
+    getGLJSearchInfo:getGLJSearchInfo,
+    updateRationGLJFromDoc:updateRationGLJFromDoc,
+    getGLJLibByEngineerID:getGLJLibByEngineerID,
+    prepareExtData:prepareExtData
+}
 
 let mongoose = require('mongoose');
 const uuidV1 = require('uuid/v1');
@@ -29,31 +54,7 @@ const gljClassModel = mongoose.model('std_glj_lib_gljClass');
 const projectDao = require('../../pm/models/project_model').project;
 const compleClassModel = mongoose.model('complementary_glj_section');
 
-module.exports = {
-    save: save,
-    getData: getData,
-    deleteByRation: deleteByRation,
-    getQuantityByProjectGLJ: getQuantityByProjectGLJ,
-    getLibInfo: getLibInfo,
-    getGLJData: getGLJData,
-    getGLJDataByCodes:getGLJDataByCodes,
-    addGLJ: addGLJ,
-    deleteGLJ:deleteGLJ,
-    insertAddTypeGLJ:insertAddTypeGLJ,
-    replaceGLJ: replaceGLJ,
-    replaceGLJByData:replaceGLJByData,
-    mReplaceGLJ: mReplaceGLJ,
-    updateRationGLJByEdit: updateRationGLJByEdit,
-    getGLJClass: getGLJClass,
-    insertGLJAsRation: insertGLJAsRation,
-    getRationTypeGLJQuantity:getRationTypeGLJQuantity,
-    getInfoFromProjectGLJ:getInfoFromProjectGLJ,
-    createNewRecord:createNewRecord,
-    getGLJSearchInfo:getGLJSearchInfo,
-    updateRationGLJFromDoc:updateRationGLJFromDoc,
-    getGLJLibByEngineerID:getGLJLibByEngineerID,
-    prepareExtData:prepareExtData
-}
+
 
 let operationMap = {
     'ut_create': create_ration_glj,

+ 14 - 1
modules/users/controllers/cld_controller.js

@@ -148,6 +148,19 @@ class CLDController {
             response.json({error: 1, msg: err});
         }
     }
+
+    /**
+     * 获取编办列表
+     *
+     * @param request
+     * @param response
+     * @return {Promise.<void>}
+     */
+    async getCompilationList (request, response) {
+        let compilationModel = new CompilationModel();
+        const compilationList = await compilationModel.getList();
+        response.json({error: 0, msg: 'success', data: compilationList});
+    }
 }
 
-export default CLDController;
+export default CLDController;

+ 256 - 92
modules/users/controllers/login_controller.js

@@ -8,7 +8,10 @@
 import UserModel from "../models/user_model";
 import SettingModel from "../models/setting_model";
 import CompilationModel from "../models/compilation_model";
-// import Captcha from "../models/captcha";
+import LogModel from "../models/log_model";
+import LogType from "../../common/const/log_type_const";
+const SMS = require('../models/sms');
+const moment = require('moment');
 // 验证码
 const Captcha = require("../models/captcha");
 
@@ -22,12 +25,102 @@ class LoginController {
      * @return {void}
      */
     async index(request, response) {
-        let sessionUser = request.session.sessionUser;
-        if (sessionUser !== undefined && sessionUser.ssoId >= 0) {
-            return response.redirect("/pm");
-        }
+        // 判断是否有带token和ssoID参数
+        if (request.query.ssoID !== undefined && request.query.token !== undefined) {
+            let ssoID = request.query.ssoID;
+            let token = request.query.token;
+            let preferenceSetting = {};
+            let compilationList = [];
+            try {
+                let userModel = new UserModel();
+                // 调用接口验证登录信息
+                let responseData = await userModel.getInfoFromSSO2(ssoID, token);
+                // 先判断返回值是否为未激活状态
+                if ( responseData === '-3') {
+                    throw '因邮箱未完成认证,账号未激活;去<a href="https://sso.smartcost.com.cn" target="_blank">激活</a>。';
+                }
+                if ( responseData === '-2') {
+                    throw 'token已过期,请重新登录Z+获取';
+                }
+                responseData = JSON.parse(responseData);
+                if (typeof responseData !== 'object') {
+                    throw 'ssoId错误或token过期';
+                }
+
+                if (responseData.length <= 0) {
+                    throw '接口返回数据错误';
+                }
+                let userData = responseData[0];
+
+                // 判断用户是否开启了只使用短信登录
+                const userInfo = await userModel.findDataByAccount(userData.mobile);
+                if (userInfo !== undefined && userInfo !== null && userInfo.isSmsLogin === 1) {
+                    let renderData = {
+                        mobile: userData.mobile,
+                    };
+                    response.render('users/html/login-sms', renderData);
+                    return;
+                }
+
+                let sessionUser = {
+                    ssoId: userData.id,
+                    username: userData.username,
+                    email: userData.useremail,
+                    mobile: userData.mobile,
+                    isUserActive: userData.isUserActive,
+                };
+
+                request.session.sessionUser = sessionUser;
+                // 记录用户数据到数据库
+                let result = await userModel.markUser(sessionUser, request);
+
+                // 获取偏好设置
+                let settingModel = new SettingModel();
+                preferenceSetting = await settingModel.getPreferenceSetting(request.session.sessionUser.id);
+                if (!result) {
+                    throw '标记用户信息失败!';
+                }
+                let compilationModel = new CompilationModel();
+                if (preferenceSetting.login_ask === 1 || preferenceSetting.select_version === ''){
+                    preferenceSetting.login_ask = 1;
+                    compilationList = await  compilationModel.getList();
+                } else {
+                    compilationList = [];
+                }
+                // 获取编办信息
+                let sessionCompilation = request.session.sessionCompilation;
 
-        response.render('users/html/login', {});
+                if (preferenceSetting.login_ask === 0 && !sessionCompilation &&
+                    preferenceSetting.select_version !== '') {
+                    let compilationData = await compilationModel.getCompilationById(preferenceSetting.select_version);
+                    // 判断当前用户的是使用免费版还是专业版
+                    let compilationVersion = await userModel.getVersionFromUpgrade(sessionUser.ssoId, preferenceSetting.select_version);
+                    request.session.compilationVersion = compilationVersion;
+                    request.session.sessionCompilation = compilationData;
+                    if(request.session.sessionUser.latest_used !== preferenceSetting.select_version) await userModel.updateLatestUsed(request.session.sessionUser.id,preferenceSetting.select_version);
+                }
+                request.session.online_start_time = +new Date();
+                console.log(`${request.session.sessionUser.real_name}--id:${request.session.sessionUser.id}--登录了系统`);
+                if (preferenceSetting.login_ask === 1 || preferenceSetting.select_version === '') {
+                    let renderData = {
+                        versionData: compilationList,
+                    };
+                    response.render('users/html/login-ver', renderData);
+                } else {
+                    return response.redirect("/pm");
+                }
+            } catch (error) {
+                console.log(error)
+                return response.redirect("/login");
+            }
+        } else {
+            let sessionUser = request.session.sessionUser;
+            if (sessionUser !== undefined && sessionUser.ssoId >= 0) {
+                return response.redirect("/pm");
+            } else {
+                response.render('users/html/login', {});
+            }
+        }
     }
 
     /**
@@ -38,15 +131,39 @@ class LoginController {
      * @return {string}
      */
     async login(request, response) {
-        let account = request.body.account;
-        let password = request.body.pw;
         let preferenceSetting = {};
         let compilationList = [];
         try {
             let userModel = new UserModel();
+            let responseData = '';
+            if (request.body.account === undefined) {
+                let mobile = request.body.mobile;
+                let codeMsg = request.session.code;
+                if (codeMsg !== undefined && request.body.code !== '') {
+                    console.log(codeMsg);
+                    const validMobile = codeMsg.split('_')[0];
+                    const code = codeMsg.split('_')[1];
+                    const time = codeMsg.split('_')[2];
+                    if (validMobile !== mobile) {
+                        throw '短信验证码错误';
+                    }
+                    if (Date.parse(new Date())/1000 > time+60*5 || request.body.code !== code) {
+                        throw '短信验证码错误或已过期';
+                    } else {
+                        delete request.session.code;
+                    }
+                } else {
+                    throw '短信验证码错误或已过期。';
+                }
+                responseData = await userModel.getInfoFromSSOMobile(mobile);
+            } else {
+                let account = request.body.account;
+                let password = request.body.pw;
+
+                // 调用接口验证登录信息
+                responseData = await userModel.getInfoFromSSO(account, password);
+            }
 
-            // 调用接口验证登录信息
-            let responseData = await userModel.getInfoFromSSO(account, password);
             // 先判断返回值是否为未激活状态
             if ( responseData === '-3') {
                 throw '因邮箱未完成认证,账号未激活;去<a href="https://sso.smartcost.com.cn" target="_blank">激活</a>。';
@@ -68,47 +185,63 @@ class LoginController {
             }
 
             //还要判断account是否是专业版用户
-            let isPro = false;
-            const userInfo = await userModel.findDataByAccount(account);
-
-            if (userInfo && userInfo.upgrade_list !== undefined) {
-                for (const ul of userInfo.upgrade_list) {
-                    if (ul.isUpgrade === true) {
-                        isPro = true;
-                        break;
-                    }
-                }
-            }
-            // 专业版短信验证码验证
-            if (isPro) {
-                const codeMsg = request.session.code;
-                if (codeMsg !== undefined && request.body.code !== '') {
-                    const code = codeMsg.split('_')[0];
-                    const time = codeMsg.split('_')[1];
-                    console.log(code);
-                    console.log(request.body.code);
-                    if (Date.parse(new Date())/1000 > time+60*5 || request.body.code !== code) {
-                        return response.json({error: 3, msg: '验证码错误。'});
-                    } else {
-                        delete request.session.code;
-                    }
-                } else {
-                    return response.json({error: 3, msg: '验证码错误。'});
-                }
-            }
+            // let isPro = false;
+            // const userInfo = await userModel.findDataByAccount(account);
+            //
+            // if (userInfo && userInfo.upgrade_list !== undefined) {
+            //     for (const ul of userInfo.upgrade_list) {
+            //         if (ul.isUpgrade === true) {
+            //             isPro = true;
+            //             break;
+            //         }
+            //     }
+            // }
+            // // 专业版短信验证码验证
+            // if (isPro) {
+            //     const codeMsg = request.session.code;
+            //     if (codeMsg !== undefined && request.body.code !== '') {
+            //         const code = codeMsg.split('_')[0];
+            //         const time = codeMsg.split('_')[1];
+            //         console.log(code);
+            //         console.log(request.body.code);
+            //         if (Date.parse(new Date())/1000 > time+60*5 || request.body.code !== code) {
+            //             return response.json({error: 3, msg: '验证码错误。'});
+            //         } else {
+            //             delete request.session.code;
+            //         }
+            //     } else {
+            //         return response.json({error: 3, msg: '验证码错误。'});
+            //     }
+            // }
 
             // 判断极验验证码是否通过
             const captcha = new Captcha();
             const captchResult = await captcha.validate(request);
+            console.log(captchResult);
             if (!captchResult) {
-                throw '验证码错误';
+                throw '极验验证码错误';
+            }
+
+            // 判断用户是否开启了只使用短信登录
+            const userInfo = await userModel.findDataByAccount(userData.mobile);
+            if (request.body.mobile === undefined && request.body.code === undefined && userInfo !== undefined && userInfo !== null && userInfo.isSmsLogin === 1) {
+                return response.json({error: 3, msg: '只能手机短信登录。', data: userData.mobile});
             }
+            //     for (const ul of userInfo.upgrade_list) {
+            //         if (ul.isUpgrade === true) {
+            //             isPro = true;
+            //             break;
+            //         }
+            //     }
+            // }
+
 
             let sessionUser = {
                 ssoId: userData.id,
                 username: userData.username,
                 email: userData.useremail,
                 mobile: userData.mobile,
+                isUserActive: userData.isUserActive,
             };
 
             request.session.sessionUser = sessionUser;
@@ -142,10 +275,41 @@ class LoginController {
                 if(request.session.sessionUser.latest_used !== preferenceSetting.select_version) await userModel.updateLatestUsed(request.session.sessionUser.id,preferenceSetting.select_version);
             }
 
+            // 登录异常短信提醒功能
+            const userinfo2 = await userModel.findDataByAccount(userData.mobile);
+            if (userinfo2.isLoginValid === 1) {
+                // 获取本次访问ip
+                let ip = request.connection.remoteAddress;
+                ip = ip.split(':');
+                ip = ip[3] === undefined ? '' : ip[3];
+                let logModel = new LogModel();
+                let logCount = await logModel.count();
+                logCount = logCount > 30 ? 30 : logCount;
+                let page = 1;
+                const loginList = await logModel.getLog(request.session.sessionUser.id, LogType.LOGIN_LOG, page, logCount);
+                let messageFlag = true;
+                for (const [index,log] of loginList.entries()) {
+                    if (log.message.ip === ip && index !== 0) {
+                        messageFlag = false;
+                        break;
+                    }
+                }
+                messageFlag = true;
+                if (messageFlag) {
+                    // 发送短信
+                    const Sms = new SMS();
+                    const logInfo = loginList[0];
+                    await Sms.sendLoginMsg(userData.mobile, request.session.sessionUser.real_name, moment(logInfo.create_time).format('YYYY-MM-DD'), moment(logInfo.create_time).format('HH:mm:ss'), logInfo.message.ip_info, logInfo.message.ip);
+                    // const msg = '【纵横云计价】您的账号(账号昵称:'+ request.session.sessionUser.real_name +')于' + moment(logInfo.create_time).format('YYYY-MM-DD HH:mm:ss') + '在' + logInfo.message.ip_info + '(' + logInfo.message.ip + ')' + '登录成功。纵横云计价提醒您,如果怀疑此次登录行为有异常,请及时修改账号密码。';
+                    // console.log(msg);
+                }
+            }
+
         } catch (error) {
             console.log(error);
             return response.json({error: 1, msg: error});
         }
+        request.session.online_start_time = +new Date();
         console.log(`${request.session.sessionUser.real_name}--id:${request.session.sessionUser.id}--登录了系统`);
         response.json({
             error: 0,
@@ -175,59 +339,59 @@ class LoginController {
      * @param response
      * @returns {Promise<void>}
      */
-    async accountIsPro(request, response) {
-        let res = {
-            error: 0,
-            msg: '',
-            result: false,
-        };
-        try{
-            const account = request.body.account;
-            const password = request.body.pw;
-
-            // 根据邮箱或手机号获取账号信息
-            let userModel = new UserModel();
-            // 调用接口验证登录信息
-            let responseData = await userModel.getInfoFromSSO(account, password);
-            // 先判断返回值是否为未激活状态
-            if ( responseData === '-3') {
-                throw '因邮箱未完成认证,账号未激活;去<a href="https://sso.smartcost.com.cn" target="_blank">激活</a>。';
-            }
-            responseData = JSON.parse(responseData);
-            if (typeof responseData !== 'object') {
-                throw '邮箱/手机 或 密码错误';
-            }
-
-            if (responseData.length <= 0) {
-                throw '接口返回数据错误';
-            }
-
-            // 正确登录后 存入session
-            let userData = responseData[0];
-
-            if (userData.mobile === '') {
-                return response.json({error: 2,ssoId: userData.id});
-            }
-
-            const userInfo = await userModel.findDataByAccount(account);
-            if (userInfo && userInfo.upgrade_list !== undefined) {
-                for (const ul of userInfo.upgrade_list) {
-                    if (ul.isUpgrade === true) {
-                        res.result = true;
-                        res.data = userInfo.mobile;
-                        break;
-                    }
-                }
-            } else {
-                res.msg = '当前未存在此用户';
-            }
-        } catch (err) {
-            res.error = 1;
-            res.msg = err;
-        }
-
-        response.json(res);
-    }
+    // async accountIsPro(request, response) {
+    //     let res = {
+    //         error: 0,
+    //         msg: '',
+    //         result: false,
+    //     };
+    //     try{
+    //         const account = request.body.account;
+    //         const password = request.body.pw;
+    //
+    //         // 根据邮箱或手机号获取账号信息
+    //         let userModel = new UserModel();
+    //         // 调用接口验证登录信息
+    //         let responseData = await userModel.getInfoFromSSO(account, password);
+    //         // 先判断返回值是否为未激活状态
+    //         if ( responseData === '-3') {
+    //             throw '因邮箱未完成认证,账号未激活;去<a href="https://sso.smartcost.com.cn" target="_blank">激活</a>。';
+    //         }
+    //         responseData = JSON.parse(responseData);
+    //         if (typeof responseData !== 'object') {
+    //             throw '邮箱/手机 或 密码错误';
+    //         }
+    //
+    //         if (responseData.length <= 0) {
+    //             throw '接口返回数据错误';
+    //         }
+    //
+    //         // 正确登录后 存入session
+    //         let userData = responseData[0];
+    //
+    //         if (userData.mobile === '') {
+    //             return response.json({error: 2,ssoId: userData.id});
+    //         }
+    //
+    //         const userInfo = await userModel.findDataByAccount(account);
+    //         if (userInfo && userInfo.upgrade_list !== undefined) {
+    //             for (const ul of userInfo.upgrade_list) {
+    //                 if (ul.isUpgrade === true) {
+    //                     res.result = true;
+    //                     res.data = userInfo.mobile;
+    //                     break;
+    //                 }
+    //             }
+    //         } else {
+    //             res.msg = '当前未存在此用户';
+    //         }
+    //     } catch (err) {
+    //         res.error = 1;
+    //         res.msg = err;
+    //     }
+    //
+    //     response.json(res);
+    // }
 
 }
 

+ 36 - 1
modules/users/controllers/sms_controller.js

@@ -5,6 +5,7 @@
  * @date 2018/4/17
  * @version
  */
+import UserModel from "../models/user_model";
 import SmsModel from "../models/sms_model";
 const SMS = require('../models/sms');
 
@@ -34,7 +35,8 @@ class SmsController {
                 // if (parseInt(JSON.stringify(returnStatus).statusCode) !== 200) {
                 //     throw '短信发送失败!';
                 // }
-                request.session.code = code + '_' + Date.parse(new Date())/1000;
+                request.session.code = mobile + '_' + code + '_' + Date.parse(new Date())/1000;
+                // console.log(request.session.code);
             } else {
                 let returnStatus = await smsModel.sendSmsFromSSO(mobile, type);
                 if (returnStatus === null) {
@@ -87,6 +89,39 @@ class SmsController {
 
         response.json(responseData);
     }
+
+    /**
+     * 检测通行账号是否已存在该手机号码注册
+     *
+     * @param {object} request
+     * @param {object} response
+     * @return {void}
+     */
+    async checkMobile (request, response) {
+        let responseData = {
+            err: 0,
+            msg: ''
+        };
+        let mobile = request.body.mobile;
+        try {
+            let userModel = new UserModel();
+            let responseData = await userModel.getInfoFromSSOMobile(mobile, 0);
+            if ( responseData === '-2') {
+                throw '参数有误或手机号码不正确';
+            }
+            responseData = JSON.parse(responseData);
+            if (typeof responseData !== 'object') {
+                throw '该手机号未注册通行账号';
+            }
+            responseData.msg = 'success';
+        } catch (error) {
+            console.log(error);
+            responseData.err = 1;
+            responseData.msg = error;
+        }
+
+        response.json(responseData);
+    }
 }
 
 export default SmsController;

+ 41 - 1
modules/users/controllers/user_controller.js

@@ -101,7 +101,8 @@ class UserController extends BaseController {
             // 获取登录信息
             let logModel = new LogModel();
             let logCount = await logModel.count();
-            let pageSize = 3;
+            logCount = logCount > 30 ? 30 : logCount;
+            let pageSize = 10;
             logList = await logModel.getLog(sessionUser.id, LogType.LOGIN_LOG, page, pageSize);
 
             // 分页数据
@@ -341,6 +342,45 @@ class UserController extends BaseController {
         }
     }
 
+    /*
+    * 更改用户账号登录方式
+    * */
+    async changeIsSmsLogin(request, response) {
+        try{
+            let status = request.body.status;
+            let userModel = new UserModel();
+            let userId = request.session.sessionUser.id;
+            let result = await userModel.updateUser({ _id: userId }, { isSmsLogin: status });
+            if (result) {
+                response.json({error: 0, msg: 'success', data: null});
+            } else {
+                throw '更新失败';
+            }
+        }
+        catch(error){
+            response.json({error: 1, msg: error, data: null});
+        }
+    }
+
+    /*
+    * 更改异常登录通知
+    * */
+    async changeIsLoginValid(request, response) {
+        try{
+            let status = request.body.status;
+            let userModel = new UserModel();
+            let userId = request.session.sessionUser.id;
+            let result = await userModel.updateUser({ _id: userId }, { isLoginValid: status });
+            if (result) {
+                response.json({error: 0, msg: 'success', data: null});
+            } else {
+                throw '更新失败';
+            }
+        }
+        catch(error){
+            response.json({error: 1, msg: error, data: null});
+        }
+    }
 }
 
 export default UserController;

+ 2 - 0
modules/users/models/engineering_lib_model.js

@@ -7,6 +7,7 @@
  */
 import mongoose from "mongoose";
 import BaseModel from "../../common/base/base_model";
+let _ = require('lodash');
 
 class EngineeringLibModel extends BaseModel {
     /**
@@ -44,6 +45,7 @@ class EngineeringLibModel extends BaseModel {
         // 查找对应的id数据
         let condition = {visible:true,valuationID: {"$in": valuationIDs}};
         let engineeringLibList = await this.findDataByCondition(condition,null,false);
+        engineeringLibList = _.sortBy(engineeringLibList,'seq');
         engineeringLibList = JSON.parse(JSON.stringify(engineeringLibList));
         // 组合计价规则id对应专业工程标准库数据
         let engineeringLib = {};

+ 3 - 2
modules/users/models/log_model.js

@@ -63,7 +63,8 @@ class LogModel extends BaseModel {
         ip = ip.split(':');
         ip = ip[3] === undefined ? '' : ip[3];
 
-        let ipInfo = '127.0.0.1';//await this.getIpInfoFromApi(ip);
+        // let ipInfo = '127.0.0.1';//await this.getIpInfoFromApi(ip);
+        let ipInfo = await this.getIpInfoFromApi(ip);
 
         let userAgentObject = new UAParser(request.headers['user-agent']);
         let osInfo = userAgentObject.getOS();
@@ -153,4 +154,4 @@ class LogModel extends BaseModel {
     }
 }
 
-export default LogModel;
+export default LogModel;

+ 41 - 2
modules/users/models/sms.js

@@ -23,7 +23,6 @@ class SMS {
         this.url = 'http://www.sendcloud.net/smsapi/send';
         this.smsUser = 'smartcost';
         this.smskey = 'kuGmqTt10n6vBXivhxXsAuG8aoCsQ1x6';
-        this.templateId = 25595;
     }
 
     /**
@@ -37,7 +36,7 @@ class SMS {
         try {
             const formData = {
                 smsUser: this.smsUser,
-                templateId: this.templateId,
+                templateId: 25595,
                 msgType: 0,
                 phone: mobile,
                 vars: '{"%code%":'+ code +'}',
@@ -72,6 +71,46 @@ class SMS {
         }
     }
 
+    async sendLoginMsg(mobile, name, date, time, local, ip) {
+        console.log(mobile, name, time, local, ip);
+        try {
+            const formData = {
+                smsUser: this.smsUser,
+                templateId: 27561,
+                msgType: 0,
+                phone: mobile,
+                vars: '{"%name%": "' + name + '", "%date%": "' + date + '", "%time%": "' + time + '", "%local%": "' + local + '", "%IP%": "' + ip + '"}',
+            };
+            const signature = await this.getSignature(this.sortDict(formData), this.smskey);
+            formData.signature = signature;
+
+            let postData = {
+                url: this.url,
+                form: formData,
+                encoding: 'utf8'
+            };
+
+            return new Promise(function (resolve, reject) {
+                try {
+                    // 请求接口
+                    Request.post(postData, function (err, postResponse, body) {
+                        if (err) {
+                            throw '请求错误';
+                        }
+                        if (postResponse.statusCode !== 200) {
+                            throw '短信发送失败!';
+                        }
+                        resolve(body);
+                    });
+                } catch (error) {
+                    reject([]);
+                }
+            });
+        } catch (error) {
+            console.log(error);
+        }
+    }
+
     md5(data) {
         var str = data;
         return crypto.createHash("md5").update(str).digest("hex");

+ 71 - 1
modules/users/models/user_model.js

@@ -81,6 +81,74 @@ class UserModel extends BaseModel {
     }
 
     /**
+     * 根据用户手机号码调用SSO接口获取信息
+     *
+     * @param {string} mobile
+     * @param {string} login 1为登录,不存在则是查询
+     * @return {object}
+     */
+    async getInfoFromSSOMobile(mobile, login = '1') {
+        const fromData = {account: mobile};
+        if (login === '1') {
+            fromData.login = 1;
+        }
+        let postData = {
+            url: 'http://sso.smartcost.com.cn/building/api/mobile/login',
+            form: fromData,
+            encoding: 'utf8'
+        };
+        return new Promise(function (resolve, reject) {
+            try {
+                // 请求接口
+                Request.post(postData, function (err, postResponse, body) {
+                    if (err) {
+                        console.log('111');
+                        throw '请求错误';
+                    }
+                    if (postResponse.statusCode !== 200) {
+                        throw '通行证验证失败!';
+                    }
+                    resolve(body);
+                });
+            } catch (error) {
+                reject([]);
+            }
+        });
+    }
+
+    /**
+     * 根据用户id和token调用SSO接口获取信息
+     *
+     * @param {string} username
+     * @param {string} password
+     * @return {object}
+     */
+    async getInfoFromSSO2(ssoID, token) {
+        let postData = {
+            url: 'http://sso.smartcost.com.cn/building/api/login/auth',
+            form: {ssoID: ssoID, token: token},
+            encoding: 'utf8'
+        };
+        return new Promise(function (resolve, reject) {
+            try {
+                // 请求接口
+                Request.post(postData, function (err, postResponse, body) {
+                    if (err) {
+                        console.log('111');
+                        throw '请求错误';
+                    }
+                    if (postResponse.statusCode !== 200) {
+                        throw '通行证验证失败!';
+                    }
+                    resolve(body);
+                });
+            } catch (error) {
+                reject([]);
+            }
+        });
+    }
+
+    /**
      * 标记用户
      *
      * @param {object} userData
@@ -105,7 +173,8 @@ class UserModel extends BaseModel {
                 email : userData.email,
                 mobile : userData.mobile,
                 ssoId : userData.ssoId,
-                latest_login:userData.latest_login
+                latest_login:userData.latest_login,
+                isUserActive: userData.isUserActive,
             };
             let updateResult = await this.updateUser(condition,UpdateData);
             if (updateResult.ok === 1) {
@@ -208,6 +277,7 @@ class UserModel extends BaseModel {
             mobile: userData.mobile,
             create_time: new Date().getTime(),
             latest_login: new Date().getTime(),
+            isUserActive: userData.isUserActive,
         };
         return this.db.create(insertData);
     }

+ 3 - 1
modules/users/routes/cld_route.js

@@ -20,7 +20,9 @@ module.exports = function (app) {
 
     router.get('/getUsersAndCompilation', cldController.getUsersAndCompilationList);
 
+    router.get('/getCompilationList', cldController.getCompilationList);
+
     router.post('/setUserUpgrade', cldController.setUsersUpgrade);
 
     app.use('/cld',router)
-};
+};

+ 1 - 1
modules/users/routes/login_route.js

@@ -19,7 +19,7 @@ module.exports = function (app) {
 // 登录操作
     router.post('/login', loginController.login);
 
-    router.post('/accountIsPro', loginController.accountIsPro);
+    // router.post('/accountIsPro', loginController.accountIsPro);
 
     // 验证码相关
     router.get('/captcha', loginController.captcha);

+ 2 - 1
modules/users/routes/sms_route.js

@@ -16,6 +16,7 @@ module.exports = function (app) {
 // action定义区域
     router.post('/code', smsController.code);
     router.post('/mobile', smsController.setMobile);
+    router.post('/check/mobile', smsController.checkMobile);
 
     app.use('/sms',router);
-};
+};

+ 3 - 1
modules/users/routes/user_route.js

@@ -24,5 +24,7 @@ module.exports = function (app) {
     router.post('/getUsers', userController.init, userController.getUsers);
 
     router.post('/getVersionInfo', userController.init, userController.getVersionInfo);
+    router.post('/change/isSmsLogin', userController.init, userController.changeIsSmsLogin);
+    router.post('/change/isLoginValid', userController.init, userController.changeIsLoginValid);
     app.use('/user',router);
-};
+};

+ 6 - 1
public/gljUtil.js

@@ -16,7 +16,8 @@ module.exports = {
     getMarketPrice:getMarketPrice,
     getBasePrice:getBasePrice,
     getAdjustPrice:getAdjustPrice,
-    getGljTypeSeq:getGljTypeSeq
+    getGljTypeSeq:getGljTypeSeq,
+    sortRationGLJ:sortRationGLJ
 };
 
 function calcProjectGLJQuantity(projectGLJDatas,rationGLJDatas,rationDatas,billsDatas,q_decimal) {
@@ -45,4 +46,8 @@ function calcPriceDiff(glj,calcOptions) {
 
 function getGljTypeSeq() {
     return gljNodeUtil.getGljTypeSeq();
+}
+
+function sortRationGLJ(list,std) {
+    return gljNodeUtil.sortRationGLJ(list,std);
 }

+ 2 - 0
public/web/common_util.js

@@ -58,9 +58,11 @@ function seqString(num,length){
 };
 
 function customRowHeader(sheet, dataLength) {
+    sheet.suspendPaint();   //提升焦点变换性能 2019年4月12日
     for (let i = 0; i < dataLength; i++) {
         sheet.setValue(i, 0, `F${i + 1}`, GC.Spread.Sheets.SheetArea.rowHeader);
     }
+    sheet.resumePaint();    //提升焦点变换性能 2019年4月12日
 };
 
 function changePropNames(object, oldNames, newNames) {

+ 5 - 3
public/web/gljUtil.js

@@ -236,6 +236,7 @@ let gljUtil = {
             return scMathUtil.roundForObj(glj.unit_price.base_price,tem_decimal);
         }
     },
+    //这个函数isRadio目前看来没有用,如果后续改了方法,导致isRadio的值会影响结果的话,需要调整调用这个方法的地方.
     getAdjustPrice:function (glj,projectGLJDatas,calcOptions,labourCoeDatas,decimalObj,isRadio,_,scMathUtil) {
         let decimal = decimalObj.glj.unitPrice;
         let price_hasM_decimal = decimalObj.glj.unitPriceHasMix?decimalObj.glj.unitPriceHasMix:decimalObj.glj.unitPrice;
@@ -302,10 +303,11 @@ let gljUtil = {
                 gljType.MACHINE_COMPOSITION,gljType.MACHINE_LABOUR,gljType.FUEL_POWER_FEE,gljType.DEPRECIATION_FEE,gljType.INSPECTION_FEE,gljType.MAINTENANCE,
                 gljType.DISMANTLING_FREIGHT_FEE,gljType.VERIFICATION_FEE,gljType.OTHER_FEE,gljType.EQUIPMENT,gljType.MANAGEMENT_FEE,gljType.PROFIT,gljType.GENERAL_RISK_FEE]
     },
-    sortRationGLJ:function (list) {
+    sortRationGLJ:function (list,std) {
         list = _.sortByAll(list, [function (item) {
-            return _.indexOf(gljTypeSeq,item.type)
-        }, "code"])
+            let typeField = std == true?"gljType":"type";
+            return _.indexOf(gljUtil.getGljTypeSeq(),item[typeField])
+        }, "code"]);
         return list;
     },
     getTotalQuantity:function(glj,ration,rd,gd){

+ 22 - 6
public/web/slideResize.js

@@ -15,6 +15,14 @@
 * */
 
 const SlideResize = (function() {
+    //函数防抖
+    let timer = null;
+    function deBounce(fn, wait) {
+        if (timer) {
+            clearTimeout(timer);
+        }
+        timer = setTimeout(fn, wait);
+    }
     //设置水平拖动条的宽度
     //@param {Object dom}resize滚动条
     function setResizeWidth (resize) {
@@ -81,10 +89,12 @@ const SlideResize = (function() {
                 eleObj.right.css('width', rightPercentWidth);
                 setResizeWidth(eleObj.resize);
                 mouseMoveCount += Math.abs(moveSize);
-                if (mouseMoveCount > triggerCBSize && callback) {
-                    callback();
-                    mouseMoveCount = 0;
-                }
+                deBounce(function () {
+                    if (callback) {
+                        callback();
+                        mouseMoveCount = 0;
+                    }
+                }, 30)
             }
         });
         $('body').mouseup(function (e) {
@@ -159,10 +169,16 @@ const SlideResize = (function() {
                 //设置下部分div内spread高度
                 eleObj.bottomSpread.height(bottomChange - limit.notBottomSpread);
                 mouseMoveCount += Math.abs(moveSize);
-                if (mouseMoveCount > triggerCBSize && callback) {
+                deBounce(function () {
+                    if (callback) {
+                        callback();
+                        mouseMoveCount = 0;
+                    }
+                }, 30);
+                /*if (mouseMoveCount > triggerCBSize && callback) {
                     callback();
                     mouseMoveCount = 0;
-                }
+                }*/
             }
         });
         $('body').mouseup(function (e) {

+ 0 - 6
public/web/tree_sheet/tree_sheet_helper.js

@@ -557,11 +557,6 @@ var TREE_SHEET_HELPER = {
                 $editor.children('div').height(cellRect.height+2);
                 return $editor;
             };
-
-            TreeNodeCellType.prototype.deactivateEditor = function (editorContext, context) {
-                    console.log("hehe")
-            };
-
             TreeNodeCellType.prototype.updateEditor = function (editorContext, cellStyle, cellRect, context) {
                 let me = this;
                 $(editorContext).append(`<div><ul class="es-list" style="display: block;"></ul></div>`);//<li class="" data-value="" style=""> <br></li> //background-color:${cellStyle.backColor}
@@ -595,7 +590,6 @@ var TREE_SHEET_HELPER = {
                     });
                     if( $('#esInput').val()) setCursor( $("#esInput")[0], $('#esInput').val().length)
                 });
-                console.log("update")
             };
 
             TreeNodeCellType.prototype.processKeyDown = function (e, context){ //当用isReservedKey 不拦截编辑模式时的input框ctrl + c时,在非编辑模式的ctrl + c事件也需要自已处理,所以在这里再调用一下复制命令

+ 23 - 13
server.js

@@ -23,6 +23,8 @@ fileUtils.getGlobbedFiles('./modules/all_models/*.js').forEach(function(modelPat
 //config.setupCache();
 let cfgCacheUtil = require("./config/cacheCfg");
 cfgCacheUtil.setupDftCache();
+let online_logs = require("./logs/online_logs");
+
 
 let app = express();
 let _rootDir = __dirname;
@@ -54,32 +56,40 @@ app.use(session({
 }));
 
 // 登录状态全局判断
-app.use(function (req, res, next) {
+app.use(async function (req, res, next) {
     let url = req.originalUrl;
-    if (/^\/login/.test(url) || /\.map|\.ico$/.test(url) || /^\/sms/.test(url) || /^\/cld/.test(url) || /^\/captcha/.test(url)  || /^\/accountIsPro/.test(url)) {
+    // if (/^\/login/.test(url) || /\.map|\.ico$/.test(url) || /^\/sms/.test(url) || /^\/cld/.test(url) || /^\/captcha/.test(url)  || /^\/accountIsPro/.test(url)) {
+    if (/^\/login/.test(url) || /\.map|\.ico$/.test(url) || /^\/sms/.test(url) || /^\/cld/.test(url) || /^\/captcha/.test(url)) {
         // 如果是登录页面或短信接口或cld接口则忽略判断数据
         next();
     } else {
         try {
-            // 判断session
-            let sessionUser = req.session.sessionUser;
-            if (!sessionUser) {
-                //处理 ajax 请求 session 过期问题
-                if (req.headers["x-requested-with"] != null
-                    && req.headers["x-requested-with"] == "XMLHttpRequest"
-                    && req.url != "/login") {
-                    return res.json({ret_code: 99, ret_msg: '登录信息失效,请您重新登录'});
-                }else {
-                    throw 'session error';
+            if (req.query.ssoID !== undefined && req.query.ssoID !== null && req.query.token !== undefined && req.query.token !== null) {
+                delete req.session.sessionUser;
+                delete req.session.sessionCompilation;
+                return res.redirect('/login' + url);
+            } else {
+                // 判断session
+                let sessionUser = req.session.sessionUser;
+                if (!sessionUser) {
+                    //处理 ajax 请求 session 过期问题
+                    if (req.headers["x-requested-with"] != null
+                        && req.headers["x-requested-with"] == "XMLHttpRequest"
+                        && req.url != "/login") {
+                        return res.json({ret_code: 99, ret_msg: '登录信息失效,请您重新登录'});
+                    } else {
+                        throw 'session error';
+                    }
                 }
+                res.locals.sessionUser = sessionUser;
             }
-            res.locals.sessionUser = sessionUser;
         } catch (error) {
             // 最后一个页面存入session
             req.session.lastPage = url;
             return res.redirect('/login');
         }
         next();
+        await online_logs.saveOnlineTime(req);//记录登录时长
     }
 });
 

+ 27 - 15
web/building_saas/complementary_glj_lib/js/glj.js

@@ -25,6 +25,10 @@ $(document).ready(function () {
     rightElesObj.left = $('#midContent');
     rightElesObj.right = $('#rightContent');
     SlideResize.horizontalSlide(rightElesObj, {min: 200, max: `$('#dataRow').width() - $('#leftContent').width() - 200`}, function () {
+        let resizeRate = 500 / $('#midContent').width(),
+            sheetRate = 100 - resizeRate;
+        $('#leftResize').css('width', `${resizeRate}%`);
+        $('#GLJListSheet').css('width', `${sheetRate}%`);
         refreshALlWorkBook();
     });
 });
@@ -409,27 +413,35 @@ let repositoryGljObj = {
         else{
             me.currentGlj = null;
         }
-
-        //组成物表能编辑则显示,否则隐藏该工作表
-        let rightWidth = getLocalCache('compleGLjrightContentWidth') || '25%';
-        rightWidth = rightWidth.replace('%', '');
-        let curMidWidth = $('#midContent')[0].style.width.replace('%', ''),
-            curRightWidth = $('#rightContent')[0].style.width.replace('%', '');
-        if (componentCanEdit && curRightWidth === '0') {
-            curMidWidth = parseFloat(curMidWidth) - parseFloat(rightWidth);
-            $('#midContent').css('width', `${curMidWidth}%`);
-            $('#rightContent').css('width', `${rightWidth}%`);
-        } else if(!componentCanEdit && curRightWidth !== '0') {
-            curMidWidth = parseFloat(curMidWidth) + parseFloat(rightWidth);
-            $('#midContent').css('width', `${curMidWidth}%`);
-            $('#rightContent').css('width', `0%`);
-        }
         //减少触发
         if (me.preComponentCanEdit !== componentCanEdit) {
+            me.spreadControl(componentCanEdit);
             refreshALlWorkBook();
         }
         me.preComponentCanEdit = componentCanEdit;
     },
+    //组成物表能编辑则显示,否则隐藏该工作表
+    spreadControl: function (showComponent) {
+        let leftWidth = getLocalCache('compleGLjleftContentWidth') || '50%',
+            midWidth = getLocalCache('compleGLjmidContentWidth') || '25%',
+            rightWidth = getLocalCache('compleGLjrightContentWidth') || '25%';
+         [leftWidth, midWidth, rightWidth] = [leftWidth, midWidth, rightWidth].map((v) => parseFloat(v.replace('%', '')));
+        let curMidWidth = parseFloat($('#midContent')[0].style.width.replace('%', '')),
+            curRightWidth = parseFloat($('#rightContent')[0].style.width.replace('%', ''));
+        if (showComponent) {
+            if (leftWidth + midWidth + rightWidth > 100) {
+                leftWidth = 50;
+                midWidth = rightWidth = 25;
+            }
+        } else {
+            midWidth = curMidWidth + curRightWidth;
+            rightWidth = 0;
+            leftWidth = 100 - midWidth;
+        }
+        $('#leftContent').css('width', `${leftWidth}%`);
+        $('#midContent').css('width', `${midWidth}%`);
+        $('#rightContent').css('width', `${rightWidth}%`);
+    },
     onEnterCell: function (sender, args) {
         let me = repositoryGljObj;
         args.sheet.repaint();

+ 3 - 1
web/building_saas/css/custom.css

@@ -8,7 +8,6 @@ label.title{
 }
 .modal-feeRate {max-width: 550px}
 
-
 div.resize{
     height: 10px;
     background: #efefef;
@@ -322,4 +321,7 @@ input.text-right{
 
 .nomargin{
     margin: 0px;
+}
+.border_bottom{
+    border-bottom:  1px solid #ccc
 }

+ 24 - 0
web/building_saas/css/main.css

@@ -36,6 +36,25 @@ a{
 .modal-footer{
     padding:.8rem 1rem;
 }
+.form-check .form-check-label,.form-radio .form-check-label{
+    cursor: pointer;
+}
+.custom-file-sm,.custom-file-sm .custom-file-label{
+    height: calc(1.5em + .5rem + 2px);
+    padding-top: .25rem;
+    padding-bottom: .25rem;
+    padding-left: .5rem;
+}
+.custom-file-sm .custom-file-label:after{
+    height: calc(1.4em + .5rem + 2px);
+    padding-top: .25rem;
+    padding-bottom: .25rem;
+    font-size: .875rem;
+}
+.custom-file-sm input{
+    height: calc(1.5em + .5rem + 2px);
+    font-size: .875rem;
+}
 /*自定义css*/
 .login-body,.login-html{
     height:100%;
@@ -59,6 +78,10 @@ a{
     background:#fff;
     box-shadow:#333 1px 1px 5px
 }
+.ver-panel {
+    width:100%;
+    top:10%;
+}
 .header {
     border-bottom: 1px solid #ccc
 }
@@ -534,6 +557,7 @@ a{
 }
 .box-text-style {
     font-size: 12px;
+    font-family:"Microsoft YaHei"
 }
 .box-text-style p{
     margin:0 0 2px 0;

+ 73 - 1
web/building_saas/glj/html/project_glj.html

@@ -8,6 +8,10 @@
         <label class="mx-2">使用单价文件:<span id="current-name"></span>(<label class="a_color" id="pop-used-list" data-original-title="" title="">与<span id="used-project-count">0</span>个单位工程同步</label>)
             <a class="btn btn-sm ml-1" href="#" data-toggle="modal" data-target="#change-unitFile"><i class="fa fa-exchange"></i> 选择其他</a>
             <a class="btn btn-sm ml-1" href="#" data-toggle="modal" data-target="#unitFile-save-as"><i class="fa fa-files-o"></i> 另存单独用</a></label>
+        <select class="form-control form-control-sm" style="width: auto; font-size: .875rem ;color: #007bff" id="adjustType">
+            <option value="priceInfo">造价信息差额调整法</option>
+            <option value="priceCoe" selected>价格指数调整法</option>
+        </select>
     </div>
 </div>
 
@@ -27,9 +31,14 @@
                 <li class="nav-item"><a class="nav-link" href="javascript:void(0)" id="ZGCL">暂估材料</a></li>
                 <li class="nav-item"><a class="nav-link" href="javascript:void(0)" id="SCHZ">三材汇总</a></li>
                 <li class="nav-item"><a class="nav-link" href="javascript:void(0)" id="ZYCL">主要材料</a></li>
+                <li class="nav-item"><a class="nav-link" href="javascript:void(0)" id="AMAE">添加承包人提供主要材料和工程设备</a></li>
             </ul>
         </div>
-        <div class="main-content col p-0" id="project-glj-main" style="overflow: hidden;">
+        <div class="main-content col p-0" id="material_adjust" style="overflow: hidden; display: none">
+            <div class="" id="material_adjust_sheet" style="overflow: hidden;width: 100%">
+            </div>
+        </div>
+        <div class="main-content col p-0" id="project-glj-main" style="overflow: hidden; ">
             <div class="top-content" id="projectGljTop" style="overflow: hidden">
                 <div class="main-data-top" style="width: 100%; overflow: hidden" id="project_glj_sheet">
                 </div>
@@ -163,3 +172,66 @@
         </div>
     </div>
 </div>
+<!--弹出 从人材机汇总中选择-->
+<div class="modal fade" id="selectFromGLJ" data-backdrop="static">
+    <div class="modal-dialog modal-lg" 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" style="padding: 0px">
+                <div class="btn-toolbar" style="margin: 10px">
+                    <div class="btn-group btn-group-sm mr-2">
+                        <button type="button" class="btn btn-outline-primary glj_sel_check_btn">全选</button>
+                        <button type="button" class="btn btn-outline-primary glj_sel_check_btn">全选主材</button>
+                        <button type="button" class="btn btn-outline-primary glj_sel_check_btn">全选设备</button>
+                    </div>
+                    <!--前面3个按钮 有选中状态,出现 取消 按钮-->
+                    <button type="button" class="btn btn-outline-danger btn-sm mr-2 glj_sel_check_btn">取消</button>
+                    <div class="input-group input-group-sm mr-2" style="width:200px">
+                        <input type="text" class="form-control form-control-sm"  placeholder="查找" id="glj_sel_input" value="">
+                        <div class="input-group-append">
+                            <button class="btn btn-outline-primary btn-sm" id="glj_sel_btn_find" type="button"><i class="fa fa-search" aria-hidden="true"></i></button>
+                        </div>
+                    </div>
+                    <div class="input-group input-group-sm">
+                        <div class="form-check form-check-inline">
+                            <input class="form-check-input glj_sel_input" type="checkbox" id="glj_sel_all" value="0">
+                            <label class="form-check-label" for="glj_sel_all">所有</label>
+                        </div>
+                        <div class="form-check form-check-inline">
+                            <input class="form-check-input glj_sel_input" type="checkbox" id="glj_sel_labour" value="1">
+                            <label class="form-check-label" for="glj_sel_labour">人</label>
+                        </div>
+                        <div class="form-check form-check-inline">
+                            <input class="form-check-input glj_sel_input" type="checkbox" id="glj_sel_material" checked value="2">
+                            <label class="form-check-label" for="glj_sel_material">材</label>
+                        </div>
+                        <div class="form-check form-check-inline">
+                            <input class="form-check-input glj_sel_input" type="checkbox" id="glj_sel_machine" value="3">
+                            <label class="form-check-label" for="glj_sel_machine">机</label>
+                        </div>
+                        <div class="form-check form-check-inline">
+                            <input class="form-check-input glj_sel_input" type="checkbox" id="glj_sel_main" checked value="4">
+                            <label class="form-check-label" for="glj_sel_main">主材</label>
+                        </div>
+                        <div class="form-check form-check-inline">
+                            <input class="form-check-input glj_sel_input" type="checkbox" id="glj_sel_eqp" checked value="5">
+                            <label class="form-check-label" for="glj_sel_eqp">设备</label>
+                        </div>
+                    </div>
+                </div>
+                <div class="modal-auto-height" style="margin-top:10px; border: 1px solid #ccc">
+                    <div  id="glj_from_sheet" style="overflow: hidden;height: 100%"></div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
+                <button type="button" class="btn btn-primary" id="glj_sel_confirm" data-dismiss="modal">确定</button>
+            </div>
+        </div>
+    </div>
+</div>

+ 1 - 0
web/building_saas/js/global.js

@@ -34,6 +34,7 @@ function autoFlashHeight(){
     $('#comments').find('textarea').height($('#comments').height() - 25);
     typeof(adaptiveTzjnrWidth)== 'function' ?adaptiveTzjnrWidth():'';
     $('#project-glj-main').width($(window).width()-($('.main-nav').width()+ 2)-($('.filterType').width()+12)-5); //2、12是padding宽度,width 是不算padding宽度的
+    $('#material_adjust').width($(window).width()-($('.main-nav').width()+ 2)-($('.filterType').width()+12)-5);//材料调差
     //typeof zmhs_obj === 'object' ? zmhs_obj.loadSideResize() : '';
 };
 

+ 9 - 7
web/building_saas/main/html/main.html

@@ -148,7 +148,7 @@
                   </div>
                   <div class="side-tabs">
                       <ul class="nav nav-tabs" role="tablist">
-                          <!--<li class="nav-item">
+                         <!-- <li class="nav-item">
                               <a class="nav-link px-3 right-nav-link"  href="javascript:void(0)" id = 'locateTab' relaPanel="#locate">查找定位</a>
                           </li>-->
                           <li class="nav-item">
@@ -165,7 +165,7 @@
                           <li class="nav-item dropdown">
                               <a class="nav-link dropdown-toggle more" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false">更多</a>
                               <div class="dropdown-menu" id="div_more_dropdown_right">
-                                  <!--<a class="dropdown-item  right-nav-link"  href="javascript:void(0)" id = 'locateTab' relaPanel="#locate">查找定位</a>-->
+                                  <a class="dropdown-item  right-nav-link"  href="javascript:void(0)" id = 'locateTab' relaPanel="#locate">查找定位</a>
                                   <!--<a class="dropdown-item" data-toggle="tab" href="#sqpz" role="tab">书签批注</a>-->
                                   <script>
                                       //2018-11-23  zhang 模板库移动到更多下拉框
@@ -491,10 +491,10 @@
                           <div class="tab-content" style="width: 99%; height: 100%; float: left">
                               <!--查找定位-->
                               <div class="tab-pane" id="locate">
-                                  <div class="sidebar-tools-bar container-fluid tools-bar-height-y" id="searchPanel">
+                                  <div class="sidebar-tools-bar container-fluid tools-bar-height-y border_bottom" id="searchPanel">
                                       <div class="p-1 row">
                                           <div class="input-group input-group-sm col-12">
-                                              <input type="text" class="form-control form-control-sm" placeholder="查找内容" value="">
+                                              <input type="text" class="form-control form-control-sm" placeholder="查找内容" id="locateInput" value="">
                                               <div class="input-group-append">
                                                   <button class="btn btn-secondary btn-sm" type="button" id="locate_btn"><i class="fa fa-search" aria-hidden="true"></i></button>
                                               </div>
@@ -520,8 +520,8 @@
                                           <!--搜索分项/清单 出现-->
                                           <div class="col-12"  id="outstandingOptions">
                                               <div class="form-group form-check mb-0">
-                                                  <input type="checkbox" class="form-check-input" id ="outstanding">
-                                                  <label class="form-check-label" for="outstanding">超 <input type="number" style="width:50px"> % 时突出显示</label>
+                                                  <input type="checkbox" class="form-check-input" id ="outstanding" checked>
+                                                  <label class="form-check-label" for="outstanding">超 <input id="outInp" type="number" value="10" style="width:50px"> % 时突出显示</label>
                                               </div>
                                           </div>
                                       </div>
@@ -1199,6 +1199,7 @@
                 </div>
                 <div class="modal-footer">
                     <a href="javascript:void(0);" id="glj_selected_conf" class="btn btn-primary">确定</a>
+                    <a href="javascript:void(0);" id="replace_next_btn" class="btn btn-primary">下一步</a>
                     <button type="button" id="componentsCacnel" class="btn btn-secondary" data-dismiss="modal">取消</button>
                 </div>
             </div>
@@ -1222,7 +1223,7 @@
                     </div>
                 </div>
                 <div class="modal-footer">
-                    <button class="btn btn-primary" id="scope_position_confirm">确定</button>
+                    <button class="btn btn-primary" id="scope_position_confirm"  data-dismiss="modal">确定</button>
                     <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
                 </div>
             </div>
@@ -2120,6 +2121,7 @@
     <script type="text/javascript" src='/web/building_saas/main/js/views/confirm_modal.js'></script>
     <script type="text/javascript" src='/web/building_saas/main/js/views/zlfb_view.js'></script>
     <script type="text/javascript" src='/web/building_saas/main/js/views/installation_fee_view.js'></script>
+    <script type="text/javascript" src='/web/building_saas/main/js/views/material_adjust_view.js'></script>
     <script type="text/javascript" src='/web/building_saas/main/js/views/project_glj_view.js'></script>
     <script type="text/javascript" src="/web/building_saas/main/js/views/importBills.js"></script>
     <!--报表-->

+ 1 - 1
web/building_saas/main/js/controllers/material_controller.js

@@ -181,7 +181,7 @@ let MaterialController = {
         $("#subSpread").removeClass("ration_glj_spread");
         $("#subSpread").css("width",""); //左右拖动调整表格大小的时候会设置css属性,所以隐藏这个div的时候也要把这个属性给去掉
         $("#replaceM").hide();
-        refreshSubSpread();
+        //refreshSubSpread();   //提升焦点变换性能 2019年4月12日
     },
     showReplaceSpread:function(node){
         $("#replaceM").addClass("ovf-hidden");

+ 53 - 97
web/building_saas/main/js/models/calc_program.js

@@ -594,49 +594,50 @@ let calcTools = {
         if (treeNode.data.type != rationType.volumePrice && treeNode.data.type != rationType.gljRation) return;
         let result = 0, me = this;
 
-        let rcj = (treeNode.data.subType === gljType.LABOUR && calcTools.inBase(baseName, 'RGF')) ||
-            (baseMaterialTypes.includes(treeNode.data.subType) && calcTools.inBase(baseName, 'CLF')) ||
-            (treeNode.data.subType === gljType.GENERAL_MACHINE && calcTools.inBase(baseName, 'JXF'));
-
-        if (typeof isJX2017 != 'undefined') {
-            rcj = rcj || (treeNode.data.subType === gljType.LABOUR && calcTools.inBase(baseName, 'SC_RGF')) ||
-                (baseMaterialTypes.includes(treeNode.data.subType) && calcTools.inBase(baseName, 'SC_CLF')) ||
-                (treeNode.data.subType === gljType.GENERAL_MACHINE && calcTools.inBase(baseName, 'SC_JXF'))
-        };
-
-        if (rcj ||
-            (treeNode.data.subType === gljType.MAIN_MATERIAL && calcTools.inBase(baseName, 'ZCF')) ||
-            (treeNode.data.subType === gljType.EQUIPMENT && calcTools.inBase(baseName, 'SBF'))) {
-            if (treeNode.data.type == rationType.volumePrice)
-                result = treeNode.data.marketUnitFee ? parseFloat(treeNode.data.marketUnitFee).toDecimal(decimalObj.ration.unitPrice) : 0
-            else if (treeNode.data.type == rationType.gljRation)
-            // result = treeNode.data.basePrice ? parseFloat(treeNode.data.basePrice).toDecimal(decimalObj.ration.unitPrice) : 0;
-            // 这里因为是算基数所以要取基价,但不能直接取basePrice,受限于项目属性的三个选项。
-                result = gljOprObj.getBasePrice(treeNode);
+        function isRCJZC(treeNode, baseName) {         // 基数名称中是否包含人材机主设,且树结点类型要匹配一致
+            let rst = (treeNode.data.subType === gljType.LABOUR && baseName.includes('人工')) ||    // 人工费、市场人工费
+                (baseMaterialTypes.includes(treeNode.data.subType) && baseName.includes('材料')) ||
+                (treeNode.data.subType === gljType.GENERAL_MACHINE && (baseName.includes('机械') || baseName.includes('机具'))) ||
+                (treeNode.data.subType === gljType.MAIN_MATERIAL && baseName.includes('主材')) ||
+                (treeNode.data.subType === gljType.EQUIPMENT && baseName.includes('设备'));
+            return rst;
         }
-        else if (treeNode.data.subType === gljType.GENERAL_MACHINE && calcTools.inBase(baseName, 'JSRGF')) {
-            let glj = {
-                'code': treeNode.data.code,
-                'name': treeNode.data.name,
-                'specs': treeNode.data.specs,
-                'unit': treeNode.data.unit,
-                'quantity': 1,
-                'type': treeNode.data.subType      // 注意:这里要取subType
-            };
-            result = me.machineDetailFee(treeNode, [glj], [], gljType.MACHINE_LABOUR, isTender);
+
+        if (baseName.includes('甲供') || baseName.includes('甲定') || baseName.includes('分包')) {
+            result = 0;
         }
-        else if (
-            (treeNode.data.type == rationType.gljRation) &&
-            ((treeNode.data.subType === gljType.LABOUR && calcTools.inBase(baseName, 'JC_RGF')) ||
-                (baseMaterialTypes.includes(treeNode.data.subType) && calcTools.inBase(baseName, 'JC_CLF')) ||
-                (treeNode.data.subType === gljType.GENERAL_MACHINE && calcTools.inBase(baseName, 'JC_JXF')) ||
-                (treeNode.data.subType === gljType.MAIN_MATERIAL && calcTools.inBase(baseName, 'JC_ZCF')) ||
-                (treeNode.data.subType === gljType.EQUIPMENT && calcTools.inBase(baseName, 'JC_SBF')))
-        ) {
-            let aprice = me.uiGLJPrice(treeNode.data.basePrice);   // 量价虚拟的工料机不可能有发文,这里直接取定额价。
-            let mprice = me.uiGLJPrice(treeNode.data.marketUnitFee);
-            result = (mprice - aprice).toDecimal(decimalObj.ration.unitPrice);
+        else if (baseName.includes('价差')) {
+            if (treeNode.data.type == rationType.gljRation) {
+                if (isRCJZC(treeNode, baseName)) {
+                    let aprice = me.uiGLJPrice(treeNode.data.basePrice);   // 量价虚拟的工料机不可能有发文,这里直接取定额价。
+                    let mprice = me.uiGLJPrice(treeNode.data.marketUnitFee);
+                    result = (mprice - aprice).toDecimal(decimalObj.ration.unitPrice);
+                }
+            }
         }
+        else if (baseName.includes('机上人工')) {
+            if (treeNode.data.subType === gljType.GENERAL_MACHINE) {
+                let glj = {
+                    'code': treeNode.data.code,
+                    'name': treeNode.data.name,
+                    'specs': treeNode.data.specs,
+                    'unit': treeNode.data.unit,
+                    'quantity': 1,
+                    'type': treeNode.data.subType      // 注意:这里要取subType
+                };
+                result = me.machineDetailFee(treeNode, [glj], [], gljType.MACHINE_LABOUR, isTender);
+            }
+        }
+        else {
+            if (isRCJZC(treeNode, baseName)) {
+                if (treeNode.data.type == rationType.volumePrice)
+                    result = treeNode.data.marketUnitFee ? parseFloat(treeNode.data.marketUnitFee).toDecimal(decimalObj.ration.unitPrice) : 0
+                else if (treeNode.data.type == rationType.gljRation)
+                // result = treeNode.data.basePrice ? parseFloat(treeNode.data.basePrice).toDecimal(decimalObj.ration.unitPrice) : 0;
+                // 这里因为是算基数所以要取基价,但不能直接取basePrice,受限于项目属性的三个选项。
+                    result = gljOprObj.getBasePrice(treeNode);
+            }
+        };
         return result;
     },
     partASupplyFee: function (treeNode, baseName, isTender, isRationPirce = true) {
@@ -650,22 +651,20 @@ let calcTools = {
             supplyT = [supplyType.JDYG];
 
         let gljT = [], compT = [];
-        if (calcTools.inBase(baseName, 'JG_RGF') || calcTools.inBase(baseName, 'JD_RGF')){
-            gljT = [gljType.LABOUR];
-        }
-        else if (calcTools.inBase(baseName, 'JG_CLF') || calcTools.inBase(baseName, 'JD_CLF')){             // 甲供、甲定材料
+        if (baseName.includes('人工')) gljT = [gljType.LABOUR];       // 含人工的:如甲供人工、甲定人工
+        else if (baseName.includes('材料')){
             gljT = baseMaterialTypes;
             compT = compositionTypes;
         }
-        else if (calcTools.inBase(baseName, 'JG_JXF') || calcTools.inBase(baseName, 'JD_JXF')) {            // 甲供、甲定机械
+        else if (baseName.includes('机械') || baseName.includes('机具')) {
             gljT = baseMachineTypes;
             compT = [gljType.GENERAL_MACHINE, gljType.INSTRUMENT, gljType.OTHER_MACHINE_USED];              // 取并集,兼容重庆2018新定额
         }
-        else if (calcTools.inBase(baseName, 'JG_ZCF') || calcTools.inBase(baseName, 'JD_ZCF')) {            // 甲供、甲定主材
+        else if (baseName.includes('主材')) {
             gljT = [gljType.MAIN_MATERIAL];
             compT = [gljType.MAIN_MATERIAL];
         }
-        else if (calcTools.inBase(baseName, 'JG_SBF') || calcTools.inBase(baseName, 'JD_SBF')) {           // 甲供、甲定设备
+        else if (baseName.includes('设备')) {
             gljT = [gljType.EQUIPMENT];
         };
         // alert(JSON.stringify(projectGLJ.testGLJs()));
@@ -951,54 +950,15 @@ let calcTools = {
                   return o.value;
               }
         };
-    },
-    inBase(baseName, kindName){
-        return rationCalcBasesNameKinds[kindName].includes(baseName);
+     },
+    getFeeRateByNode(node){
+        let decimal = getDecimal("feeRate");
+        if(node.data.feeRateID) return scMathUtil.roundForObj(projectObj.project.FeeRate.getFeeRateByID(node.data.feeRateID).rate,decimal);
+        if (node.data.feeRate || node.data.feeRate == 0) return scMathUtil.roundForObj(node.data.feeRate,decimal);
+        return 100
     }
 };
 
-let rationCalcBasesNameKinds = {
-    RGF:        ['定额基价人工费', '定额人工费'],
-    CLF:        ['定额基价材料费', '定额材料费'],
-    QTCLF:      ['定额其他材料费'],
-    JXF:        ['定额基价机械费', '定额机械费', '定额施工机具使用费'],
-    JSRGF:      ['定额基价机上人工费'],
-    ZCF:        ['主材费', '市场价主材费'],
-    SBF:        ['设备费'],
-    RGGR:       ['人工工日'],
-    ZGCLF:      ['暂估材料费'],
-
-    // 市场价人工费、市场价材料费、市场价机械费。江西用,跟重庆区分开,概念上不太统一,这里要注意区分。
-    SC_RGF:     ['人工费'],
-    SC_CLF:     ['材料费'],
-    SC_JXF:     ['机械费'],
-
-    JC_RGF:     ['人工费价差'],
-    JC_CLF:     ['材料费价差'],
-    JC_JXF:     ['机械费价差'],
-    JC_ZCF:     ['主材费价差'],
-    JC_SBF:     ['设备费价差'],
-
-    JG_RGF:     ['甲供定额基价人工费', '甲供定额人工费', '甲供人工费'],
-    JG_CLF:     ['甲供定额基价材料费', '甲供定额材料费', '甲供材料费'],
-    JG_JXF:     ['甲供定额基价机械费', '甲供定额机械费', '甲供机械费', '甲供定额施工机具费'],
-    JG_ZCF:     ['甲供主材费'],
-    JG_SBF:     ['甲供设备费'],
-
-    JD_RGF:     ['甲定定额基价人工费', '甲定定额人工费', '甲定人工费'],
-    JD_CLF:     ['甲定定额基价材料费', '甲定定额材料费', '甲定材料费'],
-    JD_JXF:     ['甲定定额基价机械费', '甲定定额机械费', '甲定机械费', '甲定定额施工机具费'],
-    JD_ZCF:     ['甲定主材费'],
-    JD_SBF:     ['甲定设备费'],
-
-    FB_RGF:     ['分包定额基价人工费', '分包人工费'],
-    FB_CLF:     ['分包定额基价材料费', '分包材料费'],
-    FB_JXF:     ['分包定额基价机械费', '分包机械费'],
-    FB_ZCF:     ['分包主材费'],
-    FB_SBF:     ['分包设备费'],
-    FB_RGGR:    ['分包人工工日']
-};
-
 let rationCalcBases = {
     '定额基价人工费': function (node, isTender) {
         return calcTools.rationBaseFee(node, [gljType.LABOUR], priceTypes.ptBasePrice, isTender);
@@ -1840,9 +1800,7 @@ class CalcProgram {
                 treeNode.data.programID = null;
                 treeNode.changed = true;
             }
-            let f = 100;
-            if (treeNode.data.feeRate || treeNode.data.feeRate == 0)
-                f = treeNode.data.feeRate;
+            let f = calcTools.getFeeRateByNode(treeNode);
             let b = treeNode.data.calcBaseValue ? treeNode.data.calcBaseValue : 0;
             let tb = treeNode.data.tenderCalcBaseValue ? treeNode.data.tenderCalcBaseValue : 0;
             let q = nQ ? nQ : 1;
@@ -2207,8 +2165,6 @@ class CalcProgram {
             };
             projectObj.project.calcProgram.calculate(treeNode, false, false, tenderTypes.ttCalc);  // 再正向算
         };
-
-
     };
 };
 

+ 391 - 92
web/building_saas/main/js/models/exportStandardInterface.js

@@ -14,6 +14,49 @@
 * */
 
 const XMLStandard = (function () {
+    //属性类型
+    const TYPE = {
+        DATE: 1,        //日期类型YYYY-MM-DD
+        DATE_TIME: 2,   //日期类型YYY-MM-DDTHH:mm:ss
+        INT: 3,         //整数类型
+        DECIMAL: 4,     //数值类型,不限制小数位数
+        NUM2: 5,        //数值类型2:最多两位小数
+        BOOL: 6         //布尔型
+    };
+    const WHITE_SPACE = {
+        COLLAPSE: 1 //移除所有空白字符(换行、回车、空格以及制表符会被替换为空格,开头和结尾的空格会被移除,而多个连续的空格会被缩减为一个单一的空格)
+    };
+    //费用类别:固定行ID对应
+    const FEE_TYPE = {
+        [fixedFlag.SUB_ENGINERRING]: '1100',
+        [fixedFlag.MEASURE]: '1200',
+        [fixedFlag.CONSTRUCTION_ORGANIZATION]: '120201',
+        [fixedFlag.SAFETY_CONSTRUCTION]: '1204',
+        [fixedFlag.PROJECT_COMPLETE_ARCH_FEE]: '10041',
+        [fixedFlag.HOUSE_QUALITY_ACCEPT_FEE]: '1206',
+        [fixedFlag.OTHER]: '1300',
+        [fixedFlag.CHARGE]: '800',
+        [fixedFlag.TAX]: '900',
+        [fixedFlag.ADDED_VALUE_TAX]: '9001',
+        [fixedFlag.ADDITIONAL_TAX]: '9002',
+        [fixedFlag.ENVIRONMENTAL_PROTECTION_TAX]: '9003',
+        0: '1800',  //其他未定义的大项费用
+    };
+    //文件类型
+    const FILE_KIND = {
+        '1': '投标',
+        '2': '招标',
+    };
+    //计税方法
+    const TAX_TYPE = {
+        '1': '一般计税法',
+        '2': '简易计税法',
+    };
+    //加载数据间隔,减少服务器压力
+    const TIMEOUT_TIME = 500;
+    function isDef(v) {
+        return typeof v !== 'undefined' && v !== null;
+    }
     return function (userID, granularity) {
         this.GRANULARITY = {
             PROJECT: 1,         //导出建设项目
@@ -24,24 +67,6 @@ const XMLStandard = (function () {
         if (!granularity || ![1, 2, 3].includes(granularity)) {
             granularity = this.GRANULARITY.PROJECT;
         }
-        //属性类型
-        const TYPE = {
-            DATE: 1,        //日期类型YYYY-MM-DD
-            DATE_TIME: 2,   //日期类型YYY-MM-DDTHH:mm:ss
-            INT: 3,         //整数类型
-            DECIMAL: 4,     //数值类型,不限制小数位数
-            NUM2: 5,        //数值类型2:最多两位小数
-            BOOL: 6         //布尔型
-        };
-        const WHITE_SPACE = {
-            COLLAPSE: 1 //移除所有空白字符(换行、回车、空格以及制表符会被替换为空格,开头和结尾的空格会被移除,而多个连续的空格会被缩减为一个单一的空格)
-        };
-        //加载数据间隔,减少服务器压力
-        const TIMEOUT_TIME = 500;
-
-        function isDef(v) {
-            return typeof v !== 'undefined' && v !== null;
-        }
         /*
          * 检查
          * 创建节点时检查节点的数据
@@ -64,41 +89,54 @@ const XMLStandard = (function () {
                     rst.filterAttrs.push(data);
                     continue;
                 }
-                let isFail = false;
+                let isFail = false,
+                    tempFail = '';
                 if (data.minLen && data.value.length < data.minLen){
                     isFail = true;
-                    rst.failHints.push(`“${data.name}”字符数不可小于${data.minLen}个`);
+                    tempFail = `“${data.name}”字符数不可小于${data.minLen}个`;
+                    //rst.failHints.push(`“${data.name}”字符数不可小于${data.minLen}个`);
                 } else if (data.maxLen && data.value.length > data.maxLen) {
                     isFail = true;
-                    rst.failHints.push(`“${data.name}”字符数不可大于${data.maxLen}个`);
+                    tempFail = `“${data.name}”字符数不可大于${data.maxLen}个`;
+                    //rst.failHints.push(`“${data.name}”字符数不可大于${data.maxLen}个`);
                 } else if (data.enumeration && !data.enumeration.includes(data.value)) {
                     isFail = true;
-                    rst.failHints.push(`“${data.name}”只能从“${data.enumeration.join(';')}”中选择`);
+                    tempFail = `“${data.name}”只能从“${data.enumeration.join(';')}”中选择`;
+                    //rst.failHints.push(`“${data.name}”只能从“${data.enumeration.join(';')}”中选择`);
                 } else if (data.type && data.type === TYPE.DATE && !dateReg.test(data.value)) {
                     isFail = true;
-                    rst.push(`“${data.name}”日期格式必须是YYYY-MM-DD`);
+                    tempFail = `“${data.name}”日期格式必须是YYYY-MM-DD`;
+                    //rst.push(`“${data.name}”日期格式必须是YYYY-MM-DD`);
                 } else if (data.type && data.type === TYPE.INT && !Number.isInteger(parseFloat(data.value))) {
                     isFail = true;
-                    rst.failHints.push(`“${data.name}”必须为整数`);
+                    tempFail = `“${data.name}”必须为整数`;
+                    //rst.failHints.push(`“${data.name}”必须为整数`);
                 } else if (data.type && data.type === TYPE.DECIMAL && isNaN(parseFloat(data.value))) {
                     isFail = true;
-                    rst.failHints.push(`“${data.name}”必须为数值`)
+                    tempFail = `“${data.name}”必须为数值`;
+                    //rst.failHints.push(`“${data.name}”必须为数值`)
                 } else if (data.type && data.type === TYPE.NUM2) {
                     let v = parseFloat(data.value);
                     if (isNaN(v)) {
                         isFail = true;
+                        tempFail = `“${data.name}”必须为数值`;
                         rst.failHints.push(`“${data.name}”必须为数值`);
                     } else if (!data.value.length || (data.value.split('.').length > 1 && data.value.split('.')[1].length > 2)){
                         isFail = true;
-                        rst.failHints.push(`“${data.name}”小数位数不得多于两位`);
+                        //tempFail = `“${data.name}”小数位数不得多于两位`;
+                        //rst.failHints.push(`“${data.name}”小数位数不得多于两位`);
                     }
                 } else if (data.type && data.type === TYPE.BOOL && !['true', 'false'].includes(data.value.toString())) {
                     isFail = true;
-                    rst.failHints.push(`“${data.name}”必须为true或false`);
+                    tempFail = `“${data.name}”必须为true或false`;
+                    //rst.failHints.push(`“${data.name}”必须为true或false`);
                 }
                 if (!isFail || data.required) {
                     rst.filterAttrs.push(data);
                 }
+                if (isFail && data.required && tempFail) {
+                    rst.failHints.push(tempFail);
+                }
             }
             return rst;
         }
@@ -156,8 +194,8 @@ const XMLStandard = (function () {
                 {name: '建设单位', value: getValueByKey(source.basicInformation, 'constructionUnit'), required: true},
                 {name: '标准版本号', value: '3.2.2', required: true},
                 {name: '标准名称', value: '重庆造价软件数据交换标准(清单计价)', required: true,},
-                {name: '文件类型', value: getValueByKey(source.basicInformation, 'fileKind'), required: true, enumeration: ['招标', '投标', '控制价', '结算', '其他']},
-                {name: '计税方法', value: 'todo', required: true},
+                {name: '文件类型', value: source.fileKind, required: true, enumeration: ['招标', '投标', '控制价', '结算', '其他']},
+                {name: '计税方法', value: source.taxType, required: true, enumeration: ['营业税', '一般计税法', '简易计税法']},
             ];
             //唯一约束
             this.constraints = {
@@ -176,7 +214,7 @@ const XMLStandard = (function () {
                 {name: '编制单位', value: getValueByKey(source.basicInformation, 'establishmentUnit'), required: true},
                 {name: '审核单位', value: getValueByKey(source.basicInformation, 'auditUnit')},
                 {name: '编制人', value: getValueByKey(source.basicInformation, 'buildingUnitAuthor'), required: true},
-                {name: '审核人', value: 'todo'},
+                {name: '审核人', value: getValueByKey(source.basicInformation, 'auditUnitAuditor')},
                 {name: '开工日期', value: getValueByKey(source.basicInformation, 'commencementDate'), type: TYPE.DATE},
                 {name: '竣工日期', value: getValueByKey(source.basicInformation, 'completionDate'), type: TYPE.DATE},
                 {name: '编制日期', value: getValueByKey(source.basicInformation, 'establishDate'), type: TYPE.DATE, required: true},
@@ -208,7 +246,7 @@ const XMLStandard = (function () {
                 {name: '投标工期', value: getValueByKey(source.basicInformation, 'biddingPeriod')},
                 {name: '投标保证金', value: getValueByKey(source.basicInformation, 'biddingMargin'), type: TYPE.NUM2},
                 {name: '质量承诺', value: getValueByKey(source.basicInformation, 'qualityCommitment')},
-                {name: '担保类型', value: getValueByKey(source.basicInformation, 'guaranteeType')},
+                {name: '担保类型', value: getValueByKey(source.basicInformation, 'guaranteeType'), enumeration: ['支票', '现金', '电汇', '汇票']},
                 {name: '投标总价', value: source.summaryInfo.engineeringCost, required: true, type: TYPE.NUM2},
             ];
             element.call(this, '投标信息', attrs);
@@ -237,6 +275,12 @@ const XMLStandard = (function () {
                 {name: '措施项目清单合计', value: summaryInfo.measure, required: true, type: TYPE.NUM2},
                 {name: '安全文明施工专项费', value: summaryInfo.safetyConstruction, required: true, type: TYPE.NUM2},
                 {name: '其他项目清单合计', value: summaryInfo.other, required: true, type: TYPE.NUM2},
+                {name: '暂列金额合计', value: summaryInfo.provisional, type: TYPE.NUM2},
+                {name: '材料暂估价合计', value: summaryInfo.materialProvisional, type: TYPE.NUM2},
+                {name: '专业工程暂估价合计', value: summaryInfo.engineeringEstimate, type: TYPE.NUM2},
+                {name: '计日工合计', value: summaryInfo.daywork, type: TYPE.NUM2},
+                {name: '总承包服务费合计', value: summaryInfo.turnKeyContract, type: TYPE.NUM2},
+                {name: '签证索赔合计', value: summaryInfo.claimVisa, type: TYPE.NUM2},
                 {name: '规费', value: summaryInfo.charge, required: true, type: TYPE.NUM2},
                 {name: '税金', value: summaryInfo.tax, required: true, type: TYPE.NUM2},
             ];
@@ -254,8 +298,8 @@ const XMLStandard = (function () {
         //单位工程定义
         function Tender(source) {
             const TAX = {
-                1: '一般计税',
-                2: '简易计税'
+                '1': '一般计税',
+                '2': '简易计税',
             };
             let attrs = [
                 {name: '编号', value: source.code, required: true, minLen: 1, maxLen: 255, whiteSpace: WHITE_SPACE.COLLAPSE},
@@ -267,8 +311,11 @@ const XMLStandard = (function () {
                     '房屋安装修缮工程', '房屋修缮单拆除'
                 ]},
                 {name: '金额', value: source.summaryInfo.engineeringCost, type: TYPE.NUM2},
-                {name: '定额库编码', value: source.defaultRationLibCode},
-                {name: '计税方法', value: TAX[source.taxType], required: true},
+                {name: '定额库编码', value: source.defaultRationLibCode, enumeration: [
+                    'CQ18TJ', 'CQ18ZS', 'CQ18AZ', 'CQ18SZ', 'CQ18YL', 'CQ18FG', 'CQ18GD', 'CQ18FS',
+                    'CQ18LS', 'CQ18GZW', 'CQ18BP', 'CQ18ZP'
+                ]},
+                {name: '计税方法', value: TAX[source.taxType], required: true, enumeration: ['营业税', '一般计税', '简易计税']},
             ];
             this.constraints = {
                 billsCode: [],                 //清单项目项目编码
@@ -283,6 +330,7 @@ const XMLStandard = (function () {
                 otherItemNo: [],               //其他列项序号
                 feeItemNo: [],                 //费用项序号
                 mainBillsCode: [],             //主要清单明细项目编码 (标准文件中constraint中没有定义此唯一性,但是主要清单明细的备注那里说明了,这里处理一下)
+                appraisalDetailCode: [],         //评审材料明细代码唯一(标准文件中constraint中没有定义此唯一性,但是评审材料明细的备注里说明了)
             };
             element.call(this, '单位工程', attrs);
         }
@@ -320,7 +368,7 @@ const XMLStandard = (function () {
                 {name: '费率', value: source.feeRate},
                 {name: '金额', value: getFee(source.fees, 'common.totalFee'), type: TYPE.NUM2},
                 {name: '其中暂估价', value: getFee(source.fees, 'estimate.totalFee'), type: TYPE.NUM2},
-                {name: '费用类别', value: source.feeType, type: TYPE.INT, required: true},
+                {name: '费用类别', value: source.feeType, type: TYPE.INT, required: true, enumeration: ['1100', '1200', '1204', '1300', '800', '900', '1800', '1']},
                 {name: '备注', value: source.remark},
             ];
             element.call(this, '计价程序费用行', attrs);
@@ -391,15 +439,15 @@ const XMLStandard = (function () {
                 {name: '单位', value: source.unit, required: true, minLen: 1, maxLen: 20, whiteSpace: WHITE_SPACE.COLLAPSE},
                 {name: '定额库编码', value: source.libCode, required: true},
                 {name: '原始定额编号', value: source.code, minLen: 1, maxLen: 80, whiteSpace: WHITE_SPACE.COLLAPSE},
-                {name: '子目类型', value: source.subType, required: true, type: TYPE.INT, enumeration: ['0', '1', '2', '3', '4', '5', '6']},  //todo
+                {name: '子目类型', value: source.subType, required: true, type: TYPE.INT, enumeration: ['0', '1', '2', '3', '4', '5', '6']},
                 {name: '工程量', value: source.quantity, required: true, type: TYPE.DECIMAL},
                 {name: '工程量计算式', value: source.quantityEXP},
                 {name: '定额单价', value: getFee(source.fees, 'rationUnitPrice.unitFee'), required: true, type: TYPE.DECIMAL},
                 {name: '定额合价', value: getFee(source.fees, 'rationUnitPrice.totalFee'), required: true, type: TYPE.NUM2},
                 {name: '综合单价', value: getFee(source.fees, 'common.unitFee'), required: true, type: TYPE.DECIMAL},
                 {name: '综合合价', value: getFee(source.fees, 'common.totalFee'), required: true, type: TYPE.NUM2},
-                {name: '单价构成文件ID', value: 0, required: true, type: TYPE.INT}, //todo
-                {name: '分包标志', value: !!source.isSubcontract, type: TYPE.BOOL},
+                {name: '单价构成文件ID', value: source.programID, required: true, type: TYPE.INT},
+                {name: '分包标志', value: false, type: TYPE.BOOL},
                 {name: '备注', value: source.remark}
             ];
             element.call(this, '定额子目', attrs);
@@ -414,7 +462,7 @@ const XMLStandard = (function () {
                 {name: '人材机代码', value: source.code, required: true, minLen: 1, maxLen: 20, whiteSpace: WHITE_SPACE.COLLAPSE},
                 {name: '消耗量', value: source.quantity, required: true, type: TYPE.DECIMAL},
                 {name: '总消耗量', value: source.totalQuantity, required: true, type: TYPE.DECIMAL},
-                {name: '数量计算方式', value: 1, required: true, type: TYPE.INT},
+                {name: '数量计算方式', value: 1, required: true, type: TYPE.INT, enumeration: ['1', '2']},
             ];
             element.call(this, '人材机含量', attrs);
         }
@@ -601,7 +649,7 @@ const XMLStandard = (function () {
             let attrs = [
                 {name: '编号', value: source.code, minLen: 1, maxLen: 20, whiteSpace: WHITE_SPACE.COLLAPSE, required: true},
                 {name: '工程名称', value: source.name, minLen: 1, maxLen: 255, whiteSpace: WHITE_SPACE.COLLAPSE, required: true},
-                {name: '计算基础', value: 0, type: TYPE.DECIMAL, required: true},   //todo
+                {name: '计算基础', value: source.calcBaseValue, type: TYPE.DECIMAL, required: true},
                 {name: '服务内容', value: source.serviceContent, maxLen: 255, required: true},
                 {name: '费率', value: source.feeRate, type: TYPE.DECIMAL, required: true},
                 {name: '金额', value: getFee(source.fees, 'common.totalFee'), type: TYPE.NUM2, required: true},
@@ -618,8 +666,9 @@ const XMLStandard = (function () {
                 {name: '数量', value: source.quantity, type: TYPE.DECIMAL, required: true},
                 {name: '单价', value: getFee(source.fees, 'common.unitFee'), type: TYPE.DECIMAL, required: true},
                 {name: '合价', value: getFee(source.fees, 'common.totalFee'), type: TYPE.NUM2, required: true},
-                {name: '依据', value: '', required: true},
-            ]
+                {name: '依据', value: source.claimVisa, required: true},
+            ];
+            element.call(this, '签证索赔计价汇总费用项', attrs);
         }
         //其他定义
         function Other(source) {
@@ -636,8 +685,8 @@ const XMLStandard = (function () {
                 {name: '计算基础', value: source.calcBase,  maxLen: 255},
                 {name: '费率', value: source.feeRate, type: TYPE.DECIMAL},
                 {name: '金额', value: source.commonTotalFee, type: TYPE.NUM2, required: true},
-                {name: '不计入合价标志', value: false, type: TYPE.BOOL},
-                {name: '招标人标志', value: true, type: TYPE.BOOL},
+                {name: '不计入合价标志', value: source.notSummray, type: TYPE.BOOL},
+                {name: '招标人标志', value: false, type: TYPE.BOOL},
                 {name: '备注', value: source.remark, maxLen: 255}
             ];
             element.call(this, '其他列项', attrs);
@@ -670,20 +719,20 @@ const XMLStandard = (function () {
             let attrs = [
                 {name: '代码', value: source.code, minLen: 1, maxLen: 20, whiteSpace: WHITE_SPACE.COLLAPSE, required: true},
                 {name: '名称', value: source.name, minLen: 1, maxLen: 255, whiteSpace: WHITE_SPACE.COLLAPSE, required: true},
-                {name: '规格', value: source.spec, maxLen: 255},
+                {name: '规格', value: source.specs, maxLen: 255},
                 {name: '单位', value: source.unit, minLen: 1, maxLen: 20, whiteSpace: WHITE_SPACE.COLLAPSE, required: true},
                 {name: '原始代码', value: source.orgCode, minLen: 1, maxLen: 20, whiteSpace: WHITE_SPACE.COLLAPSE},
                 {name: '费用类别', value: source.feeType, enumeration: ['1', '2', '3', '4'], required: true},
-                {name: '配比类别', value: '', type: TYPE.INT},  //todo
-                {name: '主要材料类别', value: '', type: TYPE.INT},  //todo
-                {name: '主要材料单位系数', value: '', type: TYPE.DECIMAL},  //todo
-                {name: '材料耗用类型', value: '', type: TYPE.INT, required: true},  //todo
-                {name: '供货方式', value: '', type: TYPE.INT},  //todo
-                {name: '暂估材料标志', value: !!source.is_evaluate, type: TYPE.BOOL},
-                {name: '不计税设备标志', value: !!source.is_adjust_price, type: TYPE.BOOL, required: true},  //todo
-                {name: '单价不从明细汇总标志', value: !!source.excludeRatio, type: TYPE.BOOL},  //todo
+                {name: '配比类别', value: source.ratioType, type: TYPE.INT},
+                {name: '主要材料类别', value: source.mainMaterialType, type: TYPE.INT},
+                {name: '主要材料单位系数', value: source.materialCoe, type: TYPE.DECIMAL},
+                {name: '材料耗用类型', value: 0, type: TYPE.INT, required: true},
+                {name: '供应方式', value: source.supply, type: TYPE.INT, enumeration: ['1', '2']},
+                {name: '暂估材料标志', value: source.is_evaluate, type: TYPE.BOOL},
+                {name: '不计税设备标志', value: source.no_tax_eqp, type: TYPE.BOOL, required: true},
+                {name: '单价不从明细汇总标志', value: source.notFromDetail, type: TYPE.BOOL},
                 {name: '定额价', value: source.basePrice, type: TYPE.DECIMAL, required: true},
-                {name: '定额价调整', value: '', type: TYPE.DECIMAL, required: true},   //todo
+                {name: '定额价调整', value: source.adjPrice, type: TYPE.DECIMAL, required: true},
                 {name: '市场价', value: source.marketPrice, type: TYPE.DECIMAL, required: true},
                 {name: '数量', value: source.quantity, type: TYPE.DECIMAL, required: true},
                 {name: '产地', value: source.originPlace, maxLen: 255},
@@ -702,6 +751,59 @@ const XMLStandard = (function () {
             ];
             element.call(this, '人材机配比', attrs);
         }
+        //评标材料表定义
+        function EvalBidMaterial() {
+            element.call(this, '评标材料表', []);
+        }
+        //暂估价材料表定义
+        function EvalEstimateMaterial() {
+            element.call(this, '暂估价材料表', []);
+        }
+        //材料明细定义
+        function MaterialDetail(source) {
+            let attrs = [
+                {name: '序号', value: source.serialNo, minLen: 1, maxLen: 20, whiteSpace: WHITE_SPACE.COLLAPSE, required: true},
+                {name: '关联材料号', value: source.orgCode, minLen: 1, maxLen: 20, whiteSpace: WHITE_SPACE.COLLAPSE},
+                {name: '材料名称', value: source.name, minLen: 1, maxLen: 255, whiteSpace: WHITE_SPACE.COLLAPSE, required: true},
+                {name: '规格型号', value: source.specs, maxLen: 255},
+                {name: '计量单位', value: source.unit, minLen: 1, maxLen: 20, whiteSpace: WHITE_SPACE.COLLAPSE, required: true},
+                {name: '数量', value: source.quantity, type: TYPE.DECIMAL},
+                {name: '单价', value: source.marketPrice, type: TYPE.DECIMAL},
+                {name: '合价', value: source.totalPrice, type: TYPE.NUM2},
+                {name: '产地', value: source.originPlace, maxLen: 255},
+                {name: '厂家', value: source.vender, maxLen: 255},
+                {name: '品牌', value: source.brand, maxLen: 255},
+                {name: '质量等级', value: source.qualityGrace, maxLen: 255},
+                {name: '备注', value: source.remark, maxLen: 255},
+            ];
+            element.call(this, '材料明细', attrs);
+        }
+        //清单综合单价计算程序定义
+        function CalcProgram() {
+            element.call(this, '清单综合单价计算程序', []);
+        }
+        //综合单价计算程序文件定义
+        function CalcProgramFile(source) {
+            let attrs = [
+                {name: 'ID', value: source.ID, type: TYPE.INT, required: true},
+                {name: '名称', value: source.name, minLen: 1, maxLen: 255, whiteSpace: WHITE_SPACE.COLLAPSE, required: true}
+            ];
+            element.call(this, '综合单价计算程序文件', attrs);
+        }
+        //综合单价计算程序费用项定义
+        function CalcProgramFeeItem(source) {
+            let attrs = [
+                {name: '序号', value: source.serialNo, minLen: 1, maxLen: 20, whiteSpace: WHITE_SPACE.COLLAPSE},
+                {name: '行代号', value: source.rowCode, minLen: 1, maxLen: 20, whiteSpace: WHITE_SPACE.COLLAPSE, required: true},
+                {name: '项目名称', value: source.name, minLen: 1, maxLen: 255, whiteSpace: WHITE_SPACE.COLLAPSE, required: true},
+                {name: '计算基础表达式', value: source.calcBase, maxLen: 255, required: true},
+                {name: '计算基础说明', value: source.statement, maxLen: 255},
+                {name: '费率', value: source.feeRate, type: TYPE.DECIMAL},
+                {name: '费用类别', value: source.feeType, type: TYPE.INT, required: true},
+                {name: '备注', value: source.remark, maxLen: 255},
+            ];
+            element.call(this, '综合单价计算程序费用项', attrs);
+        }
         //主要清单汇总定义
         function MainBillsSummary() {
             element.call(this, '主要清单汇总', []);
@@ -720,6 +822,21 @@ const XMLStandard = (function () {
             ];
             element.call(this, '主要清单明细', attrs);
         }
+        //评审材料汇总定义
+        function AppraisalSummary(){
+            element.call(this, '评审材料汇总', []);
+        }
+        //评审材料明细定义
+        function AppraisalDetail(source) {
+            let attrs = [
+                {name: '代码', value: source.orgCode, minLen: 1, maxLen: 20, whiteSpace: WHITE_SPACE.COLLAPSE, required: true},
+                {name: '名称', value: source.name, minLen: 1, maxLen: 255, whiteSpace: WHITE_SPACE.COLLAPSE, required: true},
+                {name: '规格', value: source.specs, maxLen: 255},
+                {name: '单位', value: source.unit, minLen: 1, maxLen: 20, whiteSpace: WHITE_SPACE.COLLAPSE, required: true},
+                {name: '市场价', value: source.marketPrice, type: TYPE.DECIMAL, required: true}
+            ];
+            element.call(this, '评审材料明细', attrs);
+        }
 
         async function setTimeoutSync(handle, time) {
             return new Promise(function(resolve, reject) {
@@ -858,7 +975,13 @@ const XMLStandard = (function () {
                 engData.children = sortByNext(engData.children);
             }
             //标段
-            let project = curProjectEle = new Project({basicInformation: projectData.property.basicInformation, name: projectData.name});
+            let project = new Project({
+                basicInformation: projectData.property.basicInformation,
+                name: projectData.name,
+                fileKind: projectData.property.fileKind ? FILE_KIND[projectData.property.fileKind] : '',
+                taxType: projectData.property.taxType ? TAX_TYPE[projectData.property.taxType] : ''
+            });
+            curProjectEle = project;
             //项目信息
             let projectInfo = new ProjectInfo({basicInformation: projectData.property.basicInformation});
             project.children.push(projectInfo);
@@ -881,18 +1004,31 @@ const XMLStandard = (function () {
             project.children.push(feeForm);
             //主要清单汇总    主要清单明细在loadTender中设置
             let mainBillsSummaryEle = new MainBillsSummary();
-            project.children.unshift(mainBillsSummaryEle);  //插入到第一个位置,必须要排在loadTender前,这样才能添加到明细数据,插在第一位方便后面排序
+            project.children.unshift(mainBillsSummaryEle);  //必须要排在loadTender前,这样才能添加到明细数据
+            //评审材料汇总    评审材料明细在loadTender中设置
+            let appraisalSummary = new AppraisalSummary();
+            project.children.unshift(appraisalSummary);
             //单项工程
             for (let eng of projectData.children) {
                 curPMData.engineering = eng;
                 let engElement = await loadEngineering(projectData.summaryInfo, eng);
                 project.children.push(engElement);
             }
-            //主要清单汇总 排在后面
-            project.children.shift(mainBillsSummaryEle);
-            project.children.push(mainBillsSummaryEle);
+            //主要清单汇总、评审材料汇总 排在后面
+            project.children = [...project.children.slice(2), mainBillsSummaryEle, appraisalSummary];
             return project;
         }
+        //单位工程内的人材机ID: 用于代码映射关系 //单位工程级别,C+数组下标 <==> gljID
+        let tenderGljs = [];
+        //获取人材机代码,eg: C0, C1....
+        function getGljCode(gljID) {
+            let idx = tenderGljs.findIndex(ID => ID === gljID);
+            if (~idx) {
+                return `C${idx}`;
+            }
+            tenderGljs.push(gljID);
+            return `C${tenderGljs.length - 1}`;
+        }
         /*
          * 加载单项工程数据
          * @param {Object}summaryInfo(项目汇总信息映射) {Object}engData(单项工程数据)
@@ -902,11 +1038,15 @@ const XMLStandard = (function () {
             let engineering = new Engineering(source);
             //单项工程编号要唯一
             checkUnique(curProjectEle.constraints.engCode, source.code, '单项工程编号');
+            //费用构成
+            /*let feeForm = new FeeFrom(summaryInfo[engData.ID]);
+            engineering.children.push(feeForm);*/
             //分批次获取单位工程
             for (let tenderData of engData.children) {
                 curPMData.tender = tenderData;
                 await setTimeoutSync(() => {},TIMEOUT_TIME);    //间隔一段时间再初始单位工程数据,减少服务器压力
                 let tender = await loadTender(summaryInfo, tenderData);
+                tenderGljs = [];    //清空单位工程内所有的人材机(ID)
                 engineering.children.push(tender);
             }
             return engineering;
@@ -929,6 +1069,10 @@ const XMLStandard = (function () {
                     tenderDetail.rationLibMap[lib.id] = lib;
                 }
             }
+            //初始化项目人材机代号(人材机生成的代号要由排序后的项目人材机从C0开始生成),定额下的人材机含量提前使用到了这个代码,所以需要先初始化
+            let allGljs = gljUtil.sortRationGLJ(tenderDetail.projectGLJ.datas.gljList); //人材机汇总排序
+            tenderGljs = allGljs.map(glj => glj.glj_id);
+
             //单位工程
             let tenderSource = {
                 code: getIncreamentData('projectCode'),
@@ -964,10 +1108,26 @@ const XMLStandard = (function () {
             //规费和税金清单
             let chargeTax = loadChargeTax(tenderDetail);
             tender.children.push(chargeTax);
+            //建设项目下评审材料汇总设置评审材料明细
+            let curAppraisalSummary = curProjectEle.children.find(ele => ele.name === '评审材料汇总');
             //人材机汇总
-            let gljSummary = loadGlj(tenderDetail);
-            if (gljSummary) {
+            let gljSumarryInfo = loadGlj(curAppraisalSummary, tenderDetail);
+            if (gljSumarryInfo) {
+                let {gljSummary, evalBidMaterial, evalEstimateMaterial} = gljSumarryInfo;
                 tender.children.push(gljSummary);
+                //评标材料表
+                if (evalBidMaterial && evalBidMaterial.children.length) {
+                    tender.children.push(evalBidMaterial);
+                }
+                //暂估价材料表
+                if (evalEstimateMaterial && evalEstimateMaterial.children.length) {
+                    tender.children.push(evalEstimateMaterial);
+                }
+            }
+            //清单综合单价计算程序
+            let calcProgram = loadCalcProgram(tenderDetail);
+            if (calcProgram) {
+                tender.children.push(calcProgram);
             }
             //给建设项目下主要清单汇总设置主要清单明细
             let curMainBillsSummary = curProjectEle.children.find(ele => ele.name === '主要清单汇总');
@@ -995,9 +1155,9 @@ const XMLStandard = (function () {
                     rowCode: `F${serialNo}`,
                     name: node.data.name,
                     calcBase: node.data.calcBase,
-                    feeRate: node.data.feeRate,
+                    feeRate: node.data.feeRate !== '' ? node.data.feeRate : 100,
                     fees: node.data.fees,
-                    feeType: FEE_TYPE[flag],
+                    feeType: FEE_TYPE[flag] || FEE_TYPE['0'],
                     remark: node.data.remark
                 };
                 let dxfy = new DXFYRow(source);
@@ -1092,6 +1252,24 @@ const XMLStandard = (function () {
                 }
             }
             loadFeatureContent();
+            //解析工程量计算式
+            function parseQuantityExp(rationData) {
+                if (rationData.quantityEXP === 'QDL') { //取清单工程量
+                    return node.data.quantity;
+                } else if (rationData.quantityEXP === 'GCLMXHJ') {  //从明细汇总成数值
+                    let referDetail = detail.quantity_detail.datas.filter(data => data.rationID === rationData.ID && data.isSummation);
+                    if (referDetail.length === 0) {
+                        return '';
+                    }
+                    let rst = 0;
+                    for (let d of referDetail) {
+                        rst = scMathUtil.roundForObj(rst + d.result, detail.projectInfo.property.decimal.process);
+                    }
+                    return scMathUtil.roundForObj(rst, detail.projectInfo.property.decimal.ration.quantity);
+                } else {
+                    return rationData.quantityEXP;
+                }
+            }
             /*
              * 加载定额子目
              * @param {Array}rationData(清单项目下定额数据) {Array}rationGljData(定额的人材机数据) {Object}decimal(项目小数位数)
@@ -1105,17 +1283,31 @@ const XMLStandard = (function () {
                 if (rationData.adjustState) {
                     viewCode += '换';
                 }
+                //子目类型 补充定额为“1”,标准定额无换算为“0”,标准定额有换算为“2”,安装费用,即自动生成的安装子目,为“3”,子目级人材机,即量价、与定额同级的人材机 其他待完善……
+                let subType;
+                if (rationData.type === rationType.ration && rationData.from === 'cpt') {
+                    subType = '1';
+                } else if (rationData.type === rationType.ration && !rationData.adjustState) {
+                    subType = '0';
+                } else if (rationData.type === rationType.ration && rationData.adjustState) {
+                    subType = '2';
+                } else if (rationData.type === rationType.install) {
+                    subType = '3';
+                } else if (rationData.type === rationType.volumePrice) {
+                    subType = '6';
+                }
                 let rationSource = {
                     viewCode: viewCode,
                     name: rationData.name,
                     unit: rationData.unit,
                     libCode: '',
                     code: rationData.code,
-                    subType: 1,
+                    subType: subType,
                     quantity: rationData.quantity,
-                    quantityEXP: rationData.quantityEXP,
+                    quantityEXP: parseQuantityExp(rationData),
                     fees: rationData.fees,
                     isSubcontract: rationData.isSubcontract,
+                    programID: rationData.programID,
                     remark: rationData.remark
                 };
                 if (rationData.from === 'std' && isDef(rationData.libID)) {    //来自标准库,设置定额库编码
@@ -1129,7 +1321,8 @@ const XMLStandard = (function () {
                 rationGljData = gljUtil.sortRationGLJ(rationGljData);
                 for (let rGlj of rationGljData) {
                     let gljSource = {
-                        code: rGlj.code,
+                        //code: rGlj.code,
+                        code: getGljCode(rGlj.GLJID),
                         quantity: rGlj.quantity,
                         totalQuantity: gljUtil.getTotalQuantity(rGlj,  rationData, decimal.glj.quantity, decimal.ration.quantity)
                     };
@@ -1167,9 +1360,6 @@ const XMLStandard = (function () {
                 failList.push('不存在分部分项清单');
                 return fbfxBills;
             }
-            if (subEngNode.children.length === 0) {
-                failList.push('分部分项清单下必须要有清单分部或清单项目');
-            }
             for (let node of subEngNode.children) {
                 if (node.data.type === billType.FB) {
                     if (node.children.length === 0) {
@@ -1246,10 +1436,10 @@ const XMLStandard = (function () {
                             code: node.data.code,
                             name: node.data.name,
                             calcBase: node.data.calcBase,
-                            feeRate: node.data.feeRate,
+                            feeRate: node.data.feeRate !== '' ? node.data.feeRate : 100,
                             fees: node.data.fees,
                             remark: node.data.remark,
-                            feeType: FEE_TYPE[fixedFlag.CONSTRUCTION_ORGANIZATION]
+                            feeType: FEE_TYPE[getNodeFlag(node)] || FEE_TYPE['0']
                         };
                         let formula = new FormulaCalcMeasure(source);
                         parent.children.push(formula);
@@ -1296,9 +1486,10 @@ const XMLStandard = (function () {
                 otherEle.children.push(loadEngEstimate(engEstimateNode));
             }
             //添加计日工元素
+            debugger;
             let dayWorkNode = detail.Bills.tree.items.find(node => getNodeFlag(node) === fixedFlag.DAYWORK);
-            let dayWorkEle = new DayWorkItem({fees: dayWorkNode.data.fees});
-            if (dayWorkNode && dayWorkNode.children.length > 0) {
+            let dayWorkEle = new DayWork({fees: dayWorkNode.data.fees});
+                if (dayWorkNode && dayWorkNode.children.length > 0) {
                 //要显示计日工元素,人工、材料、施工机械,最少有一条含有子项(计日工项目)
                 let filterNodes = dayWorkNode.children.filter(node => [fixedFlag.LABOUR, fixedFlag.MATERIAL, fixedFlag.MACHINE].includes(getNodeFlag(node)));
                 for (let fNode of filterNodes) {
@@ -1446,8 +1637,10 @@ const XMLStandard = (function () {
                     } else {    //总承包服务费费用项
                         let source = {
                             code: node.data.code,
+                            name: node.data.name,
                             serviceContent: node.data.serviceContent,
-                            feeRate: node.data.feeRate,
+                            calcBaseValue: node.data.calcBaseValue,
+                            feeRate: node.data.feeRate !== '' ? node.data.feeRate : 100,
                             fees: node.data.fees,
                             remark: node.data.remark
                         };
@@ -1483,8 +1676,9 @@ const XMLStandard = (function () {
                         code: node.data.code,
                         name: node.data.name,
                         calcBase: node.data.calcBase,
-                        feeRate: node.data.feeRate,
+                        feeRate: node.data.feeRate !== '' ? node.data.feeRate : 100,
                         commonTotalFee: totalFee,
+                        notSummary: belongFlag && belongFlag === fixedFlag.MATERIAL_PROVISIONAL,   //不计入合价,只有材料(工程设备)暂估价固定节点
                         remark: node.data.remark
                     });
                     otherItems.push(otherItemEle);
@@ -1521,9 +1715,9 @@ const XMLStandard = (function () {
                     rowCode: `F${serialNo}`,
                     name: node.data.name,
                     calcBase: node.data.calcBase,
-                    feeRate: node.data.feeRate,
+                    feeRate: node.data.feeRate !== '' ? node.data.feeRate : 100,
                     fees: node.data.fees,
-                    feeType: FEE_TYPE[getNodeFlag(node)],
+                    feeType: FEE_TYPE[getNodeFlag(node)] || FEE_TYPE['0'],
                     remark: node.data.remark
                 };
                 //序号唯一
@@ -1538,7 +1732,10 @@ const XMLStandard = (function () {
         * @return {void}
         * */
         function loadMainBillsItems(parent, detail) {
-            let mainBills = detail.Bills.datas.filter(data => data.mainBills);
+            let mainBills = detail.Bills.datas.filter(data => {
+                let billsNode = detail.Bills.tree.nodes[`${detail.Bills.tree.prefix}${data.ID}`];
+                return data.mainBills && billsNode && billsNode.children.length === 0;
+            });
             for (let bills of mainBills) {
                 let source = {
                     code: bills.code,
@@ -1555,31 +1752,72 @@ const XMLStandard = (function () {
             }
         }
         /*
-        * 加载人材机汇总
-        * @param {Object}detail
+        * 加载人材机汇总相关:人材机汇总、评标、暂估
+        * @param {Object}appraisalSummary(建设项目下的评审材料汇总) {Object}detail
         * @return {Object}
         * */
-        function loadGlj(detail) {
+        function loadGlj(appraisalSummary, detail) {
             let gljList = detail.projectGLJ.datas.gljList;
+            let evalBidSeq = 1,    //评标序号
+                evalEstSeq = 1;    //暂估序号
             if (gljList.length > 0) {
                 //创建人材机汇总节点
                 let gljSummary = new GljSummary();
+                //创建评标材料表节点
+                let evalBidMaterial = new EvalBidMaterial();
+                //创建暂估价材料表节点
+                let evalEstimateMaterial = new EvalEstimateMaterial();
                 //人材机节点
                 let allGljs = gljUtil.sortRationGLJ(gljList); //人材机汇总排序
                 //计算总消耗量
                 gljUtil.calcProjectGLJQuantity(detail.projectGLJ.datas,
                     detail.ration_glj.datas, detail.Ration.datas, detail.Bills.datas, curPMData.tender.property.decimal.glj.quantity, _, scMathUtil);
+                //类型-配比类别映射
+                let ratioMapping = {
+                    '202': '1', //为“混凝土”时,取“1”;
+                    '205': '2', //为“商品混凝土”时,取“2”;
+                    '203': '3', //为“砂浆”时,取“3”;
+                    '206': '4', //为“商品砂浆”时,取“4”;
+                    '204': '5'  //为“配合比”时,取“5”
+                };
+                //三材类别-主要材料类别映射 三材类型:钢材1、钢筋2、木材3、水泥4、商品砼5、商品砂浆6
+                let mainMaterialMapping = {
+                    '1': '100', //为“钢材”、“钢筋”时,取“100”;
+                    '2': '100',
+                    '4': '200', //为“水泥”时,取“200”;
+                    '3': '300', //为“木材”时,取“300”;
+                    '5': '400', //为“商品砼”、商品砂浆”时,取“400”。
+                    '6': '400'
+                };
                 for (let glj of allGljs) {
+                    //gljUtil.getAdjustPrice(glj,proGLJ.datas,calcOptions,labourCoeDatas,decimalObj,isRadio,_,scMathUtil);
                     let price = gljUtil.getGLJPrice(glj, detail.projectGLJ.datas,
                         curPMData.tender.property.calcOptions, detail.labourCoe.datas, curPMData.tender.property.decimal, false, _, scMathUtil);
+                    //调整价
+                    let adjPrice = gljUtil.getAdjustPrice(glj, detail.projectGLJ.datas,
+                        curPMData.tender.property.calcOptions, detail.labourCoe.datas, curPMData.tender.property.decimal, false, _, scMathUtil);
+                    //获取人材机费用类别: 1=人工费 2=材料费 3=机械费 4=未计价费
+                    let feeType = glj.type.toString()[0];
+                    if (feeType && !['1', '2', '3'].includes(feeType)) {
+                        feeType = '4';
+                    }
                     let gljSource = {
-                        code: glj.code,
+                        //code: glj.code,
+                        code: getGljCode(glj.glj_id),
                         name: glj.name,
-                        spec: glj.spec,
+                        specs: glj.specs,
                         unit: glj.unit,
-                        orgCode: glj.original_code,
-                        is_evaluate: glj.is_evaluate,
-                        basePrice: price.base,
+                        orgCode: glj.code,  //不取原始编码了,直接取编码
+                        feeType: feeType,
+                        ratioType: ratioMapping[glj.type],
+                        mainMaterialType: mainMaterialMapping[glj.materialType],
+                        materialCoe: glj.materialCoe,
+                        supply: glj.supply === 2 ? 2 : 1,   //供货方式为“完全甲供”,取“2”;否则取“1”。
+                        is_evaluate: !!glj.is_evaluate,
+                        no_tax_eqp: !!glj.no_tax_eqp,
+                        notFromDetail: !glj.ratio_data.length, //单价不从明细汇总标志,如果有配比组成,取“false”;否则取“true”。
+                        basePrice: price.basePrice,
+                        adjPrice: adjPrice,
                         marketPrice: price.marketPrice,
                         quantity: glj.quantity,
                         originPlace: glj.originPlace,
@@ -1596,14 +1834,73 @@ const XMLStandard = (function () {
                         ratioData = detail.projectGLJ.datas.mixRatioMap[connectKey];
                     if (ratioData && Array.isArray(ratioData)) {
                         for (let ratio of ratioData) {
-                            let gljRatio = new GljRatio({code: ratio.code, quantity: ratio.consumption});
+                            let gljRatio = new GljRatio({code: getGljCode(ratio.glj_id), quantity: ratio.consumption});
                             gljEle.children.push(gljRatio);
                         }
                     }
                     gljSummary.children.push(gljEle);
+                    gljSource.totalPrice = scMathUtil.roundForObj(gljSource.quantity * gljSource.marketPrice,
+                        detail.projectInfo.property.decimal.bills.totalPrice);
+                    //评标和暂估材料表下的材料明细中的人材机代码,要求单位工程内唯一,由于人材机汇总也有这个限制,所以这里不再处理
+                    if (glj.is_eval_material) { //评标
+                        gljSource.serialNo = evalBidSeq++;
+                        evalBidMaterial.children.push(new MaterialDetail(gljSource));
+                        //给建设项目下的评审材料汇总设置明细数据,这里需要检测代码(编号)唯一性,因为是汇总所有单位工程的,要求所有单位工程内评审材料代码唯一
+                        appraisalSummary.children.push(new AppraisalDetail(gljSource));
+                        checkUnique(curTenderEle.constraints.appraisalDetailCode, gljSource.code, '评审材料明细代码');
+                    }
+                    if (glj.is_evaluate) {  //暂估
+                        gljSource.serialNo = evalEstSeq++;
+                        evalEstimateMaterial.children.push(new MaterialDetail(gljSource));
+                    }
+                }
+                return {gljSummary, evalBidMaterial, evalEstimateMaterial};
+            }
+        }
+        /*
+        * 加载清单综合单价计算程序
+        * */
+        function loadCalcProgram(detail) {
+            let calcProgram = detail.calcProgram;
+            if (!calcProgram) {
+                return null;
+            }
+            let calcProramTemplates = calcProgram.templates;
+            if (!Array.isArray(calcProramTemplates) || calcProramTemplates.length === 0) {
+                return null;
+            }
+            //创建清单综合单价计算程序
+            let calcProgramEle = new CalcProgram();
+            //创建综合单价计算程序文件
+            for (let calcFile of calcProramTemplates) {
+                let calcFileEle = new CalcProgramFile({ID: calcFile.ID, name: calcFile.name});
+                //创建计算程序费用项
+                for (let calcItem of calcFile.calcItems) {
+                    let idx = calcFile.calcItems.indexOf(calcItem);
+                    //如果是有[]的基数则转换为其简称,如“[定额人工费]”对应的检查是"RGF"
+                    let calcBase = calcItem.dispExpr.replace(/\[[\u4e00-\u9fa5]+\]/g, str => rationBaseShort[str] ? rationBaseShort[str] : '');
+                    let feeType = calcProgram.feeTypes.find(data => data.type === calcItem.fieldName);
+                    if (feeType) {
+                        feeType = feeType.code;
+                    }
+                    let source = {
+                        serialNo: idx + 1,
+                        rowCode: `F${idx + 1}`,
+                        name: calcItem.name,
+                        calcBase: calcBase,
+                        statement: calcItem.statement,
+                        feeRate: calcItem.feeRate !== '' ? calcItem.feeRate : 100,
+                        feeType: feeType,
+                        remark: calcItem.memo
+                    };
+                    let calcItemEle = new CalcProgramFeeItem(source);
+                    calcFileEle.children.push(calcItemEle);
+                }
+                if (calcFileEle.children.length > 0) {  //计算程序文件必须有费用项
+                    calcProgramEle.children.push(calcFileEle);
                 }
-                return gljSummary;
             }
+            return calcProgramEle.children.length > 0 ? calcProgramEle : null;
         }
         //开始标签
         function startTag(ele) {
@@ -1669,11 +1966,13 @@ const XMLStandard = (function () {
         }
         /*
          * 导出数据
-         * @param {Number}tenderID(当前界面的单位工程ID,后台根据这个单位工程,去找其建设项目下所有数据)
+         * @param {Number}tenderID(当前界面的单位工程ID,后台根据这个单位工程,根据导出粒度去找其建设项目下相关数据)
          * @return {void}
          * */
         this.toXml = async function (tenderID) {
             let eleData = await loadProject(tenderID);
+            this.datas = eleData;
+            //console.log(eleData);
             if (!eleData) {
                 return;
             }
@@ -1683,8 +1982,8 @@ const XMLStandard = (function () {
             xmlStr = `<?xml version="1.0" encoding="utf-8"?>${xmlStr}`;
             //格式化
             xmlStr = formatXml(xmlStr);
-            let blob = new Blob([xmlStr], {type: 'text/plain;charset=uft-8'});
-            saveAs(blob, '重庆标准交换数据.xml');
+            let blob = new Blob([xmlStr], {type: 'text/plain;charset=utf-8'});
+            saveAs(blob, '重庆标准交换数据.qtf');
         }
     }
 })();

+ 103 - 0
web/building_saas/main/js/models/importStandardInterface.js

@@ -0,0 +1,103 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Zhong
+ * @date 2019/5/14
+ * @version
+ */
+/*
+* 尽可能减少服务器压力,前端提取后端所需数据
+* */
+const ImportXML = (() => {
+    //读取文件转换为utf-8编码的字符串
+    function readAsTextSync(file) {
+        return new Promise((resolve, reject) => {
+            let fr = new FileReader();
+            fr.readAsText(file);    //默认utf-8,如果出现乱码,得看导入文件是什么编码
+            fr.onload = function () {
+                resolve(this.result);
+            };
+            fr.onerror = function () {
+                reject('读取文件失败,请重试。');
+            }
+        });
+    }
+    /*
+    * 根据字段数组获得所要字段的值 eg: 要获取标段下的单项工程: ['标段', '单项工程'];
+    * @param {Object}obj原始对象 {Array}fields字段数组
+    * */
+    function getValue(obj, fields) {
+        let cur = obj;
+        for (let field of fields) {
+            if (!cur[field]) {
+                return '';
+            }
+            cur = cur[field];
+        }
+        return cur || '';
+    }
+    function plainType(v) {
+        return Object.prototype.toString.call(v).slice(8, -1);
+    }
+    return function () {
+        /*
+        * 获取某字段的值,返回数组
+        * */
+        function arrayValue(source, fields) {
+            let target = getValue(source, fields);
+            if (plainType(target) === 'Object') {
+                target = [target];
+            } else if (plainType(target) !== 'Array') {
+                target = []
+            }
+            return target;
+        }
+        //标段
+        function project(source) {
+            return {
+                name: getValue(source, ['标段', '_项目名称']),
+                engs: eng(source)
+            };
+        }
+        //单项工程
+        function eng(source) {
+            let engSrcs = arrayValue(source, ['标段', '单项工程']),
+                rst = [];
+            for (let src of engSrcs) {
+                rst.push({
+                    name: getValue(src, ['_名称']),
+                    tenders: tender(src)
+                });
+            }
+            return rst;
+        }
+        //单位工程
+        function tender(engSrc) {
+            let tenderSrcs = arrayValue(engSrc, ['单位工程']),
+                rst = [];
+            for (let src of tenderSrcs) {
+                rst.push({
+                    name: getValue(src, ['_名称']),
+                    engineering: getValue(src, ['_专业']),
+                });
+            }
+            return rst;
+        }
+        this.extractData = async (file) => {
+            //将二进制文件转换成字符串
+            let xmlStr = await readAsTextSync(file);
+            //将xml格式良好的字符串转换成对象
+            let x2js = new X2JS();
+            let xmlObj = x2js.xml_str2json(xmlStr);
+            if (!xmlObj) {
+                throw '无有效数据。';
+            }
+            console.log(xmlObj);
+            //提取数据
+            console.log(project(xmlObj));
+            return project(xmlObj);
+        };
+    };
+})();

+ 43 - 20
web/building_saas/main/js/models/main_consts.js

@@ -186,19 +186,22 @@ const materialType = {
     2: '钢筋',
     3: '木材',
     4: '水泥',
-    5: '标准砖'
+    5: '商品砼',
+    6: '商品砂浆'
 };
 const materialTypeMap = {
     GC: 1,
     GJ: 2,
     MC: 3,
     SN: 4,
-    SZ: 5,
+    ST: 5,
+    SS: 6,
     1:'GC',
     2:'GJ',
     3:'MC',
     4:'SN',
-    5:'SZ'
+    5:'ST',
+    6:'SS'
 
 }
 
@@ -261,21 +264,11 @@ const fixedFlag = {
     //附加税
     ADDITIONAL_TAX: 28,
     //环境保护税
-    ENVIRONMENTAL_PROTECTION_TAX: 29
-};
-//费用类别:固定行ID对应
-const FEE_TYPE = {
-    [fixedFlag.SUB_ENGINERRING]: '1100',
-    [fixedFlag.MEASURE]: '1200',
-    [fixedFlag.CONSTRUCTION_ORGANIZATION]: '120201',
-    [fixedFlag.SAFETY_CONSTRUCTION]: '1204',
-    [fixedFlag.OTHER]: '1300',
-    [fixedFlag.CHARGE]: '800',
-    [fixedFlag.TAX]: '900',
-    [fixedFlag.ADDED_VALUE_TAX]: '9001',
-    [fixedFlag.ADDITIONAL_TAX]: '9002',
-    [fixedFlag.ENVIRONMENTAL_PROTECTION_TAX]: '9003',
-    0: '1800',  //其他未定义的大项费用
+    ENVIRONMENTAL_PROTECTION_TAX: 29,
+    //建设工程竣工档案编制费
+    PROJECT_COMPLETE_ARCH_FEE:30,
+    //住宅工程质量分户验收费
+    HOUSE_QUALITY_ACCEPT_FEE:31
 };
 const gljKeyArray =['code','name','specs','unit','type'];
 const rationKeyArray =['code','name','specs','unit','subType'];
@@ -391,7 +384,8 @@ const filterType = {
     JGCL:'8',
     ZGCL:'9',
     SCHZ:'10',
-    ZYCL:'11'
+    ZYCL:'11',
+    AMAE:'12'
 };
 const filterTypeArray = ['1','2','3','4','5'];
 
@@ -404,5 +398,34 @@ const materialComboMap = [
     {text:materialType[materialTypeMap.GJ],value:materialTypeMap.GJ},
     {text:materialType[materialTypeMap.MC],value:materialTypeMap.MC},
     {text:materialType[materialTypeMap.SN],value:materialTypeMap.SN},
-    {text:materialType[materialTypeMap.SZ],value:materialTypeMap.SZ}
+    {text:materialType[materialTypeMap.ST],value:materialTypeMap.ST},
+    {text:materialType[materialTypeMap.SS],value:materialTypeMap.SS}
 ];
+
+//定额计算基数简称对应关系,目前只有导出标准接口使用
+const rationBaseShort = {
+    '[定额人工费]': 'RGF',
+    '[定额材料费]': 'CLF',
+    '[定额其他材料费]': 'QTCLF',
+    '[定额施工机具使用费]': 'JXF',
+    '[市场价主材费]': 'SCJZCF',
+    '[市场价设备费]': 'SCJSBF',
+    '[人工工日]': 'RGGR',
+    '[人工费价差]': 'RGJC',
+    '[材料费价差]': 'CLJC',
+    '[机上人工费价差]': 'JSRGJC',
+    '[燃料动力费价差]': 'RLDLJC',
+    '[建筑面积]': 'JZMJ',
+    '[甲供定额人工费]': 'JGRGF',
+    '[甲供定额材料费]': 'JGCLF',
+    '[甲供定额施工机具费]': 'JGJXF',
+    '[甲供主材费]': 'JGZCF',
+    '[暂估材料费]': 'ZGF',
+    '[机械折旧费]': 'JXZJF',
+    '[特大机械检修费]': 'TDJXJXF',
+    '[中小机械检修费]': 'ZXJXJXF',
+    '[特大机械维护费]': 'TDJXWHF',
+    '[中小机械维护费]': 'ZXJXWHF',
+    '[安拆费及场外运输费]': 'ACCWYSF',
+    '[定额仪器仪表费]': 'YQYBF',
+};

+ 13 - 8
web/building_saas/main/js/models/ration.js

@@ -395,9 +395,11 @@ var Ration = {
             let mainTree = project.mainTree;
             let nodeInfo =[];
             let refershNodes = [];
-            if(libIDs == null){
-                return;
-            }
+            if(libIDs == null) return;
+
+            //设置定额库的优先级,默认先取选中的定额库,如果没有再取default定额库
+            let selectedLib = sessionStorage.getItem("stdRationLib");
+            selectedLib&&selectedLib!='undefined'?libIDs.unshift(selectedLib):libIDs.unshift(defaultLibID);
             for(let r of recodes){
                 let needInstall = false;
                 if(projectObj.project.isInstall()) {//如果是安装工程,要看需不需要生成安装增加费
@@ -421,7 +423,7 @@ var Ration = {
                        node.data.feesIndex = {};
                        //删除定额节点下的主材和设备节点
                        project.ration_glj.removeNodeByRation(recode.ration,projectObj.mainController);
-                       project.Ration.deleteSubListOfRation(recode.ration);//删除旧定额下的相关记录
+                       project.Ration.deleteSubListOfRation(recode.ration,cleanzmhs);//删除旧定额下的相关记录
                        //添加新的记录
                        project.Ration.addSubListOfRation(recode);
 
@@ -707,12 +709,15 @@ var Ration = {
             }
             else return null;
         };
-        ration.prototype.deleteSubListOfRation = function(ration){
+        ration.prototype.deleteSubListOfRation = function(ration,cleanzmhs=false){
             projectObj.project.ration_glj.deleteByRation(ration);
             projectObj.project.ration_coe.deleteByRation(ration);
-            projectObj.project.quantity_detail.deleteByRation(ration);
-            projectObj.project.ration_installation.deleteByRation(ration);
-            projectObj.project.ration_template.deleteByRation(ration);
+            if(cleanzmhs == false){
+                projectObj.project.ration_installation.deleteByRation(ration);
+                projectObj.project.quantity_detail.deleteByRation(ration);
+                projectObj.project.ration_template.deleteByRation(ration);
+            }
+
         };
         ration.prototype.addSubListOfRation = function (data) {
              project.ration_glj.addDatasToList(data.ration_gljs);

+ 1 - 0
web/building_saas/main/js/models/ration_glj.js

@@ -841,6 +841,7 @@ let ration_glj = {
                 doc.from = "std";
             }
             for(let d of this.datas){//查询出所有需替换的工料机
+                if(!gljOprObj.scopeSelectedIDMap[d.billsItemID]) continue; //如果不在选中范围的,跳过
                 let tem_index = gljOprObj.getIndex(d, gljKeyArray);
                 if(tem_index == oldIndex){
                     let tem_doc = _.cloneDeep(doc);

+ 8 - 4
web/building_saas/main/js/views/fee_rate_view.js

@@ -200,7 +200,8 @@ var feeRateObject={
         }
     },
     initFeeRateEditDiv:function(optionValue){//“0” 手工输入; “1” 选择费率
-        let radioValue = optionValue,feeRateValue;
+        let radioValue = 1,feeRateValue;
+        $("#customFeeRate").val("");//先清空输入
         if(!gljUtil.isDef(optionValue)){
             let fID = 0;
             if ($("#calc_program_manage").is(":visible")){
@@ -210,10 +211,13 @@ var feeRateObject={
                 fID = projectObj.project.mainTree.selected.data.feeRateID;
                 feeRateValue = projectObj.project.mainTree.selected.data.feeRate;
             }
-            radioValue = fID?"1":"0";
-            $("#customFeeRate").val(feeRateValue);
+            if(fID){
+                feeRateValue = projectObj.project.FeeRate.getFeeRateByID(fID).rate;
+            }
+            if(!fID&&gljUtil.isDef(feeRateValue)) radioValue = 0; //2019-04-18 只有在没有费率ID,但是有费率值的情况下才显示自定义费率页,其它所有的情况都显示费率选择页面
+            if(!isNaN(feeRateValue))$("#customFeeRate").val(feeRateValue);
         }
-        $("input[name='editFeeRateOptions'][value='"+radioValue+"']").attr("checked",true);
+        $("input[name='editFeeRateOptions'][value='"+radioValue+"']").prop("checked",true);
         if(radioValue == "0"){
             $("#selfDiv").show();
             $("#fee_rate_sheet").hide();

+ 149 - 1
web/building_saas/main/js/views/glj_col.js

@@ -45,6 +45,8 @@ let gljCol = {
             {headerName: "暂估", headerWidth: 45, dataCode: "is_evaluate", hAlign: "center", dataType: "String",cellType:'checkBox'},
             {headerName: "主要\n材料", headerWidth: 45, dataCode: "is_main_material", hAlign: "center", dataType: "String",cellType:'checkBox'},
             {headerName: "不调价", headerWidth: 55, dataCode: "is_adjust_price", dataType: "String",cellType: "checkBox"},
+            {headerName: "不计税设备", headerWidth: 55, dataCode: "no_tax_eqp", dataType: "String",cellType: "checkBox"},
+            {headerName: "评标材料", headerWidth: 35, dataCode: "is_eval_material", dataType: "String",cellType: "checkBox"},
             {headerName: "供货方式", headerWidth: 70, dataCode: "supply", hAlign: "center", dataType: "String",cellType:'comboBox',editorValueType:true,options:supplyComboMap},
             {headerName: "甲供数量", headerWidth: 90, dataCode: "supply_quantity", hAlign: "right", dataType: "Number",validator:"number",decimalField:'glj.quantity'},
             {headerName: "三材类别", headerWidth: 70, dataCode: "materialType", hAlign: "center", dataType: "String",cellType:'comboBox',editorValueType:true,options:materialComboMap},
@@ -78,6 +80,144 @@ let gljCol = {
             lockColumns: [0,1,2,3,4,5,6,7]
         }
     },
+    scopeSetting:{
+        "emptyRows":0,
+        "headRows":1,
+        "headRowHeight":[30],
+        "defaultRowHeight": 21,
+        "treeCol": 1,
+        "cols":[
+            {
+                "width":40,
+                "readOnly": true,
+                "head":{
+                    "titleNames":["选择"],
+                    "spanCols":[1],
+                    "spanRows":[1],
+                    "vAlign":[1],
+                    "hAlign":[1],
+                    "font":["Arial"]
+                },
+                "data":{
+                    "field":"selected",
+                    "vAlign":1,
+                    "hAlign":1,
+                    "font":"Arial",
+                    "cellType":function (node) {
+                        return new GC.Spread.Sheets.CellTypes.CheckBox();
+                    }
+                }
+            },
+            {
+                "width":140,
+                "readOnly": true,
+                "head":{
+                    "titleNames":["编号"],
+                    "spanCols":[1],
+                    "spanRows":[1],
+                    "vAlign":[1],
+                    "hAlign":[1],
+                    "font":["Arial"]
+                },
+                "data":{
+                    "field":"code",
+                    "vAlign":1,
+                    "hAlign":0,
+                    "font":"Arial"
+                }
+            },
+            {
+                "width":240,
+                "readOnly": true,
+                "head":{
+                    "titleNames":["名称"],
+                    "spanCols":[1],
+                    "spanRows":[1],
+                    "vAlign":[1],
+                    "hAlign":[1],
+                    "font":["Arial"]
+                },
+                "data":{
+                    "field":"name",
+                    "vAlign":0,
+                    "hAlign":0,
+                    "font":"Arial"
+                }
+            },
+            {
+                "width":70,
+                "readOnly": true,
+                "head":{
+                    "titleNames":["单位"],
+                    "spanCols":[1],
+                    "spanRows":[1],
+                    "vAlign":[1],
+                    "hAlign":[1],
+                    "font":["Arial"]
+                },
+                "data":{
+                    "field":"unit",
+                    "vAlign":1,
+                    "hAlign":1,
+                    "font":"Arial"
+                }
+            },
+            {
+                "width":80,
+                "readOnly": true,
+                "head":{
+                    "titleNames":["工程量"],
+                    "spanCols":[1],
+                    "spanRows":[1],
+                    "vAlign":[1],
+                    "hAlign":[1],
+                    "font":["Arial"]
+                },
+                "data":{
+                    "field":"quantity",
+                    "vAlign":1,
+                    "hAlign":2,
+                    "font":"Arial"
+                }
+            },
+            {
+                "width":80,
+                "readOnly": true,
+                "head":{
+                    "titleNames":["单价"],
+                    "spanCols":[1],
+                    "spanRows":[1],
+                    "vAlign":[1],
+                    "hAlign":[1],
+                    "font":["Arial"]
+                },
+                "data":{
+                    "field":"unitPrice",
+                    "vAlign":1,
+                    "hAlign":2,
+                    "font":"Arial"
+                }
+            },
+            {
+                "width":80,
+                "readOnly": true,
+                "head":{
+                    "titleNames":["金额"],
+                    "spanCols":[1],
+                    "spanRows":[1],
+                    "vAlign":[1],
+                    "hAlign":[1],
+                    "font":["Arial"]
+                },
+                "data":{
+                    "field":"totalPrice",
+                    "vAlign":1,
+                    "hAlign":2,
+                    "font":"Arial"
+                }
+            }
+        ]
+    },
     removeCol:function (dataCode,setting) {
         let colIndex = _.findIndex(setting.header,{'dataCode':dataCode});
         if(colIndex != -1){
@@ -102,8 +242,16 @@ let gljCol = {
         gljOprObj.setting = me.ration_glj_setting;
         projectGljObject.projectGljSetting = me.project_glj_setting;
         projectGljObject.mixRatioSetting = me.mixRatio_Setting;
+        me.setScopeFormater();
+        gljOprObj.scopeSetting = me.scopeSetting;
+    },
+    setScopeFormater:function () {
+        // 综合单价、综合合价,小数部分应补0对齐。  CSL
+        for(let col of this.scopeSetting.cols){
+            if (col.data.field=="totalPrice") col.data.formatter = MainTreeCol.getNumberFormatter(decimalObj.ration.totalPrice, true);
+            if (col.data.field== "unitPrice")  col.data.formatter = MainTreeCol.getNumberFormatter(decimalObj.ration.unitPrice, true);
+        }
     }
-
 };
 /*
 $(function () {

+ 85 - 6
web/building_saas/main/js/views/glj_view.js

@@ -16,6 +16,7 @@ var gljOprObj = {
     GLJSelection: [],
     selectedGLJClass: null,
     parentNodeIds: {},
+    preActiveTab: '', //提升焦点变换性能 2019年4月12日
     activeTab: '#linkGLJ',
     rationTab:'#linkGLJ',
     billsTab:'#linkQDJL',
@@ -101,6 +102,11 @@ var gljOprObj = {
         }
     },
     gljLibSheet: null,
+    scopeSetting:{},
+    scopeSpread:null,
+    scopeSheet:null,
+    scopeDatas:[],
+    scopeSelectedIDMap:{},
     initSheet: function (sheet) {
         var me = this;
         me.sheet = sheet;
@@ -1095,7 +1101,6 @@ var gljOprObj = {
         if (me.GLJSelection.length <= 0) {
             return;
         }
-        $("#glj_tree_div").modal('hide');
         $.bootstrapLoading.start();
         project.ration_glj.insertGLJAsRation(me.GLJSelection, selected, function (parentNodeID,nextNodeID,data) {
             let newNode=null;
@@ -1138,7 +1143,6 @@ var gljOprObj = {
             return rg ? 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;
@@ -1154,8 +1158,6 @@ var gljOprObj = {
                     });
                 }
             });//doc.rationID=selected.data.ID;
-        } else {
-            $("#glj_tree_div").modal('hide');
         }
     },
     doReplaceGLJ: function () {
@@ -1164,7 +1166,6 @@ var gljOprObj = {
         let project = projectObj.project;
         let selectCode = gljOprObj.GLJSelection[0];
         let selected = projectObj.project.mainTree.selected;
-        $("#glj_tree_div").modal('hide');
         project.ration_glj.replaceGLJ(selectCode, oldData, function (result) {
             if (result) {
                 //result.adjustState;
@@ -1203,7 +1204,6 @@ var gljOprObj = {
         let oldData = me.sheetData[gljContextMenu.selectedRow];
         let project = projectObj.project;
         let selectCode = me.GLJSelection[0];
-        $("#glj_tree_div").modal('hide');
         project.ration_glj.mReplaceGLJ(selectCode, oldData, function (result,updateMap) {
             if(result == null){
                 return;
@@ -1423,6 +1423,70 @@ var gljOprObj = {
                 me.gljTreeSetting.callback.onClick(null, 'gljTree', me.rootNode);
             }
         }
+    },
+    initScopeSpread:function() {
+        if (this.scopeSpread) return this.scopeSpread.repaint();
+        this.scopeSpread = SheetDataHelper.createNewSpread($('#scopeSpread')[0]);
+        sheetCommonObj.spreadDefaultStyle(this.scopeSpread);
+        this.scopeTree = idTree.createNew({id: 'ID', pid: 'ParentID', nid: 'NextSiblingID', rootId: -1, autoUpdate: false});
+        this.scopeTreeController = TREE_SHEET_CONTROLLER.createNew(this.scopeTree, this.scopeSpread.getActiveSheet(), this.scopeSetting);
+        this.scopeSheet = this.scopeSpread.getActiveSheet();
+        this.scopeSpread.bind(GC.Spread.Sheets.Events.ButtonClicked, this.onScopeChecked);
+    },
+    showScopeDatas:function () {
+        this.initScopeSpread();
+        this.scopeDatas =this.getScopeDatas();
+        this.scopeTree.loadDatas(this.scopeDatas);
+        this.scopeTreeController.showTreeData();
+    },
+    getScopeDatas:function () {
+        let controller = projectObj.mainController, project = projectObj.project;
+        let allNodes = [],datas = []
+        for(let rootNode of controller.tree.roots){
+            allNodes.push(rootNode);
+            controller.tree.getAllSubNode(rootNode.source,allNodes);
+        }
+        for(let n of allNodes){
+            let temData = {
+                ID:n.data.ID,
+                NextSiblingID:n.data.NextSiblingID,
+                ParentID:n.data.ParentID,
+                unit : n.data.unit,
+                code : n.data.code,
+                selected:0,
+                name : n.data.name,
+                quantity:calcTools.uiNodeQty(n)?calcTools.uiNodeQty(n):"",
+            };
+            if(n.data.feesIndex && n.data.feesIndex.common){
+                temData.unitPrice = n.data.feesIndex.common.unitFee;
+                temData.totalPrice = n.data.feesIndex.common.totalFee;
+            }
+            datas.push(temData);
+        }
+        return datas;
+    },
+    onScopeChecked:function (e,args) {
+        let me = gljOprObj,nodes = [];
+        var checkboxValue = args.sheet.getCell(args.row, args.col).value();
+        var newval = checkboxValue?0:1;
+        let node = me.scopeTree.items[args.row];
+        setNodeAndChildreSelected(node,newval,nodes);
+        me.scopeTreeController.refreshTreeNode(nodes);
+
+        function setNodeAndChildreSelected(n,val,arr){
+            n.data.selected = val;
+            nodes.push(n);
+            for(let c of n.children){
+                setNodeAndChildreSelected(c,val,arr)
+            }
+
+        }
+    },
+    setScopeSelection:function () {
+        for(let node of this.scopeTree.items){
+            //挑出选中数,过滤父节点
+            if(node.data.selected == 1 && node.children.length == 0) this.scopeSelectedIDMap[node.data.ID] = true
+        }
     }
 }
 
@@ -1483,6 +1547,12 @@ $(function () {
         } else gljOprObj.showLibGLJSheetData();
     });
 
+    $('#mreplace_next_div').on('shown.bs.modal', function (e) {
+        gljOprObj.scopeSelectedIDMap = {};
+        gljOprObj.showScopeDatas();
+    });
+
+
     $('#glj_tree_div').on('hidden.bs.modal', function () {
         $('#gljSearchKeyword').val('');
     });
@@ -1538,7 +1608,16 @@ $(function () {
         }else if($('#actionType').val() == 'addMix'){
             projectGljObject.addMixRatio();
         }
+        $("#glj_tree_div").modal('hide');
     })
+    $('#scope_position_confirm').click(function () {
+        gljOprObj.setScopeSelection();
+        gljOprObj.doMReplaceGLJ();
+    });
+    $('#replace_next_btn').click(function () {
+        $("#glj_tree_div").modal('hide');
+        $("#mreplace_next_div").modal('show');
+    });
 
     $('#class_selected_conf').click(function () {
         var gljClass = $('#selected_class').val();

+ 7 - 0
web/building_saas/main/js/views/glj_view_contextMenu.js

@@ -393,6 +393,13 @@ function getGLJData(actionType) {
         $('#modalCon').width($(window).width()*0.5);
         $("input[name='glj']").get(0).checked=true;
         $.bootstrapLoading.end();
+        if(actionType == "m_replace"){
+            $('#glj_selected_conf').hide();
+            $('#replace_next_btn').show();
+        }else {
+            $('#glj_selected_conf').show();
+            $('#replace_next_btn').hide();
+        }
         $("#glj_tree_div").modal({show:true});
         setTimeout(function(){
             gljOprObj.gljLibSpresd?gljOprObj.gljLibSpresd.refresh():'';

+ 228 - 13
web/building_saas/main/js/views/locate_view.js

@@ -32,7 +32,7 @@ let locateObject={
         header:[
             {headerName: "编码", headerWidth: 120, dataCode: "code", dataType: "String"},
             {headerName: "名称", headerWidth: 80, dataCode: "name", dataType: "String"},
-            {headerName: "规格型号", headerWidth: 80, dataCode: "code", dataType: "String"},
+            {headerName: "规格型号", headerWidth: 80, dataCode: "specs", dataType: "String"},
             {headerName: "单位", headerWidth: 50, dataCode: "unit", dataType: "String",hAlign: "center"},
             {headerName: "市场价", headerWidth: 65, dataCode: "marketPrice", dataType: "Number", hAlign: "right"}
         ],
@@ -41,6 +41,7 @@ let locateObject={
             colHeaderHeight:30
         }
     },
+    datas:[],
     initMainSpread:function(){
         if(!this.mainSpread){
             this.mainSpread = SheetDataHelper.createNewSpread($("#locate_result")[0],3);
@@ -51,24 +52,24 @@ let locateObject={
         }
     },
     initMainSheet:function () {
-       // this.mainSheet = this.mainSpread .getSheet(0);
-        //this.spread.bind(GC.Spread.Sheets.Events.ButtonClicked, this.onReplaceButtonClick);
        //初始化清单表格
         sheetCommonObj.initSheet(this.mainSpread .getSheet(0),this.bills_setting);
         this.mainSpread .getSheet(0).setRowCount(0);
         //初始化定额表格
         sheetCommonObj.initSheet(this.mainSpread .getSheet(1),this.ration_setting);
         this.mainSpread .getSheet(1).setRowCount(0);
-
         //初始化人材机表格
         sheetCommonObj.initSheet(this.mainSpread.getSheet(2),this.ration_glj_setting);
         this.mainSpread .getSheet(2).setRowCount(0);
+        this.mainSpread.bind(GC.Spread.Sheets.Events.CellDoubleClick,this.onSheetDoubleClick);
+        this.mainSpread .getSheet(2).bind(GC.Spread.Sheets.Events.SelectionChanged,this.gljSelectionChange);
     },
     initSubSpread:function () {
         if(!this.subSpread){
             this.subSpread = SheetDataHelper.createNewSpread($("#locate_sub")[0]);
             sheetCommonObj.spreadDefaultStyle(this.subSpread);
             this.initSubSheet();
+            this.subSpread.bind(GC.Spread.Sheets.Events.CellDoubleClick,this.onSheetDoubleClick);
         }else {
             this.subSpread.refresh();
         }
@@ -85,14 +86,39 @@ let locateObject={
         this.subSheet = this.subSpread .getSheet(0);
         sheetCommonObj.initSheet( this.subSheet, this.ration_setting);
         this.subSheet.setRowCount(0);
-        //this.spread.bind(GC.Spread.Sheets.Events.ButtonClicked, this.onReplaceButtonClick);
         this.subSheet.name('locate_sub');
     },
-    showMainData:function () {
-        let datas = [];
-        //sheetCommonObj.showData(this.mainSheet,this.mainSettiong,datas);
+    initOutstanding:function () {
+        if(!projectObj.project.property.locateSetting) return;
+        let outstd = projectObj.project.property.locateSetting;
+        $("#outstanding").prop("checked",outstd.outstanding);
+        $("#outInp").val(parseFloat(outstd.outInp));
+    },
+    showMainData:function (datas,setting) {
+        sheetCommonObj.showData(this.mainSpread.getActiveSheet(),setting,datas);
         this.mainSpread.getActiveSheet().setRowCount(datas.length);
     },
+    showSubRateDatas:function () {
+        this.subRationDatas = this.getSubRationDatas();
+        sheetCommonObj.showData(this.subSheet,this.ration_setting,this.subRationDatas);
+        this.subSheet.setRowCount(this.subRationDatas.length);
+    },
+
+    getSubRationDatas:function () {
+        let datas = [];
+        let sheet = this.mainSpread.getActiveSheet();
+        let oldSel = sheet.getSelections()[0];
+        if(this.gljDatas && this.gljDatas.length > 0){
+            let glj = this.gljDatas[oldSel.row];
+            if(!glj) return datas;
+            let  nodes = projectObj.project.projectGLJ.getImpactRationNodes([glj.reference]);
+            for(let n of nodes){
+                datas.push(this.getShowRationDatas(n.data));
+            }
+        }
+        return datas;
+    },
+
     refreshView: function (options, refreshWorkBook) {
         let me = this;
         let mainHeight = $(window).height()-$(".header").height()-$(".toolsbar").height()-$("#searchPanel").height();
@@ -118,28 +144,201 @@ let locateObject={
             me.refreshView(options, false);
             me.initMainSpread();
             me.initSubSpread();
-            me.showMainData();
         };
+        if(options == "bills") me.initOutstanding();
         options == "bills"?$("#outstandingOptions").show(0,callback):$("#outstandingOptions").hide(0,callback);
 
     },
     findRecodes:function () {
-
+        let options = $("input[name='content_type']:checked").val();
+        let keyword = $("#locateInput").val();
+        switch (options){
+            case "bills":
+                this.billsDatas = this.findBills(keyword);
+                this.showMainData(this.billsDatas,this.bills_setting);
+                break;
+            case "ration":
+                this.rationDatas = this.findRations(keyword);
+                this.showMainData(this.rationDatas,this.ration_setting);
+                break;
+            case "ration_glj":
+                this.gljDatas = this.findGLJs(keyword);
+                this.showMainData(this.gljDatas,this.ration_glj_setting);
+                this.showSubRateDatas();
+                break;
+        }
     },
     onshow:function () {
         locateObject.init();
+    },
+
+    matchItem:function (keyword,i) {//true 匹配上,false匹配失败
+        let match = false;
+        if(keyword && keyword !="") {//如果keyword为空,匹配所有
+            if (i.code && i.code.indexOf(keyword) != -1) match = true;
+            if (i.name && i.name.indexOf(keyword) != -1) match = true;
+            if(match == false) return false
+        }
+        return true;
+    },
+    findGLJs:function(keyword){
+        let datas = [];
+        let gljList = projectObj.project.projectGLJ.datas.gljList;
+        gljList =  sortProjectGLJ(gljList);
+        for(let glj of gljList){
+            if(glj.quantity == 0 || glj.quantity == '0') continue;
+            let match = this.matchItem(keyword,glj);
+            if(match == false) continue;
+            let data = getGLJDatas(glj);
+            gljOprObj.setGLJPrice(data,glj);
+            datas.push(data);
+        }
+        return datas;
+        function getGLJDatas(tem) {
+            return{
+                ID:tem.id,
+                name:tem.name,
+                code:tem.code,
+                unit:tem.unit,
+                specs:tem.specs,
+                reference:tem
+            }
+        }
+
+    },
+    findRations:function (keyword) {
+        let datas = [];
+        let items = projectObj.project.mainTree.items;
+        for(let  i of items){
+            if(i.sourceType == ModuleNames.ration){
+                let match = this.matchItem(keyword,i.data);
+                if(match == false) continue;
+                let bills = this.getShowRationDatas(i.data);
+                datas.push(bills);
+            }
+        }
+        return datas;
+    },
+    getShowRationDatas:function(data){
+        return{
+            ID:data.ID,
+            name:data.name,
+            code:data.code,
+            unit:data.unit,
+            quantity:data.quantity
+        }
+    },
+    findBills:function(keyword){
+        let datas = [],priceMap={};
+        let items = projectObj.project.mainTree.items;
+        for(let  i of items){
+            if(i.sourceType == ModuleNames.bills){
+                let match = this.matchItem(keyword,i.data);
+                if(match == false) continue;
+                let bills = getBillData(i.data);
+                priceMap = setPriceMap(bills,priceMap);
+                datas.push(bills);
+            }
+        }
+        setBgColour(datas,priceMap);
+        datas =  _.sortByAll(datas,['code']);
+        return datas;
+
+
+        function setBgColour(bills,map) {
+            let outStd = $("#outstanding").prop("checked");
+            let outInp = $("#outInp").val();
+            if(outStd == true && outInp && outInp!=""){
+                for(let b of bills){
+                    if(b.code && b.code.length >= 9){
+                        let key = b.code.substr(0,9);
+                        if(map[key] && map[key].count > 1){
+                            let avg = map[key].total/map[key].count;
+                            let unitPrice = b.unitPrice?parseFloat(b.unitPrice):0;
+                            if(unitPrice ==0 ) continue;
+                            if(Math.abs(unitPrice - avg)/avg * 100  >= parseFloat(outInp)) b.bgColour = "#FFFACD"
+                        }
+                    }
+                }
+            }
+        }
+        function setPriceMap (bills,map) {
+            if(bills.code && bills.code.length >= 9){
+                let key = bills.code.substr(0,9);
+                let unitPrice = bills.unitPrice?parseFloat(bills.unitPrice):0;
+                if(map[key]){
+                    map[key].total += unitPrice;
+                    map[key].count ++;
+                }else {
+                    map[key] = {total:unitPrice,count:1}
+                }
+
+            }
+
+            return map;
+        }
+
+        function getBillData(data) {
+            return{
+                ID:data.ID,
+                name:data.name,
+                code:data.code,
+                unit:data.unit,
+                quantity:data.quantity,
+                unitPrice:data.feesIndex&&data.feesIndex.common?data.feesIndex.common.unitFee:"",
+                totalPrice:data.feesIndex&&data.feesIndex.common?data.feesIndex.common.totalFee:"",
+                bgColour:"white"
+            }
+        }
+    },
+    onSheetDoubleClick:function (e,args) {
+        let me = locateObject;
+        let options = $("input[name='content_type']:checked").val();
+        let sheetName = args.sheet.name()
+        if(options == "ration_glj"&&sheetName != "locate_sub" ) return;
+        let datas = options == "bills"? me.billsDatas:me.rationDatas;
+        if( args.sheet.name() == "locate_sub") datas = me.subRationDatas;
+        me.locateNode(datas[args.row].ID);
+    },
+    gljSelectionChange:function (e,args) {
+        let me = locateObject;
+        let newSel = args.newSelections[0];
+        let oldSel = args.oldSelections?args.oldSelections[0]:{};
+        if(newSel.row != oldSel.row){
+            me.showSubRateDatas();
+        }
+    },
+    locateNode:function (ID) {
+        let node =  projectObj.project.mainTree.findNode(ID);
+        if(node) projectObj.loadFocusLocation(node.serialNo(),1);
+    },
+    updateOutStanding:function (outstanding,outInp) {
+        let outstd = {outstanding:outstanding,outInp:outInp};
+        let updateData = {type:ModuleNames.project,data:{'ID' : projectObj.project.ID(),'property.locateSetting':outstd}};
+        projectObj.project.updateNodes([updateData],function () {
+            projectObj.project.property.locateSetting = outstd;
+        });
     }
-}
+
+};
 
 
 $("#locate_btn").click(function () {
     locateObject.findRecodes();
 });
 
+//回车键搜索
+$('#locateInput').bind('keypress', function (event) {
+    if(event.keyCode === 13){
+        $(this).blur();
+        locateObject.findRecodes();
+    }
+});
+
 $("input[name='content_type']").each(function(){
     $(this).click(function(){
-        let optins = $(this).val();
-        switch (optins){
+        let options = $(this).val();
+        switch (options){
             case "bills":
                 locateObject.mainSpread.setActiveSheetIndex(0);
                 break;
@@ -153,3 +352,19 @@ $("input[name='content_type']").each(function(){
         locateObject.init();
     });
 });
+
+
+$('#outInp').change(function(){
+    let me = locateObject;
+    let process = getDecimal('process');
+    var newVal = $(this).val();
+    let outInp = scMathUtil.roundForObj(newVal,process);
+    let outStd = $("#outstanding").prop("checked");
+    me.updateOutStanding(outStd,outInp);
+});
+$('#outstanding').change(function(){
+    let me = locateObject;
+    let outInp = $("#outInp").val();
+    let outStd = $("#outstanding").prop("checked");
+    me.updateOutStanding(outStd,scMathUtil.roundForObj(outInp,getDecimal('process')));
+});

+ 387 - 0
web/building_saas/main/js/views/material_adjust_view.js

@@ -0,0 +1,387 @@
+/**
+ * Created by zhang on 2019/5/15.
+ */
+let materialAdjustObj = {
+    spread:null,
+    priceInfoSheet:null,
+    priceCoeSheet:null,
+    priceInfoDatas:[],
+    priceCoeDatas:[],
+    priceInfoSetting:{
+        header: [
+            {headerName: "编号", headerWidth: 80, dataCode: "code", dataType: "String"},
+            {headerName: "材料名称", headerWidth: 160, dataCode: "name", dataType: "String",cellType:'tipsCell'},
+            {headerName: "规格型号", headerWidth: 120, dataCode: "specs", hAlign: "left", dataType: "String",cellType:'tipsCell'},
+            {headerName: "单位", headerWidth: 45, dataCode: "unit", hAlign: "center", dataType: "String"},
+            {headerName: "数量", headerWidth: 90, dataCode: "quantity", hAlign: "right", dataType: "Number",decimalField:'glj.quantity'},
+            {headerName: "风险系数", headerWidth: 90, dataCode: "riskCoe", hAlign: "right", dataType: "Number",validator:"number"},
+            {headerName: "基准单价", headerWidth: 70, dataCode: "standardPrice", hAlign: "right", dataType: "Number",validator:"number"},
+            {headerName: "投标单价", headerWidth: 70, dataCode: "marketPrice", hAlign: "right", dataType: "Number",validator:"number"},
+            {headerName: "合价", headerWidth: 70, dataCode: "totalPrice", hAlign: "right", dataType: "Number",validator:"number"},
+            {headerName: "产地", headerWidth: 80, dataCode: "originPlace", hAlign: "left", dataType: "String"},
+            {headerName: "厂家", headerWidth: 80, dataCode: "vender", hAlign: "left", dataType: "String"},
+            {headerName: "供货方式", headerWidth: 70, dataCode: "supply", hAlign: "center", dataType: "String",cellType:'comboBox',editorValueType:true,options:supplyComboMap},
+            {headerName: "备注", headerWidth: 100, dataCode: "remark", hAlign: "left", dataType: "String"}
+        ],
+        view:{ lockColumns: ["code","name","specs","unit","quantity","totalPrice","originPlace","vender","supply","remark"]}
+    },
+    priceCoeSetting:{
+        header: [
+            {headerName: "编号", headerWidth: 80, dataCode: "code", dataType: "String"},
+            {headerName: "材料名称", headerWidth: 160, dataCode: "name", dataType: "String",cellType:'tipsCell'},
+            {headerName: "规格型号", headerWidth: 120, dataCode: "specs", hAlign: "left", dataType: "String",cellType:'tipsCell'},
+            {headerName: "单位", headerWidth: 45, dataCode: "unit", hAlign: "center", dataType: "String"},
+            {headerName: "变值权重B", headerWidth: 90, dataCode: "varWeight", hAlign: "right", dataType: "Number",validator:"number"},
+            {headerName: "基本价格指数FO", headerWidth: 70, dataCode: "FO", hAlign: "right", dataType: "Number",validator:"number"},
+            {headerName: "现行价格指数FI", headerWidth: 70, dataCode: "FI", hAlign: "right", dataType: "Number",validator:"number"},
+            {headerName: "数量", headerWidth: 90, dataCode: "quantity", hAlign: "right", dataType: "Number",decimalField:'glj.quantity'},
+            {headerName: "单价", headerWidth: 70, dataCode: "marketPrice", hAlign: "right", dataType: "Number",validator:"number"},
+            {headerName: "合价", headerWidth: 70, dataCode: "totalPrice", hAlign: "right", dataType: "Number",validator:"number"},
+            {headerName: "产地", headerWidth: 80, dataCode: "originPlace", hAlign: "left", dataType: "String"},
+            {headerName: "厂家", headerWidth: 80, dataCode: "vender", hAlign: "left", dataType: "String"},
+            {headerName: "供货方式", headerWidth: 70, dataCode: "supply", hAlign: "center", dataType: "String",cellType:'comboBox',editorValueType:true,options:supplyComboMap},
+            {headerName: "备注", headerWidth: 100, dataCode: "remark", hAlign: "left", dataType: "String"}
+        ],
+        view:{ lockColumns: ["code","name","specs","unit","quantity","totalPrice","originPlace","vender","supply","remark"]}
+    },
+    refreshSheetDatas:function () {
+        $("#adjustType").val() == "priceInfo" ? materialAdjustObj.showPriceInfoDatas():materialAdjustObj.showPriceCoeDatas();
+        //let sheetIndex = $("#adjustType").val()
+    },
+    initSpread:function () {
+        if(!this.spread){
+            this.spread = SheetDataHelper.createNewSpread($("#material_adjust_sheet")[0],2);
+            sheetCommonObj.spreadDefaultStyle(this.spread);
+        }
+        this.initPriceInfoSheet();
+        this.initPriceCoeSheet();
+        //disableRightMenu("project_glj_sheet",this.projectGljSpread,this.rightClickCallback);
+        //打开别人分享的项目,只读
+        if(projectReadOnly){
+            disableSpread(this.spread);
+        }else {
+            this.initRightClick();
+        }
+    },
+    initPriceInfoSheet:function () {
+        this.priceInfoSheet = this.spread.getSheet(0);
+        sheetCommonObj.initSheet(this.priceInfoSheet,this.priceInfoSetting,30);
+        this.priceInfoSheet.bind(GC.Spread.Sheets.Events.ValueChanged, this.onSheetValueChange);
+        this.priceInfoSheet.name('priceInfoSheet');
+        this.priceInfoSheet.setRowHeight(0, 45, 1);
+    },
+    initPriceCoeSheet:function () {
+        this.priceCoeSheet = this.spread.getSheet(1);
+        sheetCommonObj.initSheet(this.priceCoeSheet,this.priceCoeSetting,30);
+        this.priceCoeSheet.bind(GC.Spread.Sheets.Events.ValueChanged, this.onSheetValueChange);
+        this.priceCoeSheet.name('priceCoeSheet');
+        this.priceCoeSheet.setRowHeight(0, 45, 1);
+    },
+    onSheetValueChange:function (a,args) {
+        let me = materialAdjustObj,row = args.row, col = args.col;
+        let projectGLJ = projectObj.project.projectGLJ;
+        let setting = $("#adjustType").val() == "priceInfo"?me.priceInfoSetting:me.priceCoeSetting;
+        let datas = $("#adjustType").val() == "priceInfo"?me.priceInfoDatas:me.priceCoeDatas;
+        let dataCode = setting.header[col].dataCode;
+        let recode = datas[row];
+        let value = args.newValue;
+        if(value === undefined ){
+            me.refreshSheetDatas();
+            return;
+        }
+        if (value&&!projectGljObject.checkData(col,setting,value)) {
+            alert('输入的数据类型不对,请重新输入!');
+            me.refreshSheetDatas();
+            return ;
+        }
+        if(recode[dataCode] == value) return;
+
+        if(dataCode=='marketPrice'){//修改市场价和修改定额价时需要重新记算很多受影响的树节点,现在改成与定字额工料机那里调相同的方法。
+            let editField ="market_price";
+            projectGLJ.updatePrice(recode,editField,value,'pg',me.refreshSheetDatas);
+            return;
+        }
+        value = scMathUtil.roundForObj(value,getDecimal("process"));
+        let updateData = {id: recode.id, field: dataCode, value: value+""};
+        projectGLJ.pGljUpdate(updateData,me.refreshSheetDatas);
+
+    },
+    deleteAdjust:function (row) {
+        let me = this;
+        let projectGLJ = projectObj.project.projectGLJ;
+        let field =  $("#adjustType").val() == "priceInfo"?"is_info_adjust":"is_coe_adjust";
+        let datas = $("#adjustType").val() == "priceInfo"?me.priceInfoDatas:me.priceCoeDatas;
+        let recode = datas[row];
+        let updateData = {id: recode.id, field: field, value: 0};
+        projectGLJ.pGljUpdate(updateData,me.refreshSheetDatas);
+    },
+    showPriceInfoDatas:function () {
+        this.spread.setActiveSheetIndex(0);
+        this.priceInfoDatas = this.getPirceInfoDatas();
+        sheetCommonObj.showData(this.priceInfoSheet, this.priceInfoSetting,this.priceInfoDatas);
+        this.priceInfoSheet.setRowCount(this.priceInfoDatas.length);
+    },
+    showPriceCoeDatas:function () {
+        this.spread.setActiveSheetIndex(1);
+        this.priceCoeDatas = this.getPriceCoeDatas();
+        sheetCommonObj.showData(this.priceCoeSheet, this.priceCoeSetting,this.priceCoeDatas);
+        this.priceCoeSheet.setRowCount(this.priceCoeDatas.length);
+    },
+    getPirceInfoDatas:function(){
+        let datas = [];
+        let gljList = this.filterAdjustGlj();
+        gljList = sortProjectGLJ(gljList);
+        for(let glj of gljList){
+            datas.push(getInfoObject(glj));
+        }
+        return datas;
+        function getInfoObject(glj) {
+            let data = materialAdjustObj.getCommonObject(glj);
+            data.riskCoe = glj.riskCoe;
+            data.standardPrice = glj.standardPrice;
+            data.totalPrice = materialAdjustObj.getTotalPrice(data);
+            return data;
+        }
+    },
+    filterAdjustGlj:function () {
+        let gljList = projectObj.project.projectGLJ.datas.gljList;
+        let field =  $("#adjustType").val() == "priceInfo"?"is_info_adjust":"is_coe_adjust";
+        return _.filter(gljList,function (item) {
+            return item.quantity !== 0 && item.quantity !== '0' && item[field] ===1
+        });
+    },
+    getPriceCoeDatas:function () {
+        let datas = [];
+        let node =  projectObj.project.Bills.getEngineeringCostNode(projectObj.mainController);
+        let totalFee = node && node.data.feesIndex && node.data.feesIndex.common?node.data.feesIndex.common.totalFee:0;
+        let gljList = this.filterAdjustGlj();
+         gljList = sortProjectGLJ(gljList);
+        for(let glj of gljList){
+            datas.push(getCoeObject(glj,totalFee));
+        }
+        return datas;
+        function getCoeObject(glj,totalFee) {
+            let data = materialAdjustObj.getCommonObject(glj);
+            data.FO = glj.FO;
+            data.FI = glj.FI;
+            data.totalPrice = materialAdjustObj.getTotalPrice(data);
+            if(totalFee !==0) data.varWeight = scMathUtil.roundForObj(data.totalPrice/totalFee,2);
+            return data;
+        }
+    },
+    getTotalPrice:function (data) {
+        return scMathUtil.roundForObj(data.quantity * data.marketPrice,getDecimal("bills.totalPrice"))
+    },
+    initRightClick : function() {
+        let me = this;
+        $.contextMenu({
+            selector: '#material_adjust_sheet',
+            build: function ($trigger, e) {
+                me.rightClickTarget = SheetDataHelper.safeRightClickSelection($trigger, e, me.spread);
+                return me.rightClickTarget.hitTestType === GC.Spread.Sheets.SheetArea.viewport ||
+                    me.rightClickTarget.hitTestType === GC.Spread.Sheets.SheetArea.rowHeader;
+            },
+            items: {
+                "addFromGLJ":{
+                    name: '从人材机汇总中选择',
+                    icon: 'fa-sign-in',
+                    disabled: function () {
+                        return false;
+                    },
+                    callback: function (key, opt) {
+                        $("#selectFromGLJ").modal('show');
+                    }
+                },
+                "deleteAdjust": {
+                    name: "删除",
+                    icon: 'fa-trash-o',
+                    disabled: function () {
+                        return me.rightClickTarget.row === undefined;
+                    },
+                    callback: function (key, opt) {
+                        let row = me.rightClickTarget.row;
+                        me.deleteAdjust(row);
+                    }
+                }
+            }
+        });
+    },
+    getCommonObject:function (glj) {
+        let data ={
+            select:0,
+            id:glj.id,
+            code:glj.code,
+            name:glj.name,
+            specs:glj.specs,
+            unit:glj.unit,
+            type:glj.type,
+            quantity:glj.quantity,
+            supply:glj.supply,
+            originPlace:glj.originPlace,
+            vender:glj.vender,
+            brand:glj.brand,
+            remark:glj.remark
+        };
+        gljOprObj.setGLJPrice(data,glj);
+        return data;
+    }
+};
+
+let gljFromObj={
+    spread:null,
+    sheet:null,
+    datas:[],
+    setting:{
+        header: [
+            {headerName: "选择", headerWidth: 45, dataCode: "select", hAlign: "center", dataType: "String",cellType:'checkBox'},
+            {headerName: "编码", headerWidth: 80, dataCode: "code", dataType: "String"},
+            {headerName: "类型", headerWidth: 30, dataCode: "short_name", hAlign: "center", dataType: "String"},
+            {headerName: "名称", headerWidth: 160, dataCode: "name", dataType: "String",cellType:'tipsCell'},
+            {headerName: "规格型号", headerWidth: 100, dataCode: "specs", hAlign: "left", dataType: "String",cellType:'tipsCell'},
+            {headerName: "单位", headerWidth: 45, dataCode: "unit", hAlign: "center", dataType: "String"},
+            {headerName: "总消耗量", headerWidth: 90, dataCode: "quantity", hAlign: "right", dataType: "Number",decimalField:'glj.quantity'},
+            {headerName: "市场价", headerWidth: 70, dataCode: "marketPrice", hAlign: "right", dataType: "Number",validator:"number"},//,decimalField:"glj.unitPrice"
+            {headerName: "供货方式", headerWidth: 70, dataCode: "supply", hAlign: "center", dataType: "String",cellType:'comboBox',editorValueType:true,options:supplyComboMap},
+            {headerName: "暂估", headerWidth: 45, dataCode: "is_evaluate", hAlign: "center", dataType: "String",cellType:'checkBox'},
+            {headerName: "产地", headerWidth: 80, dataCode: "originPlace", hAlign: "left", dataType: "String"},
+            {headerName: "厂家", headerWidth: 80, dataCode: "vender", hAlign: "left", dataType: "String"},
+            {headerName: "备注", headerWidth: 100, dataCode: "remark", hAlign: "left", dataType: "String"}
+        ],
+        view: {
+            lockColumns: ["is_evaluate"]
+        }
+    },
+    initSpread:function () {
+        if(!this.spread){
+            this.spread = SheetDataHelper.createNewSpread($("#glj_from_sheet")[0]);
+            sheetCommonObj.spreadDefaultStyle(this.spread);
+        }
+        this.sheet = this.spread.getSheet(0);
+        sheetCommonObj.initSheet(this.sheet,this.setting);
+        this.sheet.bind(GC.Spread.Sheets.Events.EditStarting,this.onEditStarting);
+        this.sheet.bind(GC.Spread.Sheets.Events.ClipboardPasting, this.onClipboardPasting);
+        this.sheet.bind(GC.Spread.Sheets.Events.ValueChanged, this.onValueChanged);
+        this.sheet.setRowHeight(0, 45, 1);
+        disableRightMenu("glj_from_sheet",this.spread);
+    },
+    onEditStarting:function (e,args) {
+         args.cancel = true;
+    },
+    onClipboardPasting:function (e,args) {
+        args.cancel = true;
+    },
+    onValueChanged:function (e,args) {
+        let me = gljFromObj;
+        let value = args.newValue;
+        value = value == true?1:0;
+        me.datas[args.row].select = value;
+    },
+    showDatas:function () {
+        this.datas = [];
+        let gljList = projectObj.project.projectGLJ.datas.gljList;
+        gljList = this.filterGLJ(gljList);
+        gljList = sortProjectGLJ(gljList);
+        for(let glj of gljList){
+            this.datas.push(this.getSheetDatas(glj));
+        }
+        this.refreshDatas();
+    },
+    filterGLJ:function (gljList) {
+        let field =  $("#adjustType").val() == "priceInfo"?"is_info_adjust":"is_coe_adjust";
+        let typeMap = {};
+        for(let ch of $(".glj_sel_input:checked")){
+            typeMap[$(ch).val()] = true;
+        }
+        let inputV = $("#glj_sel_input").val();
+        return _.filter(gljList,function (item) {
+            if(item.quantity === 0 || item.quantity === '0' || item[field] ===1) return false;
+            if(!typeMap[0]){//如果没有勾选所有,下面再细分
+                let firstS = (item.type+"").substring(0,1);
+                if(!typeMap[firstS]) return false
+            }
+            if(inputV!=""){//输入的关键字过滤
+                if(item.code.indexOf(inputV)==-1 && item.name.indexOf(inputV)==-1) return false
+            }
+            return true;
+        });
+    },
+    refreshDatas:function () {
+        sheetCommonObj.showData(this.sheet, this.setting,this.datas);
+        this.sheet.setRowCount(this.datas.length);
+    },
+    getSheetDatas(glj){
+        let projectGLJ = projectObj.project.projectGLJ;
+        let materialIdList = projectGLJ.datas.constData.materialIdList;
+        let data = materialAdjustObj.getCommonObject(glj);
+        data.select = 0;
+        data.short_name = projectGLJ.getShortNameByID(glj.type);
+        // 只有材料才显示是否暂估
+        if (materialIdList.indexOf(glj.type) >= 0) data.is_evaluate = glj.is_evaluate;
+        return data;
+    },
+    checkByType:function (type) {
+        for(let d of this.datas){
+            d.select = 0;
+            switch (type){
+                case "全选":
+                    d.select = 1;
+                    break;
+                case "全选主材":
+                    if(d.type == 4) d.select = 1;
+                    break;
+                case "全选设备":
+                    if(d.type == 5) d.select = 1;
+                    break;
+                case "取消":
+                    d.select = 0;
+                    break;
+            }
+        }
+        this.refreshDatas();
+    },
+    confirmSelect:function () {
+        let updateMap = {};
+        let adjustType =  $("#adjustType").val()
+        for(let d of this.datas){
+            if(d.select == 1){
+                let tem;
+                if(adjustType== "priceInfo"){
+                    tem = {is_info_adjust:1,riskCoe:"",standardPrice:d.marketPrice+""};
+                }else {
+                    tem = {is_coe_adjust:1,FI:"",FO:""};
+                }
+                updateMap[d.id] = tem;
+            }
+        }
+        if( _.isEmpty(updateMap)) return;
+        projectObj.project.projectGLJ.batchUpdateGLJProperty(updateMap,function () {
+            materialAdjustObj.refreshSheetDatas();
+        });
+    }
+};
+
+
+$(function () {
+    $("#adjustType").change(function () {
+        materialAdjustObj.refreshSheetDatas();
+    });
+    $('#selectFromGLJ').on('shown.bs.modal',function(e){
+        gljFromObj.initSpread();
+        gljFromObj.showDatas();
+    });
+    $(".glj_sel_input").change(function () {
+        gljFromObj.showDatas();
+    });
+
+    $("#glj_sel_btn_find").click(function () {
+        gljFromObj.showDatas();
+    });
+
+    $(".glj_sel_check_btn").click(function () {
+        gljFromObj.checkByType($(this).text())
+    })
+    
+    $("#glj_sel_confirm").click(function () {
+        gljFromObj.confirmSelect();
+    })
+});

+ 57 - 27
web/building_saas/main/js/views/project_glj_view.js

@@ -50,6 +50,10 @@ projectGljObject={
         {ID:'MAIN_MATERIAL',text:'主材'},
         {ID:'EQUIPMENT',text:'设备'}
     ],
+    initSpreads:function(){
+        if(this.projectGljSpread==null) this.initProjectGljSpread();
+        if(materialAdjustObj.spread == null) materialAdjustObj.initSpread();
+    },
     initProjectGljSpread:function () {
         if(!this.projectGljSpread){
             this.projectGljSpread = SheetDataHelper.createNewSpread($("#project_glj_sheet")[0],2);
@@ -254,7 +258,7 @@ projectGljObject={
             return false;
         }
 
-        if(isPaste == false &&(dataCode=='is_adjust_price'||dataCode=='is_evaluate'||dataCode=='is_main_material')){//除了粘贴,拖动填充等操作,其它的都不能编辑
+        if(isPaste == false &&(dataCode=='is_adjust_price'||dataCode=='is_evaluate'||dataCode=='is_main_material'||dataCode == 'is_eval_material'||dataCode=='no_tax_eqp')){//除了粘贴,拖动填充等操作,其它的都不能编辑
             return false;
         }
         if(dataCode=='basePrice'||dataCode=='marketPrice'||dataCode=='supply'){//有组成物时,市场单价、定额价、供货方式不能修改
@@ -417,7 +421,7 @@ projectGljObject={
     getUpdateData :function (id,value,dataCode,quantity,updateMap) {
         let me = projectGljObject;
         let supplyMap = {"自行采购":0,"部分甲供":1,"完全甲供":2,"甲定乙供":3};
-        let materialMap = {"钢材":1,"钢筋":2,"木材":3,"水泥":4,"标准砖":5};
+        let materialMap = {"钢材":1,"钢筋":2,"木材":3,"水泥":4,"商品砼":5,"商品砂浆":6};
         let data = updateMap[id]?updateMap[id]:{};
         //供货方式 和三材类型 粘贴和填充过来的数据,要做对应转换,因为这里value只是中文文本,并不是实际的值
         // 如果是供货方式则需要处理数据
@@ -513,6 +517,23 @@ projectGljObject={
         this.materialTreeSheet.setSelection(sel.row==-1?0:sel.row,sel.col,sel.rowCount,sel.colCount);
         this.materialTreeController.setTreeSelected(this.materialTree.items[sel.row==-1?0:sel.row]);
     },
+    initSheetViews:function(){
+        let me = projectGljObject;
+        if(me.displayType == filterType.AMAE){
+            $('#material_adjust').show();
+            $('#adjustType').show();
+            $('#project-glj-main').hide();
+        } else {
+            $('#material_adjust').hide();
+            $('#adjustType').hide();
+            $('#project-glj-main').show();
+        }
+    },
+    refreshViewsData:function(){
+        let me = projectGljObject;
+        if(me.displayType == filterType.AMAE) return materialAdjustObj.refreshSheetDatas();
+        me.refreshDataSheet();
+    },
     refreshDataSheet:function () {
         let me = projectGljObject;
         let quantityCol = _.findIndex(me.projectGljSetting.header,function (header) {
@@ -541,9 +562,10 @@ projectGljObject={
         let GC = {id:'GC',name:materialType[materialTypeMap.GC],code:'GC',unit:'t',ParentID:-1,NextSiblingID:'MC'};
         let GJ = {id:'GJ',name:materialType[materialTypeMap.GJ],code:'GJ',unit:'t',ParentID:'GC',NextSiblingID:-1};
         let MC = {id:'MC',name:materialType[materialTypeMap.MC],code:'MC',unit:'m3',ParentID:-1,NextSiblingID:'SN'};
-        let SN = {id:'SN',name:materialType[materialTypeMap.SN],code:'SN',unit:'t',ParentID:-1,NextSiblingID:'SZ'};
-        let SZ = {id:'SZ',name:materialType[materialTypeMap.SZ],code:'BZZ',unit:'千块',ParentID:-1,NextSiblingID:-1};
-        let rootDatas= [GC,GJ,MC,SN,SZ];
+        let SN = {id:'SN',name:materialType[materialTypeMap.SN],code:'SN',unit:'t',ParentID:-1,NextSiblingID:'ST'};
+        let ST = {id:'ST',name:materialType[materialTypeMap.ST],code:'SPT',unit:'m3',ParentID:-1,NextSiblingID:'SS'};
+        let SS= {id:'SS',name:materialType[materialTypeMap.SS],code:'SPJS',unit:'m3',ParentID:-1,NextSiblingID:'-1'};
+        let rootDatas= [GC,GJ,MC,SN,ST,SS];
         let parentMap = {};
         let sumMap = {};
         this.materialTree.nodes={},this.materialTree.selected = null,this.materialTree.roots = [],this.materialTree.items=[];
@@ -684,13 +706,15 @@ projectGljObject={
 
         data.is_main_material = glj.is_main_material == 1?1:0;
         //供货方式为完全甲供时设置甲供数量为总消耗量
-        if (data.supply == 2) {
-            data.supply_quantity = glj.quantity;
-        }
+        if (data.supply == 2) data.supply_quantity = glj.quantity;
+
         // 只有材料才显示是否暂估
-        if (materialIdList.indexOf(glj.type) >= 0) {
-            data.is_evaluate = glj.is_evaluate;
-        }
+        if (materialIdList.indexOf(glj.type) >= 0) data.is_evaluate = glj.is_evaluate;
+        //是“材料”、“主材”、“设备”时显示评标材料
+        if (materialIdList.indexOf(glj.type) >= 0||glj.type == gljType.MAIN_MATERIAL || glj.type == gljType.EQUIPMENT) data.is_eval_material = glj.is_eval_material ? glj.is_eval_material : 0;
+        //类型是“设备”时有效
+        if(glj.type == gljType.EQUIPMENT) data.no_tax_eqp = glj.no_tax_eqp?glj.no_tax_eqp:0;
+
         if(glj.materialCoe !== null && glj.materialCoe !==undefined){
             data.materialCoe =  scMathUtil.roundForObj(glj.materialCoe,getDecimal("material"));
         }
@@ -900,12 +924,8 @@ projectGljObject={
             if(dataCode === 'supply_quantity'){//修改数量需做4舍5入
                 value=  scMathUtil.roundForObj(value,getDecimal('glj.quantity'));
             }
-            if(dataCode === 'is_evaluate'||dataCode === 'is_adjust_price'||dataCode === 'is_main_material'){
-                if(value == true){
-                    value = 1;
-                }else if(value == false){
-                    value = 0;
-                }
+            if(dataCode === 'is_evaluate'||dataCode === 'is_adjust_price'||dataCode === 'is_main_material'||dataCode === 'no_tax_eqp'||dataCode == 'is_eval_material'){
+                value = value == true?1:0;
             }
             if(dataCode === 'materialType' && (value == null || value=="")){//删除三材类别时,清空三材系数
                 value = null;
@@ -1025,7 +1045,7 @@ projectGljObject={
     },
     changeFileCallback:function(){
         projectGljObject.unitPriceFileInit();
-        projectGljObject.refreshDataSheet();
+        projectGljObject.refreshViewsData();
         gljOprObj.refreshView();
         projectObj.project.calcProgram.calcAllNodesAndSave(calcAllType.catAll,function () {
             if(socketObject.roomInfo){
@@ -1096,12 +1116,19 @@ function getProjectResizeEles() {
 
 
 function loadProjectGljSize() {
-    let me = projectGljObject;
-    let pojGljResizeEles = getProjectResizeEles();
-    SlideResize.loadVerticalHeight(pojGljResizeEles.eleObj.module, pojGljResizeEles.eleObj, pojGljResizeEles.limit, function () {
-        me.projectGljSpread?me.projectGljSpread.refresh():'';
-        me.mixRatioSpread?me.mixRatioSpread.refresh():'';
-    });
+
+    if($('#project-glj-main').is(':visible')){
+        let me = projectGljObject;
+        let pojGljResizeEles = getProjectResizeEles();
+        SlideResize.loadVerticalHeight(pojGljResizeEles.eleObj.module, pojGljResizeEles.eleObj, pojGljResizeEles.limit, function () {
+            me.projectGljSpread?me.projectGljSpread.refresh():'';
+            me.mixRatioSpread?me.mixRatioSpread.refresh():'';
+        });
+    }else if($('#material_adjust').is(':visible')){
+        $('#material_adjust_sheet').height($(window).height()-$('.header').height()-$('#projectGljToolsBar').height()+3);
+        if(materialAdjustObj.spread)materialAdjustObj.spread.refresh();
+    }
+
 }
 //从其他建设项目中复制中,建设项目的文件层次结构名称和顺序
 function getFileHierarchyInfo(treeData){
@@ -1139,12 +1166,13 @@ $(function () {
         let me = projectGljObject;
         $(e.relatedTarget.hash).removeClass('active');
         if(me.projectGljSpread==null){
-            me.initProjectGljSpread();
+            me.initSpreads();
         }
         me.unitPriceFileInit();
         me.initMixRatio();
+        me.initSheetViews();
         //projectObj.project.projectGLJ.calcQuantity(); 在工程量有更新的地方调用
-        me.refreshDataSheet();
+        me.refreshViewsData();
         loadProjectGljSize();
     });
     /*  $('#ration_link').on('shown.bs.tab', function (e) {
@@ -1355,7 +1383,9 @@ $(function () {
         $(this).addClass('active');
         let me = projectGljObject;
         me.displayType = filterType[this.id];
-        me.refreshDataSheet();
+        me.initSheetViews();
+        me.refreshViewsData();
+        loadProjectGljSize();
     });
 });
 

+ 5 - 30
web/building_saas/main/js/views/project_view.js

@@ -641,7 +641,7 @@ var projectObj = {
                 libID = rationLibObj.compleRationLibId;
             }
             if(!rationLibObj.tree){
-                sessionStorage.setItem('stdRationLib', libID);
+                if(isDef(libID)) sessionStorage.setItem('stdRationLib', libID);
                 rationLibObj.doAfterGetRationTree = function () {
                     this.locateAtRation(libID, code);
                     this.doAfterGetRationTree = null;
@@ -1615,10 +1615,10 @@ var projectObj = {
     },
 
     // 获取上次退出时的焦点位置
-    loadFocusLocation: function() {
+    loadFocusLocation: function(orow,ocol) {
         const projectId = scUrlUtil.GetQueryString('project');
-        let row = getLocalCache('lastRow:' + projectId);
-        let col = getLocalCache('lastCol:' + projectId);
+        let row = gljUtil.isDef(orow)?orow:getLocalCache('lastRow:' + projectId);
+        let col = gljUtil.isDef(ocol)?ocol:getLocalCache('lastCol:' + projectId);
         if(row == null || col == null){
             //默认焦点定位到造价书的第一行“分项”。
             col = 1;
@@ -2333,39 +2333,23 @@ $('#tab_compilation_illustration_p').on('show.bs.tab', function () {
     let v = projectObj.project.property.compilationIllustrationProject ? projectObj.project.property.compilationIllustrationProject : '';
     $('#compilationIllustrationP textarea').val(v);
 });
-$('#compilationIllustrationP').keyup(function () {
-    let v = $('#compilationIllustrationP textarea').val();
-    if (v && v.length > 255) {
-        v = v.substr(0, 255);
-        $('#compilationIllustrationP textarea').val(v);
-        alert('编制说明字符数不能超过255个。');
-    };
-});
 //单位工程-编制说明
 $('#tab_compilation_illustration').on('show.bs.tab', function () {
     let v = projectObj.project.property.compilationIllustration ? projectObj.project.property.compilationIllustration : '';
     $('#compilationIllustration textarea').val(v);
 });
-$('#compilationIllustration').keyup(function () {
-    let v = $('#compilationIllustration textarea').val();
-    if (v && v.length > 255) {
-        v = v.substr(0, 255);
-        $('#compilationIllustration textarea').val(v);
-        alert('编制说明字符数不能超过255个。');
-    };
-});
 $('#property_ok').click(async function () {
     //test-----
     /*$.bootstrapLoading.start();
     let xmlObj = new XMLStandard(userID, 1);
     await xmlObj.toXml(projectObj.project.ID());
     console.log(xmlObj);
+    xmlObj = null;
     $.bootstrapLoading.end();
     return;*/
     //test-----
     let project = projectObj.project,
         projectID = project.ID(),
-
         properties = {},
         options = {},
         labourCoes = {},
@@ -2407,10 +2391,6 @@ $('#property_ok').click(async function () {
     }
     //建设项目-编制说明
     let compilationIllustrationP = $('#compilationIllustrationP textarea').val();
-    if (compilationIllustrationP && compilationIllustrationP.length > 255) {
-        alert('编制说明字符数不能超过255!');
-        return;
-    }
     if (compilationIllustrationP !== project.property.compilationIllustrationProject) {
         properties['property.compilationIllustrationProject'] = compilationIllustrationP;
         project.property.compilationIllustrationProject = compilationIllustrationP;
@@ -2448,11 +2428,6 @@ $('#property_ok').click(async function () {
     }
     //单位工程-编制说明
     let compilationIllustration = $('#compilationIllustration textarea').val();
-    //限制255位字符
-    if (compilationIllustration && compilationIllustration.length > 255) {
-        alert('编制说明字符数不能超过255!');
-        return;
-    }
     if (compilationIllustration !== project.property.compilationIllustration) {
         properties['property.compilationIllustration'] = compilationIllustration;
         project.property.compilationIllustration = compilationIllustration;

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

@@ -343,10 +343,12 @@ var rationLibObj = {
         me.loadSectionRations(sectionNode && sectionNode.children.length === 0 ? sectionNode.data.ID : null);
     },
     locateAtRation: function(libID, code){
+        if(!isDef(libID)) return;
         let me = rationLibObj;
+        if ($('#rationSearchResult').is(':visible')) {
+            $('#rationSearchResult a').click();
+        }
         //查找定额,以确定定额所在章节节点
-        let firstLibID = projectObj.project.projectInfo.engineeringInfo.ration_lib.length > 0 ?
-                        projectObj.project.projectInfo.engineeringInfo.ration_lib[0].id : null;
         let locateRow = 0,
             locateSubRow = 0;
         CommonAjax.post('/complementaryRation/api/getRationItem', {rationRepIds: [libID], code: code}, function (ration) {

+ 42 - 15
web/building_saas/main/js/views/sub_view.js

@@ -9,6 +9,7 @@
 
 let subSpread = null;
 let subObj = {
+    fisrtLinked: true,  //提升焦点变换性能 2019年4月12日
     TZJNRrePercent:null,
     showGljSubTab:false,
     showQDSubTab:false,
@@ -194,29 +195,37 @@ let subObj = {
     showGljSubTabData:function () {
         this.initGljSubTab();
         zmhs_obj.showDatas();
-        refreshSubSpread();
+        if (gljOprObj.activeTab !== gljOprObj.preActiveTab) {   //提高焦点变换性能 2019年4月12日
+            refreshSubSpread();
+        }
     },
     showQDSubTabData:function () {
         this.initQDSubTab();
         MaterialController.showItemCharacterText(null,"tzCharacterText");
-        refreshSubSpread();
+        if (gljOprObj.activeTab !== gljOprObj.preActiveTab) {   //提供焦点变换性能 2019年4月12日
+            refreshSubSpread();
+        }
     }
 };
 
-
-
 $("#linkGLJ").click(function(){
     $("#subItems").children().hide();//控制显示subSpread,隐藏特征及内容spread
     //show
     //MaterialController.showReplaceDiv();
-    subObj.showGljSubTabData();
+    //subObj.showGljSubTabData();   //提高焦点变换性能 2019年4月12日
     $("#subSpread").show();
     $("#itemTextDiv").show();
     $("#gljItemTab").show();
     pageCCOprObj.active = false;
     subSpread.options.allowUserDragFill = false;
-    refreshSubSpread();
+    //refreshSubSpread();   //提高焦点变换性能 2019年4月12日
     subSpread.setActiveSheetIndex(0);
+    //提高焦点变换性能 2019年4月12日--
+    if (!subObj.fisrtLinked) {
+        gljOprObj.preActiveTab = gljOprObj.activeTab;
+    }
+    subObj.fisrtLinked = false;
+    //--
     gljOprObj.activeTab='#linkGLJ';
     gljOprObj.setNodeShowTab();
 });
@@ -227,9 +236,12 @@ $("#linkAZZJF").click(function(){
     MaterialController.hideReplaceDiv();
     $("#subSpread").show();
     pageCCOprObj.active = false;
-    refreshSubSpread();
     subSpread.setActiveSheetIndex(3);
+    gljOprObj.preActiveTab = gljOprObj.activeTab;   //提高焦点变换性能 2019年4月12日
     gljOprObj.activeTab='#linkAZZJF';
+    if (gljOprObj.preActiveTab !== gljOprObj.activeTab) { //提高焦点变换性能 2019年4月12日
+        refreshSubSpread();
+    }
     gljOprObj.setNodeShowTab();
 });
 
@@ -239,8 +251,9 @@ $("#linkGCLMX").click(function(){
     $("#subSpread").show();
     subSpread.options.allowUserDragFill = true;
     pageCCOprObj.active = false;
-    refreshSubSpread();
+    //refreshSubSpread(); //提升焦点变换性能 2019年4月12日
     subSpread.setActiveSheetIndex(1);
+    gljOprObj.preActiveTab = gljOprObj.activeTab;   //提高焦点变换性能 2019年4月12日
     gljOprObj.activeTab='#linkGCLMX';
     gljOprObj.setNodeShowTab();
 });
@@ -250,7 +263,7 @@ $("#linkJSCX").click(function(){        // 计算程序
     MaterialController.hideReplaceDiv();
     $("#subSpread").show();
     pageCCOprObj.active = false;
-    refreshSubSpread();
+    //refreshSubSpread();   //提升焦点变换性能 2019年4月12日
     subSpread.setActiveSheetIndex(2);
     calcProgramObj.initSheet(subSpread.getSheet(2));
 
@@ -258,6 +271,7 @@ $("#linkJSCX").click(function(){        // 计算程序
         projectObj.mainController.tree.selected = projectObj.mainController.tree.firstNode();
     let sel = projectObj.mainController.tree.selected;
     calcProgramObj.refreshCalcProgram(sel, 3);
+    gljOprObj.preActiveTab = gljOprObj.activeTab;   //提高焦点变换性能 2019年4月12日
     gljOprObj.activeTab='#linkJSCX';
     gljOprObj.setNodeShowTab();
 });
@@ -270,6 +284,7 @@ $("#linkZMHS").click(function(){        // 子目换算
     zmhs_obj.loadSideResize();
     pageCCOprObj.active = false;
     refreshSubSpread();
+    gljOprObj.preActiveTab = gljOprObj.activeTab;   //提高焦点变换性能 2019年4月12日
     gljOprObj.activeTab='#linkZMHS';
     gljOprObj.setNodeShowTab();
 });
@@ -277,8 +292,11 @@ $("#linkMBZM").click(function(){        // 模板子目
     $("#subItems").children().hide();
     $("#tabMBZM").show();
     pageCCOprObj.active = false;
-    refreshSubSpread();
+    gljOprObj.preActiveTab = gljOprObj.activeTab;   //提高焦点变换性能 2019年4月12日
     gljOprObj.activeTab='#linkMBZM';
+    if (gljOprObj.activeTab !== gljOprObj.preActiveTab) {   //提高焦点变换性能 2019年4月12日
+        refreshSubSpread();
+    }
     gljOprObj.setNodeShowTab();
 });
 
@@ -335,21 +353,28 @@ function adaptiveTzjnrWidth() {
 }
 //清单精灵
 $('#linkQDJL').click(function () {
+    //提高焦点变换性能 2019年4月12日--
+    if (!subObj.fisrtLinked) {
+        gljOprObj.preActiveTab = gljOprObj.activeTab;
+    }
+    subObj.fisrtLinked = false;
+    //--
     gljOprObj.activeTab='#linkQDJL';
     $("#subItems").children().hide();
-    subObj.showQDSubTabData();
+    //subObj.showQDSubTabData();    //提升焦点变换性能 2019年4月12日
     $('#qdjl').show();
     $("#xmtzTextDiv").show();
     $("#tzItemTab").show();
     let selectedNode = projectObj.mainController.tree.selected;
     BillsElf.billsSelElf(selectedNode.data.code);
-    refreshSubSpread();
+    //refreshSubSpread();      //提升焦点变换性能 2019年4月12日
     gljOprObj.setNodeShowTab();
-    BillsElf.refreshWorkBook();
+    //BillsElf.refreshWorkBook();   //提升焦点变换性能 2019年4月12日
 
 });
 //特征及内容
 $("#linkTZJNR").click(function () {
+    gljOprObj.preActiveTab = gljOprObj.activeTab;   //提高焦点变换性能 2019年4月12日
     gljOprObj.activeTab='#linkTZJNR';
     $("#subItems").children().hide();
     $("#tzjnrCon").show();
@@ -697,7 +722,7 @@ function refreshSubSpread(){
     }
     BillsElf.refreshWorkBook();
     //if($('#linkZMHS').hasClass('active')) zmhs_obj.refresh();
-    if($('#rnc-zm').is(':visible')|| $('#rnc-fz').is(':visible')) zmhs_obj.refresh();
+    if($('#rnc-zm').is(':visible')|| $('#rnc-fz').is(':visible') || $('#rnc-cus')) zmhs_obj.refresh();
     if($('#linkMBZM').hasClass('active')) mbzm_obj.refresh();
 }
 
@@ -768,7 +793,9 @@ $('#linkZMHS').on('shown.bs.tab', function (e) {
 
 
 $('#linkMBZM').on('shown.bs.tab', function (e) {
-    mbzm_obj.refresh();
+    if (gljOprObj.activeTab !== gljOprObj.preActiveTab) {   //提高焦点变换性能 2019年4月12日
+        mbzm_obj.refresh();
+    }
     mbzm_obj.showMBZMData();
 });
 

+ 1 - 0
web/building_saas/main/js/views/zmhs_view.js

@@ -261,6 +261,7 @@ let zmhs_obj = {
     refresh:function () {
         $('#coeSpread').is(':visible')&&this.coeSpread?this.coeSpread.refresh():'';
         $('#coeSpread').is(':visible')&&this.coeSpread?this.showDatas():'';//这里combobox下拉框要重新加载一下
+        $('#coeSpread').is(':visible')&&this.coeSpread?this.coeSpread.refresh():'';
         $('#cusSpread').is(':visible')&&this.cusSpread?this.cusSpread.refresh():'';
         $('#assSpread').is(':visible')&&this.assSpread?this.assSpread.refresh():'';
     },

+ 195 - 4
web/building_saas/pm/html/project-management.html

@@ -226,18 +226,53 @@
             </div>
             <div class="modal-body">
                 <form id="projFirstStep">
-                    <div class="form-group">
+               <!--     <div class="form-group">
                         <label>建设项目</label>
                         <input type="text" class="form-control" placeholder="输入建设项目名称" id="project-name">
                         <span class="form-text text-danger" id="project-name-info" style="display: none;">已存在 “建筑工程1”</span>
+                    </div>-->
+                    <div class="form-group row">
+                        <label for="staticEmail" class="col-auto col-form-label col-form-label-sm">建设项目</label>
+                        <div class="col">
+                            <input type="text" class="form-control form-control-sm" placeholder="输入建设项目名称" id="project-name">
+                            <span class="form-text text-danger" id="project-name-info" style="display: none;">已存在 “建筑工程1”</span>
+                        </div>
+                    </div>
+                    <div class="form-group row">
+                        <label for="staticEmail" class="col-auto col-form-label col-form-label-sm">文件类型</label>
+                        <div class="col">
+                            <div class="custom-control custom-radio custom-control-inline">
+                                <input type="radio" value="1" checked id="radioTender" name="fileKind" class="custom-control-input">
+                                <label class="custom-control-label" for="radioTender">投标</label>
+                            </div>
+                             <div class="custom-control custom-radio custom-control-inline">
+                                 <input type="radio" value="2" id="radioBid" name="fileKind" class="custom-control-input">
+                                 <label class="custom-control-label" for="radioBid">招标</label>
+                             </div>
+                        </div>
                     </div>
+                    <span class="form-text text-danger" id="fileKind-info" style="display: none;">请选择文件类型</span>
+                    <div class="form-group row">
+                        <label for="staticEmail" class="col-auto col-form-label col-form-label-sm">计税方法</label>
+                        <div class="col">
+                            <div class="custom-control custom-radio custom-control-inline">
+                                <input type="radio" value="1" checked id="radioCommon" name="taxType" class="custom-control-input">
+                                <label class="custom-control-label" for="radioCommon">一般计税法</label>
+                            </div>
+                             <div class="custom-control custom-radio custom-control-inline">
+                                 <input type="radio" value="2" id="radioSimple" name="taxType" class="custom-control-input">
+                                 <label class="custom-control-label" for="radioSimple">简易计税法</label>
+                             </div>
+                        </div>
+                    </div>
+                    <span class="form-text text-danger" id="taxType-info" style="display: none;">请选择计税方法</span>
                 </form>
                 <form id="projInfoStep" class="hidden-area"></form>
                 <p id="project-required-warn" class="text-muted hidden-area"><i class="fa fa-warning text-warning"></i> 注:为响应重庆地区指标采集标准数据要求,以上工程信息及特征必填项为必填项,请正确填写。</p>
             </div>
             <div class="modal-footer">
                 <a href="javascript:void(0);" class="btn btn-primary hidden-area" id="add-proj-prev">上一步</a>
-                <a href="javascript:void(0);" class="btn btn-primary" id="add-proj-next">下一步</a>
+                <a href="javascript:void(0);" class="btn btn-primary hidden-area" id="add-proj-next">下一步</a>
                 <a href="javascript:void(0);" class="btn btn-primary hidden-area" id="addProjOk">确定</a>
                 <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
             </div>
@@ -299,6 +334,34 @@
                         </div>
                     </div>
                     <span class="form-text text-info" id="poj-name-info" style="display: none;">新建 “汽车生产车间5”</span>
+                    <div id="newProjectSet" style="display: none">
+                        <div class="form-group row">
+                            <label for="staticEmail" class="col-auto col-form-label col-form-label-sm">文件类型</label>
+                            <div class="col">
+                                <div class="custom-control custom-radio custom-control-inline">
+                                    <input type="radio" value="1" checked id="radioTender-tender" name="fileKind-tender" class="custom-control-input">
+                                    <label class="custom-control-label" for="radioTender-tender">投标</label>
+                                </div>
+                                <div class="custom-control custom-radio custom-control-inline">
+                                    <input type="radio" value="2" id="radioBid-tender" name="fileKind-tender" class="custom-control-input">
+                                    <label class="custom-control-label" for="radioBid-tender">招标</label>
+                                </div>
+                            </div>
+                        </div>
+                        <div class="form-group row">
+                            <label for="staticEmail" class="col-auto col-form-label col-form-label-sm">计税方法</label>
+                            <div class="col">
+                                <div class="custom-control custom-radio custom-control-inline">
+                                    <input type="radio" value="1" checked id="radioCommon-tender" name="taxType-tender" class="custom-control-input">
+                                    <label class="custom-control-label" for="radioCommon-tender">一般计税法</label>
+                                </div>
+                                <div class="custom-control custom-radio custom-control-inline">
+                                    <input type="radio" value="2" id="radioSimple-tender" name="taxType-tender" class="custom-control-input">
+                                    <label class="custom-control-label" for="radioSimple-tender">简易计税法</label>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
                     <div class="form-group row">
                         <label for="staticEmail" class="col-auto col-form-label col-form-label-sm">单项工程</label>
                         <div class="col">
@@ -383,7 +446,7 @@
                         </div>
                     </div>
                     <span class="form-text text-danger" id="feeStandard-info" style="display: none;">请选择费用标准</span>
-                    <div class="form-group row" id="taxType_div">
+                    <!--<div class="form-group row" id="taxType_div">
                         <label for="staticEmail" class="col-auto col-form-label col-form-label-sm">计税方式</label>
                         <div class="col">
                             <select class="form-control  form-control-sm" id="taxType">
@@ -391,7 +454,7 @@
                                 <option value="2">简易计税</option>&ndash;&gt;
                             </select>
                         </div>
-                    </div>
+                    </div>-->
                     <div class="form-group row">
                         <label for="staticEmail" class="col-auto col-form-label col-form-label-sm">计算程序</label>
                         <div class="col">
@@ -682,9 +745,135 @@
         </div>
     </div>
 </div>
+<!--导入标准接口-->
+<div class="modal fade" id="importInterface" 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-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="progress" style="height: 1px;">
+                <div class="progress-bar" role="progressbar" style="width: 50%;"></div>
+            </div>
+            <div class="modal-body">
+                <form>
+                    <div class="form-group row">
+                        <label for="staticEmail" class="col-auto col-form-label col-form-label-sm">选择文件</label>
+                        <div class="col">
+                            <div class="custom-file custom-file-sm">
+                                <input type="file" class="custom-file-input" id="customFile" accept=".xml,.qtf,.QTF,.xlsx" lang="zh">
+                                <label class="custom-file-label" for="customFile" style="white-space: nowrap; overflow: hidden;">请选择导入文件</label>
+                            </div>
+                            <div class="alert alert-success mt-3" id="uploadAlert" role="alert" style="display: none;"></div>
+                        </div>
+                    </div>
+                </form>
+                <form class="hidden-area selFile">
+                    <div class="form-group row">
+                        <label for="staticEmail" class="col-auto col-form-label col-form-label-sm">建设项目</label>
+                        <div class="col">
+                            <input type="text" class="form-control form-control-sm" value="建设项目名称" readonly="">
+                        </div>
+                    </div>
+                    <div class="form-group row">
+                        <label for="staticEmail" class="col-auto col-form-label col-form-label-sm">计价规则</label>
+                        <div class="col">
+                            <select class="form-control form-control-sm">
+                                <option selected>赣建价[2017]7号</option>
+                                <option value="1">One</option>
+                                <option value="2">Two</option>
+                                <option value="3">Three</option>
+                            </select>
+                        </div>
+                    </div>
+                </form>
+            </div>
+            <div class="modal-footer">
+                <a id="import-next" href="javascript:void(0)" data-toggle="modal" data-target="#import2" class="btn btn-primary">下一步</a>
+            </div>
+        </div>
+        <div class="modal-content" style="display: none">
+            <div class="modal-header">
+                <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="progress" style="height: 1px;">
+                <div class="" role="progressbar " style="width: 50%;"></div>  <div class="progress-bar" role="progressbar" style="width: 50%;"></div>
+            </div>
+            <div class="modal-body">
+                <form>
+                    <p>该文件共有10个单位工程待导入</p>
+                    <div class="form-group row">
+                        <label for="staticEmail" class="col-auto col-form-label col-form-label-sm">单项工程</label>
+                        <div class="col">
+                            <input id="tbc-engName" type="text" class="form-control form-control-sm" value="AA单项工程" readonly="">
+                        </div>
+                    </div>
+                    <div class="form-group row">
+                        <label for="staticEmail" class="col-auto col-form-label col-form-label-sm">单位工程</label>
+                        <div class="col">
+                            <input id="tbc-tenderName" type="text" class="form-control form-control-sm" value="XXXX单位" readonly="">
+                        </div>
+                    </div>
+                    <div class="form-group row">
+                        <label for="staticEmail" class="col-auto col-form-label col-form-label-sm">工程专业</label>
+                        <div class="col">
+                            <select id="tbc-engineering" class="form-control form-control-sm" readonly>
+                                <option selected>建筑工程</option>
+                                <option value="1">One</option>
+                                <option value="2">Two</option>
+                                <option value="3">Three</option>
+                            </select>
+                        </div>
+                    </div>
+                    <div class="form-group row">
+                        <label for="staticEmail" class="col-auto col-form-label col-form-label-sm">费用标准</label>
+                        <div class="col">
+                            <select class="form-control form-control-sm">
+                                <option selected>建筑工程</option>
+                                <option value="1">One</option>
+                                <option value="2">Two</option>
+                                <option value="3">Three</option>
+                            </select>
+                        </div>
+                    </div>
+                    <div class="form-group row">
+                        <label for="staticEmail" class="col-auto col-form-label col-form-label-sm">计算程序</label>
+                        <div class="col">
+                            <select class="form-control form-control-sm">
+                                <option selected>标准模板</option>
+                                <option value="1">One</option>
+                                <option value="2">Two</option>
+                                <option value="3">Three</option>
+                            </select>
+                        </div>
+                    </div>
+                    <div class="d-flex justify-content-center">
+                        <div class="btn-group btn-group-sm " role="group">
+                            <button id="tbc-prev" type="button" class="btn btn-outline-primary" disabled="">上一工程</button>
+                            <a id="tbc-info" class="btn btn-outline-secondary disabled">1/10 已确认</a>
+                            <button id="tbc-next" type="button" class="btn btn-outline-primary ">下一工程</button>
+                        </div>
+                    </div>
+                </form>
+            </div>
+            <div class="modal-footer">
+                <a id="import-prev" href="javascript:void(0);" data-toggle="modal" data-target="#import1" class="btn btn-primary">上一步</a>
+                <button type="button" class="btn btn-primary">确认导入</button>
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
+            </div>
+        </div>
+    </div>
+</div>
 <!-- JS. -->
 <script src = "/lib/spreadjs/sheets/gc.spread.sheets.all.11.1.2.min.js"></script>
 <script>GC.Spread.Sheets.LicenseKey = '<%- LicenseKey %>';</script>
+<!--<script src="/lib/x2js/xml2json.min.js"></script>-->
 <!-- inject:js -->
 <script src="/web/building_saas/js/global.js"></script>
 <script src="/public/web/uuid.js"></script>
@@ -701,6 +890,8 @@
 <script src="/public/web/socket/connection.js"></script>
 <script src="/web/building_saas/pm/js/pm_ajax.js"></script>
 <script src="/web/building_saas/pm/js/pm_newMain.js"></script>
+<!--<script src="/web/building_saas/main/js/models/importStandardInterface.js"></script>
+<script src="/web/building_saas/pm/js/pm_import.js"></script>-->
 <script src="/web/building_saas/pm/js/pm_gc.js"></script>
 <script src="/web/building_saas/pm/js/pm_share.js"></script>
 <!-- zTree -->

+ 166 - 0
web/building_saas/pm/js/pm_import.js

@@ -0,0 +1,166 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Zhong
+ * @date 2019/5/15
+ * @version
+ */
+
+const importView = (() => {
+    let xmlObj = null;  //导入xml转化后的对象
+    let tbcObj = null;  //待确认对象
+    //显示、隐藏提示相关信息
+    function showUploadAlert(success, msg){
+        if(!success){
+            $('#uploadAlert').removeClass('alert-success');
+            $('#uploadAlert').addClass('alert-danger');
+        } else{
+            $('#uploadAlert').removeClass('alert-danger');
+            $('#uploadAlert').addClass('alert-success');
+        }
+        $('#uploadAlert').text(msg);
+        $('#uploadAlert').show();
+    }
+    /*
+     * 获取单位工程数据(将所有单项工程里的单位工程放到一起,含有单项工程的一些信息)
+     * @param {Object} xmlObj @return {Array}
+     * */
+    function getTenderDatas(xmlObj) {
+        if (!xmlObj || !xmlObj.engs) {
+            return [];
+        }
+        let rst = [];
+        for (let eng of xmlObj.engs) {
+            if (Array.isArray(eng.tenders)) {
+                for (let tender of eng.tenders) {
+                    tender.engName = eng.name;
+                }
+                rst = [...rst, ...eng.tenders];
+            }
+        }
+        return rst;
+    }
+    //待确认界面相关
+    class TBC {
+        constructor(xmlObj) {
+            this.datas = getTenderDatas(xmlObj);
+            this.curIdx = 0;
+            this.init();
+        }
+        //初始化
+        init() {
+            this.next(0);   //初始化的时候,显示第第一份数据
+            $('#importInterface .modal-content:eq(1) p:eq(0)').text(`该文件共有${this.datas.length}个单位工程待导入`);  //信息统计信息
+        }
+        //下一工程
+        next(idx = null) {
+            this.curIdx = idx !== null ? idx : this.curIdx + 1;
+            let curData = this.datas[this.curIdx];
+            //显示这个工程的相关信息
+            $('#tbc-engName').val(curData.engName || '');
+            $('#tbc-tenderName').val(curData.name || '');
+            $('#tbc-engineering').val(curData.engineering || '');
+            curData.confirmed = true;   //增加确认信息(已读)
+            if (this.curIdx === this.datas.length -1) { //到最后一个数据了,无法点击下一工程
+                $('#tbc-next').prop('disabled', true);
+            }
+            if (this.curIdx !== 0) {    //不为第一个数据,可以点击上一工程
+                $('#tbc-prev').prop('disabled', false);
+            }
+            //更新x/x信息
+            $('#tbc-info').text(`${this.curIdx + 1}/${this.datas.length} 已确认`);
+        }
+        //上一工程
+        prev(idx = null) {
+            this.curIdx = idx !== null ? idx : this.curIdx - 1;
+            let curData = this.datas[this.curIdx];
+            $('#tbc-engName').val(curData.engName || '');
+            $('#tbc-tenderName').val(curData.name || '');
+            $('#tbc-engineering').val(curData.engineering || '');
+            if (this.curIdx === 0) {    //到第一个数据了,无法点击上一工程
+                $('#tbc-prev').prop('disabled', true);
+            }
+            if (this.curIdx !== this.datas.length -1 ) {    //不为最后一个数据了,可以点击下一工程
+                $('#tbc-next').prop('disabled', false);
+            }
+            //更新x/x信息
+            $('#tbc-info').text(`${this.curIdx + 1}/${this.datas.length} 已确认`);
+        }
+    }
+    function eventListen() {
+        //选择文件
+        $('#customFile').change(async function() {
+            let file = $(this)[0].files[0];
+            if (file) {
+                let reg = /(xml|XML|qtf|QTF)$/g;
+                if(file.name && !reg.test(file.name)){
+                    $('.selFile').hide();
+                    showUploadAlert(false, '请选择xml或qtf文件。');
+                    $(this).val('');
+                    return;
+                }
+                $('.custom-file-label').text(`${file.name}`);   //设置选择框文本
+                $('#uploadAlert').hide();
+                $.bootstrapLoading.start();
+                $('#loadingPage').css('z-index', '2000');
+                //转换数据
+                let importXML = new ImportXML();
+                try {
+                    xmlObj = await importXML.extractData(file);
+                    $('.selFile input').val(xmlObj && xmlObj.name ? xmlObj.name : '');
+                    $('.selFile').show();   //显示建设项目、计价规则
+                } catch (err) {
+                    showUploadAlert(false, '请选择xml或qtf文件。');
+                    $(this).val('');
+                }
+                $.bootstrapLoading.end();
+            }
+        });
+        //下一步
+        $('#import-next').click(function () {
+            let file = $('#customFile')[0].files[0];
+            if (!file) {
+                showUploadAlert(false, '请选择导入文件。');
+                return;
+            }
+            let projectName = $('.selFile input').val();
+            if (!projectName) {
+                showUploadAlert(false, '不存在有效建设项目。');
+                return;
+            }
+            $('#importInterface .modal-content:eq(0)').hide();  //隐藏第一步内容
+            $('#importInterface .modal-content:eq(1)').show();  //显示第二步内容
+            console.log(getTenderDatas(xmlObj));
+            console.log(xmlObj);
+            tbcObj = new TBC(xmlObj);
+        });
+        //上一步
+        $('#import-prev').click(function () {
+            $('#importInterface .modal-content:eq(0)').show();  //显示第一步内容
+            $('#importInterface .modal-content:eq(1)').hide();  //隐藏第二步内容
+        });
+        //待确认-下一工程
+        $('#tbc-next').click(function () {
+            tbcObj.next();
+        });
+        //待确认-上一工程
+        $('#tbc-prev').click(function () {
+            tbcObj.prev();
+        });
+        //导入窗口消失后
+        $('#importInterface').on('hidden.bs.modal', function () {
+            xmlObj = null;  //重置数据
+            tbcObj = null;
+            $('#importInterface .modal-content:eq(0)').show();  //显示第一步内容
+            $('#importInterface .modal-content:eq(1)').hide();  //隐藏第二步内容
+            $('#customFile').val('');    //清除选择
+            $('.custom-file-label').text('');   //设置选择框文本
+            $('#uploadAlert').hide();   //隐藏提示
+            $('.selFile').hide();   //隐藏建设项目及计价规则
+        });
+    }
+    return {eventListen};
+})();
+

+ 258 - 159
web/building_saas/pm/js/pm_newMain.js

@@ -13,6 +13,9 @@ let feeRateData = [];
 let isSaving = false;
 let projectProperty = [];
 let fileDelObj = null;
+let curStep = 1; //当前在第几步
+let infoData = null; //建设项目基本信息数据
+let featureData = null; //工程特征数据
 let projectType = {
     folder: 'Folder',
     tender: 'Tender',
@@ -28,7 +31,12 @@ let taxTypeMap = {
     1:"一般计税",
     2:"简易计税"
 };
-
+//文件类型:招标,投标
+const FileKind = {
+    tender: 1, //投标
+    bid: 2  //招标
+};
+let curTaxType = 1; //1:"一般计税",2:"简易计税"
 /*
 * 地区,同一费用定额下不同地区有不同单价,则新建单位工程时需要选择地区项
 * 地区项为空的时候,前端不显示该下拉项,地区的赋值覆盖在over_write相关文件中
@@ -332,6 +340,13 @@ const projTreeObj = {
                 }, 200);
             }
         },
+        importInterface: {
+            name: '导入标准接口',
+            icon: 'fa-cloud-upload',
+            callback: function () {
+                $('#importInterface').modal('show');
+            }
+        },
         manageFiles: {
             name: "管理相关文件",
             icon: 'fa-list',
@@ -402,6 +417,7 @@ const projTreeObj = {
                 "copyTo": me.contextMenuItems.copyTo,
                 "spr2": '--------',
                 "share": me.contextMenuItems.share,
+                //"importInterface": me.contextMenuItems.importInterface,
                 "spr3": '--------',
                 "manageFiles": me.contextMenuItems.manageFiles,
                 "refreshSummary": me.contextMenuItems.refreshSummary
@@ -1611,14 +1627,28 @@ $(document).ready(function() {
         showProjFirstStep();
     });
     //显示新建建设项目第一步内容
-    function showProjFirstStep() {
+    async function showProjFirstStep() {
         $('#add-project-dialog').find('.modal-title').html('<i class="fa fa-cubes"></i> 新建建设项目');
         $('#projInfoStep').hide();
         $('#addProjOk').hide();
         $('#project-required-warn').hide();
         $('#add-proj-prev').hide();
-        $('#add-proj-next').show();
         $('#projFirstStep').show();
+        let requiredData = null;
+        if (!infoData) {
+            try {
+                infoData = await ajaxPost('/pm/api/getBasicInfo', {user_id: userID});
+                console.log(infoData);
+            } catch (err) {
+                $('#add-project-dialog').modal('hide');
+            }
+        }
+        requiredData = getRequired([], infoData);
+        if (requiredData.length > 0) {
+            $('#add-proj-next').show();
+        } else {
+            $('#addProjOk').show();
+        }
     }
     //显示新建建设项目-建设项目基本信息内容
     async function showProjInfoStep() {
@@ -1629,14 +1659,7 @@ $(document).ready(function() {
         $('#addProjOk').show();
         $('#projInfoStep').show();
         $('#project-required-warn').show();
-        if (!infoData) {
-            try {
-                infoData = await ajaxPost('/pm/api/getBasicInfo', {user_id: userID});
-                showRequiredInfo($('#projInfoStep'), infoData);
-            } catch (err) {
-                $('#add-project-dialog').modal('hide');
-            }
-        }
+        showRequiredInfo($('#projInfoStep'), infoData);
     }
 
     //新建建设项目、新建单项工程、新建文件夹、重命名弹窗隐藏事件
@@ -1644,6 +1667,9 @@ $(document).ready(function() {
         projDialog.on('hidden.bs.modal', function () {
             setDangerInfo($('#project-name-info'), '', false);
             $('#project-name').val('');
+            //文件类型、计税方法恢复默认选项
+            $('input[name="fileKind"]:eq(0)').prop('checked', true);
+            $('input[name="taxType"]:eq(0)').prop('checked', true);
         });
         projDialog.on('show.bs.modal', function () {
             infoData = null;
@@ -1690,6 +1716,11 @@ $(document).ready(function() {
             }
         });
     }
+    //单位工程-新建建设项目-选择计税方法
+    $('input[name="taxType-tender"]').click(function () {
+        curTaxType = parseInt($(this).val());
+        getStdCalcProgramFiles();
+    });
     // 选择计价方式
     $("input[name='valuation_type']").click(function() {
         let type = $(this).val();
@@ -1739,10 +1770,17 @@ $(document).ready(function() {
         let isExist = hasListName(nameList, pojName);
         if(!isExist){
             if(pojName !== ''){
+                //新建建设项目,显示文件类型、计税方法选项
+                $('#newProjectSet').show();
+                $('input[name="fileKind-tender"]:eq(0)').prop('checked', true);
+                $('input[name="taxType-tender"]:eq(0)').prop('checked', true);
+                curTaxType = 1;
+                getStdCalcProgramFiles();
                 replaceClass($('#poj-name-info'), 'text-danger', 'text-info');
                 setDangerInfo($('#poj-name-info'), `新建“${pojName}”`);
             }
             else {
+                $('#newProjectSet').hide();//隐藏文件类型、计税方法选项
                 $('#poj-name-info').hide();
             }
             //清空单项工程下拉和文件下拉
@@ -1840,6 +1878,10 @@ $(document).ready(function() {
         $('#engineering-info').hide();
         $('#unit-price').find('option:first-child').text('新建单价文件');
         $('#tender-fee-rate').find('option:first-child').text('新建费率文件');
+        $('#newProjectSet').hide(); //隐藏文件类型、计税方法选项
+        $('input[name="fileKind-tender"]:eq(0)').prop('checked', true);
+        $('input[name="taxType-tender"]:eq(0)').prop('checked', true);
+        curTaxType = 1;
         //获取建设项目
         let selected = projTreeObj.tree.selected;
         let projs = getProjs(selected);
@@ -1910,9 +1952,11 @@ $(document).ready(function() {
                 return false;
             }
             let path = getAddPath();
-            if (path === addPath.p_e_t) {
+            let infoRequired = getRequired([], infoData);
+            let featureRequired = getRequired([], featureData);
+            if (path === addPath.p_e_t && infoRequired.length > 0) {
                 showInfoStep();
-            } else {
+            } else if (featureRequired.length > 0) {
                 showFeatureStep();
             }
         } else if (curStep === 3) {
@@ -1951,7 +1995,7 @@ $(document).ready(function() {
         $('#add-tender-cancel').hide();
     }
     //显示第二步内容
-    function showSecondStep() {
+    async function showSecondStep() {
         $('#add-tender-title').html(`<i class="fa fa-sticky-note-o"></i>新建单位工程`);
         $('#add-tender-confirm').hide();
         $('#featureStep').hide();
@@ -1960,26 +2004,54 @@ $(document).ready(function() {
         $('#tender-required-warn').hide();
         $('#secondStep').show();
         $('#add-tender-prev').show();
-        $('#add-tender-next').show();
+        $('#add-tender-next').hide();
+        $('#add-tender-confirm').hide();
         $('#add-tender-cancel').show();
-    }
-    //显示新建单位工程-建设项目基本信息内容
-    async function showInfoStep() {
-        $('#add-tender-title').html('建设项目基本信息');
-        $('#secondStep').hide();
-        $('#infoStep').show();
-        $('#tender-required-warn').show();
         if (!infoData) {
             try {
                 infoData = await ajaxPost('/pm/api/getBasicInfo', {user_id: userID});
-                showRequiredInfo($('#infoStep'), infoData);
             } catch (err) {
                 $('#add-tender-dialog').modal('hide');
             }
         }
+        if (!featureData) {
+            try{
+                let valuation = $("#valuation").val();
+                let engineeringName = $("#tender-engineering").val();
+                let feeName = $('#tender-feeStandard').val();
+                featureData = await ajaxPost('/pm/api/getProjectFeature',
+                    {user_id: userID, valuationID: valuation, engineeringName: engineeringName, feeName: feeName});
+            } catch (err) {
+                $('#add-tender-dialog').modal('hide');
+            }
+        }
+        let infoRequired = getRequired([], infoData),
+            featureRequired = getRequired([], featureData),
+            path = getAddPath();
+        if (path === addPath.p_e_t && infoRequired.length > 0 || featureRequired.length > 0) {
+            $('#add-tender-next').show();
+        } else {
+            $('#add-tender-confirm').show();
+        }
+    }
+    //显示新建单位工程-建设项目基本信息内容
+    function showInfoStep() {
+        $('#add-tender-title').html('建设项目基本信息');
+        $('#secondStep').hide();
+        $('#infoStep').show();
+        $('#tender-required-warn').show();
+        let featureRequired = getRequired([], featureData);
+        if (featureRequired.length > 0) {
+            $('#add-tender-next').show();
+            $('#add-tender-confirm').hide();
+        } else {
+            $('#add-tender-next').hide();
+            $('#add-tender-confirm').show();
+        }
+        showRequiredInfo($('#infoStep'), infoData);
     }
     //显示新建单位工程-工厂特征内容
-    async function showFeatureStep() {
+    function showFeatureStep() {
         $('#add-tender-next').hide();
         $('#add-tender-confirm').show();
         $('#add-tender-title').html('工程特征');
@@ -1987,37 +2059,29 @@ $(document).ready(function() {
         $('#infoStep').hide();
         $('#featureStep').show();
         $('#tender-required-warn').show();
-        if (!featureData) {
-            let valuation = $("#valuation").val();
-            let engineeringName = $("#tender-engineering").val();
-            let feeName = $('#tender-feeStandard').val();
-            try {
-                featureData = await ajaxPost('/pm/api/getProjectFeature',
-                    {user_id: userID, valuationID: valuation, engineeringName: engineeringName, feeName: feeName});
-                showRequiredInfo($('#featureStep'), featureData);
-            } catch (err) {
-                $('#add-tender-dialog').modal('hide');
+        showRequiredInfo($('#featureStep'), featureData);
+    }
+    function getRequired(rst, datas) {
+        if (!datas) {
+            return rst;
+        }
+        for (let data of datas) {
+            let required = typeof data.required === 'string' ? JSON.parse(data.required) : data.required,
+                readOnly = typeof data.readOnly === 'string' ? JSON.parse(data.readOnly) : data.readOnly;
+            if (required && !readOnly) {
+                rst.push(data);
+            }
+            if (data.items && data.items.length > 0) {
+                getRequired(rst, data.items);
             }
         }
+        return rst;
     }
     //显示必填信息(建设项目基本信息、单位工程工程特征)
     //必填信息:标记了必填required且不只读!readOnly
     function showRequiredInfo($form, datas) {
         $form.empty();
-        let requiredDatas = [];
-        function getRequired(datas) {
-            for (let data of datas) {
-                let required = typeof data.required === 'string' ? JSON.parse(data.required) : data.required,
-                    readOnly = typeof data.readOnly === 'string' ? JSON.parse(data.readOnly) : data.readOnly;
-                if (required && !readOnly) {
-                    requiredDatas.push(data);
-                }
-                if (data.items && data.items.length > 0) {
-                    getRequired(data.items);
-                }
-            }
-        }
-        getRequired(datas);
+        let requiredDatas = getRequired([], datas);
         for (let data of requiredDatas) {
             $form.append(getFormRow(data));
         }
@@ -2094,6 +2158,9 @@ $(document).ready(function() {
 
     // 新增单位工程
     $("#add-tender-confirm").click(function() {
+        if (getAddPath() === addPath.p_e_t && !validRequiredData($('#infoStep'))) {
+            return;
+        }
         if (!validRequiredData($('#featureStep'))) {
             return;
         }
@@ -2208,6 +2275,7 @@ $(document).ready(function() {
             setDangerInfo($('#rename-name-info'), `已存在“${newName}”`);
             return;
         }
+        newName = whiteSpaceCollapse(newName);
         RenameProject(select.id(), newName, select.data.ParentID, function () {
             setDangerInfo($('#rename-name-info'), '', false);
             dialog.modal('hide');
@@ -2256,15 +2324,23 @@ $(document).ready(function() {
     $("#copy-to-confirm").click(function() {
         let originalNode = projTreeObj.tree.selected;
         let toNode = projTreeObj.copySelected;
+        let projectNode;    //建设项目节点
         let parent = null,next = null,projectMap={};
         if(toNode.data.projType == projectType.engineering){//复制为目标的子节点
+            projectNode = toNode.parent;
             parent = toNode;
             next = toNode.firstChild();
         }else if(toNode.data.projType == projectType.tender){//复制为目标的后兄弟
+            projectNode = toNode.parent.parent;
             parent = toNode.parent;
             next = toNode.nextSibling;
             projectMap['update'] = {query:{ID:toNode.id()}};//前一节点的下一个节点更新;
         }
+        //目标建设项目的计税方法与单位工程的一致时,才可复制到,否则提示“当前单位工程计税方法与目标建设项目不一致,不可复制。 确定”。
+        if (projectNode.data.property.taxType != originalNode.data.property.taxType) {
+            alert('当前单位工程计税方法与目标建设项目不一致,不可复制。');
+            return false;
+        }
         let nextID = next?next.id():-1;
         let projectData = _.cloneDeep(originalNode.data);
         projectData['ParentID'] = parent.id();
@@ -2295,112 +2371,116 @@ $(document).ready(function() {
         projTreeObj.confirmFileChange();
     });
 
-    function changeEngineering(){
-        featureData = null;
-        $('#engineering-info').hide();
-        initFeeStandardSel();
-    }
+    // 选择工程专业后动态更改费率文件等数据
+    $("#tender-engineering").change(function() {
+        changeEngineering()
+    });
+    $('#tender-feeStandard').change(function () {
+       changeFeeStandard();
+    });
 
-    function changeFeeRate(engLib) {
-        if(engLib){
-           /*
-             if (engLib.fee_lib !== undefined && engLib.fee_lib.length > 0) {
-                $("#tender-fee-rate").children("option").first().val("newFeeRate-"+engLib.fee_lib[0].id);
-            }*/
-           //2018-08-29  费率改为和计税方式绑定在一起 所以在这里还不能确定标准费率库ID
-            $("#tender-fee-rate").children("option").first().val("newFeeRate@@");
-            $("#tender-engineering").parent().siblings('.hidden-area').slideDown('fast');
-        }
-        else {
-            $("#tender-fee-rate").children("option").first().val($('#tender-name').val());
-        }
-    }
+    //导入相关
+    //importView.eventListen();
+});
+function changeEngineering(){
+    featureData = null;
+    $('#engineering-info').hide();
+    initFeeStandardSel();
+}
 
-    function initFeeStandardSel() {
-        $('#tender-feeStandard').empty();
-        let feeStandards = [];
-        let engineeringList = getEngineeringList();
-        let currentEngName = $('#tender-engineering').val();
-        for(let eng of engineeringList){
-            if(eng.lib.name && eng.lib.name === currentEngName && eng.lib.feeName && eng.lib.feeName !== ''){
-                feeStandards.push(eng.lib.feeName);
-            }
-        }
-        for(let feeName of feeStandards){
-            let $opt = $(`<option value="${feeName}">${feeName}</option>`);
-            $('#tender-feeStandard').append($opt);
-        }
-        if(feeStandards.length > 0){
-            if($('#feeStandard-info').is(':visible')){
-                $('#feeStandard-info').hide();
-            }
-        }
-        let currentEngLib = getEngineeringLib(currentEngName + $('#tender-feeStandard').val(), engineeringList);
-        changeFeeRate(currentEngLib);
-        setTaxGroupHtml();
-        getStdCalcProgramFiles();
+function changeFeeRate(engLib) {
+    if(engLib){
+        /*
+         if (engLib.fee_lib !== undefined && engLib.fee_lib.length > 0) {
+         $("#tender-fee-rate").children("option").first().val("newFeeRate-"+engLib.fee_lib[0].id);
+         }*/
+        //2018-08-29  费率改为和计税方式绑定在一起 所以在这里还不能确定标准费率库ID
+        $("#tender-fee-rate").children("option").first().val("newFeeRate@@");
+        $("#tender-engineering").parent().siblings('.hidden-area').slideDown('fast');
     }
-
-    function changeFeeStandard() {
-        featureData = null;
-        let currentEngLib = getEngineeringLib($('#tender-engineering').val() + $('#tender-feeStandard').val(), getEngineeringList());
-        changeFeeRate(currentEngLib);
-        setTaxGroupHtml();
-        getStdCalcProgramFiles();
+    else {
+        $("#tender-fee-rate").children("option").first().val($('#tender-name').val());
     }
+}
 
-    function setTaxGroupHtml() {
-        let groups = getTaxGroupList();
-        let taxTypeArray = [];
-        let htmlString = "";
-        if(groups && groups.length > 0){
-            taxTypeArray = _.uniq(_.map(groups,'taxType'));
-        }
-        for(let t of taxTypeArray){
-             htmlString += "<option value='"+t+"'>"+taxTypeMap[t]+"</option>"
+function initFeeStandardSel() {
+    $('#tender-feeStandard').empty();
+    let feeStandards = [];
+    let engineeringList = getEngineeringList();
+    let currentEngName = $('#tender-engineering').val();
+    for(let eng of engineeringList){
+        if(eng.lib.name && eng.lib.name === currentEngName && eng.lib.feeName && eng.lib.feeName !== ''){
+            feeStandards.push(eng.lib.feeName);
         }
-        $('#taxType').html(htmlString);
-        if(taxTypeArray.length <2){
-            $('#taxType_div').hide();
-        }else {
-            $('#taxType_div').show();
+    }
+    for(let feeName of feeStandards){
+        let $opt = $(`<option value="${feeName}">${feeName}</option>`);
+        $('#tender-feeStandard').append($opt);
+    }
+    if(feeStandards.length > 0){
+        if($('#feeStandard-info').is(':visible')){
+            $('#feeStandard-info').hide();
         }
     }
+    let currentEngLib = getEngineeringLib(currentEngName + $('#tender-feeStandard').val(), engineeringList);
+    changeFeeRate(currentEngLib);
+    //setTaxGroupHtml();
+    getStdCalcProgramFiles();
+}
 
-    function getStdCalcProgramFiles(){
-        function getStdCPFilesHtml(taxGroups) {
-            let result = '';
-            if (taxGroups.length <= 0) {
-                return result;
-            };
+function changeFeeStandard() {
+    featureData = null;
+    let currentEngLib = getEngineeringLib($('#tender-engineering').val() + $('#tender-feeStandard').val(), getEngineeringList());
+    changeFeeRate(currentEngLib);
+    //setTaxGroupHtml();
+    getStdCalcProgramFiles();
+}
 
-            for (let group of taxGroups){
-                let groupString = ""; //把计算程序、清单模板、列设置的ID生成一个组合的值
-                let p_id = group.program_lib?group.program_lib.id:"";
-                let t_id = group.template_lib?group.template_lib.id:"";
-                let c_id = group.col_lib?group.col_lib.id:"";
-                let f_id = group.fee_lib?group.fee_lib.id:"";
-                groupString = p_id + "|-|" +t_id+"|-|"+c_id+"|-|"+f_id;
-                result += '<option value='+ groupString +'>'+ group.program_lib.name +'</option>';
-            };
-            return result;
-        };
-        let taxType = $("#taxType").val();
-        let stdCPHtml = getStdCPFilesHtml(getTaxGroupList(taxType));
-        $("#tender-calcProgram").html(stdCPHtml);
-        if ($("#tender-calcProgram")[0].options.length > 1)
-            $("#tender-calcProgram")[0].selectedIndex = 0;
+function setTaxGroupHtml() {
+    let groups = getTaxGroupList();
+    let taxTypeArray = [];
+    let htmlString = "";
+    if(groups && groups.length > 0){
+        taxTypeArray = _.uniq(_.map(groups,'taxType'));
     }
+    for(let t of taxTypeArray){
+        htmlString += "<option value='"+t+"'>"+taxTypeMap[t]+"</option>"
+    }
+    $('#taxType').html(htmlString);
+    if(taxTypeArray.length <2){
+        $('#taxType_div').hide();
+    }else {
+        $('#taxType_div').show();
+    }
+}
 
-    // 选择工程专业后动态更改费率文件等数据
-    $("#tender-engineering").change(function() {
-        changeEngineering()
-    });
-    $('#tender-feeStandard').change(function () {
-       changeFeeStandard();
-    });
-});
+function getStdCalcProgramFiles(){
+    function getStdCPFilesHtml(taxGroups) {
+        let result = '';
+        if (taxGroups.length <= 0) {
+            return result;
+        };
 
+        for (let group of taxGroups){
+            let groupString = ""; //把计算程序、清单模板、列设置的ID生成一个组合的值
+            let p_id = group.program_lib?group.program_lib.id:"";
+            let t_id = group.template_lib?group.template_lib.id:"";
+            let c_id = group.col_lib?group.col_lib.id:"";
+            let f_id = group.fee_lib?group.fee_lib.id:"";
+            groupString = p_id + "|-|" +t_id+"|-|"+c_id+"|-|"+f_id;
+            result += '<option value='+ groupString +'>'+ group.program_lib.name +'</option>';
+        };
+        return result;
+    };
+    //let taxType = $("#taxType").val();
+    let taxType = curTaxType; //计税方法取建设项目的
+    //let selected = projTreeObj.tree.selected;
+
+    let stdCPHtml = getStdCPFilesHtml(getTaxGroupList(taxType));
+    $("#tender-calcProgram").html(stdCPHtml);
+    if ($("#tender-calcProgram")[0].options.length > 1)
+        $("#tender-calcProgram")[0].selectedIndex = 0;
+}
 //获取工作表总宽度
 function getWorkBookWidth(){
     return workBookWidth = $(window).width() - $('.pm-side').width() - 90;
@@ -2494,6 +2574,12 @@ function init() {
     engineering = engineeringList !== null && engineeringList !== undefined ? JSON.parse(engineeringList) : [];
 }
 
+function whiteSpaceCollapse(v) {
+    v = v.replace(/[\r,\n,\t]/g, ' ');
+    v = v.trim();
+    return v.replace(/\s{1,}/g, ' ');
+}
+
 /**
  * 新增建设项目
  *
@@ -2505,6 +2591,17 @@ function AddProject() {
         setDangerInfo($('#project-name-info'), '请填写建设项目名称');
         return false;
     }
+    name = whiteSpaceCollapse(name);
+    let fileKind = $('input[name="fileKind"]:checked').val();
+    if (!fileKind) {
+        setDangerInfo($('#fileKind-info'));
+        return false;
+    }
+    let taxType = $('input[name="taxType"]:checked').val();
+    if (!taxType) {
+        setDangerInfo($('#taxType-info'));
+        return false;
+    }
     let existCallback = function () {
         setDangerInfo($('#project-name-info'), `已存在“${$("#project-name").val()}”`);
     };
@@ -2521,7 +2618,7 @@ function AddProject() {
      (3)、当前定位在建设项目,新建建设项目为其后兄弟。
      (4)、当前定位在单项工程或单位工程,新建项目为当前所属建设项目的后兄弟。
      * */
-    let property = {basicInformation: infoData ? infoData : []};
+    let property = {basicInformation: infoData ? infoData : [], fileKind: fileKind, taxType: taxType};
     if(!selectedItem){
         AddSiblingsItem(selectedItem, name, property, projectType.project, existCallback, sucCallback);
     }
@@ -2630,7 +2727,8 @@ function AddTenderItems(selected, projName, engName, tenderName, property, callb
                 pre = selectedProj;
                 next = selectedProj ? selectedProj.nextSibling : projTreeObj.tree.firstNode();
             }
-            let updateProjs = GetUpdateData(pre, parent, next, projName, {basicInformation: infoData ? infoData : []}, projID, {updateType: 'new', projectType: projectType.project});
+            let fileKind = $('input[name="fileKind-tender"]:checked').val();
+            let updateProjs = GetUpdateData(pre, parent, next, projName, {basicInformation: infoData ? infoData : [], fileKind: parseInt(fileKind), taxType: curTaxType}, projID, {updateType: 'new', projectType: projectType.project});
             let updateEng = {updateType: 'new', updateData: {ID: engID, ParentID: projID, NextSiblingID: -1, name: engName, projType: projectType.engineering}};
             property.rootProjectID = projID;
             let updateTender = {updateType: 'new', updateData: {ID: tenderID, ParentID: engID, NextSiblingID: -1, shareInfo: [], name: tenderName, projType: projectType.tender, property: property}};
@@ -2877,7 +2975,7 @@ function validProjectName(name) {
     if (selected && selected.data.projType === projectType.folder) {
         parent = selected ? selected : projTreeObj.tree._root;
     } else {
-        if ([projectType.tender, projectType.engineering].includes(selected.data.projType)) {
+        if (selected && [projectType.tender, projectType.engineering].includes(selected.data.projType)) {
             selected = selected.parent.data.projType === projectType.project ? selected.parent : selected.parent.parent;
         }
         parent = selected ? selected.parent : projTreeObj.tree._root;
@@ -2985,6 +3083,8 @@ function setProjOptions(projs, selected){
     $("#poj-name").val("");
     if(projs.length > 0){
         let firstProj = selected && selected.data.projType === projectType.project ? selected: projs[0];
+        curTaxType = firstProj.data.property.taxType || 1;
+        getStdCalcProgramFiles();
         $("#poj-name").val(firstProj.data.name);
         setFileOptions(firstProj.data.ID);
         setEngOptions(firstProj.data.ID);
@@ -2993,20 +3093,14 @@ function setProjOptions(projs, selected){
             $proj.addClass("dropdown-item");
             $proj.attr("href", "javascript:void(0);");
             $proj.click(function () {
+                curTaxType = projs[i].data.property.taxType || 1;
+                getStdCalcProgramFiles();
+                $('#newProjectSet').hide();//隐藏文件类型、计税方法选项
                 $("#poj-name").val(projs[i].data.name);
                 setFileOptions(projs[i].data.ID);
                 setEngOptions(projs[i].data.ID);
                 $('#poj-name-info').hide();
             });
-            /*let proj = $("<button>").val(projs[i].data.ID).text(projs[i].data.name);
-            proj.addClass("dropdown-item");
-            proj.attr("type", "button");
-            proj.click(function () {
-                $("#poj-name").val(projs[i].data.name);
-                setFileOptions(projs[i].data.ID);
-                setEngOptions(projs[i].data.ID);
-                $('#poj-name-info').hide();
-            });*/
             $("#poj-name-list").append($proj);
         }
     }
@@ -3098,7 +3192,9 @@ function replaceClass(selector, orgClass, newClass){
 }
 //设置提示信息
 function setDangerInfo(area, info, show = true){
-    area.text(info);
+    if (info) {
+        area.text(info);
+    }
     if(show)
         area.show();
     else
@@ -3116,6 +3212,7 @@ function AddEngineering() {
         setDangerInfo($('#engineering-name-info'), '请填写单项工程名称');
         return false;
     }
+    name = whiteSpaceCollapse(name);
     let existCallback = function () {
         setDangerInfo($('#engineering-name-info'), `已存在“${$("#engineering-name").val()}”`);
     };
@@ -3154,19 +3251,20 @@ function AddTender() {
             setDangerInfo($('#poj-name-info'), '请填写建设项目名称');
             return false;
         }
+        projName = whiteSpaceCollapse(projName);
         let engName = $("#eng-name").val().trim();
         if(engName === ''){
             replaceClass($('#eng-name-info'), 'text-info', 'text-danger');
             setDangerInfo($('#eng-name-info'), '请填写单项工程名称');
             return false;
         }
-
+        engName = whiteSpaceCollapse(engName);
         let tenderName = $('#tender-name').val();
         if (tenderName === '') {
             setDangerInfo($('#tender-name-info'), '请填写单位工程名称');
             return false;
         }
-
+        tenderName = whiteSpaceCollapse(tenderName);
         if(hasTender(projTreeObj.tree.selected, projName, engName, tenderName)){
             return false;
         }
@@ -3190,8 +3288,9 @@ function AddTender() {
             setDangerInfo($('#feeStandard-info'), '请选择费用标准');
             return false;
         }
-
-        let taxType = $("#taxType").val();
+        //
+        //计税方法由原本的跟单位工程绑定,改成跟建设项目绑定,为了减少项目出错几率,新建时保留单位工程的计税方法,值为建设项目的计税方法值
+        let taxTypeTender = curTaxType;
         let IDGroup = $("#tender-calcProgram").val();
         if (!IDGroup || IDGroup === '') {
             setDangerInfo($('#calcProgram-info'), '请选择计算程序');
@@ -3264,7 +3363,7 @@ function AddTender() {
             unitPriceFile: {name: unitPriceFileObj.name, id: unitPriceFileObj.id},
             feeFile: {name: feeFileObj.name, id: feeFileObj.id},
             calcProgram: {name: calcProgramName, id: calcProgram},
-            taxType:taxType,
+            taxType:taxTypeTender,
             templateLibID:templateLibID,
             colLibID:colLibID,
             featureLibID:featureLibID,

+ 13 - 4
web/building_saas/pm/js/pm_share.js

@@ -18,6 +18,7 @@ const pmShare = (function () {
     const oprType = {copy: 'copy', cancel: 'cancel'};
     let tree = null,
         actualIDShareInfo = {};//项目真实树ID与项目分享信息映射
+    let curCopySelTree = null;  //拷贝工程建设项目选项树
     const treeCol = 0;
     const treeSetting = {
         tree: {
@@ -893,9 +894,9 @@ const pmShare = (function () {
     //从其他建设项目中复制中,建设项目的文件层次结构名称和顺序
     //@param {Array}treeData @return {Array}
     function getFileHierarchyInfo(treeData){
-        let tree = idTree.createNew({id: 'ID', pid: 'ParentID', nid: 'NextSiblingID', rootId: -1});
-        tree.loadDatas(treeData);
-        let items = tree.items;
+        curCopySelTree = idTree.createNew({id: 'ID', pid: 'ParentID', nid: 'NextSiblingID', rootId: -1});
+        curCopySelTree.loadDatas(treeData);
+        let items = curCopySelTree.items;
         let rst = [];
         function getFileHierarchyName(node){
             let nodeName = node.data.name;
@@ -920,7 +921,7 @@ const pmShare = (function () {
     function setCopyModal(){
         //获取建设项目
         let projQuery = {$or: [{deleteInfo: null}, {'deleteInfo.deleted': false}], projType: {$in: [projectType.project, projectType.folder]}, userID: userID};
-        CommonAjax.post('/pm/api/getProjectsByQuery', {user_id: userID, query: projQuery, options: '-_id -property'}, function (rstData) {
+        CommonAjax.post('/pm/api/getProjectsByQuery', {user_id: userID, query: projQuery, options: '-_id'}, function (rstData) {
             let fileHierarchyData = getFileHierarchyInfo(rstData);
             $('#copyShare_selectProj').empty();
             for(let proj of fileHierarchyData){
@@ -1057,6 +1058,14 @@ const pmShare = (function () {
                 $('#copyShareProj-info').show();
                 return;
             }
+            //目标建设项目的计税方法与单位工程的一致时,才可拷贝
+            if (curCopySelTree) {
+                let projectNode = curCopySelTree.nodes[curCopySelTree.prefix + selProj];
+                if (!projectNode || !projectNode.data.property || projectNode.data.property.taxType != tree.selected.data.taxType){
+                    alert('当前单位工程计税方法与目标建设项目不一致,不可复制。');
+                    return;
+                }
+            }
             let selEng = $('#copyShare_selectEng').select().val();
             if(!selEng){
                 $('#copyShareEng-info').show();

+ 78 - 66
web/over_write/js/chongqing_2018.js

@@ -16,9 +16,9 @@ if(typeof gljUtil !== 'undefined'){
 }
 
 //允许使用的工料机类型:人工、普通材料、混凝土、砂浆、配合比、商品混凝土、商品砂浆、其他材料费、机械台班、机上人工、仪器仪表、燃料动力费、折旧费、
-// 检修费、维护费、安拆费及场外运费、校验费、其他费用、主材、企业管理费、利润、一般风险费
+// 检修费、维护费、安拆费及场外运费、校验费、其他费用、主材、设备、企业管理费、利润、一般风险费  前端工料机库编辑器下拉列表没有用到这个
 if(typeof allowGljType !== 'undefined'){
-    allowGljType = [1, 201, 202, 203, 204, 205, 206, 207, 301, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 4, 6, 7, 8];
+    allowGljType = [1, 201, 202, 203, 204, 205, 206, 207, 301, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 4,5,6, 7, 8];
 }
 if(typeof allowComponent !== 'undefined'){
     //允许含有组成物的工料机类型:混凝土、砂浆、配合比、机械台班、仪器仪表、主材
@@ -81,6 +81,9 @@ function overwriteRationCalcBases (taxType){
         rationCalcBases['市场价主材费'] = function (node, isTender) {
             return calcTools.rationBaseFee(node, [gljType.MAIN_MATERIAL], priceTypes.ptMarketPrice, isTender);
         };
+        rationCalcBases['市场价设备费'] = function (node, isTender) {
+            return calcTools.rationBaseFee(node, [gljType.EQUIPMENT], priceTypes.ptMarketPrice, isTender);
+        };
         rationCalcBases['人工工日'] = function (node, isTender) {
             return calcTools.labourDays(node, isTender);
         };
@@ -168,45 +171,74 @@ function overwriteRationCalcBases (taxType){
         rationCalcBases['暂估材料费'] = function (node, isTender) {
             return calcTools.estimateFee(node, true, isTender);
         };
-        // rationCalcBases['分包定额施工机具费'] = function (node, isTender) {
-        //     if (node.data.isSubcontract)
-        //         return calcTools.rationBaseFee(node, [gljType.GENERAL_MACHINE, gljType.INSTRUMENT, gljType.OTHER_MACHINE_USED], priceTypes.ptBasePrice, isTender)
-        //     else
-        //         return 0;
-        // };
+        rationCalcBases['分包定额基价人工费'] = function (node, isTender) {
+            if (node.data.isSubcontract)
+                return calcTools.rationBaseFee(node, [gljType.LABOUR], priceTypes.ptBasePrice, isTender)
+            else
+                return 0;
+        };
+        rationCalcBases['分包定额基价材料费'] = function (node, isTender) {
+            if (node.data.isSubcontract)
+                return calcTools.rationBaseFee(node, baseMaterialTypes, priceTypes.ptBasePrice, isTender)
+            else
+                return 0;
+        };
+        rationCalcBases['分包定额基价机械费'] = function (node, isTender) {
+            if (node.data.isSubcontract)
+                return calcTools.rationBaseFee(node, baseMachineTypes, priceTypes.ptBasePrice, isTender)
+            else
+                return 0;
+        };
+        rationCalcBases['分包主材费'] = function (node, isTender) {
+            if (node.data.isSubcontract)
+                return calcTools.rationBaseFee(node, [gljType.MAIN_MATERIAL], priceTypes.ptMarketPrice, isTender)
+            else
+                return 0;
+        };
+        rationCalcBases['分包设备费'] = function (node, isTender) {
+            if (node.data.isSubcontract)
+                return calcTools.rationBaseFee(node, [gljType.EQUIPMENT], priceTypes.ptMarketPrice, isTender)
+            else
+                return 0;
+        };
+        rationCalcBases['分包人工工日'] = function (node, isTender) {
+            if (node.data.isSubcontract)
+                return calcTools.labourDays(node, isTender)
+            else
+                return 0;
+        };
     };
 };
-
-var cpFeeTypes2018 = [
-    {type: 'rationUnitPrice', name: '定额综合单价'},
-    {type: 'labour', name: '人工费'},
-    {type: 'material', name: '材料费'},
-    {type: 'machine', name: '施工机具使用费'},
-    {type: 'mainMaterial', name: '主材费'},
-    {type: 'manage', name: '企业管理费'},
-    {type: 'profit', name: '利润'},
-    {type: 'risk', name: '一般风险费'},
-    {type: 'labourDiff', name: '人工价差'},
-    {type: 'materialDiff', name: '材料价差'},
-    {type: 'machineDiff', name: '施工机具使用价差'},
-    {type: 'otherRisk', name: '其他风险费'},
-    {type: 'unratedMaterial', name: '未计价材料费'},
-    {type: 'organizeMeasures', name: '组织措施费'},
-    {type: 'safeCivilization', name: '安全文明施工费'},
-    {type: 'document', name: '建设工程竣工档案编制费'},
-    {type: 'acceptance', name: '住宅工程质量分户验收费'},
-    {type: 'forceFee', name: '规费'},
-    {type: 'tax', name: '税金'},
-    {type: 'VAT', name: '增值税'},
-    {type: 'surtax', name: '附加税'},
-    {type: 'environmentTax', name: '环境保护税'},
-    {type: 'common', name: '工程造价'}
-];
-
-if(typeof cpFeeTypes !== 'undefined'){
-    cpFeeTypes.splice(0, cpFeeTypes.length);
-    for (let e of cpFeeTypes2018) cpFeeTypes.push(e);
-}
+//code为标准接口所需,值是字典规定的
+(function overwriteFeeTypes() {
+    if (typeof cpFeeTypes == 'undefined') return;
+    cpFeeTypes = [
+        {type: 'rationUnitPrice', name: '定额综合单价', code: '1800'},
+        {type: 'labour', name: '人工费', code: '101'},
+        {type: 'material', name: '材料费', code: '201'},
+        {type: 'machine', name: '施工机具使用费', code: '301'},
+        {type: 'mainMaterial', name: '主材费', code: '501'},
+        {type: 'equipment', name: '设备费', code: '502'},
+        {type: 'manage', name: '企业管理费', code: '600'},
+        {type: 'profit', name: '利润', code: '700'},
+        {type: 'risk', name: '一般风险费', code: '1000'},
+        {type: 'labourDiff', name: '人工价差', code: '104'},
+        {type: 'materialDiff', name: '材料价差', code: '203'},
+        {type: 'machineDiff', name: '施工机具使用价差', code: '304'},
+        {type: 'otherRisk', name: '其他风险费', code: '1800'},
+        {type: 'unratedMaterial', name: '未计价材料费', code: '1800'},
+        {type: 'organizeMeasures', name: '组织措施费', code: '1200'},
+        {type: 'safeCivilization', name: '安全文明施工费', code: '1204'},
+        {type: 'document', name: '建设工程竣工档案编制费', code: '10041'},
+        {type: 'acceptance', name: '住宅工程质量分户验收费', code: '1206'},
+        {type: 'forceFee', name: '规费', code: '800'},
+        {type: 'tax', name: '税金', code: '900'},
+        {type: 'VAT', name: '增值税', code: '1800'},
+        {type: 'surtax', name: '附加税', code: '1800'},
+        {type: 'environmentTax', name: '环境保护税', code: '1800'},
+        {type: 'common', name: '工程造价', code: '1'}
+    ];
+})();
 
 
 
@@ -265,6 +297,13 @@ if(typeof baseFigureMap !== 'undefined'){
         '甲定施工机具使用费': {base: 'JDJXF', class: 'RCJ'},
         '甲定主材费': {base: 'JDZCF', class: 'RCJ'},
         '暂估材料费(从子目汇总)': {base: 'ZGCLFFZM', class: 'RCJ'},
+        '分包费': {base: 'FBF', class: 'FBF'},
+        '分包定额人工费': {base: 'FBDEJJRGF', class: 'FBF'},
+        '分包定额材料费': {base: 'FBDEJJCLF', class: 'FBF'},
+        '分包定额机械费': {base: 'FBDEJJJXF', class: 'FBF'},
+        '分包主材费': {base: 'FBZCF', class: 'FBF'},
+        '分包设备费': {base: 'FBSBF', class: 'FBF'},
+        '分包人工工日': {base: 'FBRGGR', class: 'FBF'},
         '税前工程造价': {base: 'SQGCZJ', class: 'SQGCZJ',
             cycleCalcRef: [fixedFlag.SUB_ENGINERRING, fixedFlag.OTHER, fixedFlag.CHARGE],   //循环计算相关固定行,由于计算排除了本身,不用判断措施项目
             multiRef: [fixedFlag.SUB_ENGINERRING, fixedFlag.MEASURE, fixedFlag.OTHER, fixedFlag.CHARGE]},//相关固定行
@@ -280,33 +319,6 @@ if(typeof baseFigureTemplate !== 'undefined'){
 if(typeof figureClassTemplate !== 'undefined'){
     figureClassTemplate['ADDED_VALUE_TAX'] = {flag: fixedFlag.ADDED_VALUE_TAX, filter: ['SJ', 'ZZS', 'SQGCZJ']}
 };
-//去除分类分包费
-if(typeof $ !== 'undefined' && $('#cbClassList')){
-    $('#cbClassList').find('li:eq(5)').remove();
-}
-//测试地区============
-/*if(typeof regions !== 'undefined') {
-    regions = [
-        '兰州',
-        '定西',
-        '天水',
-        '平凉',
-        '庆阳',
-        '武威',
-        '金昌',
-        '张掖',
-        '酒泉',
-        '甘矿',
-        '嘉峪关',
-        '临夏',
-        '合作',
-        '武都',
-        '白银',
-    ];
-}*/
-//==============
-
-
 
 //这个文档浏览器库和服务器端共用,所以这个文件中用到的变量都要记得做undefined判断,不然后端读取时会有问题
 //=================================================== 前后端分割线 ======================================================================

+ 189 - 6
web/over_write/js/neimenggu_2017.js

@@ -5,15 +5,16 @@
 (function overwriteFeeTypes() {
     if (typeof cpFeeTypes == 'undefined') return;
     cpFeeTypes = [
-        {type: 'marketLabour', name: '市场价人工费'},
-        {type: 'marketMaterial', name: '市场价材料费'},
-        {type: 'marketMachine', name: '市场价机械费'},
-        {type: 'equipment', name: '设备费'},
+        {type: 'marketLabour', name: '人工费'},
+        {type: 'marketMaterial', name: '材料费'},
+        {type: 'marketMachine', name: '施工机具使用费'},
+        {type: 'labour', name: '定额人工费'},
+        {type: 'material', name: '定额材料费'},
+        {type: 'machine', name: '定额施工机具使用费'},
         {type: 'mainMaterial', name: '主材费'},
+        {type: 'equipment', name: '设备费'},
         {type: 'manage', name: '管理费'},
         {type: 'profit', name: '利润'},
-        {type: 'risk', name: '风险费'},
-        {type: 'safeMeasures', name: '安全文明施工费'},
         {type: 'safeAndEnvironment', name: '安全文明施工与环境保护费'},
         {type: 'tempFacility', name: '临时设施费'},
         {type: 'rain', name: '雨季施工增加费'},
@@ -22,10 +23,192 @@
         {type: 'secondHandling', name: '二次搬运费'},
         {type: 'specialArea', name: '特殊地区施工增加费'},
         {type: 'night', name: '夜间施工增加费'},
+        // {type: 'safeMeasures', name: '安全文明施工费'},
         {type: 'basement', name: '白天在地下室施工增加费'},
         {type: 'winter', name: '冬季施工增加费'},
+        {type: 'risk', name: '风险费'},
         {type: 'force', name: '规费'},
         {type: 'tax', name: '税金'},
         {type: 'common', name: '工程造价'}
     ];
+})();
+
+function overwriteRationCalcBases (taxType){
+    if (typeof rationCalcBases == 'undefined') return;
+    for (let key in rationCalcBases) delete rationCalcBases[key];
+
+    rationCalcBases['人工费'] = function (node, isTender) {
+        return calcTools.rationBaseFee(node, [gljType.LABOUR], priceTypes.ptMarketPrice, isTender);
+    };
+    rationCalcBases['材料费'] = function (node, isTender) {
+        return calcTools.rationBaseFee(node, baseMaterialTypes, priceTypes.ptMarketPrice, isTender);
+    };
+    rationCalcBases['施工机具使用费'] = function (node, isTender) {
+        return calcTools.rationBaseFee(node, baseMachineTypes, priceTypes.ptMarketPrice, isTender);
+    };
+    rationCalcBases['主材费'] = function (node, isTender) {
+        return calcTools.rationBaseFee(node, [gljType.MAIN_MATERIAL], priceTypes.ptMarketPrice, isTender);
+    };
+    rationCalcBases['设备费'] = function (node, isTender) {
+        return calcTools.rationBaseFee(node, [gljType.EQUIPMENT], priceTypes.ptMarketPrice, isTender);
+    };
+    rationCalcBases['定额人工费'] = function (node, isTender) {
+        return calcTools.rationBaseFee(node, [gljType.LABOUR], priceTypes.ptBasePrice, isTender);
+    };
+    rationCalcBases['定额材料费'] = function (node, isTender) {
+        return calcTools.rationBaseFee(node, baseMaterialTypes, priceTypes.ptBasePrice, isTender);
+    };
+    rationCalcBases['定额施工机具使用费'] = function (node, isTender) {
+        return calcTools.rationBaseFee(node, baseMachineTypes, priceTypes.ptBasePrice, isTender);
+    };
+    rationCalcBases['人工工日'] = function (node, isTender) {
+        return calcTools.labourDays(node, isTender);
+    };
+    rationCalcBases['甲供人工费'] = function (node, isTender) {
+        return calcTools.partASupplyFee(node, '甲供人工费', isTender, false);
+    };
+    rationCalcBases['甲供材料费'] = function (node, isTender) {
+        return calcTools.partASupplyFee(node, '甲供材料费', isTender, false);
+    };
+    rationCalcBases['甲供施工机具使用费'] = function (node, isTender) {
+        return calcTools.partASupplyFee(node, '甲供施工机具使用费', isTender, false);
+    };
+    rationCalcBases['甲供主材费'] = function (node, isTender) {
+        return calcTools.partASupplyFee(node, '甲供主材费', isTender, false);
+    };
+    rationCalcBases['甲供设备费'] = function (node, isTender) {
+        return calcTools.partASupplyFee(node, '甲供设备费', isTender, false);
+    };
+    rationCalcBases['甲定人工费'] = function (node, isTender) {
+        return calcTools.partASupplyFee(node, '甲定人工费', isTender, false);
+    };
+    rationCalcBases['甲定材料费'] = function (node, isTender) {
+        return calcTools.partASupplyFee(node, '甲定材料费', isTender, false);
+    };
+    rationCalcBases['甲定施工机具使用费'] = function (node, isTender) {
+        return calcTools.partASupplyFee(node, '甲定施工机具使用费', isTender, false);
+    };
+    rationCalcBases['甲定主材费'] = function (node, isTender) {
+        return calcTools.partASupplyFee(node, '甲定主材费', isTender, false);
+    };
+    rationCalcBases['甲定设备费'] = function (node, isTender) {
+        return calcTools.partASupplyFee(node, '甲定设备费', isTender, false);
+    };
+    rationCalcBases['暂估材料费'] = function (node, isTender) {
+        return calcTools.estimateFee(node, true, isTender);
+    };
+    rationCalcBases['分包人工费'] = function (node, isTender) {
+        if (node.data.isSubcontract)
+            return calcTools.rationBaseFee(node, [gljType.LABOUR], priceTypes.ptMarketPrice, isTender)
+        else
+            return 0;
+    };
+    rationCalcBases['分包材料费'] = function (node, isTender) {
+        if (node.data.isSubcontract)
+            return calcTools.rationBaseFee(node, baseMaterialTypes, priceTypes.ptMarketPrice, isTender)
+        else
+            return 0;
+    };
+    rationCalcBases['分包施工机具使用费'] = function (node, isTender) {
+        if (node.data.isSubcontract)
+            return calcTools.rationBaseFee(node, baseMachineTypes, priceTypes.ptMarketPrice, isTender)
+        else
+            return 0;
+    };
+    rationCalcBases['分包主材费'] = function (node, isTender) {
+        if (node.data.isSubcontract)
+            return calcTools.rationBaseFee(node, [gljType.MAIN_MATERIAL], priceTypes.ptMarketPrice, isTender)
+        else
+            return 0;
+    };
+    rationCalcBases['分包设备费'] = function (node, isTender) {
+        if (node.data.isSubcontract)
+            return calcTools.rationBaseFee(node, [gljType.EQUIPMENT], priceTypes.ptMarketPrice, isTender)
+        else
+            return 0;
+    };
+    rationCalcBases['分包定额人工费'] = function (node, isTender) {
+        if (node.data.isSubcontract)
+            return calcTools.rationBaseFee(node, [gljType.LABOUR], priceTypes.ptBasePrice, isTender)
+        else
+            return 0;
+    };
+    rationCalcBases['分包定额材料费'] = function (node, isTender) {
+        if (node.data.isSubcontract)
+            return calcTools.rationBaseFee(node, baseMaterialTypes, priceTypes.ptBasePrice, isTender)
+        else
+            return 0;
+    };
+    rationCalcBases['分包定额施工机具使用费'] = function (node, isTender) {
+        if (node.data.isSubcontract)
+            return calcTools.rationBaseFee(node, baseMachineTypes, priceTypes.ptBasePrice, isTender)
+        else
+            return 0;
+    };
+    rationCalcBases['分包人工工日'] = function (node, isTender) {
+        if (node.data.isSubcontract)
+            return calcTools.labourDays(node, isTender)
+        else
+            return 0;
+    };
+};
+
+//清单基数相关
+(() => {
+    if(typeof baseFigureMap !== 'undefined'){
+        baseFigureMap = {
+            //与清单直接关联
+            '分部分项工程费': {base: 'FBFXGCF', fixedFlag: fixedFlag.SUB_ENGINERRING, class: 'FBFX'},
+            '分部分项定额人工费': {base: 'FBFXDEJJRGF', fixedFlag: fixedFlag.SUB_ENGINERRING, class: 'FBFX'},
+            '分部分项定额材料费': {base: 'FBFXDEJJCLF', fixedFlag: fixedFlag.SUB_ENGINERRING, class: 'FBFX'},
+            '分部分项定额施工机具使用费': {base: 'FBFXDEJJJXF', fixedFlag: fixedFlag.SUB_ENGINERRING, class: 'FBFX'},
+            '分部分项主材费': {base: 'FBFXZCF', fixedFlag: fixedFlag.SUB_ENGINERRING, class: 'FBFX'},
+            '分部分项设备费': {base: 'FBFXSBF', fixedFlag: fixedFlag.SUB_ENGINERRING, class: 'FBFX'},
+            '分部分项人工工日': {base: 'FBFXRGGR', fixedFlag: fixedFlag.SUB_ENGINERRING, class: 'FBFX'},
+            '措施项目费': {base: 'CSXMF', fixedFlag: fixedFlag.MEASURE, class: 'CSXM'},
+            '组织措施项目费': {base: 'ZZCSXMF', fixedFlag: fixedFlag.CONSTRUCTION_ORGANIZATION, class: 'CSXM'},
+            '组织措施项目定额人工费': {base: 'ZZCSXMDEJJRGF', fixedFlag: fixedFlag.CONSTRUCTION_ORGANIZATION, class: 'CSXM'},
+            '组织措施项目定额材料费': {base: 'ZZCSXMDEJJCLF', fixedFlag: fixedFlag.CONSTRUCTION_ORGANIZATION, class: 'CSXM'},
+            '组织措施项目定额施工机具使用费': {base: 'ZZCSXMDEJJJXF', fixedFlag: fixedFlag.CONSTRUCTION_ORGANIZATION, class: 'CSXM'},
+            '技术措施项目费': {base: 'JSCSXMF', fixedFlag: fixedFlag.CONSTRUCTION_TECH, class: 'CSXM'},
+            '技术措施项目定额人工费': {base: 'JSCSXMDEJJRGF', fixedFlag: fixedFlag.CONSTRUCTION_TECH, class: 'CSXM'},
+            '技术措施项目定额材料费': {base: 'JSCSXMDEJJCLF', fixedFlag: fixedFlag.CONSTRUCTION_TECH, class: 'CSXM'},
+            '技术措施项目定额施工机具使用费': {base: 'JSCSXMDEJJJXF', fixedFlag: fixedFlag.CONSTRUCTION_TECH, class: 'CSXM'},
+            '技术措施项目主材费': {base: 'JSCSXMZCF', fixedFlag: fixedFlag.CONSTRUCTION_TECH, class: 'CSXM'},
+            '技术措施项目设备费': {base: 'JSCSXMSBF', fixedFlag: fixedFlag.CONSTRUCTION_TECH, class: 'CSXM'},
+            '技术措施项目人工工日': {base: 'JSCSXMRGGR', fixedFlag: fixedFlag.CONSTRUCTION_TECH, class: 'CSXM'},
+            '其他项目费': {base: 'QTXMF', fixedFlag: fixedFlag.OTHER, class: 'QTXM'},
+            '规费': {base: 'GF', fixedFlag: fixedFlag.CHARGE, class: 'GF'},
+            '税金': {base: 'SJ', fixedFlag: fixedFlag.TAX, class: 'SJ'},
+            //不与清单直接关联
+            '建筑面积': {base: 'JZMJ', class: 'FBFX'},
+            '甲供定额人工费': {base: 'JGDEJJRGF', class: 'RCJ'},
+            '甲供定额材料费': {base: 'JGDEJJCLF', class: 'RCJ'},
+            '甲供定额施工机具使用费': {base: 'JGDEJJJXF', class: 'RCJ'},
+            '甲供人工费': {base: 'JGRGF', class: 'RCJ'},
+            '甲供材料费': {base: 'JGCLF', class: 'RCJ'},
+            '甲供施工机具使用费': {base: 'JGJXF', class: 'RCJ'},
+            '甲供主材费': {base: 'JGZCF', class: 'RCJ'},
+            '甲供设备费': {base: 'JGSBF', class: 'RCJ'},
+            '甲定定额人工费': {base: 'JDDEJJRGF', class: 'RCJ'},
+            '甲定定额材料费': {base: 'JDDEJJCLF', class: 'RCJ'},
+            '甲定定额施工机具使用费': {base: 'JDDEJJJXF', class: 'RCJ'},
+            '甲定人工费': {base: 'JDRGF', class: 'RCJ'},
+            '甲定材料费': {base: 'JDCLF', class: 'RCJ'},
+            '甲定施工机具使用费': {base: 'JDJXF', class: 'RCJ'},
+            '甲定主材费': {base: 'JDZCF', class: 'RCJ'},
+            '甲定设备费': {base: 'JDSBF', class: 'RCJ'},
+            '暂估材料费(从子目汇总)': {base: 'ZGCLFFZM', class: 'RCJ'},
+            '分包费': {base: 'FBF', class: 'FBF'},
+            '分包定额人工费': {base: 'FBDEJJRGF', class: 'FBF'},
+            '分包定额材料费': {base: 'FBDEJJCLF', class: 'FBF'},
+            '分包定额施工机具使用费': {base: 'FBDEJJJXF', class: 'FBF'},
+            '分包主材费': {base: 'FBZCF', class: 'FBF'},
+            '分包设备费': {base: 'FBSBF', class: 'FBF'},
+            '分包人工工日': {base: 'FBRGGR', class: 'FBF'},
+            '税前工程造价': {base: 'SQGCZJ', class: 'SQGCZJ',
+                cycleCalcRef: [fixedFlag.SUB_ENGINERRING, fixedFlag.OTHER, fixedFlag.CHARGE],   //循环计算相关固定行,由于计算排除了本身,不用判断措施项目
+                multiRef: [fixedFlag.SUB_ENGINERRING, fixedFlag.MEASURE, fixedFlag.OTHER, fixedFlag.CHARGE]},//相关固定行
+        }
+    }
 })();

Разница между файлами не показана из-за своего большого размера
+ 90 - 0
web/users/html/login-sms.html


Разница между файлами не показана из-за своего большого размера
+ 53 - 0
web/users/html/login-ver.html


Разница между файлами не показана из-за своего большого размера
+ 54 - 21
web/users/html/login.html


+ 37 - 26
web/users/html/user-safe.html

@@ -11,9 +11,6 @@
     <link rel="stylesheet" href="/lib/font-awesome/font-awesome.min.css">
     <link rel="shortcut icon" href="/web/building_saas/css/favicon.ico">
     <link rel="icon" type="image/gif" href="/web/building_saas/css/animated_favicon1.gif">
-    <!-- JS. -->
-    <!--<script type="text/javascript" src="/lib/bootstrap/bootstrap-paginator.js"></script>-->
-    <!--<script src="/web/building_saas/js/global.js"></script>-->
 </head>
 
 <body>
@@ -41,48 +38,60 @@
                     </ul>
                 </div>
             </div>
-            <div class="col-lg-10">
+            <div class="col-lg-10 side-content">
                 <div class="col-lg-4">
                     <legend class="my-3">账号安全</legend>
                     <from>
                         <div class="form-group">
                             <label class="form-control-label">登录密码</label>
                             <div class="form-control-static">
-                                <button class="btn btn-secondary btn-sm">修改密码</button>
+                                <a class="btn btn-outline-primary btn-sm" href="https://sso.smartcost.com.cn/chpasswd" target="_blank">修改密码</a>
                             </div>
                         </div>
                         <div class="form-group">
                             <label class="form-control-label">邮箱地址</label>
-                            <p class="form-control-static mb-0"><%= userData.email %><span
-                                    class="badge badge-default">未验证</span></p>
-                            <button class="btn btn-secondary btn-sm">验证邮箱</button>
-                            <button class="btn btn-secondary btn-sm">更换邮箱</button>
-                            <p class="form-control-static mb-0"><%= userData.email %><span
-                                    class="badge badge-success">已验证</span></p>
-                            <button class="btn btn-secondary btn-sm">更换邮箱</button>
+                            <% if (userData.email !== '') { %>
+                            <p class="form-control-static mb-0"><%= userData.email %><% if (userData.isUserActive === 0) { %><span
+                                    class="badge badge-default">未验证</span><% } else { %><span
+                                    class="badge badge-success">已验证</span><% } %></p>
+                            <% if (userData.isUserActive === 0) { %><a class="btn btn-outline-primary btn-sm" target="_blank" href="https://sso.smartcost.com.cn/profile">验证邮箱</a>
+                            <a class="btn btn-outline-primary btn-sm" target="_blank" href="https://sso.smartcost.com.cn/nactchm">更换邮箱</a>
+                            <% } else { %>
+                            <a class="btn btn-outline-primary btn-sm" href="https://sso.smartcost.com.cn/changeMail" target="_blank">更换邮箱</a>
+                            <% } %>
+                            <% } else { %>
+                            <a class="btn btn-outline-primary btn-sm" href="https://sso.smartcost.com.cn/addMail" target="_blank">添加邮箱</a>
+                            <% } %>
                         </div>
                         <div class="form-group">
                             <label class="form-control-label">手机号码</label>
-                            <p class="form-control-static mb-0"><%= userData.mobile %></p>
-                            <button class="btn btn-secondary btn-sm">更换手机</button>
-                            <div class="form-control-static">
-                                <button class="btn btn-secondary btn-sm">添加手机</button>
-                            </div>
+                            <p class="form-control-static mb-0"><%= userData.mobile %> <span class="badge badge-success">已验证</span></p>
+                            <a class="btn btn-outline-primary btn-sm" href="https://sso.smartcost.com.cn/changeMobile" target="_blank">更换手机</a>
                         </div>
                         <div class="form-group">
                             <label class="form-control-label">异常登录提醒</label>
                             <div class="form-control-static">
-                                <label class="custom-control custom-checkbox" data-toggle="modal"
-                                       data-target="#phoneTips">
-                                    <input type="checkbox" class="custom-control-input">
-                                    <span class="custom-control-indicator"></span>
-                                    <span class="custom-control-description">开启</span>
-                                </label>
+                                <div class="custom-control custom-checkbox" >
+                                    <input type="checkbox" class="custom-control-input" id="isLoginValid" <% if (userData.isLoginValid === 1) { %>checked<% } %>>
+                                    <label class="custom-control-label" for="isLoginValid" >开启</label>
+                                </div>
                                 <p class="text-muted">
                                     账号出现异常登录时将给你的手机号码发送通知。
                                 </p>
                             </div>
                         </div>
+                        <div class="form-group">
+                            <label class="form-control-label">关闭账号登录</label>
+                            <div class="form-control-static">
+                                <div class="custom-control custom-checkbox" >
+                                    <input type="checkbox" class="custom-control-input" id="isSmsLogin" <% if (userData.isSmsLogin === 1) { %>checked<% } %>>
+                                    <label class="custom-control-label" for="isSmsLogin" >关闭</label>
+                                </div>
+                                <p class="text-muted">
+                                    关闭账号登录后,只能通过短信验证码方式登录。
+                                </p>
+                            </div>
+                        </div>
                     </from>
                 </div>
                 <div class="mt-5">
@@ -128,8 +137,10 @@
     </div>
 </div>
 </body>
-<!--<script type="text/javascript">-->
-    <!--autoFlashHeight();-->
-<!--</script>-->
+<script src="/web/building_saas/js/global.js"></script>
+<script src="/web/users/js/user.js"></script>
+<script type="text/javascript">
+    autoFlashHeight();
+</script>
 
 </html>

+ 171 - 74
web/users/js/login.js

@@ -50,49 +50,64 @@ $(document).ready(function () {
             if (!valid()) {
                 return false;
             }
-            let account = $("#inputEmail").val();
-            let pw = $("#inputPassword").val();
-
-            // 判断输入的邮箱/手机是否格式正确
-            if(/^1[3456789]\d{9}$/.test(account) || /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(account)) {
-                // 先判断是否是专业版用户,是的话弹出短信验证
-                $.ajax({
-                    url: '/accountIsPro',
-                    type: 'post',
-                    async: true,
-                    data: {"account": account, "pw": pw},
-                    success: function (response) {
-                        if (response.error === 0) {
-                            const ispro = response.result;
-                            if (!ispro) {
-                                login(captchaObj);
-                            } else {
-                                $('#phonepass').modal('show');
-                                $('#proMobile').val(response.data);
-                                $('#pro_mobile').text(response.data.substr(0, 3) + '****' + response.data.substr(7, 11));
-                            }
-                        } else if(response.error === 2) {
-                            $('#check_ssoId').val(response.ssoId);
-                            $('#phone').modal('show');
-                        } else {
-                            let msg = response.msg !== undefined ? response.msg : '未知错误';
-                            showError(msg, $("input"));
-                        }
-                    }
-                });
+            if ($('#changeLogin').attr('data-status') === 'user') {
+                let account = $("#inputEmail").val();
+                if(/^1[3456789]\d{9}$/.test(account) || /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(account)) {
+                    login(captchaObj);
+                } else {
+                    $('#emailHelp').text('您输入的 邮箱/手机 格式不对');
+                }
             } else {
-                $('#emailHelp').text('您输入的 邮箱/手机 格式不对');
+                let account = $("#mobileLogin").val();
+                if(/^1[3456789]\d{9}$/.test(account)) {
+                    login(captchaObj);
+                } else {
+                    $('#phoneHelp').text('您输入的 手机 格式不对');
+                }
             }
-        });
+            // let account = $("#inputEmail").val();
+            // let pw = $("#inputPassword").val();
 
-        $('#loginPro').click(function () {
-            if ($('#smsCode').val() === '') {
-                showValidError('请输入验证码',$('#smsCode'));
-            } else {
-                login(captchaObj);
-            }
+            // 判断输入的邮箱/手机是否格式正确
+            // if(/^1[3456789]\d{9}$/.test(account) || /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(account)) {
+                // // 先判断是否是专业版用户,是的话弹出短信验证
+                // $.ajax({
+                //     url: '/accountIsPro',
+                //     type: 'post',
+                //     async: true,
+                //     data: {"account": account, "pw": pw},
+                //     success: function (response) {
+                //         if (response.error === 0) {
+                //             const ispro = response.result;
+                //             if (!ispro) {
+                //                 login(captchaObj);
+                //             } else {
+                //                 $('#phonepass').modal('show');
+                //                 $('#proMobile').val(response.data);
+                //                 $('#pro_mobile').text(response.data.substr(0, 3) + '****' + response.data.substr(7, 11));
+                //             }
+                //         } else if(response.error === 2) {
+                //             $('#check_ssoId').val(response.ssoId);
+                //             $('#phone').modal('show');
+                //         } else {
+                //             let msg = response.msg !== undefined ? response.msg : '未知错误';
+                //             showError(msg, $("input"));
+                //         }
+                //     }
+            //     // });
+            // } else {
+            //     $('#emailHelp').text('您输入的 邮箱/手机 格式不对');
+            // }
         });
 
+        // $('#loginPro').click(function () {
+        //     if ($('#smsCode').val() === '') {
+        //         showValidError('请输入验证码',$('#smsCode'));
+        //     } else {
+        //         login(captchaObj);
+        //     }
+        // });
+
         $('#check-code').click(function () {
             const mobile = $("#mobile").val();
             const ssoId = $("#check_ssoId").val();
@@ -172,56 +187,114 @@ $(document).ready(function () {
         }
     });
 
+    // 切换登录方式
+    $('#changeLogin').click(function () {
+        if ($(this).attr('data-status') === 'user') {
+            $(this).attr('data-status', 'sms');
+            $('.change-login-p').text('短信登录');
+            $(this).text('账号登录');
+            $('.sms-login-modal').show();
+            $('.user-login-modal').hide();
+            $('.sms-login-modal input').attr('disabled', false);
+            $('.user-login-modal input').attr('disabled', true);
+        } else {
+            $(this).attr('data-status', 'user');
+            $('.change-login-p').text('账号登录');
+            $(this).text('短信登录');
+            $('.sms-login-modal').hide();
+            $('.user-login-modal').show();
+            $('.sms-login-modal input').attr('disabled', true);
+            $('.user-login-modal input').attr('disabled', false);
+        }
+    });
+
+    // 切换到短信登录
+    $('#changeSmsLogin').click(function () {
+        $('#changeLogin').attr('data-status', 'sms');
+        $('.change-login-p').text('短信登录');
+        $('#changeLogin').text('账号登录');
+        $('.sms-login-modal').show();
+        $('.user-login-modal').hide();
+        $('.sms-login-modal input').attr('disabled', false);
+        $('.user-login-modal input').attr('disabled', true);
+        $('#phonepass').modal('hide');
+    });
+
     $("#get-code2").click(function() {
-        const mobile = $("#proMobile").val();
-        if(!validMobile(mobile)){
+        const mobile = $("#mobileLogin").val();
+        if(!validMobile(mobile, 0)){
             return false;
         }
         const btn = $(this);
         if(!btn.hasClass('disabled')){
+            // 判断该手机号是否已注册通行账号
             $.ajax({
-                url: '/sms/code',
+                url: '/sms/check/mobile',
                 type: 'post',
-                data: { mobile: mobile, type: 3},
+                data: {mobile: mobile},
                 error: function() {
-                    showValidError('短信接口出错!',$('#smsCode'));
+                    $('#phoneHelp').text('号码查询接口出错!');
                 },
                 beforeSend: function() {
                 },
                 success: function(response) {
                     if (response.err === 0) {
-                        codeSuccess(btn);
+                        $.ajax({
+                            url: '/sms/code',
+                            type: 'post',
+                            data: { mobile: mobile, type: 3},
+                            error: function() {
+                                $('#phoneHelp').text('短信接口出错!');
+                            },
+                            beforeSend: function() {
+                            },
+                            success: function(response) {
+                                if (response.err === 0) {
+                                    codeSuccess(btn);
+                                } else {
+                                    $('#phoneHelp').text(response.msg);
+                                }
+                            }
+                        });
                     } else {
-                        showValidError(response.msg,$('#smsCode'));
+                        $('#phoneHelp').text(response.msg);
                     }
                 }
-            });
+            })
         }
     });
 });
 
 function login(captchaObj) {
-    let account = $("#inputEmail").val();
-    let pw = $("#inputPassword").val();
+
     let geetest_challenge = $('input[name="geetest_challenge"]').val();
     let geetest_validate = $('input[name="geetest_validate"]').val();
     let geetest_seccode = $('input[name="geetest_seccode"]').val();
-    let code = $("#smsCode").val();
+
+    const postData = {
+        geetest_challenge: geetest_challenge,
+        geetest_validate: geetest_validate,
+        geetest_seccode: geetest_seccode,
+    };
+    if ($('#changeLogin').attr('data-status') === 'user') {
+        let account = $("#inputEmail").val();
+        let pw = $("#inputPassword").val();
+        postData.account = account;
+        postData.pw = pw;
+    } else {
+        let mobile = $('#mobileLogin').val();
+        let code = $("#codeLogin").val();
+        postData.mobile = mobile;
+        postData.code = code;
+    }
 
     $.ajax({
         url: '/login',
         type: 'post',
-        data: {
-            "account": account,
-            "pw": pw,
-            "geetest_challenge": geetest_challenge,
-            "geetest_validate": geetest_validate,
-            "geetest_seccode": geetest_seccode,
-            "code": code,
-        },
+        data: postData,
         success: function (response) {
             if (response.error === 0) {
-                $('#phonepass').modal('hide');
+                // $('#phonepass').modal('hide');
                 const url = response.last_page !== null && response.last_page !== undefined && response.last_page !== '' ?
                     response.last_page : '/pm';
                 if (response.login_ask === 0) {
@@ -238,14 +311,16 @@ function login(captchaObj) {
                     $('#ver').modal('show');
                 }
             } else if(response.error === 2) {
-                $('#phonepass').modal('hide');
+                // $('#phonepass').modal('hide');
                 captchaObj.reset();
                 $('#check_ssoId').val(response.ssoId);
                 $('#phone').modal('show');
             } else if(response.error === 3) {
-                showValidError(response.msg,$('#smsCode'));
+                captchaObj.reset();
+                $('#phonepass').modal('show');
+                $('#mobileLogin').val(response.data);
             } else {
-                $('#phonepass').modal('hide');
+                // $('#phonepass').modal('hide');
                 let msg = response.msg !== undefined ? response.msg : '未知错误';
                 showError(msg, $("input"));
                 captchaObj.reset();
@@ -286,15 +361,23 @@ function codeSuccess(btn) {
  *
  * @return {boolean}
  */
-function validMobile(mobile) {
+function validMobile(mobile, status = 1) {
     let result = true;
     if($.trim(mobile) === ''){
-        showValidError('手机号不能为空!',$('#mobile'));
+        if (status === 1) {
+            showValidError('手机号不能为空!',$('#mobile'));
+        } else {
+            $('#phoneHelp').text('手机号不能为空!');
+        }
         return false;
     }
-    let mobileValid =  /^(((13[0-9]{1})|(15[0-9]{1})|(18[0-9]{1})|(17[0-9]{1})|(14[0-9]{1}))+\d{8})$/;
+    let mobileValid = /^1[3456789]\d{9}$/;
     if(!mobileValid.test(mobile)){
-        showValidError('手机号码格式有误!',$('#mobile'));
+        if (status === 1) {
+            showValidError('手机号码格式有误!',$('#mobile'));
+        } else {
+            $('#phoneHelp').text('手机号码格式有误!');
+        }
         return false;
     }
     return result;
@@ -331,18 +414,32 @@ function cleanValidError(element) {
  */
 function valid() {
     let result = true;
-    let account = $("#inputEmail").val();
-    if (account === undefined || account === '') {
-        showError('用户名不能为空!', $("#inputEmail"));
-        return false;
-    }
+    if ($('#changeLogin').attr('data-status') === 'user') {
+        let account = $("#inputEmail").val();
+        if (account === undefined || account === '') {
+            showError('用户名不能为空!', $("#inputEmail"));
+            return false;
+        }
 
-    let password = $("#inputPassword").val();
-    if (password === undefined || password === '') {
-        showError('密码不能为空!', $("#inputPassword"));
-        return false;
+        let password = $("#inputPassword").val();
+        if (password === undefined || password === '') {
+            showError('密码不能为空!', $("#inputPassword"));
+            return false;
+        }
+    } else {
+        let mobile = $('#mobileLogin').val();
+        if (mobile === undefined || mobile === '') {
+            showError('手机号码不能为空!', $("#mobileLogin"));
+            return false;
+        }
+        let code = $('#codeLogin').val();
+        if (code === undefined || code === '') {
+            showError('验证码不能为空!', $("#codeLogin"));
+            return false;
+        }
     }
 
+
     return result;
 }
 

+ 31 - 1
web/users/js/user.js

@@ -27,6 +27,36 @@ $(document).ready(function() {
             $('#upgrade-title').text('联系销售代表激活');
         }
         CommonHeader.getCategoryList(category);
+    });
+
+    // 关闭和开启账号登录
+    $('#isSmsLogin').click(function () {
+        let status = $(this).is(':checked') ? 1 : 0;
+        $.ajax({
+            type: 'post',
+            url: '/user/change/isSmsLogin',
+            data: {status : status},
+            success: function (response) {
+                if (response.error !== 0) {
+                    alert(response.msg);
+                }
+            }
+        })
+    });
+
+    // 关闭和开启异常登录提醒
+    $('#isLoginValid').click(function () {
+        let status = $(this).is(':checked') ? 1 : 0;
+        $.ajax({
+            type: 'post',
+            url: '/user/change/isLoginValid',
+            data: {status : status},
+            success: function (response) {
+                if (response.error !== 0) {
+                    alert(response.msg);
+                }
+            }
+        })
     })
 });
 
@@ -87,4 +117,4 @@ function cleanError() {
     $("input").removeClass('orm-control-danger');
     $("input").parent().removeClass('has-danger');
     $(".form-control-feedback").remove();
-}
+}