Browse Source

Merge branch 'master' of http://smartcost.f3322.net:3000/SmartCost/ConstructionCost

zhangweicheng 7 years ago
parent
commit
b1765d2c4f
45 changed files with 1953 additions and 423 deletions
  1. 6 3
      config/gulpConfig.js
  2. 23 0
      modules/complementary_glj_lib/models/gljModel.js
  3. 1 1
      modules/complementary_glj_lib/models/schemas.js
  4. 2 1
      modules/fee_rates/facade/fee_rates_facade.js
  5. 1 0
      modules/fee_rates/models/fee_rates.js
  6. 2 1
      modules/glj/models/schemas/unit_price_file.js
  7. 11 0
      modules/glj/models/unit_price_file_model.js
  8. 1 1
      modules/main/models/calc_program_model.js
  9. 1 1
      modules/main/models/labour_coe_model.js
  10. 62 26
      modules/pm/controllers/pm_controller.js
  11. 164 33
      modules/pm/models/project_model.js
  12. 1 4
      modules/pm/routes/pm_route.js
  13. 1 1
      modules/ration_repository/models/ration_item.js
  14. 2 2
      modules/reports/rpt_component/jpc_ex.js
  15. 154 16
      modules/reports/rpt_component/jpc_flow_tab.js
  16. 24 11
      modules/reports/rpt_component/jpc_rte.js
  17. 29 0
      modules/reports/util/rpt_construct_data_util.js
  18. 43 30
      public/calc_util.js
  19. 12 1
      public/web/rpt_value_define.js
  20. 10 0
      public/web/sheet/sheet_data_helper.js
  21. 16 0
      test/unit/reports/rpt_test_decimal.js
  22. 55 15
      test/unit/reports/test_tpl_09_1.js
  23. 1 0
      web/building_saas/complementary_glj_lib/html/tools-gongliaoji.html
  24. 33 2
      web/building_saas/complementary_glj_lib/js/glj.js
  25. 5 4
      web/building_saas/complementary_glj_lib/js/gljComponent.js
  26. 2 7
      web/building_saas/css/main.css
  27. 2 2
      web/building_saas/js/global.js
  28. 69 0
      web/building_saas/main/html/main.html
  29. 13 3
      web/building_saas/main/js/calc/bills_calc.js
  30. 289 1
      web/building_saas/main/js/models/calc_program.js
  31. 13 0
      web/building_saas/main/js/models/main_consts.js
  32. 35 1
      web/building_saas/main/js/models/ration.js
  33. 3 1
      web/building_saas/main/js/models/ration_glj.js
  34. 24 15
      web/building_saas/main/js/views/calc_program_view.js
  35. 4 2
      web/building_saas/main/js/views/character_content_view.js
  36. 2 0
      web/building_saas/main/js/views/main_tree_col.js
  37. 2 0
      web/building_saas/main/js/views/project_info.js
  38. 216 0
      web/building_saas/main/js/views/project_property_decimal_view.js
  39. 62 34
      web/building_saas/main/js/views/project_view.js
  40. 18 5
      web/building_saas/main/js/views/std_bills_lib.js
  41. 5 5
      web/building_saas/main/js/views/std_ration_lib.js
  42. 5 0
      web/building_saas/pm/html/project-management-Recycle.html
  43. 23 2
      web/building_saas/pm/html/project-management.html
  44. 210 120
      web/building_saas/pm/js/pm_gc.js
  45. 296 72
      web/building_saas/pm/js/pm_main.js

+ 6 - 3
config/gulpConfig.js

@@ -87,6 +87,7 @@ module.exports = {
         'web/building_saas/main/js/views/project_info.js',
         'web/building_saas/main/js/views/project_view.js',
         'web/building_saas/main/js/views/options_view.js',
+        'web/building_saas/main/js/views/project_property_decimal_view.js',
         'web/building_saas/main/js/main_ajax.js',
         'web/building_saas/main/js/main.js',
         'web/building_saas/main/js/controllers/project_controller.js',
@@ -97,11 +98,13 @@ module.exports = {
         'web/building_saas/main/js/views/glj_view_contextMenu.js',
         'web/building_saas/main/js/views/calc_program_view.js',
         'web/building_saas/main/js/views/confirm_modal.js',
+        'public/web/rpt_tpl_def.js',
         'public/web/treeDataHelper.js',
         'public/web/ztree_common.js',
-        'public/web/rpt_tpl_def.js',
-        'web/building_saas/main/js/rpt/rpt_main.js',
-        'web/building_saas/main/js/rpt/rpt_cfg_const.js',
+        'web/building_saas/report/js/rpt_main.js',
+        'web/building_saas/report/js/rpt_cfg_const.js',
+        'web/building_saas/report/js/jpc_output_value_define.js',
+        'web/building_saas/report/js/jpc_output.js',
         'web/building_saas/main/js/views/character_content_view.js',
         'web/building_saas/main/js/views/glj_view.js',
         'web/building_saas/main/js/views/sub_view.js',

+ 23 - 0
modules/complementary_glj_lib/models/gljModel.js

@@ -37,8 +37,29 @@ class GljDao {
             else callback(false, data);
         })
     };
+    _exist(data, attr){
+        return data && data[attr] !== 'undefined' && data[attr];
+    }
+
+    sortToNumber(datas){
+        for(let i = 0, len = datas.length; i < len; i++){
+            let data = datas[i]._doc;
+            if(this._exist(data, 'basePrice')){
+                data['basePrice'] = parseFloat(data['basePrice']);
+            }
+            if(this._exist(data, 'component')){
+                for(let j = 0, jLen = data['component'].length; j < jLen; j++){
+                    let comGljObj = data['component'][j]._doc;
+                    if(this._exist(comGljObj, 'consumeAmt')){
+                        comGljObj['consumeAmt'] = parseFloat(comGljObj['consumeAmt']);
+                    }
+                }
+            }
+        }
+    }
     //获得用户的补充工料机和用户当前所在编办的标准工料机
     getGljItems (stdGljLibId, userId, compilationId, callback){
+        let me = this;
         let rst = {stdGljs: [], complementaryGljs: []};
         async.parallel([
             function (cb) {
@@ -47,6 +68,7 @@ class GljDao {
                         cb(err);
                     }
                     else{
+                        me.sortToNumber(stdGljs);
                         rst.stdGljs = stdGljs;
                         cb(null);
                     }
@@ -58,6 +80,7 @@ class GljDao {
                         cb(err);
                     }
                     else{
+                        me.sortToNumber(complementaryGljs);
                         rst.complementaryGljs = complementaryGljs;
                         cb(null);
                     }

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

@@ -9,7 +9,7 @@ let comGjlComponentSchema = new Schema(
     {
         isStd: Boolean, //组成物里的工料机是否是标准的,否则是补充的
         ID: Number,
-        consumeAmt: Number
+        consumeAmt: String
     },
     {_id: false},
     {versionKey: false}

+ 2 - 1
modules/fee_rates/facade/fee_rates_facade.js

@@ -262,7 +262,7 @@ async function changeFeeRateStandard(jdata){
     return doc;
 }
 
-async function newFeeRateFile(updateData){
+async function newFeeRateFile(userId, updateData){
     if(updateData.property !== null){
         let property = updateData.property;
         logger.info("Create new feeRate file for project :"+updateData.ID);
@@ -280,6 +280,7 @@ async function newFeeRateFile(updateData){
             let temA = feeFile.id.split("-");
             let libID = temA[1];
             let doc={
+                userID: userId,
                 rootProjectID:rootProjectID,
                 name:name
             };

+ 1 - 0
modules/fee_rates/models/fee_rates.js

@@ -49,6 +49,7 @@ mongoose.model('fee_rates', feeRatesSchema, 'fee_rates');
 let feeRateFileSchema = new Schema({
     ID: String,
     rootProjectID:Number,//顶层项目ID
+    userID:Number,
     name:String,
     libID: String,
     libName: String,

+ 2 - 1
modules/glj/models/schemas/unit_price_file.js

@@ -6,7 +6,7 @@
  * @version
  */
 import mongoose from "mongoose";
-
+let deleteSchema = require('../../../../public/models/delete_schema');
 let Schema = mongoose.Schema;
 let collectionName = 'unit_price_file';
 let modelSchema = {
@@ -23,6 +23,7 @@ let modelSchema = {
     user_id: Number,
     // 顶层projectId
     root_project_id: Number,
+    deleteInfo: deleteSchema
 };
 let model = mongoose.model(collectionName, new Schema(modelSchema, {versionKey: false, collection: collectionName}));
 export {model as default, collectionName as collectionName};

+ 11 - 0
modules/glj/models/unit_price_file_model.js

@@ -127,6 +127,17 @@ class UnitPriceFileModel extends BaseModel {
     }
 
     /**
+     * 根据建设项目id获取单价文件数据
+     *
+     * @param {Number} root_project_id
+     * @return {Promise}
+     */
+    async getDataByRootProject(root_project_id){
+        let condition = {root_project_id: root_project_id, deleteInfo: null};
+        return  await this.findDataByCondition(condition, null, false);
+    }
+
+    /**
      * 根据用户获取对应被删除的单价文件
      *
      * @param {String} userID

+ 1 - 1
modules/main/models/calc_program_model.js

@@ -14,7 +14,7 @@ let calcItemSchema = new Schema({
     compiledExpr: String,
     statement: String,
     feeRateID: Number,
-    feeRate: Number,
+    feeRate: String,
     labourCoeID: Number
 },{versionKey:false, _id: false});
 

+ 1 - 1
modules/main/models/labour_coe_model.js

@@ -8,7 +8,7 @@ let coeSchema = new Schema({
     ID: Number,
     ParentID: Number,
     name: String,
-    coe: Number,
+    coe: String,
     _id: false
 },{versionKey:false});
 

+ 62 - 26
modules/pm/controllers/pm_controller.js

@@ -58,6 +58,13 @@ module.exports = {
             }
         });
     },
+    updateFiles: async function(req, res){
+        let data = JSON.parse(req.body.data);
+        let updateDatas = data.updateDatas;
+        await ProjectsData.udpateUserFiles(req.session.sessionUser.ssoId, updateDatas, function (err, message, data) {
+            callback(req, res, err, message, data);
+        });
+    },
     copyProjects: function (req, res) {
         let data = JSON.parse(req.body.data);
         ProjectsData.copyUserProjects(req.session.sessionUser.ssoId, data.updateData, function (err, message, data) {
@@ -145,14 +152,14 @@ module.exports = {
             if (isNaN(projectId) && projectId <= 0) {
                 throw {msg: 'id数据有误!', err: 1};
             }
-            // 获取对应建设项目下所有的单位工程id
+            /*// 获取对应建设项目下所有的单位工程id
             let idList = await ProjectsData.getTenderByProjectId(projectId);
             if (idList.length <= 0) {
                 throw {msg: '不存在对应单位工程', err: 0};
-            }
+            }*/
             // 获取对应的单价文件
             let unitPriceFileModel = new UnitPriceFileModel();
-            let unitPriceFileData = await unitPriceFileModel.getDataByTenderId(idList);
+            let unitPriceFileData = await unitPriceFileModel.getDataByRootProject(projectId);
 
             if (unitPriceFileData === null) {
                 throw {msg: '不存在对应单价文件', err: 0};
@@ -194,50 +201,79 @@ module.exports = {
     },
 
     getGCDatas: async function(request, response) {
-        let userID = req.session.sessionUser.ssoId;
+        let userID = request.session.sessionUser.ssoId;
         let rst = [];
         let _projs = Object.create(null), _engs = Object.create(null), prefix = 'ID_';
         try{
             let gc_unitPriceFiles = await ProjectsData.getGCFiles(fileType.unitPriceFile, userID);
             let gc_feeRateFiles = await ProjectsData.getGCFiles(fileType.feeRateFile, userID);
             let gc_tenderFiles = await ProjectsData.getGCFiles(projType.tender, userID);
-                for(let i = 0, len = gc_unitPriceFiles.length; i < len; i++){
-                    let gc_uf = gc_unitPriceFiles[i];
-                    let theProj = _projs[prefix + gc_uf.root_project_id] || null;
-                    if(!theProj){
-                        theProj = _projs[prefix + gc_uf.root_project_id] = await ProjectsData.getProjectsByIds([gc_uf.root_project_id]);
+            for(let i = 0, len = gc_unitPriceFiles.length; i < len; i++){
+                let gc_uf = gc_unitPriceFiles[i];
+                let theProj = _projs[prefix + gc_uf.root_project_id] || null;
+                if(!theProj){
+                    let tempProj = await ProjectsData.getProjectsByIds([gc_uf.root_project_id]);
+                    if(tempProj.length > 0 && tempProj[0].projType !== projType.folder){
+                        theProj = _projs[prefix + gc_uf.root_project_id] = tempProj[0]._doc;
                         buildProj(theProj);
                     }
+                }
+                if(theProj){
                     theProj.unitPriceFiles.push(gc_uf);
                 }
-                for(let i = 0, len = gc_feeRateFiles.length; i < len; i++){
-                    let gc_ff = gc_feeRateFiles[i];
-                    let theProj = _projs[prefix + gc_ff.rootProjectID] || null;
-                    if(!theProj){
-                        theProj = _projs[prefix + gc_ff.rootProjectID] = await ProjectsData.getProjectsByIds([gc_ff.rootProjectID]);
+            }
+            for(let i = 0, len = gc_feeRateFiles.length; i < len; i++){
+                let gc_ff = gc_feeRateFiles[i];
+                let theProj = _projs[prefix + gc_ff.rootProjectID] || null;
+                if(!theProj){
+                    let tempProj = await ProjectsData.getProjectsByIds([gc_ff.rootProjectID]);
+                    if(tempProj.length > 0 && tempProj[0].projType !== projType.folder){
+                        theProj = _projs[prefix + gc_ff.rootProjectID] = tempProj[0]._doc;
                         buildProj(theProj);
                     }
+                }
+                if(theProj) {
                     theProj.feeRateFiles.push(gc_ff);
                 }
+            }
             if(gc_tenderFiles.length > 0){
                 for(let i = 0, len = gc_tenderFiles.length; i < len; i++){
                     let gc_t = gc_tenderFiles[i];
                     let theEng = _engs[prefix + gc_t.ParentID] || null;
                     if(!theEng){
-                        theEng = _engs[prefix + gc_t.ParentID] = await ProjectsData.getProjectsByIds([gc_t.ParentID]);
-                        theEng.children = [];
+                        let tempEngs = await ProjectsData.getProjectsByIds([gc_t.ParentID]);
+                        if(tempEngs.length > 0 && tempEngs[0].projType === projType.engineering){
+                            theEng = _engs[prefix + gc_t.ParentID] = tempEngs[0]._doc;
+                            theEng.children = [];
+                        }
                     }
-                    theEng.children.push(gc_t);
-                    let theProj = _projs[prefix + theEng.ParentID] || null;
-                    if(!theProj){
-                        theProj = _projs[prefix + theEng.ParentID] = await ProjectsData.getProjectsByIds([theEng.ParentID]);
-                        buildProj(theProj);
+                    if(theEng) {
+                        theEng.children.push(gc_t);
+                        let theProj = _projs[prefix + theEng.ParentID] || null;
+                        if(!theProj){
+                            let tempProj = await ProjectsData.getProjectsByIds([theEng.ParentID]);
+                            if(tempProj.length > 0 && tempProj[0].projType === projType.project){
+                                theProj = _projs[prefix + theEng.ParentID] = tempProj[0]._doc;
+                                buildProj(theProj);
+                            }
+                        }
+                        if(theProj) {
+                            let isExist = false;
+                            for(let j = 0, jLen = theProj.children.length; j < jLen; j++){
+                                if(theProj.children[j].ID === theEng.ID){
+                                    isExist = true;
+                                    break;
+                                }
+                            }
+                            if(!isExist){
+                                theProj.children.push(theEng);
+                            }
+                        }
                     }
-                    theProj.children.push(theEng);
                 }
             }
-            for(let i of _projs){
-                rst.push(i);
+            for(let i in _projs){
+                rst.push(_projs[i]);
             }
             function buildProj(proj){
                 proj.children = [];
@@ -253,10 +289,10 @@ module.exports = {
 
     recGC: function(request, response){
         let userID = request.session.sessionUser.ssoId;
-        let nodes = JSON.parse(request.body.nodes);
+        let data = JSON.parse(request.body.data);
+        let nodes = data.nodes;
         ProjectsData.recGC(userID, nodes, function (err, msg, data) {
             callback(request, response, err, msg, data);
         });
     }
-
 };

+ 164 - 33
modules/pm/models/project_model.js

@@ -1,10 +1,10 @@
 /**
  * Created by Mai on 2017/1/18.
  */
-import UnitPriceFileModel from "../../glj/models/unit_price_file_model";
+import mongoose from 'mongoose';
 import async_c from 'async';
+import UnitPriceFileModel from "../../glj/models/unit_price_file_model";
 import UnitPriceFiles from '../../glj/models/schemas/unit_price_file';
-import mongoose from 'mongoose';
 let FeeRateFiles = mongoose.model('fee_rate_file');
 let counter = require("../../../public/counter/counter.js");
 
@@ -22,11 +22,17 @@ let projectType = {
     project: 'Project',
     engineering: 'Engineering',
 };
-//回收站恢复级别
 let fileType = {
     unitPriceFile: 'UnitPriceFile',
     feeRateFile: 'FeeRateFile'
 };
+//默认的小数位数,用于定义用户可编辑的字段(入库),用户不可编辑的字段在前端defaultDecima._def中定义即可
+const defaultDecimal = {
+    bills: {unitPrice: 2, totalPrice: 2},
+    ration: {quantity: 3, unitPrice: 2, totalPrice: 2},
+    glj: {quantity: 3, unitPrice: 2},
+    feeRate: 2,
+};
 
 let ProjectsDAO = function(){};
 
@@ -93,7 +99,10 @@ ProjectsDAO.prototype.updateUserProjects = async function(userId, datas, callbac
                         callback(1, '新增单价文件失败.', null);
                         return;
                     }
-                    data.updateData.property.unitPriceFile.id = addResult.id + '';
+                    data.updateData.property.unitPriceFile.id = addResult.id;
+                }
+                if(data.updateData.projType === projectType.tender){
+                    data.updateData.property.decimal = defaultDecimal;
                 }
                 newProject = new Projects(data.updateData);
                 // 查找同级是否存在同名数据
@@ -103,7 +112,7 @@ ProjectsDAO.prototype.updateUserProjects = async function(userId, datas, callbac
                     return;
                 }
                 if(data.updateData.projType==='Tender'){
-                    let  feeRateFileID = await feeRateFacade.newFeeRateFile(data.updateData);
+                    let  feeRateFileID = await feeRateFacade.newFeeRateFile(userId, data.updateData);
                     newProject.property.feeFile = feeRateFileID?feeRateFileID:-1;
 
                     // 新建人工系数文件 CSL, 2017.10.13
@@ -126,7 +135,54 @@ ProjectsDAO.prototype.updateUserProjects = async function(userId, datas, callbac
                 deleteInfo['deleteDateTime'] = new Date();
                 deleteInfo['deleteBy'] = userId;
                 data.updateData['deleteInfo'] = deleteInfo;
-                Projects.update({userID: userId, ID: data.updateData.ID}, data.updateData, updateAll);
+                //Projects.update({userID: userId, ID: data.updateData.ID}, data.updateData, updateAll);
+                //update
+                try{
+                    if(data.updateData.projType === projectType.project){
+                        let engineerings = await Projects.find({userID: userId, ParentID: data.updateData.ID});
+                        let isExist = false;
+                        if(engineerings.length > 0){
+                            for(let j = 0, jLen = engineerings.length; j < jLen; j++){
+                                let e_tenders = await Projects.find({userID: userId, ParentID: engineerings[j].ID});
+                                if(e_tenders.length > 0){
+                                    isExist = true;
+                                    break;
+                                }
+                            }
+                        }
+                        if(isExist){//fake
+                            await UnitPriceFiles.update({user_id: userId, root_project_id: data.updateData.ID}, {$set: {deleteInfo: deleteInfo}}, {multi: true});
+                            await FeeRateFiles.update({userID: userId, rootProjectID: data.updateData.ID}, {$set: {deleteInfo: deleteInfo}}, {multi: true});
+                            await Projects.update({userID: userId, ID: data.updateData.ID}, data.updateData, updateAll);
+                        }
+                        else {//true
+                            await UnitPriceFiles.remove({user_id: userId, root_project_id: data.updateData.ID});
+                            await FeeRateFiles.remove({userID: userId, rootProjectID: data.updateData.ID});
+                            //await Projects.update({userID: userId, NextSiblingID: data.updateData.ID, deleteInfo: null}, {$set: {NextSiblingID: data.NextSiblingID}});
+                            await Projects.remove({userID: userId, ID: data.updateData.ID}, updateAll);
+                        }
+                    }
+                    else if(data.updateData.projType === projectType.engineering){
+                        let tenders = await Projects.find({userID: userId, ParentID: data.updateData.ID});
+                        if(tenders.length > 0){//fake
+                            await Projects.update({userID: userId, ID: data.updateData.ID}, data.updateData, updateAll);
+                        }
+                        else {//true
+                            //await Projects.update({userID: userId, NextSiblingID: data.updateData.ID, deleteInfo: null}, {$set: {NextSiblingID: data.NextSiblingID}});
+                            await Projects.remove({userID: userId, ID: data.updateData.ID}, updateAll);
+                        }
+                    }
+                    else if(data.updateData.projType === projectType.tender){//fake
+                        await Projects.update({userID: userId, ID: data.updateData.ID}, data.updateData, updateAll);
+                    }
+                    else if(data.updateData.projType === projectType.folder){//true
+                        await Projects.remove({userID: userId, ID: data.updateData.ID}, updateAll);
+                    }
+                    else throw '未知文件类型,删除失败!';
+                }
+                catch (error){
+                    callback(1, error, null);
+                }
             } else {
                 hasError = true;
                 callback(1, '提交数据错误.', null);
@@ -134,6 +190,42 @@ ProjectsDAO.prototype.updateUserProjects = async function(userId, datas, callbac
         }
     }
 };
+
+ProjectsDAO.prototype.udpateUserFiles = async function (userId, datas, callback){
+    let updateType = {update: 'update', delete: 'delete'};
+    let deleteInfo = Object.create(null);
+    deleteInfo.deleted = true;
+    deleteInfo.deleteBy = userId;
+    deleteInfo.deleteDateTime = new Date();
+    try{
+        for(let i = 0, len = datas.length; i < len; i++){
+            let data = datas[i];
+            if(data.updateType === updateType.update && data.fileType === fileType.unitPriceFile){
+                await UnitPriceFiles.update({user_id: userId, id: parseInt(data.updateData.id)}, data.updateData);
+                await Projects.update({userID: userId, 'property.unitPriceFile.id': data.updateData.id}, {$set: {'property.unitPriceFile.name': data.updateData.name}});
+            }
+            else if(data.updateType === updateType.update && data.fileType === fileType.feeRateFile){
+                await FeeRateFiles.update({userID: userId, ID: data.updateData.ID}, data.updateData);
+                await Projects.update({userID: userId, 'property.feeFile.id': data.updateData.ID}, {$set: {'property.feeFile.name': data.updateData.name}});
+            }
+            else if(data.updateType === updateType.delete && data.fileType === fileType.unitPriceFile){
+                data.updateData.deleteInfo = deleteInfo;
+                await UnitPriceFiles.update({user_id: userId, id: parseInt(data.updateData.id)}, data.updateData);
+                await Projects.update({userID: userId, 'property.feeFile.id': data.updateData.id}, {$set: {'property.feeFile.name': data.updateData.name}});
+            }
+            else if(data.updateType === updateType.delete && data.fileType === fileType.feeRateFile){
+                data.updateData.deleteInfo = deleteInfo;
+                await FeeRateFiles.update({userID: userId, ID: data.updateData.ID}, data.updateData);
+            }
+            else throw '未知文件类型,删除失败'
+        }
+        callback(false, '删除成功', null);
+    }
+    catch(error){
+        callback(true, '删除失败', null);
+    }
+};
+
 ProjectsDAO.prototype.copyUserProjects = function (userId, datas, callback) {
     let data, project, updateLength = 0, hasError = false, deleteInfo = null, tempType = 1, i;
     let updateAll = function (err) {
@@ -250,41 +342,45 @@ ProjectsDAO.prototype.getGCFiles = async function (fileType, userID){
     }
     else {
         let isExist = false;
-        for(let type of projectType){
-            if(type === fileType) isExist = true;
-            break;
+        for(let type in projectType){
+            if(projectType[type] === fileType) {
+                isExist = true;
+                break;
+            }
         }
         if(!isExist) throw '不存在此项目类型!';
-        rst = await Projects.find({projType: fileType, 'deleteInfo.deleted': true});
+        rst = await Projects.find({userID: userID, projType: fileType, 'deleteInfo.deleted': true});
     }
     return rst;
 };
 
-ProjectsDAO.prototype.getFirstNodeID = async function (userID, projType) {
-    if(projType !== projectType.project) throw '只能为建设项目层';
-    let nodes = await Projects.find({userID: userID, projType: projType, deleteInfo: null});
+ProjectsDAO.prototype.getFirstNodeID = async function (userID, pid) {
+    let nodes = await Projects.find({userID: userID, ParentID: pid, deleteInfo: null});
     if(nodes.length === 0){
         return -1;
     }
     else {
+        let prefix = 'ID_';
         let chain = Object.create(null);
         for(let i = 0, len = nodes.length; i < len; i++){
+            let nodeDoc = nodes[i]._doc;
             let node = Object.create(null);
-            node.ID = nodes[i].ID;
-            node.NextSiblingID = nodes[i].NextSiblingID;
-            chain[node.ID] = node;
+            node.ID = nodeDoc.ID;
+            node.NextSiblingID = nodeDoc.NextSiblingID;
+            chain[prefix + node.ID] = node;
         }
         for(let i =0, len = nodes.length; i < len; i++){
-            let next = nodes[i].NextSiblingID > 0 ? chain[nodes[i].NextSiblingID] : null;
-            chain[nodes[i].ID].next = next;
+            let nodeDoc = nodes[i]._doc;
+            let next = nodeDoc.NextSiblingID > 0 ? chain[prefix + nodeDoc.NextSiblingID] : null;
+            chain[prefix + nodeDoc.ID].next = next;
             if(next){
-                next.pre = chain[nodes[i].ID]
+                next.pre = chain[prefix + nodeDoc.ID]
             }
         }
-        for(let node of chain){
-            let pre = node.pre || null;
+        for(let node in chain){
+            let pre = chain[node].pre || null;
             if(!pre){
-                return node.ID;
+                return chain[node].ID;
             }
         }
     }
@@ -292,34 +388,69 @@ ProjectsDAO.prototype.getFirstNodeID = async function (userID, projType) {
 
 ProjectsDAO.prototype.recGC = async function(userID, datas, callback){
     let functions = [];
+    let updateDatas = [];
     for(let i = 0, len = datas.length; i < len; i++){
-        if(datas[i].updateType === projectType.project && datas[i].updateData.NextSiblingID === undefined){//则为待查询NextSiblingID,前端无法查询
-            let firstNodeID = await this.getFirstNodeID(userID, projectType.project);
-            datas[i].updateData.NextSiblingID = firstNodeID;
+        if(datas[i].findData.ParentID !== undefined && datas[i].findData.NextSiblingID === -1 && !datas[i].findData.deleteInfo){//维护项目管理树
+            let findNode = await Projects.find(datas[i].findData);
+            if(findNode.length > 0){
+                datas[i].findData = Object.create(null);
+                datas[i].findData.ID = findNode[0].ID;
+                updateDatas.push(datas[i]);
+            }
         }
+        else {
+            if(datas[i].updateType === projectType.project && datas[i].updateData.NextSiblingID === undefined){//则为待查询NextSiblingID,前端无法查询
+                let projData = await Projects.find({userID: userID, ID: datas[i].findData.ID});//建设项目原本可能属于某文件夹、文件夹的存在需要判断
+                let projPid = projData[0].ParentID;
+                if(projPid !== -1){
+                    let projFolder = await Projects.find({userID: userID, ID: projPid});
+                    if(projFolder.length === 0){//文件夹已不存在
+                        projPid = -1;
+                        datas[i].updateData.ParentID = -1;
+                    }
+                }
+                let firstNodeID = await this.getFirstNodeID(userID, projPid);
+                datas[i].updateData.NextSiblingID = firstNodeID;
+            }
+            updateDatas.push(datas[i])
+        }
+    }
+    for(let i = 0, len = updateDatas.length; i < len; i ++){
         functions.push((function(data){
                 return function (cb) {
                     if(data.updateType === fileType.unitPriceFile){
-                        UnitPriceFiles.update(data.findData, data.updateData, function (err) {
+                        UnitPriceFiles.update({id: parseInt(data.findData.id)}, data.updateData, function (err) {
                             if(err) cb(err);
-                            else cb(false);
+                            else {
+                                Projects.update({userID: userID, 'property.unitPriceFile.id': data.findData.id}, {$set: {'property.unitPriceFile.name': data.updateData.name}}, function (err) {
+                                    if(err) cb(err);
+                                    else cb(false);
+                                });
+                            }
                         })
                     }
                     else if(data.updateType === fileType.feeRateFile){
                         FeeRateFiles.update(data.findData, data.updateData, function (err) {
                             if(err) cb(err);
-                            else cb(false);
+                            else {
+                                Projects.update({userID: userID, 'property.feeFile.id': data.findData.ID}, {$set: {'property.feeFile.name': data.updateData.name}}, function (err) {
+                                    if(err) cb(err);
+                                    else cb(false);
+                                });
+                            }
                         });
                     }
                     else{
-                        Projects.update(data.findData, data.updateData, function (err) {
-                            if(err)cb(err);
-                            else cb(false);
-                        });
+                        if(data){
+                            Projects.update(data.findData, data.updateData, function (err) {
+                                if(err)cb(err);
+                                else cb(false);
+                            });
+                        }
                     }
                 }
             }
-        )(datas[i]));
+        )(updateDatas[i]));
     }
     async_c.parallel(functions, function (err, results) {
         if(err) callback(err, 'fail', null);

+ 1 - 4
modules/pm/routes/pm_route.js

@@ -11,10 +11,6 @@ module.exports = function (app) {
 
     app.get('/pm', baseController.init, pmController.index);
 
-    app.get('/pm/gc',baseController.init, function (req, res) {
-        res.render('building_saas/pm/html/project-management-Recycle.html');
-    });
-
     let pmRouter = express.Router();
 
     pmRouter.use(function (req, res, next) {
@@ -47,6 +43,7 @@ module.exports = function (app) {
     pmRouter.post('/getNewProjectID', pmController.getNewProjectID);
     pmRouter.post('/getUnitFile', pmController.getUnitFileList);
     pmRouter.post('/getFeeRateFile', pmController.getFeeRateFileList);
+    pmRouter.post('/updateFiles', pmController.updateFiles);
     //GC
     pmRouter.post('/getGCDatas', pmController.getGCDatas);
     pmRouter.post('/recGC', pmController.recGC);

+ 1 - 1
modules/ration_repository/models/ration_item.js

@@ -47,7 +47,7 @@ var counter = require('../../../public/counter/counter');
 var rationItemDAO = function(){};
 
 rationItemDAO.prototype.getRationItemsBySection = function(sectionId,callback){
-    rationItemModel.find({"sectionId": sectionId, "$or": [{"isDeleted": null}, {"isDeleted": false} ]},function(err,data){
+    rationItemModel.find({"sectionId": sectionId, "$or": [{"isDeleted": null}, {"isDeleted": false} ]}, null, {sort: {code: 1}}, function(err,data){
         if(err) callback(true, "Fail to get items", "")
         else callback(false,"Get items successfully", data);
     })

+ 2 - 2
modules/reports/rpt_component/jpc_ex.js

@@ -146,9 +146,9 @@ JpcExSrv.prototype.createNew = function(){
             } else {
                 //
             }
-            me.totalPages = me.flowTab.preSetupPages(rptTpl, dataObj, defProperties, dftPagingOption);
+            me.totalPages = me.flowTab.preSetupPages(rptTpl, dataObj, defProperties, dftPagingOption, me);
             if (me.flowTabEx) {
-                me.exTotalPages = me.flowTabEx.preSetupPages(rptTpl, dataObj, defProperties, dftPagingOption);
+                me.exTotalPages = me.flowTabEx.preSetupPages(rptTpl, dataObj, defProperties, dftPagingOption, me);
                 //console.log('ad-hoc flow pages: ' + me.exTotalPages);
             }
             me.totalPages += me.exTotalPages;

+ 154 - 16
modules/reports/rpt_component/jpc_flow_tab.js

@@ -11,14 +11,60 @@ let JpcCommonOutputHelper = require('./helper/jpc_helper_common_output');
 let JpcAreaHelper = require('./helper/jpc_helper_area');
 
 let JpcFlowTabSrv = function(){};
+//let grpPageInfo = {"segGrpRecStartIdx": 0, "insertedGrpRecAmt": 0, "preAddPageGrpInfo": null};
 JpcFlowTabSrv.prototype.createNew = function(){
-    function private_addPageValue(ValuedIdxLst, sortedSequence, preRec, nextRec,page_seg_map, segIdx, pageIdx) {
-        let vIdx = [];
-        for (let vi = 0; vi < nextRec; vi++) {
-            if (sortedSequence.length > preRec + vi) {
-                vIdx.push(sortedSequence[preRec + vi]);
+    function private_addPageValue(ValuedIdxLst, sortedSequence, grpSequenceInfo, startRecIdx, maxRecPerPage,page_seg_map, segIdx, pageIdx, grpPageInfo) {
+        let vIdx = [], preAmt = 0, insertedGrpAmt = 0, grp_lines = 0;
+        if (grpPageInfo) {
+            //grpPageInfo[JV.PROP_INSERTED_GRP_REC] = 0;
+            for (let grpLineIdx of grpPageInfo[JV.PROP_PRE_ADD_GRP_REC_INFO]) {
+                vIdx.push([JV.DISPLAY_VAL_TYPE_GROUP, grpPageInfo[JV.PROP_SEG_GRP_IDX], grpLineIdx]);
+            }
+            preAmt = grpPageInfo[JV.PROP_PRE_ADD_GRP_REC_INFO].length;
+            grpPageInfo[JV.PROP_PRE_ADD_GRP_REC_INFO] = [];
+            grp_lines = grpPageInfo[JV.PROP_GRP_LINES];
+        }
+        for (let vi = 0; (vi + insertedGrpAmt * grp_lines) < maxRecPerPage - preAmt; vi++) {
+            if (grpPageInfo) {
+                if ((startRecIdx + vi) === grpSequenceInfo[grpPageInfo[JV.PROP_SEG_GRP_IDX]]) {
+                    //表示这里要插入grouping信息啦!
+                    //1. 首先push正常的记录
+                    vIdx.push([JV.DISPLAY_VAL_TYPE_NORMAL, sortedSequence[startRecIdx + vi]]);
+                    //2. 然后就要push grouping记录了
+                    let hasFullGrp = true;
+                    for (let i = 0; i < grp_lines; i++) {
+                        if ( (vi + i + 1) >= (maxRecPerPage - preAmt)) {
+                            for (let j = i; j < grp_lines; j++) {
+                                grpPageInfo[JV.PROP_PRE_ADD_GRP_REC_INFO].push(j);
+                            }
+                            //准备要跳出去了
+                            hasFullGrp = false;
+                            break;
+                        } else {
+                            vIdx.push([JV.DISPLAY_VAL_TYPE_GROUP, grpPageInfo[JV.PROP_SEG_GRP_IDX], i]);
+                        }
+                    }
+                    //3. 进位下一个group信息所在位置
+                    if (hasFullGrp) {
+                        grpPageInfo[JV.PROP_INSERTED_GRP_REC]++;
+                        insertedGrpAmt++;
+                        grpPageInfo[JV.PROP_SEG_GRP_IDX]++;
+                    } else {
+                        break;
+                    }
+                } else {
+                    if (sortedSequence.length > startRecIdx + vi) {
+                        vIdx.push([JV.DISPLAY_VAL_TYPE_NORMAL, sortedSequence[startRecIdx + vi]]);
+                    } else {
+                        vIdx.push([JV.DISPLAY_VAL_TYPE_NORMAL, JV.BLANK_VALUE_INDEX]);
+                    }
+                }
             } else {
-                vIdx.push(JV.BLANK_VALUE_INDEX);
+                if (sortedSequence.length > startRecIdx + vi) {
+                    vIdx.push([JV.DISPLAY_VAL_TYPE_NORMAL, sortedSequence[startRecIdx + vi]]);
+                } else {
+                    vIdx.push([JV.DISPLAY_VAL_TYPE_NORMAL, JV.BLANK_VALUE_INDEX]);
+                }
             }
         }
         page_seg_map.push([pageIdx, segIdx]);
@@ -36,7 +82,13 @@ JpcFlowTabSrv.prototype.createNew = function(){
         me.seg_sum_fields_idx = [];
         me.seg_sum_tab_fields = [];
         me.page_sum_fields_idx = [];
-        me.group_fields_idx = [];
+
+        me.group_fields = [];
+        me.group_sum_fields = [];
+        me.group_sum_values = null;
+        me.group_node_info = null; //记录在哪个seg及到哪条记录后有group sum信息
+        me.group_lines_amt = 0;    //每group一次占用多少行,计算page信息会用到
+
         me.pageStatusLst = [];
         me.groupSumValLst = [];
         me.segSumValLst = [];
@@ -48,7 +100,8 @@ JpcFlowTabSrv.prototype.createNew = function(){
         let FLOW_NODE_STR = me.isEx?JV.NODE_FLOW_INFO_EX:JV.NODE_FLOW_INFO;
         JpcFieldHelper.findAndPutDataFieldIdx(rptTpl, rptTpl[FLOW_NODE_STR][JV.NODE_FLOW_SEG_SUM][JV.PROP_SUM_FIELDS], me.seg_sum_tab_fields, me.seg_sum_fields_idx, me.isEx);
         JpcFieldHelper.findAndPutDataFieldIdx(rptTpl, rptTpl[FLOW_NODE_STR][JV.NODE_FLOW_PAGE_SUM][JV.PROP_SUM_FIELDS], null, me.page_sum_fields_idx, me.isEx);
-        JpcFieldHelper.findAndPutDataFieldIdx(rptTpl, rptTpl[FLOW_NODE_STR][JV.NODE_FLOW_GROUP][JV.PROP_GROUP_FIELDS], null, me.group_fields_idx, me.isEx);
+        JpcFieldHelper.findAndPutDataFieldIdx(rptTpl, rptTpl[FLOW_NODE_STR][JV.NODE_FLOW_GROUP][JV.PROP_GROUP_FIELDS], me.group_fields, null, me.isEx);
+        JpcFieldHelper.findAndPutDataFieldIdx(rptTpl, rptTpl[FLOW_NODE_STR][JV.NODE_FLOW_GROUP][JV.PROP_SUM_FIELDS], me.group_sum_fields, null, me.isEx);
         for (let si = 0; si < dataSeq.length; si++) {
             me.segments.push(dataSeq[si].slice(0));
         }
@@ -74,8 +127,81 @@ JpcFlowTabSrv.prototype.createNew = function(){
 
         }
     };
-    JpcFlowTabResult.preSetupPages = function (rptTpl, dataOjb, defProperties, option) {
+    JpcFlowTabResult.sumUpGrp = function ($CURRENT_RPT, dataObj, segIdx, preGrpIdx, nexGrpIdx) {
+        let me = this, segDataIdx = me.segments[segIdx];
+        for (let j = 0; j < me.group_sum_fields.length; j++) {
+            let sum_field = JE.F(me.group_sum_fields[j][JV.PROP_FIELD_ID], $CURRENT_RPT);
+            if (sum_field) {
+                let data_field = null;
+                if (sum_field[JV.PROP_AD_HOC_DATA]) {
+                    data_field = sum_field[JV.PROP_AD_HOC_DATA]
+                } else {
+                    data_field = dataObj[sum_field.DataNodeName][sum_field.DataSeq];
+                }
+                let sumV = 0;
+                for (let si = preGrpIdx; si <= nexGrpIdx; si++) {
+                    sumV += JpcFieldHelper.getValue(data_field, segDataIdx[si]);
+                }
+                me.group_sum_values[segIdx][j].push(sumV);
+            }
+        }
+        me.group_node_info[segIdx].push(nexGrpIdx);
+    };
+    JpcFlowTabResult.setupGroupingData = function (rptTpl, dataObj, $CURRENT_RPT) {
+        let me = this;
+        if (me.group_fields.length > 0 && me.group_sum_fields.length > 0) {
+            me.group_sum_values = [];
+            me.group_node_info = [];
+            let CURRENT_FLOW_INFO = (me.isEx)?JV.NODE_FLOW_INFO_EX:JV.NODE_FLOW_INFO;
+            me.group_lines_amt = rptTpl[CURRENT_FLOW_INFO][JV.NODE_FLOW_GROUP][JV.PROP_GROUP_LINES].length;
+            //
+            let preGrpIdx = 0, nexGrpIdx = 0;
+            for (let segIdx = 0; segIdx < me.segments.length; segIdx++) {
+                let segDataIdx = me.segments[segIdx];
+                me.group_sum_values.push([]);
+                me.group_node_info.push([]);
+                for (let j = 0; j < me.group_sum_fields.length; j++) {
+                    me.group_sum_values[segIdx].push([]);
+                    //me.group_node_info[segIdx].push([]);
+                }
+                for (let di = 1; di < segDataIdx.length; di++) {
+                    let hasDiff = false;
+                    for (let i = 0; i < me.group_fields.length; i++) {
+                        let grp_field = JE.F(me.group_fields[i][JV.PROP_FIELD_ID], $CURRENT_RPT);
+                        if (grp_field) {
+                            let data_field = null;
+                            if (grp_field[JV.PROP_AD_HOC_DATA]) {
+                                data_field = grp_field[JV.PROP_AD_HOC_DATA]
+                            } else {
+                                data_field = dataObj[grp_field.DataNodeName][grp_field.DataSeq];
+                            }
+                            let v1 = JpcFieldHelper.getValue(data_field, segDataIdx[di]), v2 = JpcFieldHelper.getValue(data_field, segDataIdx[di - 1]);
+                            if (v1 !== v2) {
+                                hasDiff = true;
+                                break;
+                            }
+                        }
+                    }
+                    if (hasDiff) {
+                        //then sum up the fields
+                        me.sumUpGrp($CURRENT_RPT, dataObj, segIdx, preGrpIdx, nexGrpIdx);
+                        nexGrpIdx = di;
+                        preGrpIdx = di;
+                        if (di === segDataIdx.length - 1) {
+                            me.sumUpGrp($CURRENT_RPT, dataObj, segIdx, preGrpIdx, nexGrpIdx);
+                        }
+                    } else if (di === segDataIdx.length - 1) {
+                        me.sumUpGrp($CURRENT_RPT, dataObj, segIdx, preGrpIdx, di);
+                    } else {
+                        nexGrpIdx = di;
+                    }
+                }
+            }
+        }
+    };
+    JpcFlowTabResult.preSetupPages = function (rptTpl, dataObj, defProperties, option, $CURRENT_RPT) {
         let me = this, rst = 1, counterRowRec = 0, maxRowRec = 1, pageIdx = 0;
+        me.setupGroupingData(rptTpl, dataObj, $CURRENT_RPT);
         me.paging_option = option||JV.PAGING_OPTION_NORMAL;
         let CURRENT_FLOW_INFO = (me.isEx)?JV.NODE_FLOW_INFO_EX:JV.NODE_FLOW_INFO;
         JpcFieldHelper.findAndPutDataFieldIdx(rptTpl, rptTpl[CURRENT_FLOW_INFO][JV.NODE_FLOW_CONTENT][JV.PROP_FLOW_FIELDS], null, me.disp_fields_idx, me.isEx);
@@ -91,7 +217,8 @@ JpcFlowTabSrv.prototype.createNew = function(){
                 }
                 me.pageStatusLst.push(pageStatus.slice(0));
                 pageIdx++;
-                private_addPageValue(me.dispValueIdxLst, me.segments[segIdx], 0, me.segments[segIdx].length, me.page_seg_map, segIdx, pageIdx);
+                let grpSeqInfo = (me.group_node_info)?me.group_node_info[segIdx]:null;
+                private_addPageValue(me.dispValueIdxLst, me.segments[segIdx], grpSeqInfo, 0, me.segments[segIdx].length, me.page_seg_map, segIdx, pageIdx, null);
             }
         } else {
             let bands = JpcBand.createNew(rptTpl, defProperties);
@@ -106,10 +233,17 @@ JpcFlowTabSrv.prototype.createNew = function(){
                 JpcBandHelper.setBandArea(bands, rptTpl, pageStatus, !me.isEx, me.isEx);
                 maxRowRec = JpcFlowTabHelper.getMaxRowsPerPage(bands, rptTpl, me.isEx);
             }
+            let grpPageInfo = {};
             for (let segIdx = 0; segIdx < me.segments.length; segIdx++) {
+                let grpSeqInfo = (me.group_node_info)?me.group_node_info[segIdx]:null;
+                grpPageInfo[JV.PROP_SEG_GRP_IDX] = 0;
+                grpPageInfo[JV.PROP_INSERTED_GRP_REC] = 0;
+                grpPageInfo[JV.PROP_PRE_ADD_GRP_REC_INFO] = [];
+                grpPageInfo[JV.PROP_GRP_LINES] = me.group_lines_amt;
                 private_resetBandArea();
                 let orgMaxRowRec = maxRowRec;
-                let rowSplitCnt = Math.ceil(1.0 * me.segments[segIdx].length / orgMaxRowRec);
+                let grpRecAmt = (grpSeqInfo)?(grpSeqInfo.length*me.group_lines_amt):0;
+                let rowSplitCnt = Math.ceil(1.0 * (me.segments[segIdx].length + grpRecAmt) / orgMaxRowRec);
                 pageStatus[JV.STATUS_SEGMENT_END] = true;
                 private_resetBandArea();
                 let hasAdHocRow = !JpcFlowTabHelper.chkSegEnd(bands, rptTpl, me.segments, segIdx, (rowSplitCnt - 1) * orgMaxRowRec, maxRowRec, me.isEx);
@@ -117,14 +251,16 @@ JpcFlowTabSrv.prototype.createNew = function(){
                 if (rowSplitCnt % me.multiCols > 0) {
                     rowSplitCnt++
                 }
-                for (let rowIdx = 0; rowIdx < rowSplitCnt; rowIdx++) {
-                    pageStatus[JV.STATUS_SEGMENT_END] = (rowIdx === (rowSplitCnt - 1));
+                for (let segPageIdx = 0; segPageIdx < rowSplitCnt; segPageIdx++) {
+                    pageStatus[JV.STATUS_SEGMENT_END] = (segPageIdx === (rowSplitCnt - 1));
                     if (pageIdx > 0) pageStatus[JV.STATUS_REPORT_START] = false;
                     private_resetBandArea();
                     me.pageStatusLst.push(pageStatus.slice(0));
                     pageIdx++;
-                    counterRowRec = orgMaxRowRec * rowIdx;
-                    private_addPageValue(me.dispValueIdxLst, me.segments[segIdx], counterRowRec, maxRowRec,me.page_seg_map, segIdx, pageIdx);
+                    counterRowRec = orgMaxRowRec * segPageIdx - grpPageInfo[JV.PROP_INSERTED_GRP_REC];
+                    private_addPageValue(me.dispValueIdxLst, me.segments[segIdx], grpSeqInfo, counterRowRec, maxRowRec,me.page_seg_map, segIdx, pageIdx, null);
+                    //private_addPageValue(me.dispValueIdxLst, me.segments[segIdx], grpSeqInfo, counterRowRec, maxRowRec,me.page_seg_map, segIdx, pageIdx, grpPageInfo);
+                    //备注: 考虑到分组数据是临时融入的,所以需要知道这一页的数据融入了多少条group数据,并且得考虑边界条件情况(group数据可能跨页!)
                 }
                 pageStatus[JV.STATUS_SEGMENT_END] = false;
                 pageStatus[JV.STATUS_REPORT_START] = false;
@@ -225,7 +361,9 @@ JpcFlowTabSrv.prototype.createNew = function(){
                     }
                     if (!(tab_field[JV.PROP_HIDDEN])) {
                         for (let rowIdx = 0; rowIdx < contentValuesIdx.length; rowIdx++) {
-                            rst.push(me.outputTabField(band, tab_field, data_field, contentValuesIdx[rowIdx], -1, contentValuesIdx.length, rowIdx, 1, 0, unitFactor, true, controls, multiColIdx));
+                            // rst.push(me.outputTabField(band, tab_field, data_field, contentValuesIdx[rowIdx], -1, contentValuesIdx.length, rowIdx, 1, 0, unitFactor, true, controls, multiColIdx));
+                            //测试中
+                            rst.push(me.outputTabField(band, tab_field, data_field, contentValuesIdx[rowIdx][1], -1, contentValuesIdx.length, rowIdx, 1, 0, unitFactor, true, controls, multiColIdx));
                         }
                     }
                 }

+ 24 - 11
modules/reports/rpt_component/jpc_rte.js

@@ -65,18 +65,31 @@ let JE = {
     },
     getFieldValue: function (field, dataObj, valIdx, dftVal) {
         let rst = dftVal;
-        if (!field.DataNodeName) {
-            //that means this is a self-defined discrete field!
-            field.DataNodeName = JV.DATA_DISCRETE_DATA;
-            let len = dataObj[JV.DATA_DISCRETE_DATA];
-            field.DataSeq = len;
-            dataObj[JV.DATA_DISCRETE_DATA].push([]);
-        }
-        if (dataObj[field.DataNodeName][field.DataSeq].length > valIdx) {
-            rst = dataObj[field.DataNodeName][field.DataSeq][valIdx];
+        if (field.DataNodeName === "NA") {
+            if (!field[JV.PROP_AD_HOC_DATA]) {
+                field[JV.PROP_AD_HOC_DATA] = [];
+            }
+            if (field[JV.PROP_AD_HOC_DATA].length > valIdx) {
+                rst = field[JV.PROP_AD_HOC_DATA][valIdx];
+            } else {
+                if (dftVal === null && field[JV.PROP_AD_HOC_DATA].length > 0) {
+                    rst = field[JV.PROP_AD_HOC_DATA][field[JV.PROP_AD_HOC_DATA].length - 1];
+                }
+            }
         } else {
-            if (dftVal === null && dataObj[field.DataNodeName][field.DataSeq].length > 0) {
-                rst = dataObj[field.DataNodeName][field.DataSeq][dataObj[field.DataNodeName][field.DataSeq].length - 1];
+            if (!field.DataNodeName) {
+                //that means this is a self-defined discrete field!
+                field.DataNodeName = JV.DATA_DISCRETE_DATA;
+                let len = dataObj[JV.DATA_DISCRETE_DATA];
+                field.DataSeq = len;
+                dataObj[JV.DATA_DISCRETE_DATA].push([]);
+            }
+            if (dataObj[field.DataNodeName][field.DataSeq].length > valIdx) {
+                rst = dataObj[field.DataNodeName][field.DataSeq][valIdx];
+            } else {
+                if (dftVal === null && dataObj[field.DataNodeName][field.DataSeq].length > 0) {
+                    rst = dataObj[field.DataNodeName][field.DataSeq][dataObj[field.DataNodeName][field.DataSeq].length - 1];
+                }
             }
         }
         return rst;

+ 29 - 0
modules/reports/util/rpt_construct_data_util.js

@@ -53,6 +53,32 @@ class Rpt_Common{
         }
         return rst;
     };
+    MultiPlus(arrVal, fixFormat) {
+        let rst = [];
+        for (let i = 0; i < arrVal.length; i++) {
+            let valItem = arrVal[i];
+            if (i === 0) {
+                for (let dtl of valItem) {
+                    let value = parseFloat(dtl);
+                    if (fixFormat) value = value.toFixed(fixFormat);
+                    rst.push(value);
+                }
+            } else {
+                for (let j = 0; j < valItem.length; j++) {
+                    if (j < rst.length) {
+                        let value = rst[j] + valItem[j];
+                        if (fixFormat) value = value.toFixed(fixFormat);
+                        rst[j] = value;
+                    } else {
+                        let value = parseFloat(valItem[j]);
+                        if (fixFormat) value = value.toFixed(fixFormat);
+                        rst.push(value);
+                    }
+                }
+            }
+        }
+        return rst;
+    };
     Minus(val1, val2, fixFormat) {
         let rst = [], maxLen = val1.length, minLen = val2.length;
         if (minLen > maxLen) {
@@ -454,6 +480,9 @@ function ext_getFee(feeKey, dtlFeeKey) {
             }
         }
     }
+    for (let i = 0; i < rst.length; i++) {
+        rst[i] = parseFloat(rst[i]);
+    }
     return rst;
 }
 

+ 43 - 30
public/calc_util.js

@@ -5,9 +5,11 @@
  */
 
 // 需求说小数位数固定为2位,这里预留缓冲接口,防止以后需求变卦。
-const Digit_Calc_Program = -2;      // 这里指定计算程序用到的小数位数。
-function round(num) {
-    return scMathUtil.roundTo(num, Digit_Calc_Program);
+const Digit_Calc_Program = -2;              // 需求指定计算程序用到的小数位数。
+const Digit_Calc_Program_Default = -6;      // 需求末指定时默认用到的小数位数。
+function round(value, useDef = false) {
+    let digit = useDef ? Digit_Calc_Program_Default : Digit_Calc_Program;
+    return scMathUtil.roundTo(value, digit);
 };
 
 let executeObj = {
@@ -51,9 +53,11 @@ let executeObj = {
                                    price = md["base_price"];
                                    if (!price) price = 0;
                                    mdSum = mdSum +  round(md["consumption"] * price);
+                                   mdSum = round(mdSum, true);
                                }
                            };
-                           tmpSum = tmpSum + glj["quantity"] * mdSum;     // 这里不能四舍五入
+                           tmpSum = tmpSum + round(glj["quantity"] * mdSum, true);
+                           tmpSum = round(tmpSum, true);
                        }
                 };
             }else{
@@ -70,7 +74,7 @@ let executeObj = {
                             price = mprice - aprice;
                         };
                         if (!price) price = 0;
-                        tmpSum = tmpSum + glj["quantity"] * price;
+                        tmpSum = round(tmpSum + round(glj["quantity"] * price, true), true);
                     };
                 };
             };
@@ -227,9 +231,16 @@ class Calculation {
     // 先编译公用的基础数据
     compilePublics(feeRates, labourCoes, feeTypes, calcBases){
         let me = this;
+        me.compiledFeeRates = {};
+        me.compiledLabourCoes = {};
+        me.compiledTemplates = {};
+        me.compiledFeeTypes = {};
+        me.compiledFeeTypeNames = [];
+        me.compiledCalcBases = {};
+        me.saveForReports = [];
+
         let private_compile_feeRateFile = function() {
             if (feeRates) {
-                me.compiledFeeRates = {};
                 for (let rate of feeRates) {
                     me.compiledFeeRates["feeRate_" + rate.ID] = rate;
                 }
@@ -237,7 +248,6 @@ class Calculation {
         };
         let private_compile_labourCoeFile = function() {
             if (labourCoes) {
-                me.compiledLabourCoes = {};
                 for (let coe of labourCoes) {
                     me.compiledLabourCoes["LabourCoe_" + coe.ID] = coe;
                 }
@@ -245,8 +255,6 @@ class Calculation {
         };
         let private_compile_feeType = function() {
             if (feeTypes) {
-                me.compiledFeeTypes = {};
-                me.compiledFeeTypeNames = [];
                 for (let ft of feeTypes) {
                     me.compiledFeeTypes[ft.type] = ft.name;
                     me.compiledFeeTypes[ft.name] = ft.type;    // 中文预编译,可靠性有待验证
@@ -256,7 +264,6 @@ class Calculation {
         };
         let private_compile_calcBase = function() {
             if (calcBases) {
-                me.compiledCalcBases = {};
                 for (let cb of calcBases) {
                     me.compiledCalcBases[cb.dispName] = cb;         // 中文预编译,可靠性有待验证
                 }
@@ -267,8 +274,6 @@ class Calculation {
         private_compile_labourCoeFile();
         private_compile_feeType();
         private_compile_calcBase();
-        me.compiledTemplates = {};
-        me.saveForReports = [];
     };
 
     compileTemplate(template){
@@ -378,8 +383,6 @@ class Calculation {
         };
     };
 
-
-
     calculate($treeNode){
         let me = this;
         let templateID = $treeNode.data.programID;
@@ -396,6 +399,7 @@ class Calculation {
             if (!$treeNode.data.fees) {
                 $treeNode.data.fees = [];
                 $treeNode.data.feesIndex = {};
+                $treeNode.changed = true;
             };
 
             for (let idx of template.compiledSeq) {
@@ -411,23 +415,32 @@ class Calculation {
 
                 // 费用同步到定额
                 // 引入小麦的字段检测后,快速切换定额出现计算卡顿现象,过多的循环造成。这里把她的代码拆出来,减少微循环。
-                if (!$treeNode.data.feesIndex[calcItem.fieldName]){
-                    let fee = {
-                        'fieldName': calcItem.fieldName,
-                        'unitFee': calcItem.unitFee,
-                        'totalFee': calcItem.totalFee,
-                        'tenderUnitFee': 0,
-                        'tenderTotalFee': 0
-                    };
-                    $treeNode.data.fees.push(fee);
-                    $treeNode.data.feesIndex[calcItem.fieldName] = fee;
-                }
-                else{
-                    $treeNode.data.feesIndex[calcItem.fieldName].unitFee = calcItem.unitFee;
-                    $treeNode.data.feesIndex[calcItem.fieldName].totalFee = calcItem.totalFee;
-                }
-            }
+                if (calcItem.fieldName != '') {
+                    if (!$treeNode.data.feesIndex[calcItem.fieldName]){
+                        let fee = {
+                            'fieldName': calcItem.fieldName,
+                            'unitFee': calcItem.unitFee,
+                            'totalFee': calcItem.totalFee,
+                            'tenderUnitFee': 0,
+                            'tenderTotalFee': 0
+                        };
+                        $treeNode.data.fees.push(fee);
+                        $treeNode.data.feesIndex[calcItem.fieldName] = fee;
+                        $treeNode.changed = true;
+                    }
+                    else{
+                        if ($treeNode.data.feesIndex[calcItem.fieldName].unitFee != calcItem.unitFee){
+                            $treeNode.data.feesIndex[calcItem.fieldName].unitFee = calcItem.unitFee;
+                            $treeNode.changed = true;
+                        };
 
+                        if ($treeNode.data.feesIndex[calcItem.fieldName].totalFee != calcItem.totalFee){
+                            $treeNode.data.feesIndex[calcItem.fieldName].totalFee = calcItem.totalFee;
+                            $treeNode.changed = true;
+                        };
+                    }
+                };
+            };
         }
     }
 };

+ 12 - 1
public/web/rpt_value_define.js

@@ -100,7 +100,10 @@ const JV = {
     PROP_DISCRETE_FIELDS: "discrete_field_s",
     PROP_FLOW_FIELDS: "flow_field_s",
     PROP_BILL_FIELDS: "bill_field_s",
-    PROP_GROUP_FIELDS: "group_field_s",
+    PROP_GROUP_FIELDS: "group_field_s", //用来分组的指标(如按清单、定额etc...)
+    PROP_GROUP_LINES: "group_lines",    //显示分组行,因分组的特殊性,分组的数据当成流水数据一样(行高相同),group_lines里的每一条数据占用流水的一整行,里面再细分(指标/text)
+    PROP_GROUP_SUM_KEYS: "SumKey_S",
+    PROP_SUM_KEY: "SumKey",
     PROP_SUM_FIELDS: "sum_field_s",
     PROP_TEXTS: "text_s",
     PROP_TEXT: "text",
@@ -159,6 +162,11 @@ const JV = {
     BLANK_VALUE_INDEX: -100,
     BLANK_PAGE_VALUE_INDEX: -200,
 
+    PROP_SEG_GRP_IDX: "segGrpRecStartIdx",
+    PROP_PRE_ADD_GRP_REC_INFO: "preAddPageGrpInfo",
+    PROP_INSERTED_GRP_REC: "insertedGrpRecAmt",
+    PROP_GRP_LINES: "me.group_lines_amt",
+
     RUN_TYPE_BEFORE_PAGING: "before_paging",
     RUN_TYPE_BEFORE_OUTPUT: "before_output",
 
@@ -224,6 +232,9 @@ const JV = {
     PAGING_OPTION_NORMAL: 'normal',
     PAGING_OPTION_INFINITY: 'infinity',
 
+    DISPLAY_VAL_TYPE_NORMAL: 0,
+    DISPLAY_VAL_TYPE_GROUP: 1,
+
     PAGE_SELF_DEFINE: "自定义",
     PAGE_SPECIAL_MERGE_POS: "page_merge_pos",
 

+ 10 - 0
public/web/sheet/sheet_data_helper.js

@@ -280,5 +280,15 @@ var SheetDataHelper = {
             sheet.setSelection(range.row, range.col, range.rowCount, range.colCount);
         }
         return target;
+    },
+    /**
+     * 在sheet中使用delete键,触发EndEdited事件
+     * @param sheet
+     */
+    deleteBind: function (sheet, fun) {
+        sheet.addKeyMap(46, false, false, false, false, function () {
+            let selections = sheet.getSelections();
+            
+        });
     }
 };

+ 16 - 0
test/unit/reports/rpt_test_decimal.js

@@ -0,0 +1,16 @@
+/**
+ * Created by Tony on 2017/11/8.
+ */
+
+import mongoose from "mongoose";
+let Schema = mongoose.Schema;
+let MyDecimalSchema = new Schema({
+    "ID": Number,
+    "Value1" : Number,
+    "Value2": Number,
+    "Value3": Number
+});
+
+let Rpt_Decimal_Mdl = mongoose.model("rpt_decimal_test", MyDecimalSchema, "rpt_decimal_test");
+
+export {Rpt_Decimal_Mdl as default};

+ 55 - 15
test/unit/reports/test_tpl_09_1.js

@@ -118,21 +118,27 @@ test('测试 - 测试模板啦: ', function (t) {
         //正常应该根据报表模板定义的数据类型来请求数据
         rptTplDataFacade.prepareProjectData(userId_Dft, demoPrjId, filter, function (err, msg, rawDataObj) {
             if (!err) {
-                let tplData = rptDataUtil.assembleData(rawDataObj);
-                // fsUtil.wirteObjToFile(rawDataObj, "D:/GitHome/ConstructionCost/tmp/rptTplRawDataObject.js");
-                //it's time to build the report!!!
-                let printCom = JpcEx.createNew();
-                rptTpl[JV.NODE_MAIN_INFO][JV.NODE_PAGE_INFO][JV.PROP_PAGE_SIZE] = pagesize;
-                let defProperties = rpt_cfg;
-                let dftOption = JV.PAGING_OPTION_NORMAL;
-                printCom.initialize(rptTpl);
-                printCom.analyzeData(rptTpl, tplData, defProperties, dftOption);
-                let maxPages = printCom.totalPages;
-                let pageRst = printCom.outputAsSimpleJSONPageArray(rptTpl, tplData, 1, maxPages, defProperties);
-                if (pageRst) {
-                    fsUtil.wirteObjToFile(pageRst, "D:/GitHome/ConstructionCost/tmp/testBuiltPageResult.js");
-                } else {
-                    console.log("oh! no pages were created!")
+                try {
+                    let tplData = rptDataUtil.assembleData(rawDataObj);
+                    // fsUtil.wirteObjToFile(rawDataObj, "D:/GitHome/ConstructionCost/tmp/rptTplRawDataObject.js");
+                    //it's time to build the report!!!
+                    let printCom = JpcEx.createNew();
+                    rptTpl[JV.NODE_MAIN_INFO][JV.NODE_PAGE_INFO][JV.PROP_PAGE_SIZE] = pagesize;
+                    let defProperties = rpt_cfg;
+                    let dftOption = JV.PAGING_OPTION_NORMAL;
+                    printCom.initialize(rptTpl);
+                    printCom.analyzeData(rptTpl, tplData, defProperties, dftOption);
+                    let maxPages = printCom.totalPages;
+                    let pageRst = printCom.outputAsSimpleJSONPageArray(rptTpl, tplData, 1, maxPages, defProperties);
+                    if (pageRst) {
+                        //fsUtil.wirteObjToFile(pageRst, "D:/GitHome/ConstructionCost/tmp/testBuiltPageResult.js");
+                    } else {
+                        console.log("oh! no pages were created!")
+                    }
+                } catch (ex) {
+                    console.log(ex);
+                    t.pass('pass with exception!');
+                    t.end();
                 }
 
                 t.pass('pass succeeded!');
@@ -147,6 +153,40 @@ test('测试 - 测试模板啦: ', function (t) {
 });
 //*/
 
+/*/
+test('测试 - 保存小数位数问题: ', function (t) {
+    require('./rpt_test_decimal');
+    let rpt_decimal_mdl = mongoose.model("rpt_decimal_test");
+    let num = 300000;
+    let ID = 1;
+    for (let i = 0.0001; i < 1; (i+=0.0001)) {
+        let test_doc = {};
+        test_doc.ID = ID;
+        test_doc.Value1 = num + i;
+        test_doc.Value2 = num + i + 0.1 + 0.2;
+        test_doc.Value3 = parseFloat((num + i + 0.1 + 0.2).toFixed(5));
+        rpt_decimal_mdl.create(test_doc);
+        ID++;
+    }
+    t.pass('pass save decimal ok!');
+    t.end();
+});
+//*/
+/*/
+test('测试 - 显示保存小数位数问题: ', function (t) {
+    require('./rpt_test_decimal');
+    let rpt_decimal_mdl = mongoose.model("rpt_decimal_test");
+    rpt_decimal_mdl.find({}).then(function (rst) {
+        //console.log(rst);
+        if (rst.length > 0) {
+            fsUtil.wirteObjToFile(rst, "D:/GitHome/ConstructionCost/tmp/testDecimalResult.js");
+        }
+        t.pass('pass get decimal ok!');
+        t.end();
+    });
+});
+//*/
+
 test('close the connection', function (t) {
     setTimeout(function () {
         mongoose.disconnect();

+ 1 - 0
web/building_saas/complementary_glj_lib/html/tools-gongliaoji.html

@@ -188,6 +188,7 @@
     <script type="text/javascript" src="/lib/ztree/jquery.ztree.core.js"></script>
     <script type="text/javascript" src="/lib/ztree/jquery.ztree.excheck.js"></script>
     <script type="text/javascript" src="/lib/ztree/jquery.ztree.exedit.js"></script>
+    <script type="text/javascript" src="/public/web/scMathUtil.js"></script>
     <script type="text/javascript" src="/public/web/treeDataHelper.js"></script>
     <script type="text/javascript" src="/public/web/QueryParam.js"></script>
     <script type="text/javascript" src="/web/building_saas/complementary_glj_lib/js/glj.js"></script>

+ 33 - 2
web/building_saas/complementary_glj_lib/js/glj.js

@@ -442,6 +442,12 @@ let repositoryGljObj = {
                         }
                         else if(rObj.basePrice !== me.currentEditingGlj.basePrice){//修改了单价,可修改单价的必为可成为组成物的
                             //寻找所有引用了此组成物的工料机,并从组成物中删去此工料机,并重算单价
+                            if(isNaN(parseFloat(rObj.basePrice))){
+                                alert('单价只能为数值!');
+                                args.sheet.setValue(args.row, args.col, me.currentEditingGlj.basePrice ? me.currentEditingGlj.basePrice : 0);
+                                return;
+                            }
+                            rObj.basePrice = !isNaN(parseFloat(rObj.basePrice))? scMathUtil.roundTo(parseFloat(rObj.basePrice), -2) : me.currentEditingGlj.basePrice;
                             let updateGljs = me.getUpdateGljs(rObj);
                             if(updateGljs.updateArr.length > 0 || updateGljs.updateBasePrcArr.length > 0){
                                 for(let i = 0; i < updateGljs.updateArr.length; i++){
@@ -451,7 +457,7 @@ let repositoryGljObj = {
                                     updateArr.push(updateGljs.updateBasePrcArr[i]);
                                 }
                             }
-                            rObj.basePrice = !isNaN(parseFloat(rObj.basePrice)) && (rObj.basePrice && typeof rObj.basePrice !== 'undefined') ? that.round(parseFloat(rObj.basePrice), 2) : 0;
+                            //rObj.basePrice = !isNaN(parseFloat(rObj.basePrice)) && (rObj.basePrice && typeof rObj.basePrice !== 'undefined') ? that.round(parseFloat(rObj.basePrice), 2) : 0;
                         }
                         rObj.component = me.currentGlj.component;
                         updateArr.push(rObj);
@@ -698,7 +704,7 @@ let repositoryGljObj = {
             if(!isExsit) isValid = false;
         }
         //
-        pasteObj.basePrice = !isNaN(parseFloat(pasteObj.basePrice)) && (pasteObj.basePrice && typeof pasteObj.basePrice !== 'undefined') ? that.round(parseFloat(pasteObj.basePrice), 2) :
+        pasteObj.basePrice = !isNaN(parseFloat(pasteObj.basePrice)) && (pasteObj.basePrice && typeof pasteObj.basePrice !== 'undefined') ? scMathUtil.roundTo(parseFloat(pasteObj.basePrice), 2) :
             me.currentCache[rowIdx].basePrice;
         if(pasteObj.basePrice !== me.currentCache[rowIdx].basePrice){
             reCalBasePrc = true;
@@ -951,6 +957,12 @@ let repositoryGljObj = {
     },*/
     mixUpdateRequest: function(updateArr, addArr, removeIds) {
         let me = repositoryGljObj;
+        if(updateArr.length > 0){
+            me.saveInString(updateArr);
+        }
+        if(addArr.length > 0){
+            me.saveInString(addArr);
+        }
         $.ajax({
             type:"POST",
             url:"complementartGlj/api/mixUpdateGljItems",
@@ -985,6 +997,25 @@ let repositoryGljObj = {
             }
         })
     },
+    saveInString: function (datas) {
+        for(let i = 0, len = datas.length; i < len; i++){
+            let data = datas[i];
+            if(_exist(data, 'basePrice')){
+                data['basePrice'] = data['basePrice'].toString();
+            }
+            if(_exist(data, 'component')){
+                for(let j = 0, jLen = data['component'].length; j < jLen; j++){
+                    let comGljObj = data['component'][j];
+                    if(_exist(comGljObj, 'consumeAmt')){
+                        comGljObj['consumeAmt'] = comGljObj['consumeAmt'].toString();
+                    }
+                }
+            }
+        }
+        function _exist(data, attr){
+            return data && data[attr] !== undefined && data[attr];
+        }
+    },
     getParentCache: function (nodes) {
         let me = repositoryGljObj, rst = [];
         for(let i = 0; i < me.complementaryGljList.length; i++){

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

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

+ 2 - 7
web/building_saas/css/main.css

@@ -105,7 +105,7 @@ body {
   width:0%;
 }
 .sidebar-bottom,.sidebar-bottom .col-lg-6,.sidebar-bottom .col-lg-12 {
-  height:200px
+  height:300px
 }
 .top-content, .fluid-content {
     overflow: auto;
@@ -234,7 +234,7 @@ body {
     }
 }
 .bottom-content .tab-content .main-data-bottom{
-    height: 200px;
+    height: 300px;
     overflow: auto;
 }
 .bottom-content .tab-content .ovf-hidden{
@@ -309,8 +309,3 @@ body {
   max-width: 200px;
   display: inline-block;
 }
-.gc-column-header-cell{
-    text-align: center!important;
-}
-.modal-lg{max-width: 1000px}
-.modal-feeRate {max-width: 550px}

+ 2 - 2
web/building_saas/js/global.js

@@ -6,8 +6,8 @@ function autoFlashHeight(){
     var bottomContentHeight = $(".bottom-content").height();
     var toolsBarHeightQ = $(".tools-bar-height-q").height();
     var toolsBarHeightD = $(".tools-bar-height-d").height();
-    $(".main-data-side-q").height($(window).height()-headerHeight-toolsbarHeight-toolsBarHeightQ-202);
-    $(".main-data-side-d").height($(window).height()-headerHeight-toolsbarHeight-toolsBarHeightD-202);
+    $(".main-data-side-q").height($(window).height()-headerHeight-toolsbarHeight-toolsBarHeightQ-302);
+    $(".main-data-side-d").height($(window).height()-headerHeight-toolsbarHeight-toolsBarHeightD-302);
     $(".main-data-top").height($(window).height()-headerHeight-toolsbarHeight-bottomContentHeight-1);
     $(".main-data-full").height($(window).height()-headerHeight-toolsbarHeight-1);
     $(".main-data-full-fl").height($(window).height()-headerHeight-toolsbarHeight-37);

+ 69 - 0
web/building_saas/main/html/main.html

@@ -265,6 +265,7 @@
                                 <li class="nav-item"><a class="nav-link" data-toggle="pill" href="#poj-settings-3" role="tab">指标信息</a></li>
                                 <li class="nav-item"><a class="nav-link" data-toggle="pill" href="#poj-settings-4" role="tab">关于计算</a></li>
                                 <li class="nav-item"><a class="nav-link" data-toggle="pill" href="#poj-settings-5" role="tab">清单工程精度</a></li>
+                                <li class="nav-item"><a class="nav-link" data-toggle="pill" href="#poj-settings-decimal" role="tab" id="tab_poj-settings-decimal">小数位数</a></li>
                                 <li class="nav-item"><a class="nav-link" data-toggle="pill" href="#poj-settings-6" role="tab" id="tab_poj-settings-6">人工单价调整</a></li>
                             </ul>
                         </div>
@@ -341,6 +342,73 @@
                                         清单工程精度
                                     </div>
                                 </div>
+                                <!--小数位数-->
+                                <div class="tab-pane fade" id="poj-settings-decimal" role="tabpanel">
+                                    <div class="modal-auto-height">
+                                        <fieldset class="form-group">
+                                            <h5>清单</h5>
+                                            <div class="row m-0">
+                                                <div class="col-sm-3">
+                                                    <div class="input-group input-group-sm mb-2">
+                                                        <div class="input-group-addon">单价</div>
+                                                        <input type="number" name="bills-unitPrice" class="form-control" value="2" step="1" max="6" min="0">
+                                                    </div>
+                                                </div>
+                                                <div class="col-sm-3">
+                                                    <div class="input-group input-group-sm mb-2">
+                                                        <div class="input-group-addon">合价</div>
+                                                        <input type="number" name="bills-totalPrice" class="form-control" value="2" step="1" max="6" min="0">
+                                                    </div>
+                                                </div>
+                                            </div>
+                                            <h5 class="mt-3">定额</h5>
+                                            <div class="row m-0">
+                                                <div class="col-sm-3">
+                                                    <div class="input-group input-group-sm mb-2">
+                                                        <div class="input-group-addon">工程量</div>
+                                                        <input type="number" name="ration-quantity" class="form-control" value="2" step="1" max="6" min="0">
+                                                    </div>
+                                                </div>
+                                                <div class="col-sm-3">
+                                                    <div class="input-group input-group-sm mb-2">
+                                                        <div class="input-group-addon">单价</div>
+                                                        <input type="number" name="ration-unitPrice" class="form-control" value="2" step="1" max="6" min="0">
+                                                    </div>
+                                                </div>
+                                                <div class="col-sm-3">
+                                                    <div class="input-group input-group-sm mb-2">
+                                                        <div class="input-group-addon">合价</div>
+                                                        <input type="number" name="ration-totalPrice" class="form-control" value="2" step="1" max="6" min="0">
+                                                    </div>
+                                                </div>
+                                            </div>
+                                            <h5 class="mt-3">工料机</h5>
+                                            <div class="row m-0">
+                                                <div class="col-sm-3">
+                                                    <div class="input-group input-group-sm mb-2">
+                                                        <div class="input-group-addon">工程量</div>
+                                                        <input type="number" name="glj-quantity" class="form-control" value="2" step="1" max="6" min="0">
+                                                    </div>
+                                                </div>
+                                                <div class="col-sm-3">
+                                                    <div class="input-group input-group-sm mb-2">
+                                                        <div class="input-group-addon">单价</div>
+                                                        <input type="number" name="glj-unitPrice" class="form-control" value="2" step="1" max="6" min="0">
+                                                    </div>
+                                                </div>
+                                            </div>
+                                            <h5 class="mt-3">费率</h5>
+                                            <div class="row m-0">
+                                                <div class="col-sm-3">
+                                                    <div class="input-group input-group-sm mb-2">
+                                                        <div class="input-group-addon">费率</div>
+                                                        <input type="number" name="feeRate" class="form-control" value="2" step="1" max="6" min="0">
+                                                    </div>
+                                                </div>
+                                            </div>
+                                        </fieldset>
+                                    </div>
+                                </div>
                                 <!--人工单价调整-->
                                 <div class="tab-pane fade" id="poj-settings-6" role="tabpanel">
                                     <div class="row px-3">
@@ -596,6 +664,7 @@
         <script type="text/javascript" src="/web/building_saas/main/js/views/project_info.js"></script>
         <script type="text/javascript" src="/web/building_saas/main/js/views/project_view.js"></script>
         <script type="text/javascript" src="/web/building_saas/main/js/views/options_view.js"></script>
+        <script type="text/javascript" src="/web/building_saas/main/js/views/project_property_decimal_view.js"></script>
         <script type="text/javascript" src="/web/building_saas/main/js/main_ajax.js"></script>
         <script type="text/javascript" src="/web/building_saas/main/js/main.js"></script>
         <script type="text/javascript" src="/web/building_saas/main/js/controllers/project_controller.js"></script>

+ 13 - 3
web/building_saas/main/js/calc/bills_calc.js

@@ -263,12 +263,13 @@ class BillsCalcHelper {
     };
     calcRationLeaf (node, fields, isIncre) {
         nodeCalcObj.node = node;
-        nodeCalcObj.digit = this.project.Decimal.unitFee;
+        nodeCalcObj.digit = this.project.Decimal.common.unitFee;
         calcFees.checkFields(node.data, fields);
         let nodeCalc = nodeCalcObj, virData= null, decimal = this.project.Decimal;
 
         // 清单单价:套用定额计算程序
-        if (this.project.calcFlag === billsPrice) {
+        // if (this.project.calcFlag === billsPrice) {
+        if (this.project.projSetting.billsCalcMode === billsPrice) {
             rationCalcObj.calcGljs = this.getBillsGLjs(node);
             console.log(rationCalcObj.calcGljs);
             rationCalcObj.calcFields = rationCalcFields;
@@ -322,6 +323,12 @@ class BillsCalcHelper {
             this.setTotalFee(node, field, value, isIncre);
         }
     };
+    clearFeeFields(node, fields, isIncre) {
+        for (let field of fields) {
+            node.data.feesIndex[field.type].unitFee = 0;
+            this.setTotalFee(node, field, 0, isIncre);
+        }
+    }
     calcNode(node, isIncre) {
         if (node.source.children.length > 0) {
             this.calcParent(node, this.project.calcFields, isIncre);
@@ -333,7 +340,7 @@ class BillsCalcHelper {
                     this.calcVolumePriceLeaf(node, this.project.calcFields, isIncre);
                 }
             } else {
-
+                this.clearFeeFields(node, this.project.calcFields, isIncre);
             }
         }
     };
@@ -352,6 +359,7 @@ class BillsCalcHelper {
         if (parent && parent.sourceType === this.project.Bills.getSourceType()) {
             calcFees.checkFields(parent.data, [field]);
             parent.data.feesIndex[field.type].totalFee = (parent.data.feesIndex[field.type].totalFee + Incre).toDecimal(this.project.Decimal.common.totalFee);
+            parent.data.feesIndex[field.type].unitFee = 0;   // AAAAA 临时补上,使存储 unitFee.toFixed(2) 时不出错
             this.updateParent(parent.parent, field, Incre);
         }
     };
@@ -359,9 +367,11 @@ class BillsCalcHelper {
         if (isIncre) {
             let incre = value - node.data.feesIndex[field.type].totalFee;
             node.data.feesIndex[field.type].totalFee = value;
+            node.data.feesIndex[field.type].unitFee = 0; // AAAAA 临时补上,使存储 unitFee.toFixed(2) 时不出错
             this.updateParent(node.parent, field, incre);
         } else {
             node.data.feesIndex[field.type].totalFee = value;
+            node.data.feesIndex[field.type].unitFee = 0; // AAAAA 临时补上,使存储 unitFee.toFixed(2) 时不出错
         }
     };
     converseCalc (node) {

+ 289 - 1
web/building_saas/main/js/models/calc_program.js

@@ -5,10 +5,102 @@
  *  用到费率的规则必须有feeRateID属性,当有该属性时,会自动显示费率值。
  */
 
+let defaultBillTemplate = [
+    {
+        ID: 1,
+        serialNo: '一',
+        code: "A",
+        name: "定额直接费",
+        dispExpr: "A1+A2+A3",
+        statement: "人工费+材料费+机械费",
+        feeRate: null,
+        memo: ''
+    },
+    {
+        ID: 2,
+        serialNo: '1',
+        code: "A1",
+        name: "人工费",
+        dispExpr: "HJ",
+        statement: "合计",
+        feeRate: 50,
+        fieldName: 'labour',
+        memo: ''
+    },
+    {
+        ID: 3,
+        serialNo: '2',
+        code: "A2",
+        name: "材料费",
+        dispExpr: "HJ",
+        statement: "合计",
+        feeRate: 30,
+        fieldName: 'material',
+        memo: ''
+    },
+    {
+        ID: 4,
+        serialNo: '3',
+        code: "A3",
+        name: "机械费",
+        dispExpr: "HJ",
+        statement: "合计",
+        feeRate: 20,
+        fieldName: 'machine',
+        memo: ''
+    },
+    {
+        ID: 5,
+        serialNo: '二',
+        code: "A4",
+        name: "管理费",
+        dispExpr: "A",
+        statement: "定额直接费",
+        feeRate: null,
+        fieldName: 'manage',
+        memo: ''
+    },
+    {
+        ID: 6,
+        serialNo: '三',
+        code: "B",
+        name: "利润",
+        dispExpr: "A",
+        statement: "定额直接费",
+        feeRate: null,
+        fieldName: 'profit',
+        memo: ''
+    },
+    {
+        ID: 7,
+        serialNo: '四',
+        code: "C",
+        name: "风险费用",
+        dispExpr: "",
+        statement: "",
+        feeRate: null,
+        fieldName: 'risk',
+        memo: ''
+    },
+    {
+        ID: 8,
+        serialNo: '',
+        code: "",
+        name: "综合单价",
+        dispExpr: "A+B",
+        statement: "定额直接费+利润",
+        feeRate: null,
+        fieldName: 'common',
+        memo: ''
+    }
+];
+
 class CalcProgram {
     constructor(project){
         this.project = project;
         this.datas = [];
+        this.digit = 2;
+        this.digitDefault = 6;
         this.calc = new Calculation();
         project.registerModule(ModuleNames.calc_program, this);
     };
@@ -50,8 +142,204 @@ class CalcProgram {
     };
 
     calculate(treeNode){
-        treeNode.data.gljList = this.project.ration_glj.getGljArrByRation(treeNode.data.ID);
+        let me = this;
+        if (treeNode.sourceType === this.project.Ration.getSourceType()) {
+            treeNode.data.gljList = this.project.ration_glj.getGljArrByRation(treeNode.data.ID);
+        }
+        else if (treeNode.sourceType === this.project.Bills.getSourceType()) {
+             let rations = this.project.Ration.getBillsSortRation(treeNode.source.getID());
+            treeNode.data.gljList = this.project.ration_glj.getGatherGljArrByRations(rations);
+        };
+
         this.calc.calculate(treeNode);
+
+        // 存储、刷新本结点、所有父结点
+        if (treeNode.changed) {
+            me.saveAndCalcParents(treeNode);
+            delete treeNode.changed;
+        };
+
+    };
+
+    saveAndCalcParents(treeNode) {
+        if (treeNode.parent) {
+            projectObj.converseCalculateBills(treeNode.parent);
+        };
+
+        let data = {ID: treeNode.data.ID, projectID: projectObj.project.ID(), fees: treeNode.data.fees};
+        let newDta = {'updateType': 'ut_update', 'updateData': data};
+        let newDataArr = [];
+        newDataArr.push(newDta);
+        projectObj.project.pushNow('', treeNode.sourceType, newDataArr);
         projectObj.mainController.refreshTreeNode([treeNode]);
+    };
+
+    initFees(treeNode){
+        if (!treeNode.data.fees) {
+            treeNode.data.fees = [];
+            treeNode.data.feesIndex = {};
+            treeNode.changed = true;
+        };
+    };
+
+    checkFee(treeNode, ftObj){
+        if (!treeNode.data.feesIndex[ftObj.fieldName]){
+            let fee = {
+                'fieldName': ftObj.fieldName,
+                'unitFee': ftObj.unitFee,
+                'totalFee': ftObj.totalFee,
+                'tenderUnitFee': 0,
+                'tenderTotalFee': 0
+            };
+            treeNode.data.fees.push(fee);
+            treeNode.data.feesIndex[ftObj.fieldName] = fee;
+            treeNode.changed = true;
+        }
+        else{
+            if (treeNode.data.feesIndex[ftObj.fieldName].unitFee != ftObj.unitFee){
+                treeNode.data.feesIndex[ftObj.fieldName].unitFee = ftObj.unitFee;
+                treeNode.changed = true;
+            };
+
+            if (treeNode.data.feesIndex[ftObj.fieldName].totalFee != ftObj.totalFee){
+                treeNode.data.feesIndex[ftObj.fieldName].totalFee = ftObj.totalFee;
+                treeNode.changed = true;
+            };
+        };
+    };
+
+    gatherFeeTypes(treeNode, gatherType){
+        let me = this;
+        let rst = [];
+
+        if (treeNode.sourceType === this.project.Bills.getSourceType()) {
+            me.initFees(treeNode);
+
+            let objsArr = [];
+            if (gatherType == CP_GatherType.rations){
+                objsArr = this.project.Ration.getRationsByNode(treeNode);
+            }else if (gatherType == CP_GatherType.bills){
+                objsArr = treeNode.children;
+            };
+
+            for (let ft of feeType) {
+                let ftObj = {};
+                ftObj.fieldName = ft.type;
+                ftObj.name = ft.name;
+                let uf = 0, tf = 0, tuf = 0, ttf = 0;
+                for (let item of objsArr) {
+                    let data = {};
+                    if (gatherType == CP_GatherType.rations){
+                        data = item;
+                    }else if (gatherType == CP_GatherType.bills){
+                        data = item.data;
+                    };
+                    if (data.feesIndex && data.feesIndex[ft.type]) {
+                        uf = (uf + parseFloat(data.feesIndex[ft.type].unitFee)).toDecimal(me.digitDefault);
+                        tf = (tf + parseFloat(data.feesIndex[ft.type].totalFee)).toDecimal(me.digitDefault);
+                        tuf = (tuf + parseFloat(data.feesIndex[ft.type].tenderUnitFee)).toDecimal(me.digitDefault);
+                        ttf = (ttf + parseFloat(data.feesIndex[ft.type].tenderTotalFee)).toDecimal(me.digitDefault);
+                    };
+                };
+                ftObj.unitFee = uf.toDecimal(me.digit);
+                ftObj.totalFee = tf.toDecimal(me.digit);
+                ftObj.tenderUnitFee = tuf.toDecimal(me.digit);
+                ftObj.tenderTotalFee = ttf.toDecimal(me.digit);
+
+                me.checkFee(treeNode, ftObj);
+
+                rst.push(ftObj);
+            };
+
+            if (treeNode.changed) {
+                me.saveAndCalcParents(treeNode);
+                delete treeNode.changed;
+            };
+        };
+
+        return rst;
+    };
+
+    calcDefaultBillTemp(treeNode, totalPrice){
+        let me = this;
+        let rst = [];
+        if (treeNode.sourceType != this.project.Bills.getSourceType()){return rst};
+        me.initFees(treeNode);
+        for (let item of defaultBillTemplate) {
+            let num = totalPrice;
+            item.dispExprUser = item.dispExpr;
+            item.displayFieldName = me.calc.compiledFeeTypes[item.fieldName];
+
+            if (item.feeRate)
+                item.unitFee = (totalPrice * item.feeRate * 0.01).toDecimal(me.digit)
+            else
+                item.unitFee = 0;
+
+            let quantity = treeNode.data.quantity;
+            if (!quantity) quantity = 0;
+            item.totalFee = (item.unitFee * quantity).toDecimal(me.digit);
+            item.tenderUnitFee = 0;
+            item.tenderTotalFee = 0;
+
+            me.checkFee(treeNode, item);
+        };
+
+        if (treeNode.changed) {
+            me.saveAndCalcParents(treeNode);
+            delete treeNode.changed;
+        };
+
+        rst = defaultBillTemplate;
+        return rst;
+    };
+
+    getCalcDatas(treeNode){
+        let me = this;
+        let rst = [];
+        let isRation = treeNode.sourceType === me.project.Ration.getSourceType();
+        let isBill = treeNode.sourceType === me.project.Bills.getSourceType();
+        let isLeafBill = isBill && treeNode.source.children && treeNode.source.children.length === 0;
+        let isBillPriceCalc = me.project.projSetting.billsCalcMode === billsPrice;
+
+        if (isRation) {                 // 清单单价计算模式下的叶子清单:取自己的计算程序ID,找到自己的计算程序计算。
+            me.calculate(treeNode);
+            rst = treeNode.data.calcTemplate.calcItems;
+        }
+        else if (isLeafBill) {
+            let ct = '';
+            if (treeNode.children && treeNode.children.length > 0){
+                if (treeNode.children[0].sourceType == me.project.Ration.getSourceType()){
+                    ct = childrenType.ration;
+                }
+                else if (treeNode.children[0].sourceType == me.project.VolumePrice.getSourceType()){
+                    ct = childrenType.volumePrice;
+                };
+            }
+            else{
+                ct = childrenType.formula;
+            };
+
+            if (ct == childrenType.ration){
+                if (isBillPriceCalc){   // 清单单价计算模式下的叶子清单:取自己的计算程序ID,找到自己的计算程序计算。
+                    me.calculate(treeNode);
+                    rst = treeNode.data.calcTemplate.calcItems;
+                }else{                  // 前三种计算模式下的叶子清单:汇总定额的计算程序的费用类别
+                    rst = me.gatherFeeTypes(treeNode, CP_GatherType.rations);
+                };
+            }
+            else if (ct == childrenType.volumePrice){
+                let totalPrice = 10000;
+                rst = me.calcDefaultBillTemp(treeNode, totalPrice);
+            }
+            else if (ct == childrenType.formula){
+                let totalPrice = 20000;
+                rst = me.calcDefaultBillTemp(treeNode, totalPrice);
+            };
+        }
+        else if (isBill){    // 父清单:汇总子清单的费用类别
+            rst = me.gatherFeeTypes(treeNode, CP_GatherType.bills);
+        };
+
+        return rst;
     }
 }

+ 13 - 0
web/building_saas/main/js/models/main_consts.js

@@ -25,6 +25,7 @@ const CP_Col_Width = {          // 多处计算程序界面的列宽统一设置
     rowHeader: 30,
     colHeader: 30,              // 这个是标题栏高度不是宽度,也写在一起
     code: 70,
+    serialNo: 50,
     name: 200,
     dispExprUser: 180,
     feeRate: 60,
@@ -33,4 +34,16 @@ const CP_Col_Width = {          // 多处计算程序界面的列宽统一设置
     memo: 110,
     unitFee: 90,
     totalFee: 90
+};
+
+const CP_GatherType = {
+    rations: 'rations',
+    bills: 'bills'
+};
+
+const childrenType = {
+    ration: 'ration',
+    bill: 'bill',
+    volumePrice: 'volumePrice',
+    formula: 'formula'
 };

+ 35 - 1
web/building_saas/main/js/models/ration.js

@@ -107,7 +107,7 @@ var Ration = {
             return newData;
         };
 
-        ration.prototype.getBillsSortRation = function (billsID) {
+        ration.prototype.getBillsSortRation = function (billsID) {     // 该方法只适用于叶子清单
             var rations = this.datas.filter(function (data) {
                 return data[project.masterField.ration] === billsID;
             });
@@ -117,6 +117,40 @@ var Ration = {
             return rations;
         };
 
+        // CSL, 2017-11-13 取任何清单(父清单、叶子清单)下的所有定额
+        ration.prototype.getRationsByNode = function (billNode) {
+            let rations = [];
+            let sBills = 'bills';
+            if (billNode.sourceType != sBills) return rations;
+
+            let IDs = [];
+            function getSubBillsIDs(node) {
+                if (!node) return;
+                if (node.sourceType != sBills) return;
+                if (!node.children || node.children.length == 0 || node.children[0].sourceType != sBills)
+                    IDs.push(node.data.ID)
+                else
+                    getSubBillsIDs(node.children[0]);
+                getSubBillsIDs(node.nextSibling);
+            };
+
+            if (billNode.source.children.length == 0)
+                IDs.push(billNode.data.ID)
+            else
+                getSubBillsIDs(billNode.children[0]);
+
+            for (let id of IDs){
+                let subRations = this.datas.filter(function (data) {
+                    return data[project.masterField.ration] === id;
+                });
+                rations.push(...subRations);
+            };
+            rations.sort(function (x, y) {
+                return x.serialNo - y.serialNo;
+            });
+            return rations;
+        };
+
         ration.prototype.getInsertRationData = function (billsID, preRation) {
             var br = this.getBillsSortRation(billsID);
             var updateData = [];

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

@@ -76,7 +76,9 @@ var ration_glj = {
                         sameGlj.quantity = sameGlj.quantity + (glj.quantity * ration.quantity).toDecimal(4);
                     }
                 }
-            }
+            };
+
+            result = gljOprObj.combineWithProjectGlj(result);
             return result;
         }
 

+ 24 - 15
web/building_saas/main/js/views/calc_program_view.js

@@ -205,15 +205,15 @@ let calcProgramObj = {
             {headerName: "费用名称", headerWidth: CP_Col_Width.name, dataCode: "name", dataType: "String"},
             {headerName: "计算基数", headerWidth: CP_Col_Width.dispExprUser, dataCode: "dispExprUser", dataType: "String"},
             {headerName: "费率", headerWidth: CP_Col_Width.feeRate, dataCode: "feeRate", dataType: "Number"},   // precision: 3
-            {headerName:"费用类别", headerWidth:CP_Col_Width.displayFieldName, dataCode:"displayFieldName", dataType: "String", hAlign: "center"},
-            {headerName: "基数说明", headerWidth: CP_Col_Width.statement, dataCode: "statement", dataType: "String"},
             {headerName: "单价", headerWidth: CP_Col_Width.unitFee, dataCode: "unitFee", dataType: "Number"},  // execRst
             {headerName: "合价", headerWidth: CP_Col_Width.totalFee, dataCode: "totalFee", dataType: "Number"},
+            {headerName:"费用类别", headerWidth:CP_Col_Width.displayFieldName, dataCode:"displayFieldName", dataType: "String", hAlign: "center"},
+            {headerName: "基数说明", headerWidth: CP_Col_Width.statement, dataCode: "statement", dataType: "String"},
             {headerName: "备注", headerWidth: CP_Col_Width.memo, dataCode: "memo", dataType: "String"}
         ],
         view: {
             comboBox: [],
-            lockColumns: [0,1,2,3,4,5,6,7,8],
+            lockColumns: [0,1,2,3,4,5,6,7,8,9],
             colHeaderHeight: CP_Col_Width.colHeader,
             rowHeaderWidth: CP_Col_Width.rowHeader
         }
@@ -228,19 +228,28 @@ let calcProgramObj = {
     showData: function (treeNode) {
         var me = this;
         me.treeNode = treeNode;
-        if (treeNode.sourceType === projectObj.project.Ration.getSourceType()) {
-            projectObj.project.calcProgram.calculate(treeNode);
-            if (treeNode.parent) {
-                projectObj.converseCalculateBills(treeNode.parent);
+        me.datas = projectObj.project.calcProgram.getCalcDatas(treeNode);
+
+        function checkSerialNo() {     // 检测序号列。隐藏列的方式焦点难控制,体验不佳,这里动态添加删除。
+            let srlCol = {headerName: "序号", headerWidth: CP_Col_Width.serialNo, dataCode: "serialNo", dataType: "String", hAlign: "center"};
+            if (me.datas.length > 0 && me.datas[0].serialNo){
+                if (me.setting.header[0].dataCode != "serialNo"){
+                    me.setting.header.splice(0, 0, srlCol);
+                };
             }
-            me.datas = me.treeNode.data.calcTemplate.calcItems;
-            sheetCommonObj.initSheet(me.sheet, me.setting, me.datas.length);
-            sheetCommonObj.showData(me.sheet, me.setting, me.datas);
-        }
-        else if (treeNode.sourceType === projectObj.project.Bills.getSourceType()) {
-            SheetDataHelper.loadSheetHeader(calcProgramSetting, me.sheet);
-            SheetDataHelper.loadSheetData(calcProgramSetting, me.sheet, baseCalcField);
-        }
+            else {
+                if (me.setting.header[0].dataCode == "serialNo"){
+                    me.setting.header.splice(0, 1);
+                };
+            }
+        };
+
+        checkSerialNo();
+        sheetCommonObj.initSheet(me.sheet, me.setting, me.datas.length);
+        sheetCommonObj.showData(me.sheet, me.setting, me.datas);
+
+        // SheetDataHelper.loadSheetHeader(calcProgramSetting, me.sheet);
+        //  SheetDataHelper.loadSheetData(calcProgramSetting, me.sheet, baseCalcField);
     },
 
     clearData: function (){

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

@@ -722,6 +722,7 @@ let pageCCOprObj = {
         theCha.workBook.getSheet(0).setRowCount(0);
         sheetCommonObj.cleanSheet(theCon.workBook.getSheet(0), theCon.setting, -1);
         sheetCommonObj.cleanSheet(theCha.workBook.getSheet(0), theCha.setting, -1);
+        projectObj.mainSpread.focus(true);
     },
     //更新特征及内容数据
     updateCharacterContent: function (findSet, updateObj, txtObj, oprObj) {
@@ -745,8 +746,9 @@ let pageCCOprObj = {
                     selectedNode.data[txtObj.field] = txtObj.text;
                     me.showData(oprObj.workBook.getSheet(0), oprObj.setting, oprObj.currentCache);//刷新特征及内容Spread
                     if(updateCol){
-                        projectObj.mainSpread.getActiveSheet().setValue(me.mainActiveCell.row, updateCol, txtObj.text + ''); //刷新输出显示
-                        projectObj.mainSpread.getActiveSheet().autoFitRow(me.mainActiveCell.row);
+                        let activeCell = projectObj.mainSpread.getActiveSheet().getSelections()[0];
+                        projectObj.mainSpread.getActiveSheet().setValue(activeCell.row, updateCol, txtObj.text + ''); //刷新输出显示
+                        projectObj.mainSpread.getActiveSheet().autoFitRow(activeCell.row);
                     }
                 }
             }

+ 2 - 0
web/building_saas/main/js/views/main_tree_col.js

@@ -115,6 +115,8 @@ let colSettingObj = {
         sheet.setColumnCount(setting.headRows, GC.Spread.Sheets.SheetArea.rowHeader);
         sheet.setColumnCount(1);
         sheet.getRange(-1, 0, -1, 1).cellType(this.checkBox).hAlign(GC.Spread.Sheets.HorizontalAlign.center);
+        sheet.getCell(0, 0, GC.Spread.Sheets.SheetArea.colHeader).value('显示');
+        sheet.setColumnWidth(0, 300);
 
         setting.cols.forEach(function (col, index) {
             let i, iCol = 0, cell;

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

@@ -26,6 +26,8 @@ var projectInfoObj = {
         CommonAjax.post('/pm/api/getProject', {"user_id": userID, "proj_id": scUrlUtil.GetQueryString('project')}, function (data) {
             if (data) {
                 that.projectInfo = data;
+                //init decimal
+                setDecimal(decimalObj, data.property.decimal);
                 $('#fullpath').html(that.getFullPathHtml(that.projectInfo));
             }
         });

+ 216 - 0
web/building_saas/main/js/views/project_property_decimal_view.js

@@ -0,0 +1,216 @@
+/**
+ * Created by Zhong on 2017/11/13.
+ */
+//default setting
+let defaultDecimal = {
+    min: 0,
+    max: 6,
+    _def: {//定义往这加, editable: 开放给用户编辑的(入库),定义editable为true的字段时,要在后端project_model.js defaultDecimal中添加定义,html添加input
+        bills: {editable: true, data: {unitPrice: 2, totalPrice: 2}},
+        ration: {editable: true, data: {quantity: 3, unitPrice: 2, totalPrice: 2}},
+        glj: {editable: true, data: {quantity: 3, unitPrice: 2}},
+        feeRate: {editable: true, data: 2},
+        quantity_detail: {editable: false, data: 4},
+        process: {editable: false, data: 6}
+    }
+};
+
+let decimalObj = Object.create(null);
+
+function isUndef(v) {
+    return v === undefined || v === null;
+}
+
+function isDef(v){
+    return v !== undefined && v !== null;
+}
+
+function isObj(v){
+    return isDef(v) && typeof v === 'object';
+}
+
+function isNum(v){
+    return isDef(v) && !isNaN(v);
+}
+
+function isInt(v){
+    return isNum(v) && v % 1 === 0;
+}
+
+function isValidDigit(v){
+    return isInt(v) && v >= defaultDecimal.min && v <= defaultDecimal.max;
+}
+
+//newV用户可编辑数据
+function toUpdateDecimal(orgV, newV){
+    let rst = false;
+    let len = Object.keys(newV).length;
+    reCompare(orgV, newV);
+    function reCompare(orgV, newV){
+        for(let attr in newV){
+            if(Object.keys(newV[attr]).length > 0){
+                if(isUndef(orgV[attr])){
+                    rst =  true;
+                    return;
+                }
+                else{
+                    reCompare(orgV[attr], newV[attr]);
+                }
+            }
+            else {
+                if(isUndef(orgV[attr]) || parseInt(newV[attr]) !== parseInt(orgV[attr])){
+                    rst = true;
+                    return;
+                }
+            }
+        }
+    }
+    return rst;
+}
+
+function setDecimal(_digits, data){
+    if(isDef(data)){
+        for(let attr in data){//设置入库的数据
+            _digits[attr] = data[attr] || defaultDecimal['_def'][attr]['data'];
+        }
+        for(let attr in defaultDecimal['_def']){//设置不入库的数据
+            if(!defaultDecimal['_def'][attr]['editable']){
+                _digits[attr] = defaultDecimal['_def'][attr]['data'];
+            }
+        }
+    }
+    else {
+        for(let attr in defaultDecimal['_def']){
+            _digits[attr] = defaultDecimal['_def'][attr]['data'];
+        }
+    }
+}
+
+//获取decimalPanel中要展示的数据
+function m_getInitData(data){
+    let rst = Object.create(null);
+    for(let attr in defaultDecimal['_def']){
+        if(defaultDecimal['_def'][attr]['editable'] && isDef(data[attr])){
+            rst[attr] = data[attr];
+        }
+    }
+    return rst;
+}
+//获取小数位数panel里的数据
+function m_getDecimalData(inputs){
+    let rst = Object.create(null);
+    for(let i = 0, len = inputs.length ; i < len; i++){
+       let name = $(inputs[i]).attr('name');
+        let attrs = name.split('-');
+        if(attrs.length > 1){
+            if(isUndef(rst[attrs[0]])){
+                rst[attrs[0]] = Object.create(null);
+            }
+            if(isDef(rst[attrs[0]])) {
+                rst[attrs[0]][attrs[1]] = parseInt($(inputs[i]).val());
+            }
+        }
+        else {
+            rst[attrs[0]] = parseInt($(inputs[i]).val());
+        }
+    }
+    return rst;
+}
+
+function v_initPanel(data){
+    if(this.isDef(data)){
+        for(let attr in data){
+            if(this.isObj(data[attr])){
+                for(let subAttr in data[attr]){
+                    let str = attr + '-' + subAttr;
+                    let jqs = 'input[name="' + str + '"]';
+                    $(jqs).val(data[attr][subAttr]);
+                    $(jqs).attr('min', defaultDecimal.min);
+                    $(jqs).attr('max', defaultDecimal.max);
+                }
+            }
+            else {
+                let str = attr + '';
+                let jqs = 'input[name="' + str + '"]';
+                $(jqs).val(data[attr]);
+                $(jqs).attr('min', defaultDecimal.min);
+                $(jqs).attr('max', defaultDecimal.max);
+            }
+        }
+    }
+}
+
+function e_validIn(inputs){
+    for(let i = 0, len = inputs.length; i < len; i++){
+        let orgV = $(inputs[i]).val();
+        $(inputs[i]).keydown(function () {
+            let v = $(this).val();
+            if(v.trim().length > 0 && isValidDigit(v)){
+                orgV = v;
+            }
+        });
+        $(inputs[i]).keyup(function () {
+            let v = $(this).val();
+            if(v.trim().length === 0 || !isValidDigit(v)){
+                alert('小数位数范围在0-6!');
+                $(this).val(orgV);
+            }
+            else{
+                //let newV = parseInt(v);
+            }
+        });
+    }
+}
+
+function e_bindCof(btn){
+    btn.bind('click', function () {
+        //获取更新的数据
+        let updateDecimal = m_getDecimalData($('input', '#poj-settings-decimal'));
+        if(toUpdateDecimal(decimalObj, updateDecimal)){
+            a_updateDigits(updateDecimal);
+        }
+    });
+}
+
+function e_unbindCof(btn){
+    btn.unbind();
+}
+
+function a_updateDigits(updateDecimal){
+    let url = '/pm/api/updateProjects';
+    let data = {
+        updateType: 'update',
+        updateData: {
+            ID: parseInt(scUrlUtil.GetQueryString('project')),
+            'property.decimal': updateDecimal
+        }
+    };
+    let postData = {
+        user_id: userID,
+        updateData: [data]
+    };
+    let scCaller = function (resultData) {
+        //update data
+        setDecimal(decimalObj, updateDecimal);
+        let v_data = m_getInitData(decimalObj);
+        v_initPanel(v_data);
+        console.log(decimalObj);
+    };
+    let errCaller = function () {
+        alert('更新小数位数失败!');
+        let v_data = m_getInitData(decimalObj);
+        v_initPanel(v_data);
+    };
+    CommonAjax.post(url, postData, scCaller, errCaller);
+}
+
+$(document).ready(function () {
+    $('#poj-set').on('shown.bs.modal', function (e) {
+        let v_data = m_getInitData(decimalObj);
+        v_initPanel(v_data);
+    });
+    //绑定确定按钮
+    e_bindCof($('#property_ok'));
+    //绑定小数位数输入控制
+    e_validIn($('input', '#poj-settings-decimal'));
+});

+ 62 - 34
web/building_saas/main/js/views/project_view.js

@@ -139,7 +139,9 @@ var projectObj = {
             }
             return nodes;
         }
-        value = value.toDecimal(projectObj.project.Decimal.common.quantity);
+        if (value) {
+            value = value.toDecimal(projectObj.project.Decimal.common.quantity);
+        }
         if (node.sourceType === projectObj.project.Bills.getSourceType()) {
             calcFees.setFee(node.data, fieldName, value);
             calc.calcNode(node, true);
@@ -250,15 +252,9 @@ var projectObj = {
             this.updateRationCode(node, value);
         }
     },
-    mainSpreadEditEnded: function (sender, info) {
-        let project = projectObj.project;
-        let node = project.mainTree.items[info.row];
-        let colSetting = projectObj.mainController.setting.cols[info.col];
-        let fieldName = projectObj.mainController.setting.cols[info.col].data.field;
-        // 检查输入类型等
-        let value = projectObj.checkSpreadEditingText(info.editingText, colSetting);
-
-        if (value && value !== calcFees.getFee(node.data, fieldName)) {
+    updateCellValue: function (node, value, colSetting) {
+        let project = projectObj.project, fieldName = colSetting.data.field;
+        if (value !== calcFees.getFee(node.data, fieldName)) {
             if (fieldName === 'code') {
                 projectObj.updateCode(node, value);
             } else if (fieldName === 'quantity' && project.quantity_detail.quantityEditChecking(value,node,fieldName)) {
@@ -276,7 +272,7 @@ var projectObj = {
                     project.VolumePrice.updateField(node.source, fieldName, value, true);
                 }
                 if (colSetting.data.wordWrap) {
-                    info.sheet.autoFitRow(info.row);
+                    info.sheet.autoFitRow(node.serialNo());
                 }
                 projectObj.mainController.refreshTreeNode([node]);
             }
@@ -286,6 +282,29 @@ var projectObj = {
             projectObj.mainController.refreshTreeNode([node], false);
         }
     },
+    mainSpreadEditEnded: function (sender, info) {
+        let project = projectObj.project;
+        let node = project.mainTree.items[info.row];
+        let colSetting = projectObj.mainController.setting.cols[info.col];
+        let fieldName = projectObj.mainController.setting.cols[info.col].data.field;
+        // 检查输入类型等
+        let value = projectObj.checkSpreadEditingText(info.editingText, colSetting);
+
+        projectObj.updateCellValue(node, value, colSetting);
+    },
+    mainSpreadRangeChanged: function (sender, info) {
+        let project = projectObj.project, setting = projectObj.mainController.setting;
+        if (info.changedCells.length > 0) {
+            for (let changedCell of info.changedCells) {
+                let cell = info.sheet.getCell(changedCell.row, changedCell.col);
+                let node = project.mainTree.items[changedCell.row];
+                let colSetting = setting.cols[changedCell.col];
+                let value = projectObj.checkSpreadEditingText(cell.text(), colSetting);
+
+                projectObj.updateCellValue(node, value, colSetting);
+            }
+        }
+    },
     checkMainSpread: function () {
         if (!this.mainSpread) {
             this.mainSpread = SheetDataHelper.createNewSpread($('#billsSpread')[0]);
@@ -338,6 +357,7 @@ var projectObj = {
                 that.mainController.bind(TREE_SHEET_CONTROLLER.eventName.treeSelectedChanged, that.treeSelectedChanged);
 
                 that.mainSpread.bind(GC.Spread.Sheets.Events.EditEnded, that.mainSpreadEditEnded);
+                that.mainSpread.bind(GC.Spread.Sheets.Events.RangeChanged, that.mainSpreadRangeChanged);
                 that.loadMainSpreadContextMenu();
             }
             else {
@@ -425,7 +445,7 @@ var projectObj = {
                         return !selected;
                     },
                     callback: function () {
-                        var selected = controller.tree.selected;
+                        var selected = controller.tree.selected, parent = selected.parent;
                         if (selected) {
                             if (selected.sourceType === project.Bills.getSourceType()) {
                                 project.Bills.deleteBills(selected.source);
@@ -437,6 +457,7 @@ var projectObj = {
                                 project.VolumePrice.delete(selected.source);
                                 controller.delete();
                             };
+                            projectObj.converseCalculateBills(parent);
                         }
                     }
                 },
@@ -452,16 +473,18 @@ var projectObj = {
     },
     // 计算node及node的所有父项
     converseCalculateBills: function (node) {
-        let calc = new BillsCalcHelper(this.project);
-        calc.calcNode(node, true);
-        let cur = node, nodes = [];
-        while (cur) {
-            nodes.push(cur);
-            cur = cur.parent;
+        if (node) {
+            let calc = new BillsCalcHelper(this.project);
+            calc.calcNode(node, true);
+            let cur = node, nodes = [];
+            while (cur) {
+                nodes.push(cur);
+                cur = cur.parent;
+            }
+            this.mainController.refreshTreeNode(nodes, false);
+            this.project.Bills.updateNodes(nodes, true);
+            calc = null;
         }
-        this.mainController.refreshTreeNode(nodes, false);
-        this.project.Bills.updateNodes(nodes, true);
-        calc = null;
     },
     // 计算全部清单
     calculateAll: function () {
@@ -487,7 +510,7 @@ $('#insert').click(function () {
 });
 $('#delete').click(function () {
     var controller = projectObj.mainController, project = projectObj.project;
-    var selected = controller.tree.selected;
+    var selected = controller.tree.selected, parent = selected.parent;
 
     if (selected) {
         if (selected.sourceType === project.Bills.getSourceType()) {
@@ -500,15 +523,17 @@ $('#delete').click(function () {
             project.VolumePrice.delete(selected.source);
             controller.delete();
         };
+        projectObj.converseCalculateBills(parent);
     }
 });
 $('#upLevel').click(function () {
     var controller = projectObj.mainController, project = projectObj.project;
-    var selected = controller.tree.selected;
+    var selected = controller.tree.selected, orgParent = selected.parent;
 
     if (selected && selected.sourceType === project.Bills.getSourceType()) {
         project.Bills.upLevelBills(selected.source);
         controller.upLevel();
+        projectObj.converseCalculateBills(orgParent);
     }
 });
 $('#downLevel').click(function () {
@@ -516,12 +541,13 @@ $('#downLevel').click(function () {
     var selected = controller.tree.selected;
     if (selected && selected.sourceType === project.Bills.getSourceType()) {
         project.Bills.downLevelBills(selected.source);
-        controller.downLevel();
+        controller.downLevel();       
+        projectObj.converseCalculateBills(selected.parent);
     }
 });
 $('#upMove').click(function () {
     var controller = projectObj.mainController, project = projectObj.project;
-    var selected = controller.tree.selected, pre, preSerialNo;
+    var selected = controller.tree.selected, pre = selected.preSibling, preSerialNo;
 
     if (selected.sourceType === project.Bills.getSourceType()) {
         project.Bills.upMoveBills(selected.source);
@@ -538,15 +564,17 @@ $('#downMove').click(function () {
     var controller = projectObj.mainController, project = projectObj.project;
     var selected = controller.tree.selected, next, nextSerialNo;
 
-    if (selected.sourceType === project.Bills.getSourceType()) {
-        project.Bills.downMoveBills(selected.source);
-        controller.downMove();
-    } else if (selected.sourceType === project.Ration.getSourceType()) {
-        project.Ration.changePos(selected.source, selected.nextSibling.source);
-        controller.downMove();
-    } else if (selected.sourceType === project.VolumePrice.getSourceType()) {
-        project.VolumePrice.changePos(selected.source, selected.nextSibling.source);
-        controller.downMove();
+    if (selected) {
+        if (selected.sourceType === project.Bills.getSourceType()) {
+            project.Bills.downMoveBills(selected.source);
+            controller.downMove();
+        } else if (selected.sourceType === project.Ration.getSourceType()) {
+            project.Ration.changePos(selected.source, selected.nextSibling.source);
+            controller.downMove();
+        } else if (selected.sourceType === project.VolumePrice.getSourceType()) {
+            project.VolumePrice.changePos(selected.source, selected.nextSibling.source);
+            controller.downMove();
+        }
     }
 });
 

+ 18 - 5
web/building_saas/main/js/views/std_bills_lib.js

@@ -73,16 +73,26 @@ var billsLibObj = {
             }
             return null;
         };
+        let sortJobsAndFeatures = function (arr) {
+            arr.sort(function (a, b) {
+                let rst = 0;
+                if(a.serialNo > b.serialNo) rst = 1;
+                else if(a.serialNo < b.serialNo) rst = -1;
+                return rst;
+            });
+        };
         var getBillsJobs = function (node) {
             var jobs = [], i, jobData = null;
             if (stdBillsJobData && node && node.data.jobs) {
                 for (i = 0; i < node.data.jobs.length; i++) {
                     jobData = findData(node.data.jobs[i], 'id', stdBillsJobData);
                     if (jobData) {
+                        jobData.serialNo = node.data.jobs[i].serialNo;
                         jobs.push(jobData);
                     }
                 }
             }
+            sortJobsAndFeatures(jobs);
             return jobs;
         };
         var getBillsFeatures = function (node) {
@@ -91,10 +101,12 @@ var billsLibObj = {
                 for (i = 0; i < node.data.items.length; i++) {
                     featureData = findData(node.data.items[i], 'id', stdBillsFeatureData);
                     if (featureData) {
+                        featureData.serialNo = node.data.items[i].serialNo;
                         features.push(featureData);
                     }
                 }
             }
+            sortJobsAndFeatures(features);
             return features;
         };
         var showJobs = function (jobs) {
@@ -110,6 +122,7 @@ var billsLibObj = {
             $('#stdBillsRemarkTab').hide();
             billsLibObj.refreshBillsRelaSpread();
             billsLibObj.checkBillsRelaSpread();
+            sortJobsAndFeatures(getBillsJobs(node));
             showJobs(getBillsJobs(node));
             showFeatures(getBillsFeatures(node));
         };
@@ -218,7 +231,7 @@ var billsLibObj = {
         ],
         "defaultRowHeight": 21,
         "cols":[{
-            "width":150,
+            "width":160,
             "readOnly": true,
             "head":{
                 "titleNames":["项目编码"],
@@ -235,7 +248,7 @@ var billsLibObj = {
                 "font":"Arial"
             }
         }, {
-            "width":120,
+            "width":150,
             "readOnly": true,
             "head":{
                 "titleNames":["项目名称"],
@@ -252,7 +265,7 @@ var billsLibObj = {
                 "font":"Arial"
             }
         }, {
-            "width":40,
+            "width":50,
             "readOnly": true,
             "head":{
                 "titleNames":["计量单位"],
@@ -295,7 +308,7 @@ var billsLibObj = {
         "headRowHeight":[25],
         "defaultRowHeight": 21,
         "cols":[{
-            "width":200,
+            "width":160,
             "readOnly":true,
             "head":{
                 "titleNames":["工作内容"],
@@ -319,7 +332,7 @@ var billsLibObj = {
         "headRowHeight":[25],
         "defaultRowHeight": 21,
         "cols":[{
-            "width":200,
+            "width":160,
             "readOnly":true,
             "head":{
                 "titleNames":["项目特征"],

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

@@ -131,7 +131,7 @@ var rationLibObj = {
         "defaultRowHeight": 21,
         "treeCol": 0,
         "cols":[{
-            "width":300,
+            "width":400,
             "readOnly": true,
             "head":{
                 "titleNames":["名称"],
@@ -155,7 +155,7 @@ var rationLibObj = {
         "headRowHeight":[20],
         "defaultRowHeight": 21,
         "cols":[{
-            "width":100,
+            "width":60,
             "readOnly": true,
             "head":{
                 "titleNames":["编码"],
@@ -172,7 +172,7 @@ var rationLibObj = {
                 "font":"Arial"
             }
         }, {
-            "width":180,
+            "width":220,
             "readOnly": true,
             "head":{
                 "titleNames":["名称"],
@@ -189,7 +189,7 @@ var rationLibObj = {
                 "font":"Arial"
             }
         }, {
-            "width":50,
+            "width":55,
             "readOnly":true,
             "head":{
                 "titleNames":["单位"],
@@ -206,7 +206,7 @@ var rationLibObj = {
                 "font":"Arial"
             }
         }, {
-            "width":80,
+            "width":60,
             "readOnly":true,
             "head":{
                 "titleNames":["基价"],

+ 5 - 0
web/building_saas/pm/html/project-management-Recycle.html

@@ -1,6 +1,11 @@
 <div class="toolsbar">
     <legend class="m-0 pb-1">全部</legend>
 </div>
+<div class="top-content">
+    <div class="main-data-top" id="gc_waiting">
+        <p style="text-align: center; margin-top: 30px;">正在加载数据...</p>
+    </div>
+</div>
 <div class="poj-list">
     <table class="table table-hover table-sm mb-5" id="gcTree">
         <thead>

+ 23 - 2
web/building_saas/pm/html/project-management.html

@@ -177,7 +177,7 @@
                     <div class="col-md-6">
                         <legend>单价文件</legend>
                         <table class="table table-bordered table-hover table-sm" id="summary-project-unit-price-table">
-                            <thead><th></th><th>名称</th></thead>
+                            <thead><th></th><th>名称</th><th width="50">使用</th></thead>
                             <tbody>
                             <tr><td>1</td><td>A单价文件</td></tr>
                             <tr><td>2</td><td>B单价文件</td></tr>
@@ -188,7 +188,7 @@
                     <div class="col-md-6">
                         <legend>费率文件</legend>
                         <table class="table table-bordered table-hover table-sm" id="summary-project-fee-table">
-                            <thead><th></th><th>名称</th></thead>
+                            <thead><th></th><th>名称</th><th width="50">使用</th></thead>
                             <tbody>
                             <tr><td>1</td><td>A费率文件</td></tr>
                             <tr><td>2</td><td>B费率文件</td></tr>
@@ -501,6 +501,27 @@
         </div>
     </div>
 </div>
+<!--弹出删除 单价/费率 文件-->
+<div class="modal fade" id="del-wj" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">删除确认</h5>
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">&times;</span>
+                </button>
+            </div>
+            <div class="modal-body">
+                <h5 class="text-danger">删除 "C单价文件" ?</h5>
+                <p class="">删除后,你可以在回收站找到它。</p>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary" data-dismiss="modal" id="fileDelCancel">取消</button>
+                <a href="javascript:void(0);" class="btn btn-danger" id="fileDelConfirm">删除</a>
+            </div>
+        </div>
+    </div>
+</div>
 <!-- JS. -->
 <!-- inject:js -->
 <script src="/lib/jquery/jquery-3.2.1.min.js"></script>

+ 210 - 120
web/building_saas/pm/js/pm_gc.js

@@ -1,55 +1,10 @@
 /**
  * Created by Zhong on 2017/10/30.
  */
-//测试数据
-let deleteInfo = {deleteBy: 76075, deleteDateTime: '2017-10-31', deleted: true};
-let temp_gc_datas = [
-    {ID: 1, ParentID: -1, NextSiblingID: 7, name: 'testTreeA', projType: 'Project', userID: 76075, createDateTime: '2017-10-30', deleteInfo: deleteInfo,
-        unitPriceFiles: [{name: 'uA', id: 100, root_project_id: 1, user_id: 76075, deleteInfo: deleteInfo}, {name: 'uB', id: 101, root_project_id: 1, user_id: 76075, deleteInfo: deleteInfo}],
-        feeRateFiles: [{name: 'fA', ID: 'fr-1', rootProjectID: 1, userID: 76075, deleteInfo: deleteInfo}, {name: 'fB', ID: 'fr-2', rootProjectID: 1, userID: 76075, deleteInfo: deleteInfo}]},
-    {ID: 2, ParentID: 1, NextSiblingID: 3, name: 'enA', projType: 'Engineering', userID: 76075, createDateTime: '2017-10-30', deleteInfo: deleteInfo},
-    {ID: 3, ParentID: 1, NextSiblingID: -1, name: 'enB', projType: 'Engineering', userID: 76075, createDateTime: '2017-10-30', deleteInfo: deleteInfo},
-    {ID: 4, ParentID: 3, NextSiblingID: 5, name: 'tenderA', projType: 'Tender', userID: 76075, createDateTime: '2017-10-30', deleteInfo: deleteInfo, property: {feeFile: {id: 'fr-1', name: 'fA'}, unitPriceFile: {id: 100, name: 'uA'}}},
-    {ID: 5, ParentID: 3, NextSiblingID: 6, name: 'tenderB', projType: 'Tender', userID: 76075, createDateTime: '2017-10-30', deleteInfo: deleteInfo, property: {feeFile: {id: 'unD-1', name: 'unDF'}, unitPriceFile: {id: 100, name: 'uA'}}},
-    {ID: 6, ParentID: 3, NextSiblingID: -1, name: 'tenderC', projType: 'Tender', userID: 76075, createDateTime: '2017-10-30', deleteInfo: deleteInfo, property: {feeFile: {id: 'fr-2', name: 'fB'}, unitPriceFile: {id: 101, name: 'uB'}}},
-    {ID: 7, ParentID: -1, NextSiblingID: 10, name: 'testTreeB', projType: 'Project', userID: 76075, createDateTime: '2017-10-30', deleteInfo: deleteInfo,
-        unitPriceFiles: [{name: 'uC', id: 102, root_project_id: 7, user_id: 76075, deleteInfo: deleteInfo}, {name: 'uD', id: 103, root_project_id: 7, user_id: 76075, deleteInfo: deleteInfo}],
-        feeRateFiles: [{name: 'fC', ID: 'fr-3', rootProjectID: 7, userID: 76075, deleteInfo: deleteInfo}]},
-    {ID: 8, ParentID: 7, NextSiblingID: -1, name: 'enC', projType: 'Engineering', userID: 76075, createDateTime: '2017-10-30', deleteInfo: deleteInfo},
-    {ID: 9, ParentID: 8, NextSiblingID: -1, name: 'tenderD', projType: 'Tender', userID: 76075, createDateTime: '2017-10-30', deleteInfo: deleteInfo, property: {feeFile: {id: 'fr-3', name: 'fC'}, unitPriceFile: {id: 102, name: 'uC'}}},
-    {ID: 10, ParentID: -1, NextSiblingID: -1, name: 'testTreeC', projType: 'Project', userID: 76075, createDateTime: '2017-10-30', unitPriceFiles: [], feeRateFiles: [{name: 'fC', ID: 'fr-3', rootProjectID: 7, userID: 76075, deleteInfo: deleteInfo}]},
-    {ID: 11, ParentID: 10, NextSiblingID: -1, name: 'enD', projType: 'Engineering', userID: 76075, createDateTime: '2017-10-30'},
-    {ID: 12, ParentID: 11, NextSiblingID: -1, name: 'tenderE', projType: 'Tender', userID: 76075, createDateTime: '2017-10-30', deleteInfo: deleteInfo, property: {feeFile: {id: 'fr-3', name: 'fC'}, unitPriceFile: {id: 103, name: 'uD'}}},
-    {ID: 13, ParentID: 2, NextSiblingID: 14, name: 'tenderF', projType: 'Tender', userID: 76075, createDateTime: '2017-10-30', deleteInfo: deleteInfo, property: {feeFile: {id: 'unD-2', name: 'unDF2'}, unitPriceFile: {id: 102, name: 'uC'}}},
-    {ID: 14, ParentID: 2, NextSiblingID: 15, name: 'tenderG', projType: 'Tender', userID: 76075, createDateTime: '2017-10-30', deleteInfo: deleteInfo, property: {feeFile: {id: 'fr-3', name: 'fC'}, unitPriceFile: {id: 102, name: 'uC'}}},
-    {ID: 15, ParentID: 2, NextSiblingID: -1, name: 'tenderH', projType: 'Tender', userID: 76075, createDateTime: '2017-10-30', deleteInfo: deleteInfo, property: {feeFile: {id: 'qwqw', name: 'qweq'}, unitPriceFile: {id: 300, name: 'u30'}}}
-
-];
-
-/*function getTestDatas(){
-    let rst = [];
-    for(let i = 0; i < 1000; i ++){
-        let nid = i + 1 < 1000 ? i + 1 : -1;
-        let obj = {ID: i, ParentID: -1, NextSiblingID: nid, name : 'test' + i, projType: 'Project', userID: 76075, createDateTime: '2017-11-3', deleteInfo: deleteInfo,
-            unitPriceFiles: [{name: 'up' + i, id: i + 10000, deleteInfo: deleteInfo}], feeRateFiles: [{name: 'ff' + i, id: 'fr-' + i, deleteInfo: deleteInfo}]};
-        rst.push(obj);
-    }
-    return rst;
-}*/
 let gcTree = null;
 let decDate = null;//恢复后的名称后缀(时间+恢复)
-/*let projectType = {
-    folder: 'Folder',
-    tender: 'Tender',
-    project: 'Project',
-    engineering: 'Engineering'
-};*/
-let fileType = {
-    unitPriceFile: 'UnitPriceFile',
-    feeRateFile: 'FeeRateFile'
-};
 //恢复路径t = tender, e = engineering, p = project
-let recPath = {t: 'T', t_e: 'T_E', t_e_p: 'T_E_P', e: 'E', e_p: 'E_P', p: 'P'};
+const recPath = {t: 'T', t_e: 'T_E', t_e_p: 'T_E_P', e: 'E', e_p: 'E_P', p: 'P'};
 let gcTreeSetting = {
     tree: {
         id: 'ID',
@@ -103,9 +58,13 @@ let gcTreeSetting = {
             event: {
                 getText: function (html, node, text) {
                     if (node.data.projType === projectType.tender) {
-                        html.push(deleted(node) ?
-                            node.data.deleteInfo.deleteDateTime : '');
-                     //   html.push(text ? new Date(text).Format('yyyy-MM-dd') : '');
+                        if(deleted(node)){
+                            let localDateTime = new Date(node.data.deleteInfo.deleteDateTime).toLocaleDateString();
+                            html.push(new Date(localDateTime).Format('yyyy-MM-dd'));
+                        }
+                        else{
+                            html.push('');
+                        }
                     }
                 }
             }
@@ -117,7 +76,13 @@ let gcTreeSetting = {
             event: {
                 getText: function (html, node, text) {
                     if (node.data.projType === projectType.tender) {
-                      html.push(text ? text : '');
+                        if(deleted(node)){
+                            let localDateTime = new Date(text).toLocaleDateString();
+                            html.push(new Date(localDateTime).Format('yyyy-MM-dd'));
+                        }
+                        else{
+                            html.push('');
+                        }
                     }
                 }
             }
@@ -148,11 +113,6 @@ let gcTreeSetting = {
                         $('p', '#rePoj .modal-body').remove();
                         $('#rePoj .modal-header').html(v_getTitle(node));
                         $('#rePoj .modal-body').html(v_getMoBody(node, tenderNodes));
-                        console.log(node);
-                        console.log(node.preSibling());
-                        //test 获取更新的数据
-                        /*let updateDatas = m_getRecDatas(node);
-                        console.log(updateDatas);*/
                     });
                 }
             }
@@ -202,6 +162,7 @@ let gcTreeSetting = {
 
 $(document).ready(function () {
     $('#tab_pm_gc').on('show.bs.tab', function () {
+        $('#gc_waiting').show();
         gc_init();
         Tree = null;
     });
@@ -213,9 +174,12 @@ function gc_init(){
     let table = $('#gcTree');
     $('thead', table).remove();
     $('tbody', table).remove();
-    m_buildVirtualTree(temp_gc_datas);
-    gcTree = $.fn.treeTable.init(table, gcTreeSetting, temp_gc_datas);
-    //gcTree = $.fn.treeTable.init(table, gcTreeSetting, getTestDatas());
+    a_getGC(function (datas) {
+        m_buildVirtualTree(datas);
+        let normalDatas = m_VTreeToDatas(datas);
+        gcTree = $.fn.treeTable.init(table, gcTreeSetting, normalDatas);
+        $('#gc_waiting').hide();
+    });
 }
 
 //项目恢复模态框标题
@@ -294,6 +258,7 @@ function v_recFiles(project, fileIds, type){
                 let id = projFiles[i].id || projFiles[i].ID || null;
                 if(id && id === fileIds[j]){
                     isExist = true;
+                    break;
                 }
             }
             if(!isExist){
@@ -313,7 +278,12 @@ function v_removeNode(node){
     gcTree.removeNode(node);
     let parent = node.parent || null;
     if(parent && parent.children.length === 0 && parent.data !== undefined){
-        v_removeNode(parent);
+        if(parent.data.projType === projectType.project && fileEmpty(parent)){
+            v_removeNode(parent);
+        }
+        else if(parent.data.projType !== projectType.project){
+            v_removeNode(parent);
+        }
     }
 }
 
@@ -331,6 +301,9 @@ function v_refreshNode(node){
 //将获取的回收站中的数据建虚拟树
 function m_buildVirtualTree(datas){
     for(let i = 0, len = datas.length; i < len; i++){
+        if(datas[i].projType === projectType.project){
+            datas[i].ParentID = -1;
+        }
         let children = datas[i].children || null;
         if(children){
             m_buildVirtualTree(children);
@@ -345,6 +318,25 @@ function m_buildVirtualTree(datas){
     }
 }
 
+function m_VTreeToDatas(datas){
+    let rst = [];
+    let projs = datas;
+    rst = rst.concat(projs);
+    for(let i = 0, len = projs.length; i < len; i++){
+        let p_engs = projs[i].children || null;
+        if(p_engs){
+            rst = rst.concat(p_engs);
+            for(let j = 0, jLen = p_engs.length; j < jLen; j ++){
+                let e_tenders = p_engs[j].children || null;
+                if(e_tenders){
+                    rst = rst.concat(e_tenders);
+                }
+            }
+        }
+    }
+    return rst;
+}
+
 //获得当前节点的tenders数据,模态提示框用
 function m_getTenders(node){
     if(node.data.projType === projectType.tender) return [node];
@@ -412,6 +404,7 @@ function m_getRecDatas(oprNode){
             for(let i = 0, len = tenders.length; i < len; i++){
                 rstProj = rstProj.concat(getUpdateDatas(projectType.tender, tenders[i], true, false));
             }
+            rstProj = deWeightName(rstProj);
             //恢复单价、费率文件
             rstFile = rstFile.concat(getUpdateFiles(tenders, project));
         }
@@ -427,42 +420,54 @@ function m_getRecDatas(oprNode){
         path = recPath.p;
         let engineerings = oprNode.children;
         if(engineerings.length > 0){
-            let allTenders = [];
+            let allTenders = [], rstEngs = [];
             for(let i = 0, len = engineerings.length; i < len; i++){
                 //恢复单项工程
-                rstProj = rstProj.concat(getUpdateDatas(projectType.engineering, engineerings[i], false, false));
+                rstEngs = rstEngs.concat(getUpdateDatas(projectType.engineering, engineerings[i], false, false));
                 let tenders = engineerings[i].children;
                 allTenders = allTenders.concat(tenders);
+                let rstTends = [];
                 for(let j = 0, jLen = tenders.length; j < jLen; j++){
                     //恢复单位工程
-                    rstProj = rstProj.concat(getUpdateDatas(projectType.tender, tenders[j], false, false));
+                    rstTends = rstTends.concat(getUpdateDatas(projectType.tender, tenders[j], false, false));
                 }
+                //去除重名
+                rstTends = deWeightName(rstTends);
+                rstProj = rstProj.concat(rstTends);
             }
+            //去除重名
+            rstEngs = deWeightName(rstEngs);
+            rstProj = rstProj.concat(rstEngs);
             //恢复单价、费率文件
             rstFile = rstFile.concat(getUpdateFiles(allTenders, oprNode));
+
         }
         //恢复建设项目
         rstProj = rstProj.concat(getUpdateDatas(projectType.project, oprNode, false, false));
     }
-    console.log(path);
     rst.proj = rstProj;
     rst.file = rstFile;
     return rst;
 }
 
 //获得勾选的单价、费率文件的id
-function m_getFilesIds(nodes){
-    let rstSet = new Set();
+function m_getFilesObjs(nodes){
+    let rst = [];
     for(let i = 0, len = nodes.length; i < len; i++){
         let fileId = $(nodes[i]).attr('fileId') || null;
         if(fileId){
+            let dispName = $('td:eq(0)', $(nodes[i])[0].parentNode.parentNode)[0].textContent;
+            let name = dispName.slice(0, dispName.length - 4);
             if($(nodes[i]).attr('fileType') === fileType.unitPriceFile){
                 fileId = parseInt(fileId);
             }
-            rstSet.add(fileId);
+            let obj = Object.create(null);
+            obj.id = fileId;
+            obj.name = name;
+            rst.push(obj);
         }
     }
-    return Array.from(rstSet);
+    return rst;
 }
 
 function m_project(node){
@@ -481,21 +486,40 @@ function m_project(node){
 //点击单价文件、费率文件下的恢复操作(确认)
 function e_recFiles(btn){
     btn.bind('click', function () {
-        let recIds = m_getFilesIds($('[name = "fileItems"]:checked'));
-        let fileType = $('[name = "fileItems"]:checked').attr('fileType');
+        decDate = '(' + new Date().Format('MM-dd hh:mm:ss') + '恢复)';
+        let recObjs = m_getFilesObjs($('[name = "fileItems"]:checked'));
+        let recIds = [];
+        for(let i of recObjs){
+            recIds.push(i.id);
+        }
+        let type = $('[name = "fileItems"]:checked').attr('fileType');
         let selected = gcTree.selected();
         //backend
         let updateDatas = [];
-        for(let i = 0, len = recIds.length; i < len; i++){
-            updateDatas.push(getUpdateObj(fileType, {id: recIds[i]}, {deleteInfo: null}));
-        }
-        //front
-        if(recIds.length > 0){
-            v_recFiles(selected, recIds, fileType);
-            if(deleted(selected)){
-                delete selected.data.deleteInfo;
+        for(let i = 0, len = recObjs.length; i < len; i++){
+            let findData = type === fileType.unitPriceFile ? {id: recObjs[i].id} : {ID: recObjs[i].id};
+            updateDatas.push(getUpdateObj(type, findData, {deleteInfo: null, name: delPostFix(recObjs[i].name) + decDate}));
+        }
+        //恢复建设项目
+        if(updateDatas.length > 0 && deleted(selected)){
+            updateDatas.push(getUpdateObj(projectType.project, {ID: selected.data.ID}, {deleteInfo: null, name: delPostFix(selected.data.name) + decDate}));
+        }
+        updateDatas = deWeightName(updateDatas);
+        a_rec(updateDatas, caller);
+        function caller(){
+            //front
+            if(recIds.length > 0){
+                v_recFiles(selected, recIds, type);
+                if(deleted(selected)){
+                    delete selected.data.deleteInfo;
+                }
+                if(fileEmpty(selected) && selected.children.length === 0){
+                    gcTree.removeNode(selected);
+                }
+                else {
+                    gcTree.refreshNodesDom([selected]);
+                }
             }
-            gcTree.refreshNodesDom([selected]);
         }
     });
 }
@@ -508,20 +532,21 @@ function e_recProj(btn){
         let updateObj = m_getRecDatas(selected);
         let updateDatas = updateObj.proj.concat(updateObj.file);
         let fileObj = getRecFileObj(updateObj.file);
-        console.log(updateObj);
         //保存成功后回调front
-        //front
-        let project = m_project(selected);
-        if(project){
-            if(fileObj[fileType.unitPriceFile].length > 0){
-                v_recFiles(project, fileObj[fileType.unitPriceFile], fileType.unitPriceFile);
-            }
-            if(fileObj[fileType.feeRateFile].length > 0){
-                v_recFiles(project, fileObj[fileType.feeRateFile], fileType.feeRateFile);
+        a_rec(updateDatas, caller);
+        function caller() {
+            let project = m_project(selected);
+            if(project){
+                if(fileObj[fileType.unitPriceFile].length > 0){
+                    v_recFiles(project, fileObj[fileType.unitPriceFile], fileType.unitPriceFile);
+                }
+                if(fileObj[fileType.feeRateFile].length > 0){
+                    v_recFiles(project, fileObj[fileType.feeRateFile], fileType.feeRateFile);
+                }
             }
+            v_removeNode(selected);
+            v_refreshNode(selected);
         }
-        v_removeNode(selected);
-        v_refreshNode(selected);
     });
 }
 
@@ -529,6 +554,7 @@ function a_getGC(callback){
     $.ajax({
         type: 'post',
         url: '/pm/api/getGCDatas',
+        data: {data: JSON.stringify({user_id: userID})},
         dataType: 'json',
         timeout: 5000,
         success: function (result) {
@@ -538,14 +564,14 @@ function a_getGC(callback){
                 }
             }
         }
-    })
+    });
 }
 
 function a_rec(nodes, callback){
     $.ajax({
         type: 'post',
         url: '/pm/api/recGC',
-        data: {nodes: JSON.stringify(nodes)},
+        data: {data: JSON.stringify({user_id: userID, nodes: nodes})},
         dataType: 'json',
         timeout: 5000,
         success: function (result) {
@@ -558,14 +584,67 @@ function a_rec(nodes, callback){
     })
 }
 
+//去除重名,回收站不处理重名,只保证恢复到项目管理中不出现重名
+function deWeightName(datas){
+    let rst = [];
+    let _deWeight = Object.create(null), prefix = 'name_';
+    //按同名分组
+    for(let i = 0, len = datas.length; i < len; i++){
+        let data = datas[i];
+        let _names = _deWeight[prefix + data.updateData.name] || null;
+        if(!_names){
+            _names = _deWeight[prefix + data.updateData.name] = [];
+        }
+        if(_names){
+            _names.push(data);
+        }
+    }
+    for(let _name in _deWeight){
+        let sameNameDatas = _deWeight[_name];
+        let count = 0;
+        for(let i = 0, len = sameNameDatas.length; i < len; i++){
+            let postFix = '(' + count + ')';
+            if(i > 0){
+                sameNameDatas[i].updateData.name = sameNameDatas[i].updateData.name + postFix;
+            }
+            count ++;
+            rst.push(sameNameDatas[i]);
+        }
+    }
+    return rst;
+}
+
+//去除名称后缀(Date恢复)
+function delPostFix (str){
+    let rst = '';
+    rst = delPostRecFix(delPostNameFix(str));
+    return rst;
+}
+function delPostRecFix (str){
+    let rst = '';
+    let re = /(+[0-9]{2}-[0-9]{2}\s+[0-9]{2}:[0-9]{2}:[0-9]{2}恢复)/g;
+    rst = str.replace(re, '');
+    return rst;
+}
+function delPostNameFix (str){
+    let rst = '';
+    let re = /(+[0-9][0-9]*)/g;
+    rst = str.replace(re, '');
+    return rst;
+}
+
 function deleted(node){
-    return node.data.deleteInfo !== undefined && node.data.deleteInfo.deleted;
+    return node.data.deleteInfo !== undefined && node.data.deleteInfo && node.data.deleteInfo.deleted;
+}
+
+function fileEmpty(node){
+    return node.data.unitPriceFiles.length === 0 && node.data.feeRateFiles.length === 0;
 }
 
 function fIsExist(files, id, type){
     let isExist = false;
     for(let i = 0, len = files.length; i < len; i++){
-        if((type === fileType.unitPriceFile && files[i].id === id) || (type === fileType.feeRateFile && files[i].ID === id)){
+        if((type === fileType.unitPriceFile && files[i].id === parseInt(id)) || (type === fileType.feeRateFile && files[i].ID === id)){
             isExist = true;
             break;
         }
@@ -577,12 +656,12 @@ function getRecFileObj(files){
     let rst = Object.create(null);
     let rst_UF = [], rst_FF = [];
     for(let i = 0, len = files.length; i < len; i++){
-        if(files[i].findData !== undefined && files[i].findData.id !== undefined){
-            if(files[i].updateType === fileType.unitPriceFile){
+        if(files[i].findData !== undefined){
+            if(files[i].updateType === fileType.unitPriceFile && files[i].findData.id !== undefined){
                 rst_UF.push(files[i].findData.id);
             }
-            else if(files[i].updateType === fileType.feeRateFile){
-                rst_FF.push(files[i].findData.id);
+            else if(files[i].updateType === fileType.feeRateFile && files[i].findData.ID !== undefined){
+                rst_FF.push(files[i].findData.ID);
             }
         }
     }
@@ -601,56 +680,67 @@ function getUpdateObj(updateType, findData, updateData){
 
 function getUpdateDatas(updateType, node, mtNID, mtPM){
     let rst = [];
-    if(!decDate){
-        decDate = '(' + new Date().Format('MM-dd hh:mm:ss') + '恢复)';
-    }
+    decDate = '(' + new Date().Format('MM-dd hh:mm:ss') + '恢复)';
+    let newName = delPostFix(node.data.name) + decDate;
     if(updateType === projectType.tender || updateType === projectType.engineering){
-        //维护回收站树
-     /*   if(mtGC){
-            rst.push(getUpdateObj(updateType, {NextSiblingID: node.data.ID, 'deleteInfo.deleted': true}, {NextSiblingID: node.data.NextSiblingID}));
-        }*/
         //维护项目管理树
         if(mtPM){
             rst.push(getUpdateObj(updateType, {ParentID: node.data.ParentID, NextSiblingID: -1, deleteInfo: null}, {NextSiblingID: node.data.ID}));
         }
         //恢复
         if(mtNID){
-            rst.push(getUpdateObj(updateType, {ID: node.data.ID, name: node.data.name + decDate}, {deleteInfo: null, NextSiblingID: -1}));
+            rst.push(getUpdateObj(updateType, {ID: node.data.ID}, {name: newName, deleteInfo: null, NextSiblingID: -1}));
         }
         else {
-            rst.push(getUpdateObj(updateType, {ID: node.data.ID, name: node.data.name + decDate}, {deleteInfo: null}));
+            rst.push(getUpdateObj(updateType, {ID: node.data.ID}, {name: newName, deleteInfo: null}));
         }
     }
     else if(updateType === projectType.project){
-       /* //维护回收站树
-        if(mtGC){
-            rst.push(getUpdateObj(updateType, {NextSiblingID: node.data.ID, 'deleteInfo.deleted': true}, {NextSiblingID: node.data.NextSiblingID}));
-        }*/
         //恢复
-        rst.push(getUpdateObj(updateType, {ID: node.data.ID, name: node.data.name + decDate}, {deleteInfo: null}));//NextSibling为undefined,后端处理
+        rst.push(getUpdateObj(updateType, {ID: node.data.ID}, {deleteInfo: null, name: newName}));//NextSibling为undefined,后端处理
     }
     return rst;
 }
 //unitPriceFile or feeRateFile
 function getUpdateFiles(tenders, project){
-    let unitFiles = [], feeFiles = [], rst = [];
+    let rstUF = [], rstFF = [], rst = [];
+    let _unitFiles = Object.create(null), _feeFiles = Object.create(null), prefix = '_id';
+    decDate = '(' + new Date().Format('MM-dd hh:mm:ss') + '恢复)';
     for(let i = 0, len = tenders.length; i < len; i++){
         //恢复单价文件
         if(project && project.data.unitPriceFiles.length > 0 && fIsExist(project.data.unitPriceFiles, tenders[i].data.property.unitPriceFile.id, fileType.unitPriceFile)){
-            unitFiles.push(tenders[i].data.property.unitPriceFile.id);
+            //unitFiles.push(tenders[i].data.property.unitPriceFile.id);
+            let propId = tenders[i].data.property.unitPriceFile.id;
+            let propName = tenders[i].data.property.unitPriceFile.name;
+            let _uf = _unitFiles[prefix + propId] || null;
+            if(!_uf){
+                let obj = Object.create(null);
+                obj.id = parseInt(propId);
+                obj.name = propName;
+                _unitFiles[prefix + propId] = obj;
+            }
         }
         //恢复费率文件
         if(project && project.data.feeRateFiles.length > 0 && fIsExist(project.data.feeRateFiles, tenders[i].data.property.feeFile.id, fileType.feeRateFile)){
-            feeFiles.push(tenders[i].data.property.feeFile.id);
+            //feeFiles.push(tenders[i].data.property.feeFile.id);
+            let propId = tenders[i].data.property.feeFile.id;
+            let propName = tenders[i].data.property.feeFile.name;
+            let _ff = _feeFiles[prefix + propId] || null;
+            if(!_ff){
+                let obj = Object.create(null);
+                obj.id = propId;
+                obj.name = propName;
+                _feeFiles[prefix + propId] = obj;
+            }
         }
     }
-    let ufIds = Array.from(new Set(unitFiles));
-    let ffIds = Array.from(new Set(feeFiles));
-    for(let i = 0, len = ufIds.length; i < len; i++){
-        rst.push(getUpdateObj(fileType.unitPriceFile, {id: ufIds[i]}, {deleteInfo: null}));
+    for(let uf in _unitFiles){
+        rstUF.push(getUpdateObj(fileType.unitPriceFile, {id: _unitFiles[uf].id}, {deleteInfo: null, name: delPostFix(_unitFiles[uf].name) + decDate}));
     }
-    for(let i = 0, len = ffIds.length; i < len; i++){
-        rst.push(getUpdateObj(fileType.feeRateFile, {id: ffIds[i]}, {deleteInfo: null}));
+    for(let ff in _feeFiles){
+        rstFF.push(getUpdateObj(fileType.feeRateFile, {ID: _feeFiles[ff].id}, {deleteInfo: null, name: delPostFix(_feeFiles[ff].name) + decDate}));
     }
+    rst = rst.concat(deWeightName(rstUF));
+    rst = rst.concat(deWeightName(rstFF));
     return rst;
 }

+ 296 - 72
web/building_saas/pm/js/pm_main.js

@@ -12,12 +12,17 @@ let engineering = [];
 let feeRateData = [];
 let isSaving = false;
 let projectProperty = [];
+let fileDelObj = null;
 let projectType = {
     folder: 'Folder',
     tender: 'Tender',
     project: 'Project',
     engineering: 'Engineering'
 };
+let fileType = {
+    unitPriceFile: 'UnitPriceFile',
+    feeRateFile: 'FeeRateFile'
+};
 let ProjTreeSetting = {
     tree: {
         id: 'ID',
@@ -64,7 +69,6 @@ let ProjTreeSetting = {
                         html.push('class="'+ aClassName +'" ');
                     }
                     if (node && node.data) {
-                        //html.push('href="/main?project=', node.id(), '"');
                         html.push('href="javacript:void(0);"');
                     }
                     html.push('>', icon, '&nbsp;', text, '<a>');
@@ -123,14 +127,19 @@ let ProjTreeSetting = {
                 case projectType.project:
                     $("#add-engineering-btn").removeClass("disabled");
                     $("#add-project-btn").removeClass("disabled");
+                    $("#del-btn").removeClass("disabled");
                     break;
                 case projectType.folder:
+                    if(!node.children || node.children.length === 0){
+                        $("#del-btn").removeClass("disabled");
+                    }
                     $("#add-folder-btn").removeClass("disabled");
                     $("#add-project-btn").removeClass("disabled");
                     break;
                 case projectType.engineering:
                     $("#add-tender-btn").removeClass("disabled");
                     $("#add-engineering-btn").removeClass("disabled");
+                    $("#del-btn").removeClass("disabled");
                     break;
                 case projectType.tender:
                     $("#add-tender-btn").removeClass("disabled");
@@ -138,9 +147,9 @@ let ProjTreeSetting = {
                     $("#copy-to-btn").removeClass("disabled");
                     $("#share-btn").removeClass("disabled");
                     $("#cooperate-btn").removeClass("disabled");
+                    $("#del-btn").removeClass("disabled");
                     break;
             }
-            $("#del-btn").removeClass("disabled");
             $("#rename-btn").removeClass("disabled");
             $('td:eq(0)', node.row).append($('<i class="fa fa-sort" data-toggle="tooltip" data-placement="top" title="长安拖动"></i>'));
         }
@@ -161,6 +170,28 @@ $(document).ready(function() {
         $(".slide-sidebar").animate({width:"800"}).addClass("open");
     });
 
+    //单价、费率文件删除确认
+    $('#fileDelConfirm').click(function () {
+        if(fileDelObj && fileDelObj.id){
+            let updateObj = Object.create(null);
+            updateObj.updateType = 'delete';
+            updateObj.updateData = Object.create(null);
+            if(fileDelObj.fileType === fileType.unitPriceFile){
+                updateObj.updateData.id = fileDelObj.id;
+            }
+            else if(fileDelObj.fileType === fileType.feeRateFile){
+                updateObj.updateData.ID = fileDelObj.id;
+            }
+            updateObj.fileType = fileDelObj.fileType;
+            console.log(updateObj);
+            a_updateFiles([updateObj], function () {
+                fileDelObj = null;
+                $('#fileDelCancel').click();
+                //refresh front?
+            });
+        }
+    });
+
     // 新增建设项目点击
     $('#add-project-btn').click(function () {
         let selectedItem = Tree.selected();
@@ -253,9 +284,30 @@ $(document).ready(function() {
         }
 
         // 获取单价文件数据
-        getUnitFile(projectInfo.data.ID);
+        getUnitFile(projectInfo.data.ID, function (response) {
+            if (response.data.length <= 0) {
+                return false;
+            }
+            let unitFileHtml = '';
+            for(let tmp of response.data) {
+                unitFileHtml += '<option value="'+ tmp.id +'">'+ tmp.name +'</option>';
+            }
+            $("#unit-price").children("option:not(':first')").remove();
+            $("#unit-price").children("option").first().after(unitFileHtml);
+        });
         // 获取费率文件数据
-        getFeeRateFile(projectInfo.data.ID);
+        getFeeRateFile(projectInfo.data.ID, function (response) {
+            if (response.data.length <= 0) {
+                return false;
+            }
+            var first = $("#tender-fee-rate").children("option").first();
+            $("#tender-fee-rate").empty();
+            $("#tender-fee-rate").append(first);
+            for(let tmp of response.data) {
+                var option =  $("<option>").val(tmp.ID).text(tmp.name);
+                $("#tender-fee-rate").append(option);
+            }
+        });
     });
 
     // 新增单位工程
@@ -330,6 +382,7 @@ $(document).ready(function() {
         let dialog = $('#del');
         if (Tree) {
             updateData = GetDeleteUpdateData(Tree.selected());
+            console.log(updateData);
             UpdateProjectData(updateData, function () {
                 dialog.modal('hide');
                 Tree.removeNode(Tree.selected());
@@ -875,10 +928,12 @@ function GetDeleteUpdateData(node) {
     let datas = [], updateData,
         pre = node.preSibling(),
         deleteNodeData = function (node) {
-            var data = {};
+            var data = Object.create(null);
             data['updateType'] = 'delete';
-            data['updateData'] = {};
+            //data['NextSiblingID'] = node.data.NextSiblingID;
+            data['updateData'] = Object.create(null);
             data['updateData'][Tree.setting.tree.id] = node.id();
+            data['updateData']['projType'] = node.data.projType;
             if (node.data.projType === 'Tender') {
                 data['updateData']['FullFolder'] = GetFullFolder(node.parent);
             }
@@ -1085,7 +1140,7 @@ function getProperty(projectInfo) {
  * @param {Number} parentID
  * @return {void}
  */
-function getUnitFile(parentID) {
+function getUnitFile(parentID, callback) {
     parentID = parseInt(parentID);
     if (isNaN(parentID) && parentID <= 0) {
         return;
@@ -1106,15 +1161,9 @@ function getUnitFile(parentID) {
             if (response.error === 1) {
                 alert('获取失败!');
             } else {
-                if (response.data.length <= 0) {
-                    return false;
+                if(callback){
+                    callback(response);
                 }
-                let unitFileHtml = '';
-                for(let tmp of response.data) {
-                    unitFileHtml += '<option value="'+ tmp.id +'">'+ tmp.name +'</option>';
-                }
-                $("#unit-price").children("option:not(':first')").remove();
-                $("#unit-price").children("option").first().after(unitFileHtml);
             }
         }
     });
@@ -1127,7 +1176,7 @@ function getUnitFile(parentID) {
  * @param {Number} parentID
  * @return {void}
  */
-function getFeeRateFile(parentID) {
+function getFeeRateFile(parentID, callback) {
     parentID = parseInt(parentID);
     if (isNaN(parentID) && parentID <= 0) {
         return;
@@ -1148,15 +1197,8 @@ function getFeeRateFile(parentID) {
             if (response.error === 1) {
                 alert('获取失败!');
             } else {
-                if (response.data.length <= 0) {
-                    return false;
-                }
-                var first = $("#tender-fee-rate").children("option").first();
-                $("#tender-fee-rate").empty();
-                $("#tender-fee-rate").append(first);
-                for(let tmp of response.data) {
-                    var option =  $("<option>").val(tmp.ID).text(tmp.name);
-                    $("#tender-fee-rate").append(option);
+                if(callback){
+                    callback(response);
                 }
             }
         }
@@ -1179,76 +1221,258 @@ function setDataToSideBar() {
     $(target).show();
     $(target + '-name').html(name);
 
-    if (selectedItem.children.length <= 0) {
+    /*if (selectedItem.children.length <= 0) {
         return;
-    }
-    // 建设项目相关
-    let counter = 1;
-    let html = '';
-    for(let tmp of selectedItem.children) {
+    }*/
+    if(selectedItem.children.length > 0){
+        // 建设项目相关
+        let counter = 1;
+        let html = '';
+        for(let tmp of selectedItem.children) {
+            html += '<tr>' +
+                '<td>'+ counter +'</td>' +
+                '<td>'+ counter +'</td>' +
+                '<td>'+ tmp.data.name +'</td>' +
+                '<td></td>' +
+                '<td></td>' +
+                '<td></td>' +
+                '<td></td>' +
+                '</tr>';
+
+        }
         html += '<tr>' +
-            '<td>'+ counter +'</td>' +
-            '<td>'+ counter +'</td>' +
-            '<td>'+ tmp.data.name +'</td>' +
+            '<td>'+ (counter + 1) +'</td>' +
+            '<td> </td>' +
+            '<td>合计</td>' +
             '<td></td>' +
             '<td></td>' +
             '<td></td>' +
             '<td></td>' +
             '</tr>';
-
+        $(target + '-table tbody').html(html);
     }
-    html += '<tr>' +
-        '<td>'+ (counter + 1) +'</td>' +
-        '<td> </td>' +
-        '<td>合计</td>' +
-        '<td></td>' +
-        '<td></td>' +
-        '<td></td>' +
-        '<td></td>' +
-        '</tr>';
-    $(target + '-table tbody').html(html);
 
     // 加载单价文件与费率文件
     if (selectedItem.data.projType === projectType.project) {
         let engineeringData = selectedItem.children !== null && selectedItem.children.children !== null ?
             selectedItem.children : [];
-        if (engineeringData.length <= 0) {
+        /*if (engineeringData.length <= 0) {
             return;
-        }
-        let unitPriceFileHtml = '';
-        let feeFileHtml = '';
-        let unitPriceFileCounter = 1;
-        let feeFileCounter = 1;
+        }*/
         let unitPriceFileList = [];
         let feeFileList = [];
-        for(let engineering of engineeringData) {
-            let tenderData = engineering.children !== null ? engineering.children : [];
-            if (tenderData.length <= 0) {
-                continue;
+        let poj_tenders = getProjTenders(selectedItem);
+        getUnitFile(selectedItem.data.ID, function (response) {
+            unitPriceFileList = unitPriceFileList.concat(response.data);
+            $(target + '-unit-price-table tbody').empty();
+            set_file_table(target, poj_tenders, unitPriceFileList, fileType.unitPriceFile);
+        });
+        getFeeRateFile(selectedItem.data.ID, function (response) {
+            feeFileList = feeFileList.concat(response.data);
+            $(target + '-fee-table tbody').empty();
+            set_file_table(target, poj_tenders, feeFileList, fileType.feeRateFile);
+        });
+    }
+}
+
+//获取建设项目的全部单位工程
+function getProjTenders(proj) {
+    let rst = [];
+    let engineerings = proj.children || null;
+    if (engineerings) {
+        for (let i = 0, len = engineerings.length; i < len; i++) {
+            let e_tenders = engineerings[i].children || null;
+            if (e_tenders && e_tenders.length > 0) {
+                rst = rst.concat(e_tenders);
             }
-            for(let tmp of tenderData) {
-                tmp = tmp.data.property !== null ? tmp.data.property : null;
-                if(tmp === null) {
-                    continue;
-                }
+        }
+    }
+    return rst;
+}
+//单价、费率文件汇总信息的使用次数、使用该文件的单位工程
+function getUsedObj(tenders, fileId, type){
+    let rst = Object.create(null), usedCount = 0, usedInfo = '';
+    rst.usedCount = 0;
+    rst.usedInfo = null;
+    for(let i = 0, len = tenders.length; i < len; i++){
+        let theFile = type === fileType.unitPriceFile ? tenders[i].data.property.unitPriceFile || null : tenders[i].data.property.feeFile || null;
+        if(theFile && theFile.id == fileId){
+            usedCount ++;
+            usedInfo += tenders[i].data.name + '<br>';
+        }
+    }
+    if(usedCount > 0){
+        usedInfo = usedInfo.slice(0, usedInfo.length - 4);
+        rst.usedCount = usedCount;
+        rst.usedInfo = usedInfo;
+    }
+    return rst;
+}
 
-                if (tmp.feeFile !== undefined && feeFileList.indexOf(tmp.feeFile.name) < 0) {
-                    feeFileHtml += '<tr><td>'+ feeFileCounter +'</td><td>'+ tmp.feeFile.name +'</td></tr>';
-                    feeFileCounter++;
-                    feeFileList.push(tmp.feeFile.name);
+//单价、费率文件汇总界面交互
+function bindEvents_file_table(jqS, usedObj, targetBody, type){
+    //悬浮显示删除和重命名按钮
+    $(jqS).hover(function(){
+        $('p',this).show();
+    },function(){
+        $('p', this).hide();
+    });
+    //删除
+    if(usedObj.usedCount > 0){//不可删除
+        $(jqS + ' p a:eq(0)').addClass('disabled');
+        $(jqS + ' p a:eq(0)').removeAttr('data-target');
+    }
+    else {
+        $(jqS + ' p a:eq(0)').on('click', function () {
+            let dispName = $(jqS + ' div:eq(0)')[0].childNodes[0].textContent;
+            //刷新删除弹出框内容
+            let delData = '删除"' + dispName + '"?';
+            $('#del-wj .modal-body h5').text(delData);
+            let attrId = $(jqS).attr('id');
+            let id = attrId.slice(5, attrId.length);
+            if(type === fileType.unitPriceFile){
+                id = parseInt(id);
+            }
+            let obj = Object.create(null);
+            obj.fileType = type;
+            obj.id = id;
+            fileDelObj = obj;
+        });
+    }
+    //重命名
+    $(jqS + ' p a:eq(1)').on('click', function () {
+        let fileObjs = getFileObj(targetBody, type);
+        let orgDispName = $(jqS + ' div:eq(0)')[0].childNodes[0].textContent;
+        let postFix = orgDispName.slice(orgDispName.length - 4, orgDispName.length);
+        let orgName = orgDispName.slice(0, orgDispName.length - 4);
+        $(jqS + ' input').val(orgName);
+        $(jqS + ' div:eq(0)').hide();
+        $(jqS + ' div:eq(1)').show();
+        //确认重命名
+        $(jqS + ' .btn-success').on('click', function () {
+            let attrId = $(jqS).attr('id');
+            let id = attrId.slice(5, attrId.length);
+            let newName = $(jqS + ' input').val().trim();
+            if(newName !== orgName){
+                if(hasThisFileName(fileObjs, newName)){
+                    alert('本建设项目已存在该文件名,请重新输入!');
+                    $(jqS + ' input').val(orgName);
+                    return;
                 }
-
-                if (tmp.unitPriceFile !== undefined && unitPriceFileList.indexOf(tmp.unitPriceFile.name) < 0) {
-                    unitPriceFileHtml += '<tr><td>'+ unitPriceFileCounter +'</td><td>'+ tmp.unitPriceFile.name +'单价文件</td></tr>';
-                    unitPriceFileCounter++;
-                    unitPriceFileList.push(tmp.unitPriceFile.name);
+                if(newName.trim().length === 0){
+                    alert('文件名不可为空!');
+                    $(jqS + ' input').val(orgName);
+                    return;
+                }
+                //ajax
+                let updateObj = Object.create(null);
+                updateObj.fileType = type;
+                updateObj.updateType = 'update';
+                updateObj.updateData = Object.create(null);
+                if(type === fileType.unitPriceFile){
+                    updateObj.updateData.id = parseInt(id);
+                    //updateObj.updateData.id = id;
+                }
+                else if(type === fileType.feeRateFile){
+                    updateObj.updateData.ID = id;
                 }
+                updateObj.updateData.name = newName;
+                a_updateFiles([updateObj], function () {
+                    let newDispName = newName + postFix;
+                    $(jqS + ' div:eq(0)')[0].childNodes[0].textContent = newDispName;
+                    $(jqS + ' div:eq(0)').show();
+                    $(jqS + ' div:eq(1)').hide();
+                });
+                console.log(updateObj);
             }
+            let newDispName = newName + postFix;
+            $(jqS + ' div:eq(0)')[0].childNodes[0].textContent = newDispName;
+            $(jqS + ' div:eq(0)').show();
+            $(jqS + ' div:eq(1)').hide();
+        });
+        //取消重命名
+        $(jqS + ' .btn-secondary').on('click', function () {
+            $(jqS + ' div:eq(0)').show();
+            $(jqS + ' div:eq(1)').hide();
+        });
+    });
+    //悬浮框显示使用该文件的单位工程
+    $($(jqS)[0].nextSibling).popover({
+            placement:"bottom",
+            html:true,
+            trigger:"hover", //| focus
+            content: usedObj.usedInfo ? usedObj.usedInfo : ''
+        }
+    );
+}
 
-            $(target + '-unit-price-table tbody').html(unitPriceFileHtml);
-            $(target + '-fee-table tbody').html(feeFileHtml);
+//更新单价、费率文件(删除、重命名)
+function a_updateFiles(updateDatas, callback){
+    $.ajax({
+        type: 'post',
+        url: '/pm/api/updateFiles',
+        data: {data: JSON.stringify({user_id: userID, updateDatas: updateDatas})},
+        dataType: 'json',
+        success: function (result){
+            if(!result.error && callback){
+                callback();
+            }
         }
+    })
+}
 
+function set_file_table(target, poj_tenders, fileList, type){
+    let fileCounter = 1;
+    for(let i = 0, len = fileList.length; i < len; i++){
+        let fileId = type === fileType.unitPriceFile ? fileList[i].id : fileList[i].ID;
+        let usedObj = getUsedObj(poj_tenders, fileId, type);
+        let usedHtml = usedObj.usedCount > 0 ?  '<td class="text-center"><a href="javascript:void(0);">' + usedObj.usedCount + '</a></td>' : '<td class="text-center">' + usedObj.usedCount + '</td>';
+        let hoverHtml = '<p style="display: none"><a class="btn btn-sm" href="javascript:void(0);" data-toggle="modal" data-target="#del-wj">删除</a><a class="btn btn-sm" href="javascript:void(0);">重命名</a></p></div>';
+        let renHtml = '<div class="input-group" style="display: none;">'
+            + '<input class="form-control form-control-sm" value="">'
+            + '<span class="input-group-btn">'
+            + '<button class="btn btn-success btn-sm" type="button"><i class="fa fa-check"></i></button>'
+            + '</span>'
+            + '<span class="input-group-btn">'
+            + '<button class="btn btn-secondary btn-sm" type="button"><i class="fa fa-remove"></i></button>'
+            + '</span>'
+            + '</div></td>';
+        let fileTypeStr = type === fileType.unitPriceFile ? '单价文件' : '费率文件';
+        let fileHtml = '<tr><td>' + fileCounter + '</td><td id="file_' + fileId + '"><div>' + fileList[i].name + fileTypeStr + hoverHtml + renHtml + usedHtml + '</tr>';
+        fileCounter++;
+        let targetBody = type === fileType.unitPriceFile ? target + '-unit-price-table tbody' : target + '-fee-table tbody';
+        $(targetBody).append(fileHtml);
+        let jqS = '#file_' + fileId;
+        bindEvents_file_table(jqS, usedObj, targetBody, type);
     }
+}
+
+function getFileObj(jqS, type){
+    let rst = [];
+    let trs = $('tr', jqS);
+    for(let i = 0, len = trs.length; i < len; i++){
+        let td = $('td:eq(1)', trs[i])[0];
+        let dispName = td.childNodes[0].childNodes[0].textContent;
+        let name = dispName.slice(0, dispName.length - 4);
+        let attrId = $(td).attr('id');
+        let id = attrId.slice(5, attrId.length);
+        if(type === fileType.unitPriceFile){
+            id = parseInt(id);
+        }
+        let obj = Object.create(null);
+        obj.id = id;
+        obj.name = name;
+        rst.push(obj);
+    }
+    return rst;
+    //let orgDispName = $(jqS + ' div:eq(0)')[0].childNodes[0].textContent;
+}
 
-}
+function hasThisFileName(fileObjs, name){
+    for(let i = 0, len = fileObjs.length; i < len; i++){
+        if(fileObjs[i].name === name){
+            return true;
+        }
+    }
+    return false;
+}