Преглед изворни кода

Merge branch 'master' of http://192.168.1.41:3000/SmartCost/YangHuCost

zhangweicheng пре 5 година
родитељ
комит
3acefa4f83

+ 7 - 0
modules/import/controllers/import_controller.js

@@ -43,6 +43,13 @@ let controller = {
         result.data = await Project.getDataSync(req.body.project_id);
         return result;
     },
+    async importInterface (req) {
+        const result = {
+            error: 0
+        };
+        result.data = await pm_facade.importInterface(req.body.key, req.body.session);
+        return result;
+    },
 };
 
 

+ 1 - 0
modules/import/routes/import_route.js

@@ -9,6 +9,7 @@ module.exports = function (app) {
     importRouter.post('/importProject',importController.action);
     importRouter.post('/exportProject',importController.action);
     importRouter.post('/copyProject',importController.action);
+    importRouter.post('/importInterface',importController.action);
     importRouter.post('/copyConstructionProject',importController.action);
     importRouter.post('/prepareInitialData',importController.action);
     importRouter.post('/getDataForInterface',importController.action);

+ 4 - 3
modules/main/facade/project_facade.js

@@ -3,6 +3,7 @@
  */
 
 module.exports = {
+  setChildren,
   sortChildren,
   markUpdateProject: markUpdateProject,
   removeProjectMark: removeProjectMark,
@@ -612,7 +613,7 @@ async function getBillsByProjectID(projectID) {
     parentMap[b.ParentID] ? parentMap[b.ParentID].push(doc) : parentMap[b.ParentID] = [doc];
   } //设置子节点
   for (let r of roots) {
-    setChildren(r, parentMap, 1);
+    setChildren(r, parentMap);
   }
   roots = sortChildren(roots);
   return {
@@ -628,11 +629,11 @@ async function getBillsByProjectID(projectID) {
   }
 }
 
-function setChildren(bill, parentMap, level) {
+function setChildren(bill, parentMap) {
   let children = parentMap[bill.ID];
   if (children) {
     for (let c of children) {
-      setChildren(c, parentMap, level + 1)
+      setChildren(c, parentMap);
     }
     bill.children = children;
   } else {

+ 16 - 1
modules/pm/controllers/pm_controller.js

@@ -936,7 +936,22 @@ module.exports = {
         } catch (err) {
             callback(req, res, 1, err, null);
         }
-    }
+    },
+    importInterface: async function (req, res) {
+        const data = JSON.parse(req.body.data);
+        let result = {
+            error: 0
+        };
+        try {
+            data.session = req.session;
+            result = await redirectToImportServer(data, "importInterface", req);
+        } catch (err) {
+            console.log(err);
+            result.error = 1;
+            result.message = err.message;
+        }
+        res.json(result);
+    },
 };
 
 

+ 211 - 8
modules/pm/facade/pm_facade.js

@@ -51,6 +51,7 @@ module.exports={
     uploadToken:uploadToken,
     downLoadProjectFile:downLoadProjectFile,
     importProcessChecking:importProcessChecking,
+    importInterface,
     copyConstructionProject,
     isTenderOverrun,
     getWelcomeInfo:getWelcomeInfo
@@ -65,12 +66,25 @@ let glj_facade = require('../../glj/facade/glj_facade');
 let project_facade = require('../../main/facade/project_facade');
 let logger = require("../../../logs/log_helper").logger;
 const uuidV1 = require('uuid/v1');
+const billsUtil = require('../../../public/billsUtil');
 let projectModel = mongoose.model('projects');
 let projectSettingModel =  mongoose.model('proj_setting');
 let billsModel = mongoose.model('bills');
 let rationModel = mongoose.model('ration');
 let gljListModel = mongoose.model("glj_list");
 let calcProgramsModel = mongoose.model('calc_programs');
+const {
+    defaultDecimal,
+    billsQuantityDecimal,
+    displaySetting,
+    calcOptions,
+    tenderSetting,
+    bookmarkSetting
+} = require('../models/project_property_template');
+const labourCoeFacade = require('../../main/facade/labour_coe_facade');
+const calcProgramFacade = require('../../main/facade/calc_program_facade');
+const mainColLibModel = mongoose.model('std_main_col_lib');
+const EngineeringLibModel = require("../../users/models/engineering_lib_model");
 let labourCoesModel = mongoose.model('labour_coes');
 let feeRateModel = mongoose.model('fee_rates');
 let feeRateFileModel = mongoose.model('fee_rate_file');
@@ -1723,12 +1737,12 @@ async function doDownLoadAndImport(privateDownloadUrl,info) {
         });
 }
 
-async function importProcessChecking(data){
-    let result = {error:0};
+async function importProcessChecking(data) {
+    let result = { error: 0 };
     const query = data.key ? { key: data.key } : { userID: data.userID, compilationID: data.compilationID };
     let log = await importLogsModel.findOne(query);
-    if(log){
-        if(log.status == "finish"){
+    if (log) {
+        if (log.status == "finish") {
             result.status = "complete";
             await importLogsModel.remove(query);
             // 获取导入的项目数据
@@ -1740,21 +1754,197 @@ async function importProcessChecking(data){
                 }
                 result.data = projects;
             }
-        }else if(log.status == "start"){
+        } else if (log.status == "start") {
             result.status = "processing";
             result.content = log.content;
-        }else if(log.status == "error"){
+        } else if (log.status == "error") {
             result.error = 1;
             result.msg = log.errorMsg;
             await importLogsModel.remove(query);
         }
-    }else {
+    } else {
         result.status = "complete";
     }
     return result;
 
 }
 
+// 从cdn服务器下载文件,key就是文件名
+async function downloadFileSync(key) {
+    const filePath = path.join(__dirname, '../../../tmp/', key);
+    const bucketManager2 = new qiniu.rs.BucketManager(mac, null);
+    const publicBucketDomain = qiniu_config.Domain; // "http://serverupdate.smartcost.com.cn";//这里不支持https
+    const deadline = parseInt(Date.now() / 1000) + 3600; // 1小时过期
+    const privateDownloadUrl = bucketManager2.privateDownloadUrl(publicBucketDomain, key, deadline);
+    const stream = fs.createWriteStream(filePath);
+    return new Promise((resolve, reject) => {
+        request(privateDownloadUrl)
+            .on('error', reject)
+            .pipe(stream)
+            .on('close', () => {
+                // 读取文件返回字符串
+                const srcData = fs.readFileSync(stream.path, 'utf-8');
+                resolve({
+                    path: stream.path,
+                    srcData: srcData
+                });
+            })
+            .on('error', reject);
+    });
+}
+
+// 导入接口
+async function importInterface(key, session) {
+    const logData = {
+        key: key,
+        content: '正在导入接口文件,请稍候……',
+        userID: session.sessionUser.id,
+        compilationID: session.sessionCompilation._id,
+        status: 'start',
+        create_time: +new Date()
+    };
+    await importLogsModel.create(logData);
+    handleImportInterface(key, session);
+    return 'importing interface';
+}
+
+async function handleImportInterface(key, session) {
+    const doc = { status: 'finish' };
+    // 源文件内容文本
+    let downloadFilePath = '';
+    try {
+        const { path, srcData } = await downloadFileSync(key);
+        downloadFilePath = path;
+        if (!srcData) {
+            throw '无有效数据';
+        }
+        const userID = session.sessionUser.id;
+        const compilationID = session.sessionCompilation._id;
+        const importData = JSON.parse(srcData);
+        const tenderCount = importData.tenders.length;
+        if (await isTenderOverrun(tenderCount, session)) {
+            throw '您创建的项目个数超限,请联系我们的客服人员,或者导出建设项目保存到本地备份,删除云上数据。';
+        }
+        const overWriteUrl = session.sessionCompilation.overWriteUrl;
+        const projectID = await importInterfaceProject(importData, userID, compilationID, overWriteUrl);
+        doc.projectID = projectID;
+    } catch (err) {
+        console.log(err);
+        doc.errorMsg = typeof err === 'string' ? err : err.toString();
+        doc.status = 'error';
+    } finally {
+        await importLogsModel.update({ key }, doc);
+        fs.unlinkSync(downloadFilePath);
+    }
+}
+
+/*
+* 接口导入 项目详细数据都导入完成了,再生成项目数据(项目管理界面数据)
+* */
+async function importInterfaceProject(importObj, userID, compilationID, overWriteUrl) {
+    const toInsertProjects = [importObj];  //待新增项目数据
+    let defaultCalcMode;
+    if (overWriteUrl) {
+        const overWrite = require('../../..' + overWriteUrl);
+        if(overWrite.defaultCalcMode) { // 重写清单计费取费方式
+            defaultCalcMode = overWrite.defaultCalcMode;
+        }
+    }
+    await setupProject(importObj);
+    // 设置项目相关数据
+    for (const curTender of importObj.tenders) {
+        await setupProject(curTender);
+        // 插入单位工程的详细数据
+        await importTenderDetail(curTender);
+        delete curTender.bills;
+        toInsertProjects.push(curTender);
+    }
+    delete importObj.tenders;
+    // 项目内部数据设置、新增完毕后,插入项目本身的数据,更新前节点数据
+    const bulks = [];
+    // 如果有前节点,更新前节点
+    if (importObj.preID !== -1) {
+        bulks.push({
+            updateOne: { filter: { ID: importObj.preID }, update: { $set: { NextSiblingID: importObj.ID } } }
+        });
+    }
+    for (let insertP of toInsertProjects) {
+        bulks.push({
+            insertOne: { document: insertP }
+        });
+    }
+    if (bulks.length > 0) {
+        await projectModel.bulkWrite(bulks);
+    }
+    return importObj.ID; // 返回建设项目ID
+
+    //给项目数据设置一些需要的数据
+    async function setupProject(data) {
+        data.userID = userID;
+        data.compilation = compilationID;
+        data.fileVer = await index.getVersion();
+        data.createDateTime = new Date();
+        if (data.projType === 'Tender') {
+            await setupTender(data);
+        }
+    }
+    //给单位工程设置一些数据
+    async function setupTender(data) {
+        if (!data.property.decimal) {
+            //小数位数 需要修改,所以深拷贝
+            data.property.decimal = JSON.parse(JSON.stringify(defaultDecimal));
+        }
+        //清单工程量精度 需要修改,所以深拷贝
+        data.property.billsQuantityDecimal = JSON.parse(JSON.stringify(billsQuantityDecimal));
+        // 清单工程量精度默认为3
+        const billsQuantityDecimalValue = data.property.billsQuantityDecimalValue || 3;
+        data.property.billsQuantityDecimal.forEach(data => data.decimal = billsQuantityDecimalValue);
+        //呈现选项
+        data.property.displaySetting = displaySetting;
+
+        data.property.billsCalcMode = defaultCalcMode || data.property.valuationType == 'bill' ? 2 : 1;
+        data.property.zanguCalcMode = 0;
+        //计算选项
+        data.property.calcOptions = calcOptions;
+        //锁定清单
+        data.property.lockBills = true;
+        //工料机单价调整系数
+        data.property.tenderSetting = tenderSetting;
+        // 书签
+        data.property.bookmarkSetting =  bookmarkSetting;
+    }
+}
+
+//插入单位工程内部详细数据
+async function importTenderDetail(tenderData) {
+    // 单价文件
+    const upFile = {
+        id: tenderData.property.unitPriceFile.id,
+        name: tenderData.name,
+        project_id: tenderData.ID,
+        user_id: tenderData.userID,
+        root_project_id: tenderData.property.rootProjectID
+    };
+    await unitPriceFileModel.create(upFile);
+    // 费率文件
+    const feeRateFileID = await feeRate_facade.newFeeRateFile(tenderData.userID, tenderData);
+    tenderData.property.feeFile = feeRateFileID ? feeRateFileID : -1;
+    // 人工系数文件
+    const lcFile = await labourCoeFacade.newProjectLabourCoe(tenderData);
+    tenderData.property.labourCoeFile = lcFile ? lcFile : null;
+    const cpFile = await calcProgramFacade.newProjectCalcProgramFile(tenderData);
+    tenderData.property.calcProgramFile = cpFile ? cpFile : null;
+    // 列设置
+    const engineeringModel = new EngineeringLibModel();
+    const engineering = await engineeringModel.getEngineering(tenderData.property.engineering_id);
+    const mainTreeCol = await mainColLibModel.findOne({ ID: tenderData.property.colLibID });
+    await projectSettingModel.create({ projectID: tenderData.ID, main_tree_col: mainTreeCol.main_tree_col, glj_col: engineering.glj_col });
+    // 清单
+    if (tenderData.bills && tenderData.bills.length) {
+        await billsModel.insertMany(tenderData.bills);
+    }
+}
+
 async function copyConstructionProject(data){
   let log_data = {
     key:data.key,
@@ -2331,12 +2521,25 @@ async function getImportTemplateData(compilationID, valuationID, projectCount) {
     const featureLibID = engineeringLib.feature_lib[0].id;
     const featureLib = await featureLibModel.findOne({ ID: featureLibID }).lean();
     const billsTemplateLibID = engineeringLib.tax_group[0].template_lib.id;
-    const billsTemplate = await stdBillsTemplateModel.find({ libID: billsTemplateLibID }).lean();
+    let billsTemplate = await stdBillsTemplateModel.find({ libID: billsTemplateLibID }, '-_id -libID').lean();
+    billsUtil.resetTreeData(billsTemplate, uuidV1); // 重新建立新的ID数据
+    const parentMap = {};
+    billsTemplate.forEach(bills => {
+        // 工程量清单单价分析默认勾选,导入必为工程量清单,因此清单模板单价分析全部勾选先
+        bills.unitPriceAnalysis = 1;
+        (parentMap[bills.ParentID] || (parentMap[bills.ParentID] = [])).push(bills);
+    });
+    const roots = parentMap['-1'];
+    roots.forEach(root => project_facade.setChildren(root, parentMap));
+    billsTemplate = project_facade.sortChildren(roots);
     const projectCounter = await counter.counterDAO.getIDAfterCountSync(counter.moduleName.project, projectCount);
+    const tenderCount = projectCount - 1;
+    const unitFileCounter = await counter.counterDAO.getIDAfterCountSync(counter.moduleName.unit_price_file, tenderCount);
     return {
         basicInfo: infoLib && infoLib.info || [],
         feature: featureLib && featureLib.feature || [],
         bills: billsTemplate,
         projectBeginID: projectCounter.sequence_value - projectCount + 1,
+        unitPriceFileBeginID: unitFileCounter.sequence_value - tenderCount + 1,
     };
 }

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

@@ -70,6 +70,10 @@ module.exports = function (app) {
 
 
     app.use('/pm/api', pmRouter);
+
+    const importRouter = express.Router();
+    importRouter.post('/importInterface', pmController.importInterface);
+    app.use('/pm/import', importRouter);
 };
 
 

+ 60 - 0
public/billsUtil.js

@@ -0,0 +1,60 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Zhong
+ * @date 2019/11/1
+ * @version
+ */
+
+((factory) => {
+    if (typeof module !== 'undefined') {
+        module.exports = factory();
+    } else {
+        window.BILLS_UTIL = factory();
+    }
+})(() => {
+    // 清单模板各清单重设ID时,重新转换清单基数的ID引用
+    function parseCalcBase(calcBase, uuidMapping) {
+        const orgIDRefs = [...new Set(calcBase.match(/@\d+/g))];
+        orgIDRefs.forEach(orgRef => {
+            const orgID = orgRef.match(/\d+/)[0];
+            const newID = uuidMapping[orgID] || null;
+            // ID匹配不上则不转换这个引用
+            if (!newID) {
+                return;
+            }
+            const replaceStr = `@${newID}`;
+            calcBase = calcBase.replace(new RegExp(`${orgRef}\\b`, 'g'), replaceStr);
+        });
+        return calcBase;
+    }
+
+    /*
+     * @param {Array}billsList (完整的清单树结构数据)
+     * @param {Function}idFactory 生成ID的方法
+     * @return {void}
+     * */
+    function resetTreeData(billsList, idFactory) {
+        const idMapping = {};
+        idMapping['-1'] = -1;
+        // 建立新ID-旧ID映射
+        billsList.forEach(bills => idMapping[bills.ID] = idFactory());
+        const reg = /@\d+/;
+        billsList.forEach(function (bills) {
+            bills.ID = idMapping[bills.ID] ? idMapping[bills.ID] : -1;
+            bills.ParentID = idMapping[bills.ParentID] ? idMapping[bills.ParentID] : -1;
+            bills.NextSiblingID = idMapping[bills.NextSiblingID] ? idMapping[bills.NextSiblingID] : -1;
+            const needToParseCalcBase = bills.calcBase && reg.test(bills.calcBase);
+            if (needToParseCalcBase) {
+                bills.calcBase = parseCalcBase(bills.calcBase, idMapping);
+            }
+        });
+    }
+
+    return {
+        parseCalcBase,
+        resetTreeData
+    };
+});

+ 11 - 1
public/common_constants.js

@@ -160,11 +160,20 @@
     // 定额类型
     const RationType = {
         RATION: 1,
-        VOLUME_PRICE:2,
+        VOLUME_PRICE: 2,
         GLJ_RATION: 3,
         INSTALL: 4,
     };
 
+    // 清单类型
+    const BillType = {
+        DXFY: 1, // 大项费用
+        FB: 2, // 分部
+        FX: 3, // 分项
+        BILL: 4, // 清单
+        BX: 5 // 补项
+    };
+
     return {
         fixedFlag,
         GRANULARITY,
@@ -179,5 +188,6 @@
         BlankType,
         EXPORT_KIND,
         RationType,
+        BillType,
     };
 })

+ 2 - 1
public/counter/counter.js

@@ -21,7 +21,8 @@ const COUNTER_MODULE_NAME = {
     billsLib: 'billsLib',
     coeList: 'coeList',
     complementaryCoeList: 'complementary_coe_list',
-    glj_list: 'glj_list'
+    glj_list: 'glj_list',
+    unit_price_file: 'unit_price_file',
 }
 /*const PROJECT_COUNTER = 'projects', USER_COUNTER = 'users', BILL_COUNTER = 'bills', RATION_COUNTER = 'rations',
     REPORT_COUNTER = 'rptTemplates', FEE_COUNTER = 'fees'*/

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

@@ -218,13 +218,7 @@ const fixedFlag = commonConstants.fixedFlag;
 const gljKeyArray =['code','name','specs','unit','type'];
 const rationKeyArray =['code','name','specs','unit','subType'];
 const gljLibKeyArray =['code', 'name', 'specs', 'unit', 'gljType'];
-const billType ={
-    DXFY:1,//大项费用
-    FB:2,//分部
-    FX:3,//分项
-    BILL:4,//清单
-    BX:5//补项
-};
+const billType = commonConstants.BillType;
 const billText = {
     1:'费用',
     2:'分部',

+ 3 - 8
web/building_saas/standard_interface/import/anhui_maanshan.js

@@ -7,13 +7,11 @@
 // INTERFACE_EXPORT =,必须这么写,这样才能在导入时动态加载脚本后,覆盖前端代码
 INTERFACE_IMPORT = (() => {
   'use strict';
-
-
-
   /**
    * 
    * @param {String} areaKey - 地区标识,如:'安徽@马鞍山',有些地区的接口只是取值上有不同,共有一个接口脚本, 需要通过地区标识确定一些特殊处理
    * @param {Object} xmlObj - xml经过x2js转换后的xml对象
+   * @return {Object} - 返回的格式需要统一,具体参考函数内返回的内容。返回的内容会经过一系列的统一处理形成可入库的数据。
    */
   async function entry(areaKey, xmlObj) {
     const {
@@ -68,7 +66,7 @@ INTERFACE_IMPORT = (() => {
       const feeRateItems = arrayValue(tenderSrc, ['Qfxx', 'JjFlx', 'JjFlxMx']);
       const locationItem = feeRateItems.find(item => getValue(item, ['_Mc']) === '工程所在地');
       if (locationItem) {
-        feature.push(locationItem);
+        feature.push({ key: 'location', value: getValue(locationItem, ['_ShuZhi']) });
       }
       return feature;
     }
@@ -212,7 +210,6 @@ INTERFACE_IMPORT = (() => {
           arrayValue(midSrc, ['Dwgcxx'])
             .forEach(tenderSrc => tenders.push(setupTender(midSrc, tenderSrc)))
         });
-
       return {
         name: getValue(projectSrc, ['_Xmmc']),
         info: setupInformation(projectSrc),
@@ -221,9 +218,7 @@ INTERFACE_IMPORT = (() => {
     }
 
 
-    const test = setupProject(getValue(xmlObj, ['JingJiBiao']));
-    console.log(test);
-    return test;
+    return setupProject(getValue(xmlObj, ['JingJiBiao']));
   }
 
   return {

+ 115 - 31
web/building_saas/standard_interface/import/base.js

@@ -58,6 +58,11 @@ const INTERFACE_EXPORT_BASE = (() => {
     return cur || '';
   }
 
+  // 获取布尔型的数据
+  function getBool(source, fields) {
+    return getValue(source, fields) === 'true' ? true : false;
+  }
+
   // 获取数据类型
   function _plainType(v) {
     return Object.prototype.toString.call(v).slice(8, -1);
@@ -108,20 +113,21 @@ const INTERFACE_EXPORT_BASE = (() => {
     return feesA;
   }
 
-  // 将A对象的一部分属性赋值到B对象上
-  function assignAttr(target, source, attrs, isExcepted = false) {
+  // 将A对象的属性赋值到B对象上
+  function assignAttr(target, source, attrs) {
     if (!source || !target) {
       return;
     }
-    const sourceAttrs = attrs
-      ? isExcepted
-        ? Object.keys(source).filter(attr => !attrs.includes(attr))
-        : attrs
-      : Object.keys(source);
-    sourceAttrs.forEach(attr => {
-      // 如果是价格,不能简单地覆盖,要合并两个对象的价格
-      target[attr] = attr === 'fees' ? mergeFees(target[attr], source[attr]) : source[attr];
-    });
+    const sourceAttrs = attrs || Object.keys(source);
+    for (const attr of sourceAttrs) {
+      // 如果值是undefined,则不进行赋值覆盖处理
+      if (attr === 'children' || source[attr] === undefined) {
+        continue;
+      }
+      target[attr] = attr === 'fees'
+        ? mergeFees(target[attr], source[attr]) // 如果是价格,不能简单地覆盖,要合并两个对象的价格
+        : source[attr];
+    }
   }
 
   // 获取固定ID
@@ -129,11 +135,6 @@ const INTERFACE_EXPORT_BASE = (() => {
     return data.flags && data.flags[0] && data.flags[0].flag || 0;
   }
 
-  // 获取布尔型的数据
-  function getBool(v) {
-    return v === 'true' ? true : false;
-  }
-
   // 设置成树结构数据
   function setTreeData(data, parent, next) {
     const defalutID = -1;
@@ -179,6 +180,7 @@ const INTERFACE_EXPORT_BASE = (() => {
     escapeXMLEntity,
     restoreXMLEntity,
     getValue,
+    getBool,
     arrayValue,
     getFee,
     mergeFees,
@@ -186,7 +188,6 @@ const INTERFACE_EXPORT_BASE = (() => {
     setTreeData,
     mergeDataRecur,
     getFlag,
-    getBool,
     extractItemsRecur,
   });
 
@@ -217,15 +218,86 @@ const INTERFACE_EXPORT_BASE = (() => {
     }
   }
 
-  // 处理清单
-  function handleBills(tenderBills, tenderID, billsTemplate) {
-    // 给清单设置ID、projectID
+  const { fixedFlag, BillType } = window.commonConstants;
+
+  /**
+   * 将提取出来的清单合并进清单模板
+   * @param {Array} source - 从xml提取出来的清单
+   * @param {Array} target - 清单模板数据
+   * @param {Object} parent - 匹配到模板清单的父清单
+   * @return {void}
+   */
+  function mergeBills(source, target, parent) {
+    source.forEach(bills => {
+      const simpleName = bills.name ? bills.name.replace(/\s/g, '') : '';
+      let matched;
+      if (!parent) {
+        if (/100章至第700章|100章至700章/.test(simpleName)) {
+          matched = target.find(bills => getFlag(bills) === fixedFlag.ONE_SEVEN_BILLS);
+        } else if (/包含在清单合计中的材料、工程设备、专业工程暂估/.test(simpleName)) {
+          matched = target.find(bills => getFlag(bills) === fixedFlag.PROVISIONAL_TOTAL);
+        } else if (/清单合计减去材料、工程设备、专业工程暂估价/.test(simpleName)) {
+          matched = target.find(bills => getFlag(bills) === fixedFlag.BILLS_TOTAL_WT_PROV);
+        } else if (/计日工合计/.test(simpleName)) {
+          matched = target.find(bills => getFlag(bills) === fixedFlag.DAYWORK_LABOR);
+        } else if (/暂列金额[((]不含/.test(simpleName)) {
+          matched = target.find(bills => getFlag(bills) === fixedFlag.PROVISIONAL);
+        } else if (/报价/.test(simpleName)) {
+          matched = target.find(bills => getFlag(bills) === fixedFlag.TOTAL_COST);
+        }
+      } else {
+        const parentSimpleName = parent.name ? parent.name.replace(/\s/g, '') : '';
+        if (/100章至第700章|100章至700章/.test(parentSimpleName) && /100章总则/.test(simpleName)) {
+          matched = target.find(bills => getFlag(bills) === fixedFlag.ONE_HUNDRED_BILLS);
+        } else if (/计日工合计/.test(parentSimpleName) && /劳务/.test(simpleName)) {
+          matched = target.find(bills => getFlag(bills) === fixedFlag.LABOUR_SERVICE);
+        } else if (/计日工合计/.test(parentSimpleName) && /材料/.test(simpleName)) {
+          matched = target.find(bills => getFlag(bills) === fixedFlag.MATERIAL);
+        } else if (/计日工合计/.test(parentSimpleName) && /机械/.test(simpleName)) {
+          matched = target.find(bills => getFlag(bills) === fixedFlag.CONSTRUCTION_MACHINE);
+        }
+      }
+      if (matched) {
+        assignAttr(matched, bills);
+        if (bills.children && bills.children.length) {
+          mergeBills(bills.children, matched.children, matched);
+        }
+      } else {
+        target.push(bills);
+      }
+    });
+
+  }
+
+  /**
+   * 处理清单
+   * @param {Array} tenderBills - 从xml提取出来的清单
+   * @param {Array} billsTarget - 拷贝一份的模板清单,用于合并提取清单
+   * @param {Number} tenderID - 单位工程ID
+   * @return {Array}
+   */
+  function handleBills(tenderBills, billsTarget, tenderID) {
+    const rst = [];
+    // 将提取的清单数据合并进清单模板数据
+    mergeBills(tenderBills, billsTarget, null);
+    // 给清单设置数据
     const rowCodeData = []; // 行号数据,用于转换行引用
     const toBeTransformBills = []; // 待转换的清单
-    function setBills(bills) {
-      bills.forEach(child => {
-        child.ID = uuid.v1();
+    function setBills(bills, parentID) {
+      bills.forEach((child, index) => {
+        rst.push(child);
         child.projectID = tenderID;
+        // 如果本身清单就有ID,那不用处理,可以减少处理清单模板的一些ID引用问题
+        if (!child.ID) {
+          child.ID = uuid.v1();
+        }
+        child.ParentID = parentID;
+        child.type = parentID === -1 ? BillType.DXFY : BillType.BILL;
+        child.NextSiblingID = -1;
+        const preChild = bills[index - 1];
+        if (preChild) {
+          preChild.NextSiblingID = child.ID;
+        }
         if (child.rowCode) {
           rowCodeData.push({ reg: new RegExp(`\\b${child.rowCode}\\b`, 'g'), ID: child.ID });
         }
@@ -233,18 +305,19 @@ const INTERFACE_EXPORT_BASE = (() => {
           toBeTransformBills.push(child);
         }
         if (child.children && child.children.length) {
-          setBills(child.children);
+          setBills(child.children, child.ID);
         }
       });
     }
-    setBills(tenderBills);
+    setBills(billsTarget, -1);
     // 转换计算基数,将行引用转换为ID引用
     toBeTransformBills.forEach(bills => {
       rowCodeData.forEach(({ reg, ID }) => {
         bills.calcBase = bills.calcBase.replace(reg, `@${ID}`);
       });
     });
-    // 将提取的清单数据合并进清单模板数据
+    rst.forEach(bills => delete bills.children);
+    return rst;
   }
 
   // 处理单位工程数据
@@ -264,6 +337,11 @@ const INTERFACE_EXPORT_BASE = (() => {
         throw '不存在可用工程专业。';
       }
       const taxData = engineeringLib.lib.tax_group[0];
+      const featureSource = [
+        ...tender.feature,
+        { key: 'valuationType', value: '工程量清单' }, // 导入的时候以下项不一定有数据,但是需要自动生成
+        { key: 'feeStandard', value: engineeringLib.lib.feeName },
+      ];
       tender.property = {
         rootProjectID: tender.ParentID,
         region: '全省',
@@ -278,16 +356,16 @@ const INTERFACE_EXPORT_BASE = (() => {
         valuationType: commonConstants.ValuationType.BOQ, // 必为工程量清单
         boqType: commonConstants.BOQType.BID_SUBMISSION, // 导入后必为投标
         taxType: taxData.taxType,
-        projectFeature: mergeInfo(tender.feature, featureTarget),
+        projectFeature: mergeInfo(featureSource, featureTarget),
         featureLibID: engineeringLib.lib.feature_lib[0] && engineeringLib.lib.feature_lib[0].id || '',
         calcProgram: { name: taxData.program_lib.name, id: taxData.program_lib.id },
         colLibID: taxData.col_lib.id,
         templateLibID: taxData.template_lib.id,
-        unitPriceFile: { name: tender.name, id: '' }, // 新建单价文件
+        unitPriceFile: { name: tender.name, id: templateData.unitPriceFileBeginID + index }, // 新建单价文件
         feeFile: { name: tender.name, id: `newFeeRate@@${taxData.fee_lib.id}` } // 新建费率文件
       };
       delete tender.feature;
-      handleBills(tender.bills, tender.ID, templateData.bills);
+      tender.bills = handleBills(tender.bills, _.cloneDeep(templateData.bills), tender.ID,); // 必须要拷贝一份,否则多单位工程情况下,前单位工程的清单数据会被后单位工程的覆盖
     });
   }
 
@@ -310,6 +388,12 @@ const INTERFACE_EXPORT_BASE = (() => {
     }
     console.log(templateData);
     // 处理建设项目数据
+    // 确定建设项目的名称(不允许重复)
+    const sameDepthProjs = getProjs(projTreeObj.tree.selected);
+    const matchedProject = sameDepthProjs.find(node => node.data.name === importData.name);
+    if (matchedProject) {
+      importData.name += `(${moment(Date.now()).format('YYYY-MM-DD HH:mm:ss')})`;
+    }
     importData.compilation = compilationData._id;
     importData.userID = userID;
     importData.ID = templateData.projectBeginID;
@@ -318,7 +402,7 @@ const INTERFACE_EXPORT_BASE = (() => {
     importData.preID = preProjectID;
     importData.NextSiblingID = nextProjectID;
     importData.projType = projectType.project;
-    importData.proprty = {
+    importData.property = {
       valuationType: commonConstants.ValuationType.BOQ, // 必为工程量清单
       boqType: commonConstants.BOQType.BID_SUBMISSION, // 导入后必为投标
       basicInformation: mergeInfo(importData.info, templateData.basicInfo) // 将提取的基本信息数据与标准基本信息数据进行合并(目前只赋值,没有匹配到的不追加)
@@ -326,7 +410,6 @@ const INTERFACE_EXPORT_BASE = (() => {
     delete importData.info;
     // 处理单位工程数据
     handleTenderData(importData.tenders, templateData);
-
     console.log(importData);
   }
 
@@ -373,6 +456,7 @@ const INTERFACE_EXPORT_BASE = (() => {
     }
     const importData = await entryFunc(areaKey, xmlObj);
     await handleImportData(importData);
+    return importData;
   }
 
   return {

+ 16 - 7
web/building_saas/standard_interface/import/view.js

@@ -33,6 +33,10 @@ const IMPORT_VIEW = (() => {
     // 导入确认事件
     $('#interface-import-confirm').click(async function () {
       try {
+        if (STATE.importing) {
+          return;
+        }
+        STATE.importing = true;
         const file = $('#interface-import-file')[0].files[0];
         if (!file) {
           throw '请选择导入文件。';
@@ -44,25 +48,30 @@ const IMPORT_VIEW = (() => {
           throw '请选择有效地区。';
         }
         const areaKey = `${parentArea}@${subArea}`;
-        $.bootstrapLoading.progressStart('导入文件', true);
-        $("#progress_modal_body").text('正在导入接口文件,请稍候……');
         await STD_INTERFACE.loadScriptByArea(areaKey, STD_INTERFACE.ScriptType.IMPORT);
         const importData = await INTERFACE_EXPORT_BASE.extractImportData(INTERFACE_IMPORT.entry, file, areaKey);
+        $('#interface-import-modal').modal('hide');
+        $.bootstrapLoading.progressStart('导入文件', true);
+        $("#progress_modal_body").text('正在导入接口文件,请稍候……');
+        //return;
         // 转换成File实例
-        /* const blob = new Blob([JSON.stringify(importData)], { type: 'text/plain;charset=utf-8' });
+        const blob = new Blob([JSON.stringify(importData)], { type: 'text/plain;charset=utf-8' });
         const key = `${uuid.v1()}.json`;
-        const file = new File([blob], key);
+        const uploadFile = new File([blob], key);
         // 上传文件
         await projTreeObj.getUploadToken();
-        await UPLOAD_CDN.uploadSync(file, key, projTreeObj.uptoken);
+        await UPLOAD_CDN.uploadSync(uploadFile, key, projTreeObj.uptoken);
         // 下载并处理文件
         await ajaxPost('/pm/import/importInterface', { key });
-        await importProcessChecking(key, null, (projectData) => handleProjectAfterChecking(projectData)); */
+        await importProcessChecking(key, null, (projectData) => handleProjectAfterChecking(projectData));
       } catch (err) {
         console.log(err);
         alert(err);
       } finally {
-        setTimeout(() => $.bootstrapLoading.progressEnd(), 500);
+        projTreeObj.uptoken = null;
+        setTimeout(function () {
+          STATE.importing = false;
+        }, 500);
       }
     });
   }