浏览代码

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

TonyKang 7 年之前
父节点
当前提交
54377b3469

文件差异内容过多而无法显示
+ 16 - 0
lib/js-xlsx/xlsx.core.min.js


+ 2 - 2
modules/glj/models/glj_list_model.js

@@ -223,9 +223,9 @@ class GLJListModel extends BaseModel {
         return result;
     }
     getGLJPrice(glj){
-        let glj_basePrice = scMathUtil.roundTo(parseFloat(glj.unit_price.base_price), -2);
+        let glj_basePrice = parseFloat(glj.unit_price.base_price);
         glj.unit_price.base_price = glj_basePrice;
-        glj.unit_price.market_price = scMathUtil.roundTo(parseFloat(glj.unit_price.market_price), -2);
+        glj.unit_price.market_price = parseFloat(glj.unit_price.market_price);
     }
 
     /**

+ 245 - 81
modules/main/controllers/bills_controller.js

@@ -23,6 +23,8 @@ const billType ={
     BILL:4,//清单
     BX:5//补项
 };
+//上传的09表、广联达表
+const uploadType = {lj: 'lj', gld: 'gld'};
 // 上传控件
 const multiparty = require("multiparty");
 const fs = require("fs");
@@ -198,12 +200,12 @@ module.exports = {
         }
 
     },
-
+    //导入清单
     upload: async function(req, res){
         let responseData = {
             err: 0,
             msg: '',
-            data: null
+            data: []
         };
         const allowHeader = ['application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'];
         const uploadOption = {
@@ -226,67 +228,75 @@ module.exports = {
                 if (file.headers['content-type'] === undefined || allowHeader.indexOf(file.headers['content-type']) < 0) {
                     throw '不支持该类型';
                 }
+                //导入表类型(09表lj、广联达gld)
+                const fileType = fields.fileType !== undefined && fields.fileType.length > 0 ? fields.fileType[0] : uploadType.lj;
                 // 重命名文件名
                 uploadFullName = uploadOption.uploadDir + '/' + file.originalFilename;
                 fs.renameSync(file.path, uploadFullName);
 
-                const sheet = excel.parse(uploadFullName);
-                if (sheet[0] === undefined || sheet[0].data === undefined) {
+                const sheets = excel.parse(uploadFullName);
+                if (sheets[0] === undefined || sheets[0].data === undefined) {
                     throw 'excel没有对应数据';
                 }
-                //导入的数据是否含有固定行(分部分项、施工技术措施项目、施工组织措施项目,通过文件名判断)
-                let flag = getImportFlag(file.originalFilename);
-                if(!flag){
-                    throw 'excel数据错误';
-                }
-                let fixedBill = await billsData.model.findOne({projectID: projectID, 'flags.flag': flag, deleteInfo: null});
-                let insertFixedBill = null;
-                //导入xx措施项目,若不存在此固定清单,则先插入相关固定清单
-                if(!fixedBill){
-                    //分部分项工程(不可删除)应存在
-                    if(flag === fixedFlag.SUB_ENGINERRING){
-                        throw '项目不存在分部分项工程'
+                //出现错误的表表名,前端提示用
+                let invalidSheets = [];
+                let validSheets = {fbfx: [], jscsxm: [], zzcsxm: []};
+                //获得选择的导入的表及导入位置
+                const uploadWorkBook = fields.uploadWorkBook !== undefined && fields.uploadWorkBook.length > 0 ? JSON.parse(fields.uploadWorkBook[0]) : [];
+                //识别非法和合法表,sheetInfo存储前端勾选的表的位置索引以及选择的导入位置
+                for(let sheetInfo of uploadWorkBook){
+                    let sheet = sheets[sheetInfo.sheetIdx];
+                    if(sheet.data === undefined){
+                        invalidSheets.push(sheet.name);
+                        continue;
                     }
-                    //措施项目是否存在
-                    let csxm = await billsData.model.findOne({projectID: projectID, 'flags.flag': fixedFlag.MEASURE, deleteInfo: null});
-                    if(!csxm){
-                        throw '项目不存在措施项目'
+                    //获取表的列设置确定导入的格式是否合法(09、广联达)
+                    let colMapping = getColMapping(sheet.data);
+                    if(!isValidSheet(colMapping, fileType)){
+                        invalidSheets.push(sheet.name);
+                        continue;
                     }
-                    //插入清单固定行(施工技术措施项目、施工组织措施项目可删除)
-                    insertFixedBill = {projectID: projectID, name: flag === fixedFlag.CONSTRUCTION_TECH ? '施工技术措施项目' : '施工组织措施项目', code: '1',
-                        ID: uuidV1(), NextSiblingID: -1, ParentID: csxm.ID, flags: [{fieldName: 'fixed', flag: flag}], type: billType.BILL};
-                    //更新前节点
-                    let preDatas = await billsData.model.find({projectID: projectID, ParentID: csxm.ID, deleteInfo: null});
-                    for(let preData of preDatas){
-                        if(preData.NextSiblingID == -1){
-                            await billsData.model.update({ID: preData.ID}, {$set: {NextSiblingID: insertFixedBill.ID}});
-                            break;
-                        }
+                    //合法的表
+                    sheet.colMapping = colMapping;
+                    //将合法的表按导入位置分类当做一个表来处理
+                    if(validSheets[sheetInfo.position] !== undefined){
+                        validSheets[sheetInfo.position].push(sheet)
+                    }
+                }
+                //合并同类表并提取表的有效数据
+                let toImportSheets = [];
+                for(let uploadPosition in validSheets){
+                    let validExcelData = [];
+                    for(let uSheet of validSheets[uploadPosition]){
+                        validExcelData = validExcelData.concat(getValidImportData(uSheet.colMapping, uSheet.data))
+                    }
+                    if(validSheets[uploadPosition].length > 0){
+                        toImportSheets.push({position: uploadPosition, colMapping: validSheets[uploadPosition][0].colMapping, validExcelData: validExcelData});
                     }
-                    await billsData.model.create(insertFixedBill);
-                    fixedBill = insertFixedBill;
                 }
-                console.log(`fixedBill--------------`);
-                console.log(fixedBill);
                 //匹配的清单库
                 const billsLibId = fields.billsLibId !== undefined && fields.billsLibId.length > 0 && fields.billsLibId[0]? parseInt(fields.billsLibId[0]) : null;
                 let stdBills = [], stdJobs = [], stdCharacters = [];
                 if(billsLibId){
-                    stdBills = await stdBillsModel.find({billsLibId: billsLibId, deleted: false}, '-_id code jobs items engineering');
+                    stdBills = await stdBillsModel.find({billsLibId: billsLibId, deleted: false}, '-_id code jobs items engineering sectionInfo');
                     stdJobs = await stdBillJobsModel.find({billsLibId: billsLibId, deleted: false});
                     stdCharacters = await stdBillCharacterModel.find({billsLibId: billsLibId, deleted: false});
                 }
-                //将excel数据转换成清单树结构数据
-                let insertDatas = parseToBillData(getValidImportData(sheet[0].data, fixedBill), getColMapping(sheet[0].data), fixedBill, projectID, {stdBills: stdBills, stdJobs: stdJobs, stdCharacters: stdCharacters});
-                //删除相关数据
-                let deleteDatas = await billsData.deepDeleteBill([fixedBill], req.session.sessionUser.id);
-                //新增清单数据
-                await billsData.importBills(insertDatas);
-                //返回数据以更新前端
-                if(insertFixedBill){
-                    insertDatas.push(insertFixedBill);
+                let stdData = {stdBills: stdBills, stdJobs: stdJobs, stdCharacters: stdCharacters};
+                //导入表
+                for(let importData of toImportSheets){
+                    let updateFrontData = await importSheet(importData, req.session.sessionUser.id, projectID, stdData);
+                    if(updateFrontData){
+                        responseData.data.push(updateFrontData);
+                    }
+                }
+                if(responseData.data.length === 0){
+                    throw 'excel无有效数据';
+                }
+                if(invalidSheets.length > 0){
+                    let msg = invalidSheets.join('、');
+                    responseData.msg = `${msg},导入失败`;
                 }
-                responseData.data = {fixedBill: fixedBill, insert: {bill: insertDatas, ration: []}, remove: {bill: deleteDatas.bill, ration: deleteDatas.ration}};
                 //删除暂存文件
                 fs.unlink(uploadFullName);
                 res.json(responseData);
@@ -296,39 +306,146 @@ module.exports = {
                     fs.unlink(uploadFullName);
                 }
                 responseData.err = 1;
-                responseData.msg = error;
+                console.log(error);
+                responseData.msg = typeof error === 'object' ? '上传失败' : error;
                 res.json(responseData);
             }
 
         });
     }
-
 };
 
+//
+async function importSheet(importData, userID, projectID, stdData){
+        //导入位置的有固定行
+        let flag = getImportFlag(importData.position);
+        if(!flag){
+            throw 'excel数据错误';
+        }
+        let fixedBill = await billsData.model.findOne({projectID: projectID, 'flags.flag': flag, deleteInfo: null});
+        let insertFixedBill = null;
+        //导入xx措施项目,若不存在此固定清单,则先插入相关固定清单
+        if(!fixedBill){
+            //分部分项工程(不可删除)应存在
+            if(flag === fixedFlag.SUB_ENGINERRING){
+                throw '项目不存在分部分项工程'
+            }
+            //措施项目是否存在
+            let csxm = await billsData.model.findOne({projectID: projectID, 'flags.flag': fixedFlag.MEASURE, deleteInfo: null});
+            if(!csxm){
+                throw '项目不存在措施项目'
+            }
+            //插入清单固定行(施工技术措施项目、施工组织措施项目可删除)
+            insertFixedBill = {projectID: projectID, name: flag === fixedFlag.CONSTRUCTION_TECH ? '施工技术措施项目' : '施工组织措施项目', code: '1',
+                ID: uuidV1(), NextSiblingID: -1, ParentID: csxm.ID, flags: [{fieldName: 'fixed', flag: flag}], type: billType.BILL};
+            //更新前节点
+            let preDatas = await billsData.model.find({projectID: projectID, ParentID: csxm.ID, deleteInfo: null});
+            for(let preData of preDatas){
+                if(preData.NextSiblingID == -1){
+                    await billsData.model.update({ID: preData.ID}, {$set: {NextSiblingID: insertFixedBill.ID}});
+                    break;
+                }
+            }
+            await billsData.model.create(insertFixedBill);
+            fixedBill = insertFixedBill;
+        }
+        //将excel数据转换成清单树结构数据
+        let insertDatas = parseToBillData(importData.validExcelData, importData.colMapping, fixedBill, projectID, stdData);
+        /*if(insertDatas.length === 0){
+            throw 'excel无有效数据';
+        }*/
+        if(insertDatas.length === 0){
+            return null;
+        }
+        //删除相关数据
+        let deleteDatas = await billsData.deepDeleteBill([fixedBill], userID);
+        //新增清单数据
+        await billsData.importBills(insertDatas);
+        //返回数据以更新前端
+        if(insertFixedBill){
+            insertDatas.push(insertFixedBill);
+        }
+        return {fixedBill: fixedBill, insert: {bill: insertDatas, ration: []}, remove: {bill: deleteDatas.bill, ration: deleteDatas.ration}};
+}
+
+//是否是有效的表头列格式,只要含有各表需要的列就行,不严格控制多少列
+function isValidSheet(colMapping, fileType){
+    function hasField(field, all){
+        for(let i of all){
+            if(field === i){
+                return true;
+            }
+        }
+        return false;
+    }
+    let needFields;
+    if(fileType === uploadType.lj){
+        //09表:序号、项目编码、项目名称、项目特征、计量单位、工程量、金额
+        needFields = ['serialNo', 'code', 'name', 'money'];
+    }
+    else {
+        //广联达表:序号、项目编码、项目名称、项目特征、计量单位、工程量、工程量明细、费用明细
+        needFields = ['serialNo', 'code', 'name', 'itemCharacterText', 'unit', 'quantity', 'quantityDetail', 'feeDetail'];
+    }
+    let hasFieldCount = 0;
+    for(let attr in colMapping){
+        if(hasField(attr, needFields)){
+            hasFieldCount++;
+        }
+    }
+    return hasFieldCount === needFields.length;
+}
+
 //提取excel表头列对应数据
 function getColMapping(sheetData){
     //获取表头
-    let headRow = [];
-    for(let rData of sheetData) {
-        if (rData[0] && rData[0].toString().replace(/\s/g, '') === '序号') {
-            headRow = rData;
-            break;
+    function getHeadRow(sheetData){
+        for(let rData of sheetData) {
+            //寻找含有序号的行,认作表头行
+            for(let cData of rData){
+                if (cData && cData.toString().replace(/\s/g, '') === '序号') {
+                    headRow = rData;
+                    return rData;
+                }
+            }
         }
+        return [];
     }
-    //获取表头列与列号对应关系
+    //获取需要的表头列与列号对应关系
     let colMapping = {};
+    let headRow = getHeadRow(sheetData);
     for(let c = 0; c < headRow.length; c++){
         if(headRow[c]){
             headRow[c] = headRow[c].toString().replace(/\s/g, '');
-            switch(headRow[c]){
-                case '序号': colMapping.serialNo = c; break;
-                case '项目编码': colMapping.code = c; break;
-                case '项目名称': colMapping.name = c; break;
-                case '项目特征': colMapping.itemCharacterText = c; break;
-                case '计量单位': colMapping.unit = c; break;
-                case '工程量': colMapping.quantity = c; break;
+            //重复的,只取第一个
+            console.log(headRow[c]);
+            if(headRow[c] === '序号' && colMapping.serialNo === undefined){
+                colMapping.serialNo = c;
+            }
+            else if((headRow[c] === '编码' || headRow[c] === '项目编码') && colMapping.code === undefined){
+                colMapping.code = c;
+            }
+            else if((headRow[c] === '名称' || headRow[c] === '项目名称') && colMapping.name === undefined){
+                colMapping.name = c;
+            }
+            else if((headRow[c] === '特征' || headRow[c] === '项目特征') && colMapping.itemCharacterText === undefined){
+                colMapping.itemCharacterText = c;
+            }
+            else if((headRow[c] === '单位' || headRow[c] === '计量单位') && colMapping.unit === undefined){
+                colMapping.unit = c;
+            }
+            else if((headRow[c] === '工程量' || headRow[c] === '项目工程量') && colMapping.quantity === undefined){
+                colMapping.quantity = c;
+            }
+            else if(headRow[c].includes('金额') && colMapping.money === undefined){
+                colMapping.money = c;
+            }
+            else if(headRow[c] === '工程量明细' && colMapping.quantityDetail === undefined){
+                colMapping.quantityDetail = c;
+            }
+            else if(headRow[c] === '费用明细' && colMapping.feeDetail === undefined){
+                colMapping.feeDetail = c;
             }
-
         }
     }
     return colMapping;
@@ -342,12 +459,39 @@ function rowExistData(rowData){
     return false;
 }
 //提取excel表数据中的有效数据(去表头表尾,提取其中的excel数据)(根据fixedBill获取栏头占行数)
-function getValidImportData(sheetData, fixedBill){
+function getValidImportData(colMapping, sheetData){
     let withingD = false;
     let validData = [];
+    function isHead(rData){
+        return rData[colMapping.serialNo] && rData[colMapping.serialNo].toString().replace(/\s/g, '') === '序号';
+    }
+    function isTail(rData){
+        for(let cData of rData){
+            if(cData){
+                let trimCData = cData.toString().replace(/\s/g, '');
+                if(trimCData === '本页小计' || trimCData === '本页小计'){
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
     for(let r = 0; r < sheetData.length; r++){
         let rData = sheetData[r];
-        if(rData[0]){
+        if(isHead(rData)){
+            withingD = true;
+          /*  if(fixedBill.name !== '施工组织措施项目'){
+                r++;
+            }*/
+            continue;
+        }
+        else if(isTail(rData)){
+            withingD = false;
+        }
+        if(withingD && rowExistData(rData)){
+            validData.push(rData);
+        }
+        /*if(rData[0]){
             //首列去空格
             rData[0] = rData[0].toString().replace(/\s/g, '');
             //表头
@@ -365,19 +509,17 @@ function getValidImportData(sheetData, fixedBill){
         }
         if(withingD && rowExistData(rData)){
             validData.push(rData);
-        }
+        }*/
     }
     return validData;
 }
 
-function getImportFlag(sheetName){
-    const fixedItem = {'分部分项': fixedFlag.SUB_ENGINERRING, '施工技术措施项目': fixedFlag.CONSTRUCTION_TECH, '施工组织措施项目': fixedFlag.CONSTRUCTION_ORGANIZATION};
-    for(let flag in fixedItem){
-        if(sheetName.includes(flag)){
-            return fixedItem[flag];
-        }
-    }
-    return null;
+function getImportFlag(position){
+    const fixedItem = {'fbfx': fixedFlag.SUB_ENGINERRING, 'jscsxm': fixedFlag.CONSTRUCTION_TECH, 'zzcsxm': fixedFlag.CONSTRUCTION_ORGANIZATION};
+    return fixedItem[position] ? fixedItem[position] : null;
+}
+function isDef(data){
+    return typeof data !== 'undefined' && data !== null && data !== '';
 }
 //excel数据转换成清单数据
 function parseToBillData(validData, colMapping, fixedBill, projectID, stdData){
@@ -386,17 +528,29 @@ function parseToBillData(validData, colMapping, fixedBill, projectID, stdData){
     let preRootID = -1,
         preLeafID = -1,
         preID = -1;
-    //合并了项目特征,且行有数据
+    //去除转义字符
+    function removeESC(data){
+        return isDef(data) ? data.toString().replace(/[\r,\n,\s,\t]/g, '') : data;
+    }
+    //父节点:1.无序号 2有编码
     function isRoot(rData){
-        return rData[colMapping.itemCharacterText] !== undefined && rData[colMapping.itemCharacterText] === '' && rowExistData(rData);
+        //序号和编码去除转义字符(有的表格单元格看起来是没数据,实际含有\r,\n等数据)
+        let serialNo = removeESC(rData[colMapping.serialNo]);
+        let code = removeESC(rData[colMapping.code]);
+        return !isDef(serialNo) && isDef(code);
     }
-    //不合并且有序号
+    //子节点:有序号
     function isLeaf(rData){
-        return (rData[colMapping] === undefined || rData[colMapping] !== '') && rData[colMapping.serialNo] && rData[colMapping.serialNo] !== '';
+        let serialNo = removeESC(rData[colMapping.serialNo]);
+        return isDef(serialNo);
     }
-    //续数据,上一行数据是有效节点且无序号
+    //续数据:1. 前数据有效 2.无序号 3.无编码 4.有名称或特征
     function isExtend(preData, rData){
-        return preData && (isRoot(preData) || isLeaf(preData)) &&  !isRoot(rData) && (rData[colMapping.serialNo] === undefined || rData[colMapping.serialNo] === '');
+        let serialNo = removeESC(rData[colMapping.serialNo]);
+        let code = removeESC(rData[colMapping.code]);
+        let name = rData[colMapping.name];
+        let itemCharacterText = rData[colMapping.itemCharacterText];
+        return isDef(preData) && (isRoot(preData) || isLeaf(preData)) && !isDef(serialNo) && !isDef(code) && (isDef(name) || isDef(itemCharacterText));
     }
     function getBillType(rData, flag){
         if(flag === fixedFlag.CONSTRUCTION_TECH || flag === fixedFlag.CONSTRUCTION_ORGANIZATION){
@@ -418,6 +572,7 @@ function parseToBillData(validData, colMapping, fixedBill, projectID, stdData){
                 if(nineCode == stdBill.code){
                     isMatch = true;
                     excelBill.programID = stdBill.engineering ? stdBill.engineering : null;
+                    excelBill.sectionInfo = stdBill.sectionInfo ? stdBill.sectionInfo : null;
                     //set jobContent and itemCharacter
                     let tempJob = [], tempCharacter = [];
                     for(let billJob of stdBill.jobs){
@@ -443,11 +598,20 @@ function parseToBillData(validData, colMapping, fixedBill, projectID, stdData){
                 }
             }
         }
-        if(!isMatch && excelBill.type === billType.FX){//分项在标准清单中不匹配,则识别为补项
+        if(!isMatch && excelBill.type === billType.FX){//分项不为空,同时在标准清单中不匹配,则识别为补项
                 excelBill.type = billType.BX;
         }
     }
     for(let r = 0; r < validData.length; r++){
+       /* //序号和编码去除转义字符(有的表格单元格看起来是没数据,实际含有\r,\n等数据)
+        let serialNo = validData[r][colMapping.serialNo];
+        let code = validData[r][colMapping.code];
+        if(isDef(serialNo)){
+            serialNo = removeESC(serialNo);
+        }
+        if(isDef(code)){
+            code = removeESC(code);
+        }*/
         let preData = validData[r-1],
             rData = validData[r];
         if(fixedBill.flags[0].flag == fixedFlag.CONSTRUCTION_TECH && rData[colMapping.name] === '施工技术措施项目'
@@ -484,8 +648,8 @@ function parseToBillData(validData, colMapping, fixedBill, projectID, stdData){
             //set bill data
             billIdx[newID] = {
                 ID: newID, ParentID: pID, NextSiblingID: -1,
-                code: rData[colMapping.code] ? rData[colMapping.code] : '',
-                name: rData[colMapping.name] ? rData[colMapping.name] : '',
+                code: rData[colMapping.code] ? removeESC(rData[colMapping.code]) : '',
+                name: rData[colMapping.name] ? removeESC(rData[colMapping.name]) : '',
                 itemCharacterText: rData[colMapping.itemCharacterText] ? rData[colMapping.itemCharacterText] : '',
                 itemCharacter: [],
                 jobContentText: '',

+ 7 - 6
modules/main/facade/calc_program_facade.js

@@ -29,13 +29,14 @@ module.exports = {
 async function newProjectCalcProgramFile(data) {
     logger.info(`Create new CalcProgram file for project : ${data.ID}`);
     let rst = null;
-    let egnrModel = new EngineeringLibModel();
-    let egnr = await egnrModel.getEngineering(data.property.engineering_id);
-    if(!egnr) return rst;
-    let valid_CP_libs = egnr._doc.program_lib;
-    if (valid_CP_libs == undefined  || valid_CP_libs.length == 0) return rst;
+    // let egnrModel = new EngineeringLibModel();
+    // let egnr = await egnrModel.getEngineering(data.property.engineering_id);
+    // if(!egnr) return rst;
+    // let valid_CP_libs = egnr._doc.program_lib;
+    // if (valid_CP_libs == undefined  || valid_CP_libs.length == 0) return rst;
     // 绑定多个计算程序标准文件时,默认取第一个作为标准模板。
-    let stdCP = await getStdCalcProgramFile(valid_CP_libs[0].id);
+    // let stdCP = await getStdCalcProgramFile(valid_CP_libs[0].id);
+    let stdCP = await getStdCalcProgramFile(data.property.calcProgram.id);
     let doc={
         ID: uuidV1(),
         projectID: data.ID,

+ 1 - 1
public/web/PerfectLoad.js

@@ -27,7 +27,7 @@ jQuery.bootstrapLoading = {
             //提示颜色
             delayTime: 500,
             //页面加载完成后,加载页面渐出速度
-            zindex: 999,
+            zindex: 2000,
             //loading页面层次
             sleep: 0
             //设置挂起,等于0时则无需挂起

+ 3 - 3
web/building_saas/main/html/calc_program_manage.html

@@ -16,14 +16,14 @@
     <div class="container-fluid">
         <div class="row">
             <div class="col-lg-2 p-0">
-                <div id="divSelect" style="text-align:left;height:45px;line-height:45px;">
-                    <!--<label>计算程序文件:</label>-->
+<!--                <div id="divSelect" style="text-align:left;height:45px;line-height:45px;">
+                    &lt;!&ndash;<label>计算程序文件:</label>&ndash;&gt;
                     <select id='calcProgramFileSelect' style="width:98%;height:30px;">
                         <option value="none">无</option>
                         <option value="1" selected>计算程序2013标准</option>
                         <option value="2">计算程序2018标准</option>
                     </select>
-                </div>
+                </div>-->
                 <div class="main-data-not" id="mainSpread"></div>
             </div>
             <div class="col-lg-10 p-0">

文件差异内容过多而无法显示
+ 30 - 0
web/building_saas/main/html/main.html


+ 30 - 5
web/building_saas/main/js/models/calc_base.js

@@ -861,6 +861,7 @@ let baseFigureTemplate = {
     'ZGCLFFZM': function (tender) {//暂估材料费(从子目汇总)
         const totalFeeType = tender ? 'tenderTotalFee' : 'totalFee';
         let bill = calcBase.fixedBills[calcBase.fixedFlag.ENGINEERINGCOST]['bill'];
+        projectObj.project.calcProgram.calculate(cbTools.getNodeByID(bill.ID), false, false, true);
         if(cbTools.isUnDef(bill)) return 0;
         if(cbTools.isUnDef(bill.feesIndex) || Object.keys(bill.feesIndex).length === 0) return 0;
         return cbTools.isDef(bill.feesIndex.estimate) && cbTools.isDef(bill.feesIndex.estimate[totalFeeType]) ? bill.feesIndex.estimate[totalFeeType] : 0;
@@ -1066,16 +1067,19 @@ let cbAnalyzer = {
         let cnExps = cbParser.getCN(exp);
         let expFigures = cbParser.getFigure(exp);
         if(cnExps.length !== expFigures.length){
+            throw '清单基数必须要用花括号{}括起来'
             return false;
         }
         for(let i = 0, len = cnExps.length; i < len; i++){
             if(cnExps[i] !== expFigures[i]){
+                throw '清单基数必须要用花括号{}括起来'
                 return false;
             }
         }
         //基数存在性
         for(let i = 0, len = expFigures.length; i < len; i++){
             if(cbTools.isUnDef(baseFigures[expFigures[i]])){
+                throw `清单基数{${expFigures[i]}}不存在`;
                 return false;
             }
         }
@@ -1151,7 +1155,7 @@ let cbAnalyzer = {
             return stack;
         }
     },
-    //四则运算合法性,前端控制不允许重复出现运算符,这里主要判断()的使用问题,这里再判断一次,控制行引用只能F
+    //四则运算合法性,控制不允许重复出现运算符,这里再判断一次,控制行引用只能F
     arithmeticLegal: function (exp) {
         let ilegalRex = /[\+,\-,\*,\/]{2}/g;
         let rex2 = /[{]{2}/g;
@@ -1164,7 +1168,25 @@ let cbAnalyzer = {
     //
     legalExp: function (node) {
         let exp = this.standar(node.data.userCalcBase);
-        if(this.inputLegal(exp)){
+        if(!this.inputLegal(exp)){
+            throw '表达式含有无效字符';
+        }
+        if(!this.arithmeticLegal(exp)){
+            throw '表达式含有无效字符';
+        }
+        if(!this.baseLegal(cbTools.getFigure(node), exp)){
+            throw '清单基数不合法';
+        }
+        if(!this.fLegal(calcBase.project.mainTree.items, exp)){
+            throw '行引用不合法';
+        }
+        //转换成ID引用
+        exp = cbParser.toIDExpr(exp);
+        if(this.cycleCalc(node, cbTools.getFigure(node), exp)){
+            throw '出现循环计算';
+        }
+        return exp;
+     /*   if(this.inputLegal(exp)){
             if(this.arithmeticLegal(exp)){
                 if(this.baseLegal(cbTools.getFigure(node), exp)){
                     if(this.fLegal(calcBase.project.mainTree.items, exp)){
@@ -1180,7 +1202,7 @@ let cbAnalyzer = {
                 }
             }
         }
-        return null;
+        return null;*/
     }
 };
 
@@ -1470,13 +1492,13 @@ let calcBase = {
             let calcExp = $CBP.percentToNum(compileExp);
             let calcBaseValue = eval(calcExp);
             if(!cbTools.isNum(calcBaseValue)){
-                throw '表达式不正确';
+                throw '基数计算结果不为数值';
             }
             //调价
             let tenderCalcExp = calcExp.replace(new RegExp('base', 'g'), 'tenderBase');
             let tenderCalcBaseValue = eval(tenderCalcExp);
             if(!cbTools.isNum(tenderCalcBaseValue)){
-                throw '表达式不正确';
+                throw '调价基数计算结果不为数值';
             }
 
             //存储
@@ -1487,6 +1509,9 @@ let calcBase = {
             node.changed = true;
         }
         catch (err){
+            if(typeof err === 'object'){
+                err = '表达式不正确'
+            }
             alert(err);
         }
     }

+ 18 - 1
web/building_saas/main/js/models/main_consts.js

@@ -306,7 +306,24 @@ const cpFeeTypes = [
     {type: 'machineDiff', name: '机械价差'},
     {type: 'adjustLabour', name: '调整人工费'},
     {type: 'adjustMachineLabour', name: '调整机上人工费'},
-    // {type: 'estimate', name: '暂估费'},
+    {type: 'unratedMaterial', name: '未计价材料费'},
+    {type: 'organizeMeasures', name: '组织措施费'},
+    {type: 'safeCivilization', name: '安全文明施工费'},
+    {type: 'night', name: '夜间施工费'},
+    {type: 'secondHandling', name: '二次搬运费'},
+    {type: 'winterRainy', name: '冬雨季施工增加费'},
+    {type: 'protection', name: '已完工程及设备保护费'},
+    {type: 'clean', name: '工程定位复测点交及场地清理费'},
+    {type: 'quotaDetermine', name: '工程定额测定费'},
+    {type: 'materialInspect', name: '材料检验试验费'},
+    {type: 'acceptance', name: '住宅工程质量分户验收费'},
+    {type: 'docManage', name: '档案管理费'},
+    {type: 'forceFee', name: '规费'},
+    {type: 'fiveOne', name: '五险一金'},
+    {type: 'sewage', name: '工程排污费'},
+    {type: 'tax', name: '税金'},
+
+// {type: 'estimate', name: '暂估费'},
     {type: 'common', name: '工程造价'},
     {type: 'fee1', name: '费用1'}//,
     // {type: 'fee2', name: '费用2'},

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

@@ -490,7 +490,7 @@ $(document).ready(function(){
         sessionStorage.setItem('mainTab', '#tab_calc_program_manage');
         $(e.relatedTarget.hash).removeClass('active');
         calcProgramManage.buildSheet();
-        calcProgramManage.getStdCalcProgramFiles();
+        // calcProgramManage.getStdCalcProgramFiles();
     });
 
     $("#calcProgramFileSelect").change(function() {

+ 180 - 67
web/building_saas/main/js/views/project_view.js

@@ -1961,6 +1961,69 @@ function canInsertRationNode(selected) {//判断是否能插入定额、量价
     }
 }
 
+//导入类型(09表、广联达)
+const uploadType = {lj: 'lj', gld: 'gld'};
+let fileType = uploadType.lj;
+
+$('#uploadLj').click(function () {
+    fileType = uploadType.lj;
+});
+$('#uploadGld').click(function () {
+   fileType = uploadType.gld;
+});
+
+//设置导入表内容
+function setUploadSheets(sheetNames){
+    let sheetArea = $('#uploadSheets');
+    $('#uploadSheets').height('');
+    sheetArea.empty();
+    for(let sheetName of sheetNames){
+        let sheetDiv = $(`<div style="margin-left: 5px;margin-top: 5px;" class="input-group form-check"><label class="form-check-label" style="width:270px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis">
+                           <input class="form-check-input" type="checkbox">${sheetName}</label></div>`);
+        sheetDiv.find('input[type="checkbox"]').click(function () {
+            if($('#uploadAlert').is(':visible')){
+                $('#uploadAlert').hide();
+            }
+        });
+        let sel = $(`<select style="margin-left: 5px; border-radius: .20rem;"><option value="fbfx">分部分项工程</option><option value="zzcsxm">施工组织措施项目</option><option value="jscsxm">施工技术措施项目</option></select>`);
+        if(sheetName.includes('分部分项工程项目清单计价表')){
+            sheetDiv.find('input[type="checkbox"]').prop('checked', true);
+            sel.find('option:eq(0)').prop('selected', true);
+        }
+        else if(sheetName.includes('施工组织措施项目清单计价表')){
+            sheetDiv.find('input[type="checkbox"]').prop('checked', true);
+            sel.find('option:eq(1)').prop('selected', true);
+        }
+        else if(sheetName.includes('施工技术措施项目清单计价表')){
+            sheetDiv.find('input[type="checkbox"]').prop('checked', true);
+            sel.find('option:eq(2)').prop('selected', true);
+        }
+        sheetDiv.append(sel);
+        sheetArea.append(sheetDiv);
+
+    }
+    if(sheetNames.length > 0){
+        sheetArea.show();
+    }
+    if($('#uploadSheets').height() > 250){
+        console.log('aa');
+        sheetArea.css('overflow', 'auto');
+        sheetArea.height(250);
+    }
+    else {
+        sheetArea.css('overflow', 'hidden');
+    }
+}
+//获得选择导入的表信息(表索引及导入位置)
+function getUploadSheets(){
+    let rst = [];
+    let sheetArea = $('#uploadSheets');
+    let checkedInputs = sheetArea.find('input:checked');
+    for(let checked of checkedInputs){
+        rst.push({sheetIdx: $(checked).parent().parent().index(), position: $(checked).parent().next().select().val()})
+    }
+    return rst;
+}
 
 //选择要导入的excel文件
 $('#customFile').change(function () {
@@ -1968,6 +2031,33 @@ $('#customFile').change(function () {
     if(file.files.length > 0){
         $('.custom-file-label').text(`${file.files[0].name} 准备导入上传`);
         $('#uploadAlert').hide();
+        console.log(file.files);
+        //读取各个表及表名
+        $.bootstrapLoading.start();
+        let fileReader = new FileReader();
+        fileReader.onload = function(ev) {
+            console.log('enter');
+            try {
+                let data = ev.target.result;
+                console.log(data);
+                // 以二进制流方式读取得到整份excel表格对象
+                let workbook = XLSX.read(data, {type: 'binary'});
+                // 遍历每张表读取
+                let sheetNames = [];
+                for (let sheetName in workbook.Sheets) {
+                    sheetNames.push(sheetName);
+                }
+                setUploadSheets(sheetNames);
+                $.bootstrapLoading.end();
+            } catch (e) {
+                console.log(e);
+                $.bootstrapLoading.end();
+                alert('文件类型不正确');
+                return;
+            }
+        };
+        // 以二进制方式打开文件
+        fileReader.readAsBinaryString(file.files[0]);
     }
     else{
         $('.custom-file-label').text(`请选择上传文件`);
@@ -1988,6 +2078,14 @@ $('#uploadConfirm').click(function () {
         if(!projectID || projectID <= 0){
             throw '项目数据出错';
         }
+        //导入表类型(09lj、广联达gld)
+        formData.append('fileType', fileType);
+        //选择的表及导入位置
+        let uploadWorkBook = getUploadSheets();
+        if(uploadWorkBook.length === 0){
+            throw '请选择要导入的表';
+        }
+        formData.append('uploadWorkBook', JSON.stringify(uploadWorkBook));
         formData.append('projectID', projectID);
         //要去匹配的清单库(第一个)
         let matchBillLibId = projectInfoObj.projectInfo.engineeringInfo.bill_lib.length > 0 ? projectInfoObj.projectInfo.engineeringInfo.bill_lib[0].id : null;
@@ -2012,6 +2110,9 @@ $('#uploadConfirm').click(function () {
                     $('#import').modal("hide");
                     //更新前端
                     doAfterImport(response.data);
+                    if($.bootstrapLoading.isLoading()){
+                        $.bootstrapLoading.end();
+                    }
                 } else {
                     const message = response.msg !== undefined ? response.msg : '上传失败!';
                     $.bootstrapLoading.end();
@@ -2044,75 +2145,81 @@ function showUploadAlert(success, msg){
     $('#uploadAlert').text(msg);
     $('#uploadAlert').show();
 }
+//导入清单到一个固定行位置后,更新数据
+function doAfterImportPosition(positionData){
+    let nodes = projectObj.project.mainTree.nodes;
+    let bills_datas = projectObj.project.Bills.datas;
+    let ration_datas = projectObj.project.Ration.datas;
+    let quantity_detail_datas = projectObj.project.quantity_detail.datas;
+    let ration_glj_datas = projectObj.project.ration_glj.datas;
+    let ration_coe_datas = projectObj.project.ration_coe.datas;
+    let ration_install_datas = projectObj.project.ration_installation.datas;
+    let delNodes = [];
+    for(let billID of positionData.remove.bill){
+        if(nodes['id_' + billID]){
+            delNodes.push(nodes['id_' + billID]);
+        }
+        //清除清单数据
+        _.remove(bills_datas, {ID: billID});
+        //清除清单工程量明细数据
+        _.remove(quantity_detail_datas, {billID: billID});
+    }
+    for(let rationID of positionData.remove.ration){
+        //清除定额数据
+        _.remove(ration_datas, {ID: rationID});
+        //清除定额下的相关数据
+        _.remove(ration_glj_datas, {rationID: rationID});
+        _.remove(ration_coe_datas, {rationID: rationID});
+        _.remove(ration_install_datas, {rationID: rationID});
+        _.remove(quantity_detail_datas, {rationID: rationID});
+
+    }
+    //删除节点
+    if(delNodes.length > 0){
+        //通过固定清单获得删除节点的起始行索引
+        let beginRow = null;
+        if(positionData.fixedBill){
+            let fixedNode = nodes['id_' + positionData.fixedBill.ID];
+            if(fixedNode){
+                beginRow = projectObj.project.mainTree.items.indexOf(fixedNode) + 1;
+            }
+        }
+        //删除主树节点
+        projectObj.mainController.m_delete(delNodes, beginRow);
+        //删除清单树节点
+        let delIdNodes = [];
+        for(let delNode of delNodes){
+            delIdNodes.push(delNode.source);
+        }
+        projectObj.project.Bills.tree.m_delete(delIdNodes);
+    }
+    if(positionData.insert.bill.length > 0){
+        //设置flagsIndex
+        for(let insertBill of positionData.insert.bill){
+            insertBill.flagsIndex = {};
+            if (insertBill.flags) {
+                insertBill.flags.forEach(function (flag) {
+                    insertBill.flagsIndex[flag.fieldName] = flag;
+                });
+            }
+        }
+        //插入清单节点
+        projectObj.project.Bills.tree.insertByDatas(positionData.insert.bill);
+        //插入主树节点
+        let newNodes = projectObj.project.mainTree.insertByDatas(positionData.insert.bill);
+        for(let node of newNodes){
+            node.source = projectObj.project.Bills.tree.nodes['id_' + node.getID()];
+            node.data = node.source.data;
+            node.sourceType = projectObj.project.Bills.getSourceType();
+        }
+        ProjectController.syncDisplayNewNodes(projectObj.mainController, newNodes);
+    }
+}
 //导入后更新操作
 function doAfterImport(resData){
-    if(resData){
-        let nodes = projectObj.project.mainTree.nodes;
-        let bills_datas = projectObj.project.Bills.datas;
-        let ration_datas = projectObj.project.Ration.datas;
-        let quantity_detail_datas = projectObj.project.quantity_detail.datas;
-        let ration_glj_datas = projectObj.project.ration_glj.datas;
-        let ration_coe_datas = projectObj.project.ration_coe.datas;
-        let ration_install_datas = projectObj.project.ration_installation.datas;
-        let delNodes = [];
-        for(let billID of resData.remove.bill){
-            if(nodes['id_' + billID]){
-                delNodes.push(nodes['id_' + billID]);
-            }
-            //清除清单数据
-            _.remove(bills_datas, {ID: billID});
-            //清除清单工程量明细数据
-            _.remove(quantity_detail_datas, {billID: billID});
-        }
-        for(let rationID of resData.remove.ration){
-            //清除定额数据
-            _.remove(ration_datas, {ID: rationID});
-            //清除定额下的相关数据
-            _.remove(ration_glj_datas, {rationID: rationID});
-            _.remove(ration_coe_datas, {rationID: rationID});
-            _.remove(ration_install_datas, {rationID: rationID});
-            _.remove(quantity_detail_datas, {rationID: rationID});
-
-        }
-        //删除节点
-        if(delNodes.length > 0){
-            //通过固定清单获得删除节点的起始行索引
-            let beginRow = null;
-            if(resData.fixedBill){
-                let fixedNode = nodes['id_' + resData.fixedBill.ID];
-                if(fixedNode){
-                    beginRow = projectObj.project.mainTree.items.indexOf(fixedNode) + 1;
-                }
-            }
-            //删除主树节点
-            projectObj.mainController.m_delete(delNodes, beginRow);
-            //删除清单树节点
-            let delIdNodes = [];
-            for(let delNode of delNodes){
-                delIdNodes.push(delNode.source);
-            }
-            projectObj.project.Bills.tree.m_delete(delIdNodes);
-        }
-        if(resData.insert.bill.length > 0){
-            //设置flagsIndex
-            for(let insertBill of resData.insert.bill){
-                insertBill.flagsIndex = {};
-                if (insertBill.flags) {
-                    insertBill.flags.forEach(function (flag) {
-                        insertBill.flagsIndex[flag.fieldName] = flag;
-                    });
-                }
-            }
-            //插入清单节点
-            projectObj.project.Bills.tree.insertByDatas(resData.insert.bill);
-            //插入主树节点
-            let newNodes = projectObj.project.mainTree.insertByDatas(resData.insert.bill);
-            for(let node of newNodes){
-                node.source = projectObj.project.Bills.tree.nodes['id_' + node.getID()];
-                node.data = node.source.data;
-                node.sourceType = projectObj.project.Bills.getSourceType();
-            }
-            ProjectController.syncDisplayNewNodes(projectObj.mainController, newNodes);
+    if(resData && resData.length > 0){
+        for(let positionData of resData){
+            doAfterImportPosition(positionData);
         }
         //如果清单未锁定,导入后锁定清单
         if(!projectInfoObj.projectInfo.property.lockBills){
@@ -2140,6 +2247,12 @@ $(function () {
         $('.custom-file-label').text(`请选择上传文件`);
     });
 
+    $('#import').on('hidden.bs.modal', function () {
+        //恢复选择表高度
+        $('#uploadSheets').height('');
+        $('#uploadSheets').hide();
+    });
+
     $("#billsSpread").mouseover(function(){
         spreadAutoFocus(projectObj.mainSpread,subSpread);
     });

+ 6 - 1
web/building_saas/pm/html/project-management.html

@@ -322,9 +322,14 @@
                     <span class="form-text text-danger" id="valuation-info" style="display: none;">请选择计价规则</span>
                     <div style="margin-top: 15px;" class="input-group">
                         <label style="margin-top: 8px;">工程专业</label>
-                        <select style="margin-left: 5px; border-radius: .25rem;" class="form-control" id="tender-engineering"><option value="">请选择对应的工程专业</option></select>
+                        <select style="margin-left: 5px; border-radius: .25rem;" class="form-control" id="tender-engineering"><option value="">请选择工程专业</option></select>
                     </div>
                     <span class="form-text text-danger" id="engineering-info" style="display: none;">请选择工程专业</span>
+                    <div style="margin-top: 15px;" class="input-group">
+                        <label style="margin-top: 8px;">计算程序</label>
+                        <select style="margin-left: 5px; border-radius: .25rem;" class="form-control" id="tender-calcProgram"><option value="">请选择计算程序</option></select>
+                    </div>
+                    <span class="form-text text-danger" id="calcProgram-info" style="display: none;">请选择计算程序</span>
                 </form>
             </div>
             <div class="modal-footer">

+ 42 - 1
web/building_saas/pm/js/pm_newMain.js

@@ -1410,6 +1410,25 @@ $(document).ready(function() {
             $("#tender-fee-rate").children("option").first().val("newFeeRate-"+currentLib.fee_lib[0].id);
         }
         $("#tender-engineering").parent().siblings('.hidden-area').slideDown('fast');
+
+        function getStdCalcProgramFiles(){
+            function getStdCPFilesHtml(stdCPLibs) {
+                let result = '<option value="">请选择计算程序</option>';
+                if (stdCPLibs.length <= 0) {
+                    return result;
+                };
+
+                for (let lib of stdCPLibs){
+                    result += '<option value='+ lib.id +'>'+ lib.name +'</option>';
+                };
+                return result;
+            };
+            let stdCPHtml = getStdCPFilesHtml(getcalcProgramList());
+            $("#tender-calcProgram").html(stdCPHtml);
+            if ($("#tender-calcProgram")[0].options.length > 1)
+                $("#tender-calcProgram")[0].selectedIndex = 1;
+        };
+        getStdCalcProgramFiles();
     }
     // 选择工程专业后动态更改费率文件等数据
     $("#tender-engineering").change(function() {
@@ -1787,6 +1806,19 @@ function getEngineeringList(){
     return engineeringList;
 }
 
+function getcalcProgramList(){
+    let egrs = getEngineeringList();
+    let calcProgramList = [];
+    let egrID = $("#tender-engineering").val();
+    for(let egr of egrs) {
+        if (egr.engineering == egrID) {
+            calcProgramList = egr.lib.program_lib;
+            break;
+        }
+    }
+    return calcProgramList;
+}
+
 function getNodeByName(name, nodes){
     for(let i = 0, len = nodes.length; i < len; i++){
         if(name === nodes[i].data.name){
@@ -2070,6 +2102,12 @@ function AddTender() {
             return false;
         }
 
+        let calcProgram = $("#tender-calcProgram").val();
+        if (calcProgram === '') {
+            setDangerInfo($('#calcProgram-info'), '请选择计算程序');
+            return false;
+        }
+
         let valuationName = $("#valuation").children("option:selected").text();
         let valuationType = $("input[name='valuation_type']:checked").val();
 
@@ -2095,6 +2133,7 @@ function AddTender() {
         if (!libs.program_lib)  throw '编办没有绑定计算程序标准文件';
 
         let engineeringName = $('#tender-engineering').children("option:selected").text();
+        let calcProgramName = $('#tender-calcProgram').children("option:selected").text();
 
         let callback = function() {
             $('#add-tender-confirm').removeClass('disabled');
@@ -2102,6 +2141,7 @@ function AddTender() {
             $('#tender-name').val('');
             $("#tender-fee-rate").children("option").removeAttr("selected");
             $("#tender-engineering").children("option").removeAttr("selected");
+            $("#tender-calcProgram").children("option").removeAttr("selected");
             $("#poj-name").val('');
             $("#poj-name-info").hide();
             $("#eng-name").val('');
@@ -2117,7 +2157,8 @@ function AddTender() {
             engineering_id: engineering_id,
             engineeringName: engineeringName,
             unitPriceFile: {name: unitPriceFileObj.name, id: unitPriceFileObj.id},
-            feeFile: {name: feeFileObj.name, id: feeFileObj.id}
+            feeFile: {name: feeFileObj.name, id: feeFileObj.id},
+            calcProgram: {name: calcProgramName, id: calcProgram}
         };
         AddTenderItems(selectedItem, projName, engName, tenderName, tenderInfo, callback);
 

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

@@ -8,8 +8,15 @@
         <li class="nav-item">
             <a class="nav-link" href="#" aria-expanded="false" data-toggle="modal" data-target="#poj-set"><i class="fa fa-cube"></i> 项目属性</a>
         </li>
-        <li class="nav-item">
+        <!--<li class="nav-item">
             <a class="nav-link" href="#import" data-toggle="modal" data-target="#import"><i class="fa fa-cloud-upload"></i> 导入</a>
+        </li>-->
+        <li class="nav-item dropdown">
+            <a class="nav-link dropdown-toggle" href="javascript:void(0);" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><i class="fa  fa-cloud-upload"></i> 导入</a>
+            <div class="dropdown-menu">
+                <a id="uploadLj" class="dropdown-item" href="#import" data-toggle="modal" data-target="#import">导入09表Excel清单</a>
+                <a id="uploadGld" class="dropdown-item" href="#import" data-toggle="modal" data-target="#import">导入广联达算量Excel清单</a>
+            </div>
         </li>
         <li class="nav-item dropdown">
             <a class="nav-link dropdown-toggle" href="#" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><i class="fa fa-wrench"></i> 工具</a>