Explorar o código

feat: 导入Excel清单,新增一个格式

TASK #3498
vian %!s(int64=4) %!d(string=hai) anos
pai
achega
07b62491ec

+ 1 - 1
modules/main/controllers/bills_controller.js

@@ -189,7 +189,7 @@ module.exports = {
     downloadExample: function(request, response) {
         try {
             //导入类型(09表、广联达)对应的示例文件名
-            const uploadTypeMap = { lj: '分部分项工程项目清单计价表.xlsx', gld: '算量示例-清单汇总表.xlsx' };
+            const uploadTypeMap = { general: '示例1-分部分项工程项目清单.xlsx', lj: '示例2-分部分项工程项目清单计价表.xlsx', gld: '算量示例-清单汇总表.xlsx' };
             const type = request.query.type;
             const fileName = uploadTypeMap[type];
             const filePath = `./public/static/${fileName}`;

BIN=BIN
public/static/示例1-分部分项工程项目清单.xlsx


public/static/分部分项工程项目清单计价表.xlsx → public/static/示例2-分部分项工程项目清单计价表.xlsx


+ 9 - 0
web/building_saas/css/custom.css

@@ -518,4 +518,13 @@ margin-right: 100px !important;
 .limit-price-input {
   display: inline-block;
   width: 80px;
+}
+
+.inline-wrapper {
+  display: flex;
+  justify-content: space-between;
+}
+
+.inline-wrapper .inline-tools {
+  display: inline-block;
 }

+ 10 - 5
web/building_saas/main/html/main.html

@@ -100,7 +100,7 @@
                 <a id="importDropDown" class="dropdown-toggle" href="#"><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">导入报表Excel清单</a>
+                    data-target="#import">导入Excel清单</a>
                   <a id="uploadGld" class="dropdown-item" href="#import" data-toggle="modal"
                     data-target="#import">导入广联达算量Excel清单</a>
                 </div>
@@ -2014,10 +2014,15 @@
           </div>
           <div id="uploadSheets"></div>
         </div>
-        <div class="modal-footer">
-          <button style="margin-right: 215px;" type="button" class="btn btn-primary" id="uploadExample">下载示例</button>
-          <a href="javascript:void(0);" class="btn btn-primary" id="importConfirm">确定导入</a>
-          <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
+        <div class="modal-footer inline-wrapper">
+          <div class="inline-tools">
+            <button type="button" class="btn btn-primary" id="uploadExample-general">下载示例1</button>
+            <button type="button" class="btn btn-primary" id="uploadExample">下载示例2</button>
+          </div>
+          <div class="inline-tools">
+            <a href="javascript:void(0);" class="btn btn-primary" id="importConfirm">确定导入</a>
+            <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
+          </div>
         </div>
       </div>
     </div>

+ 173 - 14
web/building_saas/main/js/views/importBills.js

@@ -50,10 +50,13 @@ const importBills = (function(){
 
     //上传类型
     const uploadType = {
+        general: 'general',
         lj: 'lj',
         gld: 'gld',
     };
 
+    let curFileType = uploadType.general;
+
     //设置导入表内容(选择导入位置)
     //@param {Object}workBook
     function setImportSheetsInfo(sheets){
@@ -131,14 +134,14 @@ const importBills = (function(){
     }
 
     //提取excel表头列名与列下标映射
-    //@
     function getColMapping(dataTable){
         //获取表头
         function getHeadRow(dataTable){
+            const headTexts = [colText.serialNo[0], ...colText.code];
             for(let rowIdx in dataTable ){
                 for(let colIdx in dataTable[rowIdx]){
                     let cellData = dataTable[rowIdx][colIdx]['value'];
-                    if(cellData && _deESC(cellData) === colText.serialNo[0]){
+                    if(cellData && headTexts.includes(_deESC(cellData))){
                         return dataTable[rowIdx];
                     }
                 }
@@ -194,8 +197,16 @@ const importBills = (function(){
         return colMapping;
     }
 
+    // 根据列设置判断从excel表上传界面,上传的具体类型是什么:自定义通用表和09表使用了同一个按钮,因此在获取列设置的时候,还要再区分general和lj
+    function getExactlyFileType(colMapping) {
+        // 只需要区分自定义通用表和09表
+        return !_isDef(colMapping.serialNo) && !_isDef(colMapping.money)
+            ? uploadType.general
+            : uploadType.lj;
+    }
+
     //是否是有效的表头列格式,只要含有各表需要的列就行,不严格控制多少列
-    function isValidSheet(colMapping, fileType){
+    function isValidSheet(colMapping){
         function hasField(field, all){
             for(let i of all){
                 if(field === i){
@@ -205,11 +216,13 @@ const importBills = (function(){
             return false;
         }
         let needFields;
-        if(fileType === uploadType.lj){
+        if (curFileType === uploadType.general) {
+            // 自定义通用表:项目编码	项目名称	项目特征	计量单位	工程量
+            needFields = ['code', 'name', 'itemCharacterText', 'unit', 'quantity'];
+        } else if(curFileType === uploadType.lj){
             //09表:序号、项目编码、项目名称、项目特征、计量单位、工程量、金额
             needFields = ['serialNo', 'code', 'name', 'money'];
-        }
-        else {
+        } else {
             //广联达表:序号、项目编码、项目名称、项目特征、计量单位、工程量、工程量明细、费用明细
             needFields = ['serialNo', 'code', 'name', 'itemCharacterText', 'unit', 'quantity', 'quantityDetail', 'feeDetail'];
         }
@@ -225,8 +238,9 @@ const importBills = (function(){
     //获取要无效和有效导入表
     //@param {Array}importSheetInfo 勾选要导入的表
     function getImportSheets(sheets, sheetsInfo, fileType){
+        curFileType = fileType; // 自定义通用表和09表使用了同一个按钮,因此在获取列设置的时候,还要再区分general和lj
+        let isGetType = false;
         let rst = {invalidSheets: [], validSheets: {fbfx: [], jscsxm: [], zzcsxm: []}};
-
         for(let sheetInfo of sheetsInfo){
             let sheet = getSheetByIndex(sheets, sheetInfo.index);
             if(!sheet){
@@ -237,12 +251,17 @@ const importBills = (function(){
                 rst.invalidSheets.push(sheet.name);
                 continue;
             }
-            //获取表的列设置确定导入的格式是否合法(09、广联达)
+            //获取表的列设置确定导入的格式是否合法(通用excel、09、广联达)
             let colMapping = getColMapping(sheet.data.dataTable);
-            if(!isValidSheet(colMapping, fileType)){
+            // 以第一个有效的sheet作为类型基准
+            if (!isGetType && fileType === uploadType.lj) {
+                curFileType = getExactlyFileType(colMapping);
+            }
+            if(!isValidSheet(colMapping)){
                 rst.invalidSheets.push(sheet.name);
                 continue;
             }
+            isGetType = true;
             //合法的表
             sheet.colMapping = colMapping;
             //将合法的表按导入位置分类当做一个表来处理
@@ -269,7 +288,9 @@ const importBills = (function(){
         let withingD = false;
         let validData = [];
         function isHead(rData){
-            return rData[colMapping.serialNo] && _deESC(rData[colMapping.serialNo]['value']) === colText.serialNo[0];
+            return curFileType === uploadType.general
+                ? rData[colMapping.code] && colText.code.includes(_deESC(rData[colMapping.code]['value']))
+                : rData[colMapping.serialNo] && _deESC(rData[colMapping.serialNo]['value']) === colText.serialNo[0];
         }
         function isTail(rowData){
             for(let colIdx in rowData){
@@ -306,6 +327,147 @@ const importBills = (function(){
         let preRootID = -1,
             preLeafID = -1,
             preID = -1;
+        //父节点
+        function isRoot(rData){
+            //序号和编码去除转义字符(有的表格单元格看起来是没数据,实际含有\r,\n等数据)
+            const code = rData[colMapping.code] ? _deESC(rData[colMapping.code]['value']) : '';
+            const unit = rData[colMapping.unit] ? _deESC(rData[colMapping.unit]['value']) : '';
+            if (curFileType === uploadType.general) {
+                // 通用表:项目编码非12位或者无单位的
+                return _isDef(code) && !/\w{12}/.test(code) || !_isDef(unit);
+            } else {
+                // 09、广联达表:1.无序号 2有编码
+                const serialNo = rData[colMapping.serialNo] ? _deESC(rData[colMapping.serialNo]['value']) : '';
+                return !_isDef(serialNo) && _isDef(code);
+            }
+        }
+        //子节点
+        function isLeaf(rData){
+            if (curFileType === uploadType.general) {
+                // 通用表:项目编码12位且有单位的
+                const code = rData[colMapping.code] ? _deESC(rData[colMapping.code]['value']) : '';
+                const unit = rData[colMapping.unit] ? _deESC(rData[colMapping.unit]['value']) : '';
+                return _isDef(code) && /\w{12}/.test(code) && _isDef(unit);
+            } else {
+                // 09、广联达表:有序号
+                const serialNo = rData[colMapping.serialNo] ? _deESC(rData[colMapping.serialNo]['value']) : '';
+                return _isDef(serialNo);
+            }
+        }
+        //续数据:1. 前数据有效 2.无序号 3.无编码 4.有名称或特征
+        function isExtend(preData, rData){
+            if (curFileType === uploadType.general) {
+                // 通用表:1. 前数据有效 2.无编码 3.无单位 4.有名称或特征
+                let code = rData[colMapping.code] ? _deESC(rData[colMapping.code]['value']) : '';
+                let unit = rData[colMapping.unit] ? _deESC(rData[colMapping.unit]['value']) : '';
+                let name = rData[colMapping.name] ? _deESC(rData[colMapping.name]['value']) : '';
+                let itemCharacterText = rData[colMapping.itemCharacterText] ? _deESC(rData[colMapping.itemCharacterText]['value']) : '';
+                return _isDef(preData) && (isRoot(preData) || isLeaf(preData)) && !_isDef(unit) && !_isDef(code) && (_isDef(name) || _isDef(itemCharacterText));
+            } else {
+                // 09、广联达表:1. 前数据有效 2.无序号 3.无编码 4.有名称或特征
+                let serialNo = rData[colMapping.serialNo] ? _deESC(rData[colMapping.serialNo]['value']) : '';
+                let code = rData[colMapping.code] ? _deESC(rData[colMapping.code]['value']) : '';
+                let name = rData[colMapping.name] ? _deESC(rData[colMapping.name]['value']) : '';
+                let itemCharacterText = rData[colMapping.itemCharacterText] ? _deESC(rData[colMapping.itemCharacterText]['value']) : '';
+                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){
+                return billType.BILL;
+            }
+            else if(flag === fixedFlag.SUB_ENGINERRING){
+                return isLeaf(rData) ? billType.FX : billType.FB;
+            }
+            return null;
+        }
+        let preData = null;
+        for(let r = 0; r < validData.length; r++){
+            let rData = validData[r];
+            if(flag == fixedFlag.CONSTRUCTION_TECH && rData[colMapping.name] && rData[colMapping.name]['value'] === '施工技术措施项目'
+                || flag == fixedFlag.CONSTRUCTION_ORGANIZATION && rData[colMapping.name] && rData[colMapping.name]['value'] === '施工组织措施项目'){
+                continue;
+            }
+            //过滤无效数据
+            if(!isRoot(rData) && !isLeaf(rData) && !isExtend(preData, rData)){
+                continue;
+            }
+            if(isExtend(preData, rData)){
+                let preBill = billIdx[preID];
+                if(preBill){
+                    //合并续数据
+                    preBill.code += rData[colMapping.code] && rData[colMapping.code]['value'] && _isDef(_deESC(rData[colMapping.code]['value'])) ? rData[colMapping.code]['value'] : '';
+                    preBill.name += rData[colMapping.name] && rData[colMapping.name]['value'] && _isDef(_deESC(rData[colMapping.name]['value'])) ? rData[colMapping.name]['value'] : '';
+                    preBill.itemCharacterText += rData[colMapping.itemCharacterText] && rData[colMapping.itemCharacterText]['value'] && _isDef(_deESC(rData[colMapping.itemCharacterText]['value']))
+                        ? '\n' + _deNR(rData[colMapping.itemCharacterText]['value']) : '';
+                    preBill.unit += rData[colMapping.unit] && rData[colMapping.unit]['value'] && _isDef(_deESC(rData[colMapping.unit]['value'])) ? rData[colMapping.unit]['value'] : '';
+                    preBill.quantity += rData[colMapping.quantity] && rData[colMapping.quantity]['value'] && _isDef(_deESC(rData[colMapping.quantity]['value'])) ? rData[colMapping.quantity]['value'] : '';
+                }
+            } else {
+                let newID = uuid.v1();
+                let pID = -1;
+                let preBill = null;
+                let preRoot = null,
+                    preLeaf = null;
+                let nodeType = 'root';//后端以此标记来设置ParentID
+                let preSerialBill = billIdx[preID];
+                if(isRoot(rData)){
+                    //pID = 'fixedBillID';
+                    preBill = billIdx[preRootID];
+                    preRoot = billIdx[preRootID];
+                } else if(isLeaf(rData)){
+                    nodeType = 'leaf';
+                    //pID = preRootID !== -1 ? preRootID : fixedBill.ID;
+                    preBill = billIdx[preLeafID];
+                    preLeaf = billIdx[preLeafID];
+                }
+                //set bill data
+                billIdx[newID] = {
+                    nodeType: nodeType,
+                    ID: newID, ParentID: pID, NextSiblingID: -1,
+                    code: rData[colMapping.code] && rData[colMapping.code]['value'] ? _deESC(rData[colMapping.code]['value']) : '',
+                    name: rData[colMapping.name] && rData[colMapping.name]['value'] ? _deESC(rData[colMapping.name]['value']) : '',
+                    itemCharacterText: rData[colMapping.itemCharacterText] && rData[colMapping.itemCharacterText]['value'] ? _deNR(rData[colMapping.itemCharacterText]['value']) : '',
+                    itemCharacter: [],
+                    jobContentText: '',
+                    jobContent: [],
+                    programID: null,
+                    unit: rData[colMapping.unit] && rData[colMapping.unit]['value'] ? _deESC(rData[colMapping.unit]['value']) : '',
+                    quantity: rData[colMapping.quantity] && rData[colMapping.quantity]['value'] ? _deESC(rData[colMapping.quantity]['value']) : '',
+                    quantityEXP: rData[colMapping.quantity] && rData[colMapping.quantity]['value'] ? _deESC(rData[colMapping.quantity]['value']) : '',
+                    //安全文明
+                    flags: flag === fixedFlag.CONSTRUCTION_ORGANIZATION && (rData[colMapping.name] && (rData[colMapping.name]['value'] === '安全文明施工专项费用' || rData[colMapping.name]['value'] === '安全文明施工费')) ?
+                        [{fieldName: 'fixed', flag: fixedFlag.SAFETY_CONSTRUCTION}] : [],
+                    fees: [],
+                    projectID: projectID,
+                    type: getBillType(rData, flag)};
+                //update preBill NextSibling
+                if(nodeType === 'root' && preRoot){
+                    preRoot.NextSiblingID = newID;
+                } else if(nodeType === 'leaf' && preLeaf && preSerialBill && preSerialBill.nodeType === preLeaf.nodeType){
+                    preLeaf.NextSiblingID = newID;
+                }
+               /* if(preBill){
+                    preBill.NextSiblingID = newID;
+                }*/
+                //set new preID
+                preID = newID;
+                preRootID = isRoot(rData) ? newID : preRootID;
+                preLeafID = isLeaf(rData) ? newID : preLeafID;
+            }
+            preData = rData;
+        }
+        for(let i in billIdx){
+            rst.push(billIdx[i]);
+        }
+        return rst;
+    }
+    /* function parseToBillData(validData, colMapping, flag, projectID){
+        let rst = [];
+        let billIdx = {};
+        let preRootID = -1,
+            preLeafID = -1,
+            preID = -1;
         //父节点:1.无序号 2有编码
         function isRoot(rData){
             //序号和编码去除转义字符(有的表格单元格看起来是没数据,实际含有\r,\n等数据)
@@ -404,9 +566,6 @@ const importBills = (function(){
                 else if(nodeType === 'leaf' && preLeaf && preSerialBill && preSerialBill.nodeType === preLeaf.nodeType){
                     preLeaf.NextSiblingID = newID;
                 }
-               /* if(preBill){
-                    preBill.NextSiblingID = newID;
-                }*/
                 //set new preID
                 preID = newID;
                 preRootID = isRoot(rData) ? newID : preRootID;
@@ -418,7 +577,7 @@ const importBills = (function(){
             rst.push(billIdx[i]);
         }
         return rst;
-    }
+    } */
 
     function getImportData(validSheets, projectID){
         let rst = {fbfx: [], jscsxm: [], zzcsxm: []};

+ 11 - 3
web/building_saas/main/js/views/project_view.js

@@ -3075,14 +3075,18 @@ function canInsertRationNode(selected) {//判断是否能插入定额、量价
 }
 
 //导入类型(09表、广联达)
-const uploadType = {lj: 'lj', gld: 'gld'};
+const uploadType = {general: 'general', lj: 'lj', gld: 'gld'};
 let fileType = uploadType.lj;
 
 $('#uploadLj').click(function () {
+    $('#uploadExample-general').show();
+    $('#uploadExample').text('下载示例2');
     fileType = uploadType.lj;
 });
 $('#uploadGld').click(function () {
-   fileType = uploadType.gld;
+    $('#uploadExample-general').hide();
+    $('#uploadExample').text('下载示例');
+    fileType = uploadType.gld;
 });
 
 let importJson = null;
@@ -3196,6 +3200,7 @@ $('#importConfirm').click(function () {
         });
     }
     catch (err){
+        console.log(err);
         showUploadAlert(false, err);
         $(me).removeClass('disabled');
         if($.bootstrapLoading.isLoading()){
@@ -3318,8 +3323,11 @@ function doAfterImport(resData){
     }
 }
 //下载导入清单示例文件
+$('#uploadExample-general').click(function () {
+    window.location.href = `/bills/downloadExamp?type=${uploadType.general}`;
+});
 $('#uploadExample').click(function () {
-    window.location.href = `/bills/downloadExamp?type=${fileType}`;
+    window.location.href = `/bills/downloadExamp?type=${fileType === uploadType.lj ? uploadType.lj : uploadType.gld}`;
 });
 
 $(function () {