Bläddra i källkod

Merge branch 'master' of http://192.168.1.12:3000/SmartCost/ConstructionCost

TonyKang 7 år sedan
förälder
incheckning
67fe79096e

+ 1 - 1
config/config.js

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

+ 3 - 2
config/gulpConfig.js

@@ -11,7 +11,8 @@ module.exports = {
         'lib/bootstrap/bootstrap.min.js',
         'web/building_saas/js/*.js',
         'public/web/scMathUtil.js',
-        'public/web/PerfectLoad.js'
+        'public/web/PerfectLoad.js',
+        'lib/lodash/lodash.js',
     ],
     common_css:[
         'lib/bootstrap/css/bootstrap.min.css',
@@ -47,7 +48,7 @@ module.exports = {
         'lib/jquery-ui/jquery-ui.min.js',
         'lib/jquery-ui/jquery-ui-datepickerCN.js',
         'lib/jquery-contextmenu/*.js',
-        'lib/lodash/lodash.js',
+        //'lib/lodash/lodash.js',
         // 'test/tmp_data/test_ration_calc/ration_calc_base.js',
         'web/building_saas/main/js/models/main_consts.js',
         'public/web/common_util.js',

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

@@ -31,7 +31,8 @@ module.exports={
     setFeeRateToBill:setFeeRateToBill,
     updateFeeRate:updateFeeRate,
     updateRates:update_rates,
-    feeRateFileSaveAs:feeRateFileSaveAs
+    feeRateFileSaveAs:feeRateFileSaveAs,
+    getFeeRateByID:getFeeRateByID
 };
 let operationMap={
     'ut_create':create_fee_rate,
@@ -405,4 +406,10 @@ async function changeFeeRateFileFromOthers(jdata) {
 
 async function getGCFeeRateFiles(userID){
     return await feeRateFileModel.find({userID: userID, 'deleteInfo.deleted': true});
+}
+
+async function getFeeRateByID(ID) {
+    let feeRateFile = await feeRateFileModel.findOne({'ID':ID});
+    let feeRate = await feeRateModel.findOne({ID:feeRateFile.feeRateID});
+    return [feeRateFile,feeRate]
 }

+ 2 - 0
modules/glj/controllers/glj_controller.js

@@ -270,6 +270,8 @@ class GLJController extends BaseController {
                 // 归类
                 if (rootProjectID == projectList[index].ID) {
                     result.self = unitPriceFileData;
+                    result.currentProjectName =  projectList[index].name;
+
                 } else {
                     result.other.push(projectList[index]);
                 }

+ 18 - 3
modules/pm/controllers/pm_controller.js

@@ -16,6 +16,7 @@ let projectModel = mongoose.model('projects');
 let unitPriceFileModel = mongoose.model('unit_price_file');
 let feeRateFileModel = mongoose.model('fee_rate_file');
 let asyncTool = require('async');
+let pm_facade = require('../facade/pm_facade');
 
 //统一回调函数
 let callback = function(req, res, err, message, data){
@@ -355,7 +356,7 @@ module.exports = {
         ProjectsData.recGC(userID, nodes, function (err, msg, data) {
             callback(request, response, err, msg, data);
         });
-    },
+   },
 
     delGC: async function(request, response){
         let data = JSON.parse(request.body.data);
@@ -383,9 +384,23 @@ module.exports = {
                 await feeRateFileModel.bulkWrite(bulkFFs);
             }
             callback(request, response, 0, 'success', null);
-        }
-        catch(err){
+        } catch(err){
             callback(request, response, 1, err, null);
         }
+    },
+    moveProject:async function(req,res){
+        let result={
+            error:0
+        };
+        try {
+            let data = req.body.data;
+            let rdata= await pm_facade.moveProject(data);
+            result.data= rdata;
+        }catch (err){
+            console.log(err);
+            result.error=1;
+            result.message = err.message;
+        }
+        res.json(result);
     }
 };

+ 179 - 0
modules/pm/facade/pm_facade.js

@@ -0,0 +1,179 @@
+/**
+ * Created by zhang on 2018/4/17.
+ */
+let mongoose = require('mongoose');
+let _ = require("lodash");
+let feeRate_facade = require('../../fee_rates/facade/fee_rates_facade');
+const uuidV1 = require('uuid/v1');
+let projectModel = mongoose.model('projects');
+let feeRateModel = mongoose.model('fee_rates');
+let feeRateFileModel = mongoose.model('fee_rate_file');
+let unitPriceFileModel = mongoose.model("unit_price_file");
+let mixRatioModel = mongoose.model("mix_ratio");
+let unitPriceModel = mongoose.model("unit_price");
+import CounterModel from "../../glj/models/counter_model";
+
+module.exports={
+    moveProject:moveProject  
+};
+
+async function moveProject(data) {
+    data = JSON.parse(data);
+    let projectMap = data.projectMap,feeRateMap = data.feeRateMap,unitPriceMap = data.unitPriceMap;
+    let projectTasks = [],feeRateTask=[],feeRateFileTask=[],unitPriceTask=[],unitPriceFileTask=[],mixRatioTask=[];
+    let feeUniqMap=[],priceUniqMap={};//费率、单价文件唯一映射表当移动多个项目时,任务有可能出现重复的情况
+    if(!_.isEmpty(feeRateMap)&&data.rootProjectID){//如果费率有修改
+        let feeRates =await feeRate_facade.getFeeRatesByProject(data.rootProjectID);//取目标建设项目的所有费率文件,重名检查
+        for(let projectID in feeRateMap){
+            let rename = feeRateMap[projectID].name;
+            let feeRateID =  feeRateMap[projectID].query.ID;
+            let checkFee = _.find(feeRates,{'name':rename});
+            let newID = feeRateID;
+            if(checkFee){//说明费率名字已存在,需要重命名
+                rename = feeUniqMap[feeRateID]?feeUniqMap[feeRateID].name:rename + '(' + new Date().Format('MM-dd hh:mm:ss') + '移动)';
+                projectMap[projectID]['update']['property.feeFile.name'] = rename;
+                if(feeRateMap[projectID].action == 'move'){
+                    feeRateMap[projectID].update.name = rename;
+                }
+            }
+            if(feeRateMap[projectID].action == 'copy'){
+                newID = feeUniqMap[feeRateID]?feeUniqMap[feeRateID].ID:uuidV1();
+                feeRateMap[projectID].name = rename;
+                feeRateMap[projectID].ID =  newID;
+                projectMap[projectID]['update']['property.feeFile.id'] = newID;
+            }
+            if(!feeUniqMap[feeRateID]){
+                await generateFeeRateTask(feeRateMap[projectID],feeRateTask,feeRateFileTask);
+                feeUniqMap[feeRateID] = {name:rename,ID:newID};
+            }
+        }
+    }
+    if(!_.isEmpty(unitPriceMap)&&data.rootProjectID) {//如果单价文件有修改
+        let unitPriceFiles =  await unitPriceFileModel.find({root_project_id: data.rootProjectID, deleteInfo: null});
+        for(let projectID in unitPriceMap){
+            let checkUn = _.find(unitPriceFiles,{'name':unitPriceMap[projectID].name});
+            let rename = unitPriceMap[projectID].name;
+            let unitPriceID = unitPriceMap[projectID].query.id;
+            let newID = unitPriceID;
+            if(checkUn){//重名检查
+                rename = priceUniqMap[unitPriceID]?priceUniqMap[unitPriceID].name:rename + '(' + new Date().Format('MM-dd hh:mm:ss') + '移动)';
+                projectMap[projectID]['update']['property.unitPriceFile.name'] = rename;
+                if(unitPriceMap[projectID].action =='move'){
+                    unitPriceMap[projectID].update.name = rename;
+                }
+            }
+            if(unitPriceMap[projectID].action =='copy'){
+                newID =  priceUniqMap[unitPriceID]?priceUniqMap[unitPriceID].id: await getCounterID("unit_price_file");
+                unitPriceMap[projectID].name = rename;
+                unitPriceMap[projectID].id = newID;
+                projectMap[projectID]['update']['property.unitPriceFile.id'] = newID;
+            }
+            if(!priceUniqMap[unitPriceID]){
+                await generateUnitPriceTask(unitPriceMap[projectID],projectID,data.user_id,unitPriceTask,unitPriceFileTask,mixRatioTask);
+                priceUniqMap[unitPriceID] = {name:rename,id:newID};
+            }
+        }
+    }
+
+    if(!_.isEmpty(projectMap)){
+        for(let projectID in projectMap){
+            projectTasks.push({updateOne:{filter : projectMap[projectID].query, update : projectMap[projectID].update}});
+        }
+    }
+
+    projectTasks.length>0?await projectModel.bulkWrite(projectTasks):'';
+    feeRateTask.length>0?await feeRateModel.bulkWrite(feeRateTask):'';
+    feeRateFileTask.length>0?await feeRateFileModel.bulkWrite(feeRateFileTask):'';
+    unitPriceFileTask.length>0?await unitPriceFileModel.bulkWrite(unitPriceFileTask):'';
+    unitPriceTask.length>0?await insertMany(unitPriceTask,unitPriceModel):'';
+    mixRatioTask.length>0?await insertMany(mixRatioTask,mixRatioModel):'';
+    return projectMap;
+}
+
+async function generateFeeRateTask(data,feeRateTask,feeRateFileTask) {
+    if(data.action == 'move'){
+        let task = {
+            updateOne:{
+                filter : data.query,
+                update : data.update
+            }
+        };
+        feeRateFileTask.push(task);
+    }
+    if(data.action == 'copy'){//copy 费率的时候用insertOne方法
+        let [feeRateFile,feeRate] =await feeRate_facade.getFeeRateByID(data.query.ID);
+        let newFeeRateID =  uuidV1();
+        let newFeeRate = {
+            ID:newFeeRateID,
+            rates:feeRate.rates
+        };
+        let newFeeRateFile = {
+            ID:data.ID,
+            rootProjectID:data.rootProjectID,
+            userID:feeRateFile.userID,
+            name:data.name,
+            libID:feeRateFile.libID,
+            libName:feeRateFile.libName,
+            feeRateID:newFeeRateID
+        };
+        feeRateTask.push({insertOne :{document:newFeeRate}});
+        feeRateFileTask.push({insertOne :{document:newFeeRateFile}});
+    }
+}
+
+async function generateUnitPriceTask(data,projectID,user_id,unitPriceTask,unitPriceFileTask,mixRatioTask){
+    if(data.action == 'move'){
+        let task = {
+            updateOne:{
+                filter : data.query,
+                update : data.update
+            }
+        };
+        unitPriceFileTask.push(task);
+    }
+    if(data.action == 'copy'){
+        let newUnitFile = {
+            id:data.id,
+            name: data.name,
+            project_id:projectID,
+            user_id:user_id,
+            root_project_id: data.rootProjectID
+        };
+        unitPriceFileTask.push({insertOne :{document:newUnitFile}});
+        let mixRatios = await mixRatioModel.find({unit_price_file_id: data.query.id});
+        mixRatios = JSON.stringify(mixRatios);
+        mixRatios = JSON.parse(mixRatios);
+        for(let m of mixRatios){
+            delete m._id;  // 删除原有id信息
+            delete m.id;
+            m.unit_price_file_id = data.id;
+            m.id = await getCounterID('mix_ratio');
+            mixRatioTask.push(m);
+        }
+        let unitPrices = await unitPriceModel.find({unit_price_file_id: data.query.id});//unit_price_file_id
+        unitPrices = JSON.stringify(unitPrices);
+        unitPrices = JSON.parse(unitPrices);
+        for(let u of unitPrices){
+            delete u._id;  // 删除原有id信息
+            delete u.id;
+            u.unit_price_file_id = data.id;
+            u.id = await getCounterID('unit_price');
+            unitPriceTask.push(u);
+        }
+    }
+}
+
+
+async function getCounterID(collectionName){
+    let counterModel = new CounterModel();
+    return  await counterModel.getId(collectionName);
+}
+
+async function insertMany(datas,model) {
+    while (datas.length>1000){//因为mongoose限制了批量插入的条数为1000.所以超出限制后需要分批插入
+        let newList = datas.splice(0,1000);//一次插入1000条
+        model.insertMany(newList);
+    }
+    model.insertMany(datas);
+
+}

+ 1 - 0
modules/pm/models/project_model.js

@@ -90,6 +90,7 @@ ProjectsDAO.prototype.updateUserProjects = async function (userId, compilationId
             callback(1, '提交数据出错.', null);
         }
     };
+    console.log(datas);
     if (datas) {
         for (i = 0; i < datas.length && !hasError; i++) {
             data = datas[i];

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

@@ -32,6 +32,7 @@ module.exports = function (app) {
      */
     pmRouter.post('/updateProjects', pmController.updateProjects);
     pmRouter.post('/updateMixDatas', pmController.updateMixDatas);
+    pmRouter.post('/moveProject', pmController.moveProject);
 
     /*
      req.body = {data: '{user_id: user_id, updateData: [{updateType, updateData}]}'}

+ 2 - 2
web/building_saas/fee_rates/fee_rate.html

@@ -44,12 +44,12 @@
             <div class="modal-body">
                 <div class="form-group">
                     <label class="custom-control custom-radio">
-                        <input id="radio1" name="chaneg-lv-Radio" type="radio" value="0" onclick="changeFRadioClick()" class="custom-control-input" checked>
+                        <input id="radio1" name="chaneg-lv-Radio" type="radio" value="0" onclick="changeFRadioClick()" class="" checked>
                         <span class="custom-control-indicator"></span>
                         <span class="custom-control-description">从本建设项目中选择</span>
                     </label>
                     <label class="custom-control custom-radio">
-                        <input id="radio2" name="chaneg-lv-Radio" type="radio" value="1" onclick="changeFRadioClick()" class="custom-control-input">
+                        <input id="radio2" name="chaneg-lv-Radio" type="radio" value="1" onclick="changeFRadioClick()" class="">
                         <span class="custom-control-indicator"></span>
                         <span class="custom-control-description">从其他建设项目中复制</span>
                     </label>

+ 2 - 2
web/building_saas/glj/html/project_glj.html

@@ -63,12 +63,12 @@
             <div class="modal-body">
                 <div class="form-group">
                     <label class="custom-control custom-radio">
-                        <input name="change-type" type="radio" class="custom-control-input" checked="checked" value="0">
+                        <input name="change-type" type="radio" class="" checked="checked" value="0">
                         <span class="custom-control-indicator"></span>
                         <span class="custom-control-description">从本建设项目中选择</span>
                     </label>
                     <label class="custom-control custom-radio">
-                        <input name="change-type" type="radio" class="custom-control-input" value="1">
+                        <input name="change-type" type="radio" class="" value="1">
                         <span class="custom-control-indicator"></span>
                         <span class="custom-control-description">从其他建设项目中复制</span>
                     </label>

+ 1 - 1
web/building_saas/main/html/main.html

@@ -1085,7 +1085,7 @@
         <script type="text/javascript" src="/lib/jquery-ui/jquery-ui-datepickerCN.js"></script>
         <script type="text/javascript" src="/lib/jquery-contextmenu/jquery.contextMenu.js"></script>
         <script type="text/javascript" src="/lib/jquery-contextmenu/jquery.ui.position.js"></script>
-        <script type="text/javascript" src="/lib/lodash/lodash.js"></script>
+        <!--<script type="text/javascript" src="/lib/lodash/lodash.js"></script>-->
     <!-- Common -->
         <script type="text/javascript" src="/public/web/common_ajax.js"></script>
         <script type="text/javascript" src="/public/web/url_util.js"></script>

+ 51 - 81
web/building_saas/main/js/models/calc_program.js

@@ -719,22 +719,22 @@ let analyzer = {
         return arrBase ? arrBase : [];
     },
 
-    getID: function (FName, calcTemplate) {          // F13 → 4
+    getID: function (FName, template) {          // F13 → 4
         let idx = FName.slice(1) - 1;
-        let id = calcTemplate.calcItems[idx] ? calcTemplate.calcItems[idx].ID : null;
+        let id = template.calcItems[idx] ? template.calcItems[idx].ID : null;
         return id;
     },
-    getFName: function (atID, calcTemplate) {          // @3 → F7
-        for (var i = 0; i < calcTemplate.calcItems.length; i++) {
-            if (calcTemplate.calcItems[i].ID == atID.slice(1)) {
+    getFName: function (atID, template) {          // @3 → F7
+        for (var i = 0; i < template.calcItems.length; i++) {
+            if (template.calcItems[i].ID == atID.slice(1)) {
                 return 'F'+ (i + 1);
             }
         }
         return null;
     },
-    getItem: function (FName, calcTemplate){      // F3 → calcItems[2] → {Object}
+    getItem: function (FName, template){      // F3 → calcItems[2] → {Object}
         let idx = FName.slice(1) - 1;
-        return calcTemplate.calcItems[idx];
+        return template.calcItems[idx];
     },
     isCycleCalc: function (expression, ID, template) {     // 这里判断expression,是ID引用: @5+@6
         if (expression.includes(`@${ID}`))
@@ -749,7 +749,7 @@ let analyzer = {
 
         return false;
     },
-    isLegal: function (dispExpr, itemID, calcTemplate) {  // 检测包括:无效字符、基数是否中括号、基数是否定义、行引用、循环计算
+    isLegal: function (dispExpr, itemID, template) {  // 检测包括:无效字符、基数是否中括号、基数是否定义、行引用、循环计算
         let me = analyzer;
         let expr = me.standard(dispExpr);
         let invalidChars = /[^0-9\u4e00-\u9fa5\+\-\*\/\(\)\.\[\]FL%]/g;
@@ -791,14 +791,14 @@ let analyzer = {
         let arrF = me.getFArr(expr);
         for (let F of arrF){
             let num = F.slice(1);
-            if (num > calcTemplate.calcItems.length){
+            if (num > template.calcItems.length){
                 alert('表达式中 “F'+ num +'” 行号引用错误!');
                 return false;
             };
         };
 
-        let expression = me.getExpression(expr, calcTemplate);
-        if (me.isCycleCalc(expression, itemID, calcTemplate)){
+        let expression = me.getExpression(expr, template);
+        if (me.isCycleCalc(expression, itemID, template)){
             alert('表达式中有循环计算!');
             return false;
         }
@@ -822,10 +822,10 @@ let analyzer = {
     },
     refLineToIDs: function (dispExpr, template) {   // F7、F8 → @2、@3
         let rst = analyzer.standard(dispExpr);
-        let fArr = me.getFArr(rst);
+        let fArr = analyzer.getFArr(rst);
         let IDArr = [];
         for (let F of fArr) {
-            let ID = me.getID(F, template);
+            let ID = analyzer.getID(F, template);
             IDArr.push(ID);
         };
         for (let i = 0; i < fArr.length; i++) {
@@ -835,11 +835,11 @@ let analyzer = {
         };
         return rst;
     },
-    getDispExpr: function (expression, calcTemplate) {
-        return analyzer.refIDToLines(expression, calcTemplate);
+    getDispExpr: function (expression, template) {
+        return analyzer.refIDToLines(expression, template);
     },
-    getExpression: function (dispExpr, calcTemplate) {
-        return analyzer.refLineToIDs(dispExpr, calcTemplate);
+    getExpression: function (dispExpr, template) {
+        return analyzer.refLineToIDs(dispExpr, template);
     },
     getDispExprUser: function (dispExpr, labourCoe) {   // labourCoe 是 str 类型
         let rst = analyzer.standard(dispExpr);
@@ -862,6 +862,39 @@ let analyzer = {
         rst = rst.replace(/\]/g, "')");
         rst = rst.replace(/L/g, labourCoe);
         return rst;
+    },
+
+    calcItemMaxID: function(template){
+        let MaxID = 0;
+        for (let item of template.calcItems){
+            if (item.ID > MaxID)
+                MaxID = item.ID;
+        };
+        return MaxID;
+    },
+    calcItemIsUsed: function(template, calcItem){
+        let atID = '@' + calcItem.ID;
+        for (var i = 0; i < template.calcItems.length; i++) {
+            let item = template.calcItems[i];
+            let atIDArr = analyzer.getAtIDArr(item.expression);
+            if (atIDArr.indexOf(atID) >= 0){
+                calcItem.tempUsed = i;
+                return true;
+            }
+        }
+        return false;
+    },
+    calcItemLabourCoe: function(calcItem){
+        let lc = 0;
+        if (calcItem.labourCoeID)
+            lc = projectObj.project.calcProgram.compiledLabourCoes[calcItem.labourCoeID].coe.toString();
+        return lc;
+    },
+    templateRefresh: function(template){
+        for (let item of template){
+            item.dispExpr = analyzer.getDispExpr(item.expression, template);
+            item.dispExprUser = analyzer.getDispExprUser(item.dispExpr, me.calcItemLabourCoe(item));
+        };
     }
 };
 
@@ -993,53 +1026,6 @@ class CalcProgram {
         template.hasCompiled = false;
         template.errs = [];
 
-        /*let private_extract_ID = function(str, idx){
-            let rst = '', lBracket = 0, rBracket = 0, firstIdx = idx, lastIdx = 0;
-            for (let i = idx; i < str.length; i++) {
-                if (str[i] === '(') {
-                    lBracket++;
-                    if (lBracket == 1) firstIdx = i + 1;
-                }
-                if (str[i] === ')') {
-                    rBracket++;
-                    if (lBracket == rBracket) {
-                        lastIdx = i - 1;
-                        if (lastIdx > firstIdx) {
-                            if (str[firstIdx] === "'") firstIdx++;
-                            if (str[lastIdx] !== "'") lastIdx++;
-                            if (lastIdx > firstIdx) {
-                                rst = str.slice(firstIdx, lastIdx);
-                            }
-                        }
-                        break;
-                    }
-                }
-            }
-            return rst;
-        };*/
-        /*let private_parse_ref = function(item, itemIdx){
-            let idx = item.expression.indexOf('@(', 0);
-            while (idx >= 0) {
-                let ID = private_extract_ID(item.expression, idx);
-                if (ID.length > 0) {
-                    let subItem = template.compiledCalcItems[ID];
-                    if (subItem) {
-                        if (subItem.ID !== item.ID) {
-                            private_parse_ref(subItem, template.compiledCalcItems[ID + "_idx"]);
-                        } else {
-                            template.errs.push("There exists the self refer ID: " + ID);
-                        }
-                    } else {
-                        template.errs.push("There exists the invalid ID by which could not find the item: " + ID);
-                        console.log('invalid ID: ' + ID);
-                    }
-                }
-                idx = item.expression.indexOf('@(', idx + ID.length + 3);
-            }
-            if (template.compiledSeq.indexOf(itemIdx) < 0) {
-                template.compiledSeq.push(itemIdx);
-            }
-        };*/
         let private_extract_ID = function(str, idx){
             let subStr = str.slice(idx);
             let patt = new RegExp(/@\d+/);
@@ -1075,7 +1061,7 @@ class CalcProgram {
             for (let idx of template.compiledSeq) {
                 let item = template.calcItems[idx];
 
-                let lc = me.getLabourCoe(item);
+                let lc = analyzer.calcItemLabourCoe(item);
                 // 用于界面显示。disExpr是公式模板,不允许修改:人工系数占位符被修改后变成数值,第二次无法正确替换。
                 item.dispExprUser = analyzer.getDispExprUser(item.dispExpr, lc);
                 if (item.expression == 'HJ')
@@ -1125,22 +1111,6 @@ class CalcProgram {
             }
         };
     };
-    
-    refreshTemplate(template){
-        let me = this;
-        for (let item of template){
-             item.dispExpr = analyzer.getDispExpr(item.expression, template);
-             item.dispExprUser = analyzer.getDispExprUser(item.dispExpr, me.getLabourCoe(item));
-        };
-    };
-
-    getLabourCoe(calcItem){
-        let me = this;
-        let lc = 0;
-        if (calcItem.labourCoeID)
-            lc = me.compiledLabourCoes[calcItem.labourCoeID].coe.toString();
-        return lc;
-    };
 
     // 存储、刷新零散的多个结点。
     saveNodes(treeNodes, callback){

+ 1 - 1
web/building_saas/main/js/views/calc_base_view.js

@@ -281,7 +281,7 @@ let calcBaseView = {
                 if (calcItem.dispExpr != expr){
                     if (analyzer.isLegal(expr, calcItem.ID, template)){
                         let cp = projectObj.project.calcProgram;
-                        let lc = cp.getLabourCoe(calcItem);
+                        let lc = analyzer.calcItemLabourCoe(calcItem);
                         calcItem.dispExpr = expr;
                         calcItem.dispExprUser = analyzer.getDispExprUser(expr, lc);
                         calcItem.expression = analyzer.getExpression(expr, template);

+ 97 - 4
web/building_saas/main/js/views/calc_program_manage.js

@@ -62,6 +62,7 @@ let calcProgramManage = {
         me.detailSpread.getSheet(0).bind(GC.Spread.Sheets.Events.CellChanged, me.onDetailCellChanged);
         me.detailSpread.getSheet(0).bind(GC.Spread.Sheets.Events.EditEnded, me.onDetailEditEnded);
         me.detailSpread.getSheet(0).bind(GC.Spread.Sheets.Events.EnterCell, me.onEnterCell);
+
         let mSheet = me.mainSpread.getSheet(0);
         sheetCommonObj.showData(mSheet, me.mainSetting, me.datas);
 
@@ -70,6 +71,9 @@ let calcProgramManage = {
         feeRateObject.setFeeRateCellCol(dSheet,_.findIndex(me.detailSetting.header,{'dataCode':'feeRate'}));
         dSheet.getRange(-1, _.findIndex(me.detailSetting.header, {'dataCode': 'dispExprUser'}), -1, 1).cellType(calcBaseView.getCalcBaseCellType('ration'));
         sheetCommonObj.showData(dSheet, me.detailSetting, me.datas[0].calcItems);
+
+        me.loadMainContextMenu();
+        me.loadDetailContextMenu();
     },
     onMainEnterCell: function(sender, args) {
         var me = calcProgramManage;
@@ -118,13 +122,99 @@ let calcProgramManage = {
     onEnterCell: function (sender, args) {
         let t = calcProgramManage.getSelectionInfo().template;
         let c = calcProgramManage.getSelectionInfo().calcItem;
-        let lc = projectObj.project.calcProgram.getLabourCoe(c);
+        let lc = analyzer.calcItemLabourCoe(c);
         c.dispExpr = analyzer.getDispExpr(c.expression, t);
         c.dispExprUser = analyzer.getDispExprUser(c.dispExpr, lc);
         c.compiledExpr = analyzer.getCompiledExpr(c.expression, lc);
         let e = c.expression + ' ' + c.dispExpr + ' ' + c.dispExprUser + ' ' + c.compiledExpr;
         projectObj.testDisplay('', e);
     },
+    loadMainContextMenu: function () {
+        $.contextMenu({
+            selector: '#mainSpread',
+            build: function ($trigger, e) {
+                SheetDataHelper.safeRightClickSelection($trigger, e, calcProgramManage.mainSpread);
+            },
+            items: {
+                "copyTemplate": {
+                    name: "另存为...",
+                    icon: 'fa-sign-in',
+                    disabled: function () {
+                        return false;
+                    },
+                    visible: function(key, opt){
+                        return true;
+                    },
+                    callback: function (key, opt) {
+                        // doCopy
+                    }
+                },
+                "spr1": '--------',
+                "deleteTemplate": {
+                    name: '删除',
+                    icon: 'fa-remove',
+                    disabled: function () {
+                        return false;
+                    },
+                    visible: function(key, opt){
+                        return true;
+                    },
+                    callback: function () {
+                        // doDelete
+                    }
+                }
+            }
+        });
+    },
+    loadDetailContextMenu: function () {
+        $.contextMenu({
+            selector: '#detailSpread',
+            build: function ($triggerElement, event) {
+                SheetDataHelper.safeRightClickSelection($triggerElement, event, calcProgramManage.detailSpread);
+            },
+            items: {
+                "newCalcItem": {
+                    name: "插入行",
+                    icon: 'fa-sign-in',
+                    callback: function () {
+                        let template = calcProgramManage.getSelectionInfo().template;
+                        var idx = calcProgramManage.detailSpread.getActiveSheet().getActiveRowIndex();
+
+                        let newItem = {};
+                        newItem.ID = analyzer.calcItemMaxID(template) + 1;
+                        newItem.memo = '用户自定义';
+                        newItem.expression = '0';
+                        template.calcItems.splice(idx + 1, 0, newItem);
+
+                        projectObj.project.calcProgram.compileTemplate(template);
+                        calcProgramManage.refreshDetailSheet();
+                        calcProgramManage.detailSpread.getActiveSheet().setSelection(idx + 1, 0, 1, 1);
+                    }
+                },
+                "deleteCalcItem": {
+                    name: '删除行',
+                    icon: 'fa-remove',
+                    callback: function () {
+                        let template = calcProgramManage.getSelectionInfo().template;
+                        var idx = calcProgramManage.detailSpread.getActiveSheet().getActiveRowIndex();
+                        let item = template.calcItems[idx];
+
+                        if (analyzer.calcItemIsUsed(template, item)){
+                            alert(`第 ${idx + 1} 行“${item.name}”已被第 ${item.tempUsed + 1} 行引用,不允许删除!`);
+                            delete item.tempUsed;
+                        }
+                        else{
+                            template.calcItems.splice(idx, 1);
+                            projectObj.project.calcProgram.compileTemplate(template);
+                            calcProgramManage.refreshDetailSheet();
+                        }
+                    }
+                }
+            }
+        });
+
+
+    },
     saveCalcItem: function (data,callback) {//data
         let me = this;
         $.ajax({
@@ -156,10 +246,13 @@ let calcProgramManage = {
         return info;
     },
     refreshDetailSheet:function () {
-        var me=this;
+        var me = this;
         if(me.mainSpread && me.detailSpread){
-            var mainSheetIndex = me.mainSpread.getActiveSheet().getActiveRowIndex();
-            sheetCommonObj.showData(me.detailSpread.getSheet(0), me.detailSetting,me.datas[mainSheetIndex].calcItems);
+            let mainRowIdx = me.mainSpread.getActiveSheet().getActiveRowIndex();
+            let calcItems = me.datas[mainRowIdx].calcItems;
+            let detailSheet = me.detailSpread.getActiveSheet();
+            detailSheet.setRowCount(calcItems.length);
+            sheetCommonObj.showData(detailSheet, me.detailSetting, calcItems);
         }
     }
 

+ 1 - 4
web/building_saas/main/js/views/project_glj_view.js

@@ -539,10 +539,6 @@ $(function () {
     );
     // 单价文件切换弹框
     $('#change-unitFile').on('shown.bs.modal', function () {
-        // 获取当前建设项数据
-        let projectName = projectInfoObj.projectInfo.fullFolder !== undefined &&
-        projectInfoObj.projectInfo.fullFolder.length > 0 ? projectInfoObj.projectInfo.fullFolder[0] : '';
-        $("#current-project-name").text(projectName);
         let rootProjectID = projectInfoObj.projectInfo.property.rootProjectID;
         // 获取切换单价文件相关数据
         $.ajax({
@@ -556,6 +552,7 @@ $(function () {
                     return false;
                 }
                 let data = response.data;
+                $("#current-project-name").text(data.currentProjectName);
                 // 本项目中的单价文件
                 if (data.self.length > 0) {
                     let selfFileHtml = '';

+ 1 - 1
web/building_saas/main/js/views/project_view.js

@@ -1161,7 +1161,7 @@ var projectObj = {
             }
         });
      
-},
+    },
     loadLockBillsButton:function () {
         if(projectInfoObj.projectInfo.property.lockBills == true){
             $("a[name='lockBills']").prop("title","解锁清单");

+ 38 - 0
web/building_saas/pm/js/pm_ajax.js

@@ -85,4 +85,42 @@ var BeforeOpenProject = function (projId, updateData, callback) {
             alert('error ' + textStatus + " " + errorThrown)
         }
     });
+}
+
+var CommonAjax = {
+    get: function (url, data, cb, dataType) {
+        $.get(url, data, cb, dataType)
+    },
+    post: function (url, data, successCallback, errorCallback) {
+        $.ajax({
+            type: "POST",
+            url: url,
+            data: {'data': JSON.stringify(data)},
+            dataType: 'json',
+            cache: false,
+            timeout: 50000,
+            success: function (result) {
+                if (result.error === 0) {
+                    if (successCallback) {
+                        successCallback(result.data);
+                    }
+                } else {
+                    alert('error: ' + result.message);
+                    if (errorCallback) {
+                        errorCallback();
+                    }
+                }
+            },
+            error: function (jqXHR, textStatus, errorThrown) {
+                alert('url: ' + url + ' error ' + textStatus + " " + errorThrown);
+                if (errorCallback) {
+                    errorCallback();
+                }
+            }
+        });
+    }
+}
+
+function moveProjects(data,callback) {
+    CommonAjax.post( '/pm/api/moveProject',data,callback)
 }

+ 13 - 13
web/building_saas/pm/js/pm_gc.js

@@ -948,16 +948,16 @@ function m_getRecDatas(oprNode){
         //恢复单价、费率文件
         rstFile = rstFile.concat(getUpdateFiles([oprNode], project));
         if(path === recPath.t){
-            rstProj = rstProj.concat(getUpdateDatas(projectType.tender, oprNode, true, true));
+            rstProj = rstProj.concat(getUpdateDatas(projectType.tender, oprNode, -1, true));
         }
         else if(path === recPath.t_e){
-            rstProj = rstProj.concat(getUpdateDatas(projectType.tender, oprNode, true, false));
-            rstProj = rstProj.concat(getUpdateDatas(projectType.engineering, engineering, true, true));
+            rstProj = rstProj.concat(getUpdateDatas(projectType.tender, oprNode, -1, false));
+            rstProj = rstProj.concat(getUpdateDatas(projectType.engineering, engineering, -1, true));
         }
         else if(path === recPath.t_e_p){
-            rstProj = rstProj.concat(getUpdateDatas(projectType.tender, oprNode, true, false));
-            rstProj = rstProj.concat(getUpdateDatas(projectType.engineering, engineering, true, false));
-            rstProj = rstProj.concat(getUpdateDatas(projectType.project, project, true, false));
+            rstProj = rstProj.concat(getUpdateDatas(projectType.tender, oprNode, -1, false));
+            rstProj = rstProj.concat(getUpdateDatas(projectType.engineering, engineering, -1, false));
+            rstProj = rstProj.concat(getUpdateDatas(projectType.project, project, -1, false));
         }
     }
     else if(oprNode.data.projType === projectType.engineering){
@@ -971,18 +971,18 @@ function m_getRecDatas(oprNode){
         let tenders = oprNode.children;
         if(tenders.length > 0){
             for(let i = 0, len = tenders.length; i < len; i++){
-                rstProj = rstProj.concat(getUpdateDatas(projectType.tender, tenders[i], true, false));
+                rstProj = rstProj.concat(getUpdateDatas(projectType.tender, tenders[i], tenders[i].data.NextSiblingID, false));
             }
             rstProj = deWeightName(rstProj);
             //恢复单价、费率文件
             rstFile = rstFile.concat(getUpdateFiles(tenders, project));
         }
         if(path === recPath.e){
-            rstProj= rstProj.concat(getUpdateDatas(projectType.engineering, oprNode, true, true));
+            rstProj= rstProj.concat(getUpdateDatas(projectType.engineering, oprNode, -1, true));
         }
         else if(path === recPath.e_p){
-            rstProj= rstProj.concat(getUpdateDatas(projectType.engineering, oprNode, true, false));
-            rstProj= rstProj.concat(getUpdateDatas(projectType.project, project, true, false));
+            rstProj= rstProj.concat(getUpdateDatas(projectType.engineering, oprNode, -1, false));
+            rstProj= rstProj.concat(getUpdateDatas(projectType.project, project, -1, false));
         }
     }
     else if(oprNode.data.projType === projectType.project){
@@ -992,13 +992,13 @@ function m_getRecDatas(oprNode){
             let allTenders = [], rstEngs = [];
             for(let i = 0, len = engineerings.length; i < len; i++){
                 //恢复单项工程
-                rstEngs = rstEngs.concat(getUpdateDatas(projectType.engineering, engineerings[i], false, false));
+                rstEngs = rstEngs.concat(getUpdateDatas(projectType.engineering, engineerings[i], engineerings[i].data.NextSiblingID, false));
                 let tenders = engineerings[i].children;
                 allTenders = allTenders.concat(tenders);
                 let rstTends = [];
                 for(let j = 0, jLen = tenders.length; j < jLen; j++){
                     //恢复单位工程
-                    rstTends = rstTends.concat(getUpdateDatas(projectType.tender, tenders[j], false, false));
+                    rstTends = rstTends.concat(getUpdateDatas(projectType.tender, tenders[j], tenders[j].data.NextSiblingID, false));
                 }
                 //去除重名
                 rstTends = deWeightName(rstTends);
@@ -1360,7 +1360,7 @@ function getUpdateDatas(updateType, node, mtNID, mtPM){
         }
         //恢复
         if(mtNID){
-            rst.push(getUpdateObj(updateType, {ID: node.data.ID}, {name: newName, deleteInfo: null, NextSiblingID: -1}));
+            rst.push(getUpdateObj(updateType, {ID: node.data.ID}, {name: newName, deleteInfo: null, NextSiblingID: mtNID}));
         }
         else {
             rst.push(getUpdateObj(updateType, {ID: node.data.ID}, {name: newName, deleteInfo: null}));

+ 209 - 3
web/building_saas/pm/js/pm_newMain.js

@@ -52,9 +52,10 @@ const projTreeObj = {
             tabStripVisible:  false,
             allowCopyPasteExcelStyle : false,
             allowExtendPasteRange: false,
-            allowUserDragDrop : false,
+            allowUserDragDrop : true,
             allowUserDragFill: false,
-            scrollbarMaxAlign : true
+            scrollbarMaxAlign : true,
+            showDragDropTip:false
         }
     },
     renderSheetFuc: function (sheet, fuc) {
@@ -74,11 +75,14 @@ const projTreeObj = {
         let sheet = workBook.getActiveSheet();
         //改变选中节点set selected
         sheet.bind(_events.SelectionChanging, this.onSelectionChanging);
+        sheet.bind(_events.EditStarting, this.onCellEditing);
+        sheet.bind(_events.ClipboardPasting,this.onCellEditing);
+        workBook.bind(_events.DragDropBlock, this.onDragDropBlock);
     },
     buildHeader: function (sheet, headers) {
         let me = this;
         let fuc = function () {
-            sheet.options.isProtected = true;
+            //sheet.options.isProtected = true;  //为了拖动功能,这里不设置protect,输入限制在editstarting控制
             sheet.options.protectionOptions = {
                 allowResizeRows: true,
                 allowResizeColumns: true
@@ -169,6 +173,173 @@ const projTreeObj = {
         let me = projTreeObj;
         me.initSelection(args.newSelections[0], args.oldSelections[0]);
     },
+    onDragDropBlock : function (sender,args) {//拖动移动项目位置
+        let selected = projTreeObj.tree.selected;
+        let targetNode = projTreeObj.tree.items[args.toRow];
+        let projectMap = {},feeRateMap={},unitPriceMap = {},parent=null,next = null;
+        let rootProjectID = null;//记录建设项目ID
+        //let updateObj = {project:[],feeRateFile:[],unitPriceFile:[]};
+        args.cancel = true;//首先取消填充的动作
+        if(selected.preSibling()!=null&&selected.preSibling() == targetNode){
+            return;
+        }
+
+        if(selected.data.projType=="Tender"){//移动单位工程
+            if(targetNode.data.projType=="Tender"||targetNode.data.projType=="Engineering"){//只能移动到这两个地方
+                if(targetNode.data.projType=="Tender"){//移动到单位工程后面
+                    [parent,next] = getMoveUpdateData(selected,targetNode,projectMap,true);
+                    rootProjectID = targetNode.parent.pid();
+                }else{//移动后成为单项工程的子项
+                    [parent,next] =getMoveUpdateData(selected,targetNode,projectMap,false);
+                    rootProjectID = targetNode.pid();
+                }
+                if(selected.data.property.rootProjectID != rootProjectID){//如果不属于同一个建设项目,要检查是否移动或复制单价文件和费率文件
+                    projectMap[selected.id()].update['property.rootProjectID'] = rootProjectID; //要改变根项目ID
+                    //检查费率文件和单价文件
+                    moveOrCopy(selected,rootProjectID,feeRateMap,unitPriceMap,false);
+                }
+            }
+        }
+        if(selected.data.projType=="Engineering"){//移动的是单项工程
+            if(targetNode.data.projType=="Engineering"||targetNode.data.projType=="Project"){//目标位置是单项工程和建设项目
+                if(targetNode.data.projType=="Engineering"){
+                    [parent,next] = getMoveUpdateData(selected,targetNode,projectMap,true);
+                    rootProjectID = targetNode.pid();
+                }else {
+                    [parent,next]  = getMoveUpdateData(selected,targetNode,projectMap,false);
+                    rootProjectID = targetNode.id();
+                }
+                if(selected.pid() != rootProjectID){//跨了建设项目,更新子项目,同时要检查是否移动或复制子项的单价文件和费率文件。
+                    //检查费率文件和单价文件
+                    //todo
+                    for(let c of selected.children){
+                        projectMap[c.id()] = {query:{'ID':c.id()},update:{'property.rootProjectID':rootProjectID}};
+                        moveOrCopy(c,rootProjectID,feeRateMap,unitPriceMap,true);
+                    }
+                }
+            }
+        }
+        if(selected.data.projType=="Project"){//移动的是建设项目
+            if(targetNode.data.projType=="Project"||targetNode.data.projType=="Folder") {//目标位置是建设项目和文件夹
+                if(targetNode.data.projType=="Project"){
+                    [parent,next]  = getMoveUpdateData(selected,targetNode,projectMap,true);
+                }else {
+                    [parent,next]  = getMoveUpdateData(selected,targetNode,projectMap,false);
+                }
+            }
+        }
+        if(selected.data.projType=="Folder"&&targetNode.data.projType=="Folder") {//文件夹只能移动到文件夹下
+            [parent,next]  =  getMoveUpdateData(selected,targetNode,projectMap,false);//成为子项
+        }
+        if(_.isEmpty(projectMap)){//项目数据为空
+            return;
+        }
+        $.bootstrapLoading.start();
+        moveProjects({"user_id": userID,rootProjectID:rootProjectID,projectMap:projectMap,feeRateMap:feeRateMap,unitPriceMap:unitPriceMap},function (result) {
+            for(let key in result){//更新前端节点数据
+                let updateData = result[key].update;
+                let node = projTreeObj.tree.findNode(result[key].query.ID);
+                if(node){
+                    for(let ukey in updateData){
+                        _.set(node.data,ukey,updateData[ukey]);
+                    }
+                }
+            }
+            projTreeObj.moveTo(selected,targetNode,parent,next);
+            $.bootstrapLoading.end();
+        });
+        function getMoveUpdateData(sel,target,projectMap,sameLevel) {
+            let tem_parent = null,tem_next = null;
+            if(sameLevel==true){//移动后的位置的级别相同
+                tem_parent = target.parent;
+                tem_next = target.nextSibling;
+                //移动到target的后面
+                projectMap[target.id()] = {query:{'ID':target.id()},update:{'NextSiblingID':sel.id()}};
+                console.log(target.id()+'----'+target.data.name);
+            }else {//移动后变成子项
+                tem_parent = target;
+                tem_next = target.firstChild();
+            }
+            let nextID = tem_next?tem_next.id():-1;
+            if(sel.preSibling() != null){//如果原来有前项,更新下一节点
+                projectMap[sel.preSibling().id()] = {query:{'ID':sel.preSibling().id()},update:{'NextSiblingID':sel.data.NextSiblingID}};
+                console.log(sel.preSibling().id()+'----'+sel.preSibling().data.name);
+            }
+            if(sel.parent.id() == tem_parent.id()){//移动前移动后的父项相同,只改变next
+                projectMap[selected.id()] = {query:{'ID':selected.id()},update:{'NextSiblingID':nextID}};
+                console.log(selected.id()+'----'+selected.data.name);
+            }else {//
+                let temData = {query:{'ID':selected.id()},update:{'ParentID':tem_parent.id(),'NextSiblingID':nextID}};
+                let reName = projTreeObj.projectNameChecking(tem_parent,selected);//重名检查
+                if(reName){
+                    temData.update['name'] = reName
+                }
+                projectMap[selected.id()] = temData;
+                console.log(selected.id()+'----'+selected.data.name);
+            }
+            return [tem_parent,tem_next]
+        }
+
+        function moveOrCopy(sel,rootProjectID,feeRateMap,unitPriceMap,sameCheck) {//检查选中的项目使用的费率文件和单价文件是要移动还是复制一份
+            let project = sel.parent.parent;//取建设项目
+            let sameParent_f = true;
+            let sameParent_u = true;
+            let useFeeRateProject = getTendersByFile(fileType.feeRateFile,sel.data.property.feeFile.id,project);
+            let useUnitPriceProject = getTendersByFile(fileType.unitPriceFile,sel.data.property.unitPriceFile.id,project);
+            let tem_feeRate = {action:'copy',rootProjectID:rootProjectID,name:sel.data.property.feeFile.name,query:{ID:sel.data.property.feeFile.id}};
+            let tem_unitPrice = {action:'copy',rootProjectID:rootProjectID,name:sel.data.property.unitPriceFile.name,query:{id:sel.data.property.unitPriceFile.id}}
+            if(useFeeRateProject.length==1){//如果没有其它项目使用同一个费率文件,则移动
+                tem_feeRate.action = 'move';
+                tem_feeRate.update = {rootProjectID:rootProjectID};
+            }
+            if(useUnitPriceProject.length == 1){//如果没有其它项目使用同一个单价文件,则移动
+                tem_unitPrice.action =  'move';
+                tem_unitPrice.update = {root_project_id:rootProjectID};
+            }
+            feeRateMap[sel.id()] = tem_feeRate;
+            unitPriceMap[sel.id()] = tem_unitPrice;
+            if(sameCheck == true){//需要检查是否属于同一个单项工程,移动单项工程的时候用
+                if(useUnitPriceProject.length > 1){
+                    for(let f of useFeeRateProject){
+                        if(f.pid()!=sel.pid()){//父节点不同
+                            sameParent_f = false;
+                            break;
+                        }
+                    }
+                    if(sameParent_f == true){//如果使用的都是同属于一个单项工程,在移动单项工程的时候,移动费率文件和单价文件就可以了
+                        feeRateMap[sel.id()].action = 'move';
+                        feeRateMap[sel.id()].update = {rootProjectID:rootProjectID};
+                    }
+                }
+                if(useUnitPriceProject.length > 1){
+                    for(let u of useUnitPriceProject){
+                        if(u.pid()!=sel.pid()){//父节点不同
+                            sameParent_u = false;
+                            break;
+                        }
+                    }
+                    if(sameParent_u == true){
+                        unitPriceMap[sel.id()].action =  'move';
+                        unitPriceMap[sel.id()].update = {root_project_id:rootProjectID};
+                    }
+                }
+            }
+        }
+
+
+    },
+    projectNameChecking:function (parent,node) {
+        //decDate = '(' + new Date().Format('MM-dd hh:mm:ss') + '恢复)';
+        for(let c of parent.children){
+           if(c.data.name == node.data.name){//如果存在同名的项目
+               return  node.data.name + '(' + new Date().Format('MM-dd hh:mm:ss') + '移动)';
+           }
+        }
+        return null;
+    },
+    onCellEditing:function (sender,args) {
+        args.cancel = true;
+    },
     getShowData: function (datas) {
         for(let data of datas){
             data.name = data.name ? data.name : '';
@@ -457,6 +628,7 @@ const projTreeObj = {
                     let dataCode = headers[j].dataCode;
                     //sheet.setValue(i, j, nodes[i]['data'][dataCode]);
                 }
+                sheet.getCell(i, 1,GC.Spread.Sheets.SheetArea.viewport).locked(true);
             }
         };
         me.renderSheetFuc(sheet, fuc);
@@ -509,6 +681,40 @@ const projTreeObj = {
     move: function (orgRow, newRow) {
         this.workBook.getActiveSheet().deleteRows(orgRow, 1);
         this.addRow(this.tree.items[newRow]);
+    },
+    moveTo : function(select,target,parent,next){
+        let me = this;
+        let fromRow = select.serialNo();
+        let rCout = select.posterityCount() + 1;//删除的行数
+        let toRow = target.serialNo();
+        let sheet = me.workBook.getActiveSheet();
+        me.tree.removeNode(select);//删除旧节点
+        let newNode  = addNewNodes(select, parent, next);
+       // projTreeObj.remove(sheet, fromRow, rCout);
+
+        me.renderSheetFuc(sheet, function () {
+            sheet.deleteRows(fromRow, rCout);
+            sheet.addRows(newNode.serialNo(),rCout);
+            let oldSelection = sheet.getSelections()[0];
+            let newSelection = {row: newNode.serialNo(), rowCount: 1,col:oldSelection.col,colCount:oldSelection.colCount};
+            me.initSelection({row: newNode.serialNo(), rowCount: oldSelection.rowCount}, oldSelection);
+            sheet.setSelection(newNode.serialNo(),oldSelection.col,oldSelection.rowCount,oldSelection.colCount);
+            let children = newNode.getAllChildren();
+            children.push(newNode);
+            for(let c of children){
+                sheet.getCell(c.serialNo(), 0).cellType(me.getTreeNodeCell(me.tree));
+                me.refreshNodeData(c);
+            }
+        });
+
+        function addNewNodes(node,parent,next) {
+            let newNode = me.tree.addNodeData(node.data, parent, next);
+            for(let c of node.children){
+                addNewNodes(c,newNode,null);
+            }
+            return newNode;
+        }
+
     }
 };
 

+ 12 - 2
web/building_saas/pm/js/pm_tree.js

@@ -157,8 +157,18 @@ const pmTree = {
 
             Node.prototype.propertyJoin = function (dataName) {
                 return this.parent ? this.parent.propertyJoin(dataName) + ';' + this.data[dataName] : this.data[dataName];
-            }
-
+            };
+            Node.prototype.getAllChildren = function () {
+                let childrenList = [];
+                getChildren(this);
+                function getChildren(node) {
+                    for(let c of node.children){
+                        childrenList.push(c);
+                        getChildren(c)
+                    }
+                }
+                return childrenList;
+            };
             return Node;
         })();
 

+ 1 - 0
web/common/html/header.html

@@ -93,4 +93,5 @@
 <script type="text/javascript" src="/web/building_saas/js/message.js"></script>
 <script type="text/javascript" src="/public/web/scMathUtil.js"></script>
 <script type="text/javascript" src="/public/web/PerfectLoad.js"></script>
+<script type="text/javascript" src="/lib/lodash/lodash.js"></script>
 <!-- endinject -->