'use strict';

const importXML = (() => {
    // 通用设置和工具
    const config = importXMLBase.CONFIG;
    const util = importXMLBase.UTIL;
    const {
        fixedFlag,
        billType,
        rationType,
        projectType,
        TaxType,
    } = commonConstants;
    const { AdjustType } = config;
    const {
        getValue,
        arrayValue,
        getFee,
        mergeFees,
        assignAttr,
        mergeDataRecur,
        getFlag,
        getBool,
        getItemsRecur,
        extractItemsRecur,
    } = util;

    //导入的文件类型，界面选的文件类型是生成项目的文件类型，这里的文件类型指的是，要导入文件的类型，
    //导入文件类型不同，导入数据不同
    let importFileKind = '';
    // 投标文件和控制价文件是完全导入数据的
    let isFullyImport = false;
    // 文件类型
    const FileKind = {
        '6': 1, // 投标
        'tender': 1,
        '4': 2, // 招标
        'bid': 2,
        '5': 3, // 控制价
        'control': 3,
    };
    // 地区类别
    const AreaKind = {
        '1': '一类地区',
        '2': '二类地区',
        '3': '三类地区',
        '4': '四类地区',
    }
    // 一些数据是需要从后端获取自增数字后赋值的，这里是记录，用完会在extractProject清空
    let countData = {
        projectCount: 0,    //项目数量
        projectGLJCount: 0,  //项目人材机数量
        ratioCount: 0,      //组成物数量
        unitPriceCount: 0,   //单价数量
        unitPriceFileCount: 0,  //单价文件数量
    };
    // 精度
    const Decimal = {
        // 工料机消耗量、含量、用量类小数精度
        GLJ: 4,
        // 工程量、数量类小数精度
        QUANTITY: 3,
        // 金额、合价、费用类小数精度
        FEE: 2,
        // 费率、指数、比例（%）类小数精度
        RATE: 3,
        // 默认、中间过程
        PROCESS: 6
    };
    // 根据上方精度要求得到的项目属性，小数位数的值(定额工程量小数位数需求改成六位)
    const tenderPropertyDecimal = {
        bills: { unitPrice: Decimal.FEE, totalPrice: Decimal.FEE },
        ration: { quantity: Decimal.PROCESS, unitPrice: Decimal.FEE, totalPrice: Decimal.FEE },
        glj: { quantity: Decimal.GLJ, unitPriceHasMix: Decimal.FEE, unitPrice: Decimal.FEE },
        feeRate: Decimal.RATE,
        quantity_detail: 4,
        material: 5,//三材系数
        process: 6
    };
    const tenderPropertyBillsQuantityDecimal = Decimal.QUANTITY;
    // 工程量表达式相关
    const GCLMXHj = 'GCLMXHJ';
    const QDL = 'QDL';
    // 工程量表达式累加映射
    const summationMap = {
        '1': 1, // 累加
        '2': 0 // 不累加
    };

    /* 
     * 从导入的xml文件中提取有用的数据
     *
    */

    // 建设项目
    function extractProject(xmlObjMap) {
        Object.keys(countData).forEach(key => countData[key] = 0); // 清缓存
        countData.projectCount++;
        const projectXMLObj = xmlObjMap['Project.xml'];
        const projectSrc = getValue(projectXMLObj, ['ConstructionProject']);
        importFileKind = FileKind[getValue(projectSrc, ['_FileKind'])]; // 标记当前导入的文件类型
        isFullyImport = [FileKind.tender, FileKind.control].includes(importFileKind);
        const rst = {
            projType: projectType.Project,
            name: getValue(projectSrc, ['_Name']),
            engs: extractEngs(projectSrc, xmlObjMap),
            property: {
                compilationIllustration: getValue(projectSrc, ['_Explains'])
            },
            basicInformation: extractBasicInfo(projectSrc)
        };
        countData.unitPriceCount = countData.projectGLJCount;
        if (!isFullyImport) {
            countData = {
                projectCount: countData.projectCount,
                unitPriceFileCount: countData.unitPriceFileCount
            };
        }
        return rst;
    }
    // 从xml对象中提取基本信息相关
    function extractBasicInfo(projectSrc) {
        const projectInfo = getValue(projectSrc, ['ConstructionInfo', 'ProjectInfo']); // 估概预算信息
        const tendereeInfo = getValue(projectSrc, ['ConstructionInfo', 'TendereeInfo']); // 招标信息
        const bidderInfo = getValue(projectSrc, ['ConstructionInfo', 'BidderInfo']); // 投标信息
        return [
            { key: 'projNum', value: getValue(projectSrc, ['_Number']) }, // 编码
            { key: 'projectType', value: getValue(projectSrc, ['_ProjectType']) }, // 工程类型
            { key: 'projectCategory', value: getValue(projectSrc, ['_ProjectCategory']) }, // 工程类别
            { key: 'constructionType', value: getValue(projectSrc, ['_ConstructionType']) }, // 建设性质
            { key: 'regionalCategories', value: AreaKind[getValue(projectSrc, ['_AreaKind'])] }, // 地区类被
            { key: 'projLocation', value: getValue(projectSrc, ['_ProjectSite']) }, // 工程地点
            { key: 'constructingUnits', value: getValue(projectSrc, ['_BulidUnit']) }, // 建设单位
            { key: 'constructingUnitsPerson', value: getValue(projectSrc, ['_BulidAuthorizer']) }, // 建设单位法定代表人或其授权人
            { key: 'rangeOfCompilation', value: getValue(projectSrc, ['_RangeOfCompilation']) }, // 建设（编制）范围
            { key: 'scale', value: getValue(projectSrc, ['_Scale']) }, // 建设规模
            { key: 'unit', value: getValue(projectSrc, ['_Unit']) }, // 建设规模单位
            { key: 'designUnits', value: getValue(projectInfo, ['_Designer']) }, // 设计单位
            { key: 'constructionUnits', value: getValue(projectInfo, ['_Contractor']) }, // 承包单位
            { key: 'establishUnit', value: getValue(projectInfo, ['_CompileCompany']) }, // 编制单位
            { key: importFileKind === FileKind.tender ? 'bidCompileDate' : 'tenderCompileDate', value: getValue(projectInfo, ['_CompileDate']) }, // 编制时间
            { key: 'authorizer', value: getValue(projectInfo, ['_Authorizer']) }, // 编制单位法定代表人或其授权人
            { key: 'tendereeName', value: getValue(tendereeInfo, ['_TendereeName']) }, // 招标人
            { key: 'tenderAuthorizer', value: getValue(tendereeInfo, ['_TenderAuthorizer']) }, // 招标单位法定代表人或其授权人
            { key: 'tenderCompiler', value: getValue(tendereeInfo, ['_TenderCompiler']) }, // 招标单位编制人
            { key: 'tenderCompilerCertNo', value: getValue(tendereeInfo, ['_TenderCompilerCertNo']) }, // 招标单位编制人资格证书编号
            { key: 'tenderCompileDate', value: getValue(tendereeInfo, ['_TenderCompileDate']) }, // 招标单位编制时间
            { key: 'tenderExaminer', value: getValue(tendereeInfo, ['_TenderExaminer']) }, // 招标单位审核人
            { key: 'tenderExaminerCertNo', value: getValue(tendereeInfo, ['_TenderExaminerCertNo']) }, // 招标单位审核人资格证书编号
            { key: 'tenderExamineDate', value: getValue(tendereeInfo, ['_TenderExamineDate']) }, // 招标单位审核时间
            { key: 'tenderApprover', value: getValue(tendereeInfo, ['_TenderApprover']) }, // 招标单位审定人
            { key: 'tenderApproverCertNo', value: getValue(tendereeInfo, ['_TenderApproverCertNo']) }, // 招标单位审定人资格证书编号
            { key: 'proxy', value: getValue(tendereeInfo, ['_Proxy']) }, // 招标代理
            { key: 'proxyCertNo', value: getValue(tendereeInfo, ['_ProxyCertNo']) }, // 招标代理资质证书编号
            { key: 'proxyAuthorizer', value: getValue(tendereeInfo, ['_ProxyAuthorizer']) }, // 招标代理法定代表人或其授权人
            { key: 'proxyCompiler', value: getValue(tendereeInfo, ['_ProxyCompiler']) }, // 招标代理编制人员
            { key: 'proxyCompilerCertNo', value: getValue(tendereeInfo, ['_ProxyCompilerCertNo']) }, // 招标代理编制人员资格证书编号
            { key: 'proxyCompileDate', value: getValue(tendereeInfo, ['_ProxyCompileDate']) }, // 招标代理编制时间
            { key: 'proxyExaminer', value: getValue(tendereeInfo, ['_ProxyExaminer']) }, // 招标代理审核人
            { key: 'proxyExaminerCertNo', value: getValue(tendereeInfo, ['_ProxyExaminerCertNo']) }, // 招标代理审核人资格证书编号
            { key: 'proxyExamineDate', value: getValue(tendereeInfo, ['_ProxyExamineDate']) }, // 招标代理审核时间
            { key: 'proxyApprover', value: getValue(tendereeInfo, ['_ProxyApprover']) }, // 招标代理审定人
            { key: 'proxyApproverCertNo', value: getValue(tendereeInfo, ['_ProxyApproverCertNo']) }, // 招标代理审定人资格证书编号
            { key: 'proxyApproveDate', value: getValue(tendereeInfo, ['_ProxyApproveDate']) }, // 招标代理审定时间
            { key: 'consultant', value: getValue(tendereeInfo, ['_Consultant']) }, // 造价咨询
            { key: 'consultantCertNo', value: getValue(tendereeInfo, ['_ConsultantCertNo']) }, // 造价咨询资质证书编号
            { key: 'consultantCompiler', value: getValue(tendereeInfo, ['_ConsultantCompiler']) }, // 造价咨询编制人
            { key: 'consultantCompilerCertNo', value: getValue(tendereeInfo, ['_ConsultantCompilerCertNo']) }, // 造价咨询编制人资格证书
            { key: 'consultantCompileDate', value: getValue(tendereeInfo, ['_ConsultantCompileDate']) }, // 造价咨询编制时间
            { key: 'consultantExaminer', value: getValue(tendereeInfo, ['_ConsultantExaminer']) }, // 造价咨询审核人
            { key: 'consultantExaminerCertNo', value: getValue(tendereeInfo, ['_ConsultantExaminerCertNo']) }, // 造价咨询审核人资格证书编号
            { key: 'consultantExamineDate', value: getValue(tendereeInfo, ['_ConsultantExamineDate']) }, // 造价咨询审核时间
            { key: 'consultantApprover', value: getValue(tendereeInfo, ['_ConsultantApprover']) }, // 造价咨询审定人
            { key: 'consultantApproverCertNo', value: getValue(tendereeInfo, ['_ConsultantApproverCertNo']) }, // 造价咨询审定人资格证书编号
            { key: 'consultantApproveDate', value: getValue(tendereeInfo, ['_ConsultantApproveDate']) }, // 造价咨询审定时间
            { key: 'bidName', value: getValue(bidderInfo, ['_BidName']) }, // 投标人
            { key: 'bidAuthorizer', value: getValue(bidderInfo, ['_BidAuthorizer']) }, // 投标单位法定代表人或其授权人
            { key: 'bidCompiler', value: getValue(bidderInfo, ['_BidCompiler']) }, // 投标单位编制人
            { key: 'bidCompilerCertNo', value: getValue(bidderInfo, ['_BidCompilerCertNo']) }, // 投标单位编制人资格证书编号
            { key: 'bidCompileDate', value: getValue(bidderInfo, ['_BidCompileDate']) }, // 投标单位编制时间
            { key: 'bidExaminer', value: getValue(bidderInfo, ['_BidExaminer']) }, // 投标单位审核人
            { key: 'bidExaminerCertNo', value: getValue(bidderInfo, ['_BidExaminerCertNo']) }, // 投标单位审核人资格证书编号
            { key: 'bidExamineDate', value: getValue(bidderInfo, ['_BidExamineDate']) }, // 投标单位审核时间
            { key: 'bidApprover', value: getValue(bidderInfo, ['_BidApprover']) }, // 投标单位审定人
            { key: 'bidApproverCertNo', value: getValue(bidderInfo, ['_BidApproverCertNo']) }, // 投标单位审定人资格证书
            { key: 'bidApproveDate', value: getValue(bidderInfo, ['_BidApproveDate']) }, // 投标单位审定时间
        ];

    }
    // 从xml对象中提取单项工程数据
    function extractEngs(projectSrc, xmlObjMap) {
        const sectionWorks = arrayValue(projectSrc, ['ProjectInstallationWorkCost', 'SectionalWorks']);
        return sectionWorks.map(src => {
            countData.projectCount++;
            return {
                projType: projectType.Engineering,
                name: getValue(src, ['_Name']),
                code: getValue(src, ['_Number']),
                tenders: extractTenders(src, xmlObjMap)
            }
        });
    }
    // 从xml对象中提取单位工程数据

    // 额外新增的数据（文件里没有，需要额外生成）
    function extractTenders(sectionWorkSrc, xmlObjMap) {
        const unitWorks = arrayValue(sectionWorkSrc, ['UnitWorks']);
        return unitWorks.map(unitWorckSrc => {
            countData.projectCount++;
            countData.unitPriceFileCount++;
            const fileName = getValue(unitWorckSrc, ['_FileName']);
            const tenderXMLObj = xmlObjMap[fileName];
            const src = getValue(tenderXMLObj, ['UnitWorks']); // 单位工程文件的数据
            return {
                projType: projectType.Tender,
                name: getValue(src, ['_Name']),
                code: getValue(src, ['_Number']),
                compilationIllustration: getValue(src, ['_Explains']),
                engineering: getValue(src, ['_ProjectType']),
                projectFeature: extractProjectFeature(src),
                workSummary: extractWorkSummary(src),
                fbfx: extractFBFX(src),
                csxm: extractCSXM(src),
                other: extractOther(src),
                tax: extractTax(src),
                evalGLJFromOther: extractProvisionalMaterialEquipmentItem(src),
                ...extractGLJSummary(src),
                bidEvaluationSummary: extractBidEvaluationSummary(src)
            };
        });
    }
    // 提取工程特征数据
    function extractProjectFeature(tenderSrc) {
        // 支持嵌套子项
        const attrInfoSrc = getValue(tenderSrc, ['AttrInfo']);
        const featureData = extractItemsRecur(attrInfoSrc, [['AttrInfoItem']], (src) => ({
            dispName: getValue(src, ['_Name']),
            value: getValue(src, ['_Value'])
        }));
        clearEmptyItems(featureData);
        console.log(featureData);
        // 特殊处理几个工程特征（有的文件AttrInfo中不包含一些工程特征）
        const hasProjectType = featureData.some(item => item.dipName === '工程类型');
        const hasScale = featureData.some(item => item.dipName === '建设规模');
        const hasScaleUnit = featureData.some(item => item.dipName === '建设规模单位');
        if (!hasProjectType) {
            featureData.push({
                dispName: '工程类型',
                value: getValue(tenderSrc, ['_ProjectType'])
            });
        }
        if (!hasScale) {
            featureData.push({
                dispName: '建设规模',
                value: getValue(tenderSrc, ['_Scale'])
            });
        }
        if (!hasScaleUnit) {
            featureData.push({
                dispName: '建设规模单位',
                value: getValue(tenderSrc, ['_Unit'])
            });
        }
        return featureData;

        function clearEmptyItems(items) {
            items.forEach(item => {
                if (!item.items) {
                    return;
                }
                if (!item.items.length) {
                    delete item.items;
                } else {
                    clearEmptyItems(item.items);
                }
            })
        }
    }
    // 提取大项费用
    function extractWorkSummary(tenderSrc) {
        const summarySrc = getValue(tenderSrc, ['UnitWorksSummary']);
        const fields = [['UnitWorksSummaryGroup'], ['UnitWorksSummaryItem']];
        return extractItemsRecur(summarySrc, fields, (src, curField) => {
            const item = {
                code: getValue(src, ['_Number']),
                name: getValue(src, ['_Name']),
                quantity: getValue(src, ['_Quantity']),
                remark: getValue(src, ['_Remark']),
                feeCode: getValue(src, ['_Code']), //费用字典
                fees: [{
                    fieldName: 'common', totalFee: getValue(src, ['_Total']) || '0',
                    unitFee: getValue(src, ['_TechnicalAndEconomicIndex']) || '0'
                }]
            };
            if (curField[0] === fields[1][0]) {
                item.calcBase = getValue(src, ['_QtyFormula']);
                const feeRate = getValue(src, ['_Rate']);
                item.feeRate = +feeRate !== 100 && feeRate || undefined;
            }
            if (!isFullyImport) {
                delete item.feeRate
                delete item.fees;
            }
            return item;
        });
    }
    // 从src子项的SummaryOfBasicCost中获取一部分费用
    function getFeesFromBasicCost(src) {
        const summaryCost = getValue(src, ['SummaryOfBasicCost']);
        if (!summaryCost) {
            return [];
        }
        // SummaryOfBasicCost中有用的价格只有：人工费、材料费、主材费、机械费、管理费、利润
        return [
            { fieldName: 'labour', totalFee: getValue(summaryCost, ['_Labor']) || '0' },
            { fieldName: 'material', totalFee: getValue(summaryCost, ['_Material']) || '0' },
            { fieldName: 'mainMaterial', totalFee: getValue(summaryCost, ['_MainMaterial']) || '0' },
            { fieldName: 'machine', totalFee: getValue(summaryCost, ['_Machine']) || '0' },
            { fieldName: 'manage', totalFee: getValue(summaryCost, ['_Overhead']) || '0' },
            { fieldName: 'profit', totalFee: getValue(summaryCost, ['_Profit']) || '0' },
        ];
    }
    // 提取分部分项
    function extractFBFX(tenderSrc) {
        const fbfxSrc = getValue(tenderSrc, ['DivisionalAndElementalWorks']);
        const fields = [['DivisionalWorks'], ['WorkElement']];
        const fees = getFeesFromBasicCost(fbfxSrc); // 分部分项的汇总价
        const items = getItemsRecur(fbfxSrc, fields, (itemSrc, curField) => {
            if (curField[0] === fields[0][0]) { // 分部
                return extractDivisionalWorks(itemSrc, billType.FB);
            } else {
                return extractWorkElement(itemSrc, billType.FX);
            }
        });
        return {
            fees,
            items
        };
    }
    // 提取分部
    function extractDivisionalWorks(divisionalSrc, type) {
        // 分部不需要基数，因此不用把基数导入
        const item = {
            type,
            code: getValue(divisionalSrc, ['_Number']),
            name: getValue(divisionalSrc, ['_Name']),
            unit: getValue(divisionalSrc, ['_Unit']),
            quantity: getValue(divisionalSrc, ['_Quantity']),
            feeCode: getValue(divisionalSrc, ['_Code']),
            remark: getValue(divisionalSrc, ['_Remark']),
        };
        // 特殊处理措施项目的费用代号(广联达这两条数据没有导出费用代号，跟我们的清单模板不匹配，从而导致从合并变成了新增)，详看mergeBills中的mergeitems放啊
        if (type === billType.BILL) {
            if (item.code === 'AQFHWMSG') {
                item.feeCode = FlagFeeCodeMap[fixedFlag.SAFETY_CONSTRUCTION];
            } else if (item.code === 'QTCSF') {
                item.feeCode = FlagFeeCodeMap[fixedFlag.OTHER_MEASURE_FEE];
            }
        }
        if (isFullyImport) {
            const summaryFees = getFeesFromBasicCost(divisionalSrc);
            const fees = [{ fieldName: 'common', totalFee: getValue(divisionalSrc, ['_Total']), unitFee: getValue(divisionalSrc, ['_TechnicalAndEconomicIndex']) }];
            item.fees = mergeFees(fees, summaryFees);
        }
        return item;
    }
    // 提取分项清单数据（包含定额、定额人材机）
    function extractWorkElement(workElementSrc, type) {
        const itemCharacterText = getItemCharacterText(getValue(workElementSrc, ['_Attr']));
        const bills = {
            type: type, // 清单类型
            code: getValue(workElementSrc, ['_Number']),
            name: getValue(workElementSrc, ['_Name']),
            itemCharacterText,
            itemCharacter: getItemCharacter(itemCharacterText),
            jocContentText: getValue(workElementSrc, ['_WorkContent']),
            jobContent: getJobContent(workElementSrc),
            unit: getValue(workElementSrc, ['_Unit']),
            quantity: getValue(workElementSrc, ['_Quantity']),
            mainBills: getBool(getValue(workElementSrc, ['_Major'])),
            feeCode: getValue(workElementSrc, ['_Code']),
            remark: getValue(workElementSrc, ['_Remark']),
            quantityDetails: extractQuantityDetails(workElementSrc),
            rations: extractRations(workElementSrc)
        };
        if (type === billType.BILL) {
            // 分项不需要基数、费率
            bills.calcBase = getValue(workElementSrc, ['_QtyFormula']);
            const feeRate = getValue(workElementSrc, ['_Rate']);
            bills.feeRate = +feeRate !== 100 && feeRate || undefined;
        }
        // 投标和控制价，需要导入最高限价
        if (isFullyImport) {
            const maxPrice = getValue(workElementSrc, ['_PriceHigh']);
            //不为0才输出
            if (maxPrice && maxPrice !== '0') {
                bills.outPutLimitPrice = true;
                bills.outPutMaxPrice = true;
                bills.maxPrice = maxPrice;
            }
            const summaryFees = getFeesFromBasicCost(workElementSrc);
            const fees = [
                { fieldName: 'common', unitFee: getValue(workElementSrc, ['_Price']), totalFee: getValue(workElementSrc, ['_Total']) },
                { fieldName: 'equipment', unitFee: getValue(workElementSrc, ['_EquipmentPrice']) },
            ];
            bills.fees = mergeFees(fees, summaryFees);
        }
        return bills;
        // 获取项目特征文本
        function getItemCharacterText(attr) {
            // 去掉除换行符以外的空白符
            const newLine = '{newLine}';
            attr = attr
                .replace(/[\r\n]/g, newLine)
                .replace(/\s/g, '')
                .replace(new RegExp(newLine, 'g'), '\n');
            const matchStrs = ['&#xA;', ';'];
            const matchStr = matchStrs.find(str => attr.includes(str));
            if (matchStr) {
                attr = attr.replace(new RegExp(matchStr, 'g'), '\n');
            }
            return attr;
        }
        // 获取清单项目特征窗口数据
        function getItemCharacter(itemText) {
            const itemsList = itemText.split('\n');
            const reg = /\d+\.([^:]+)[:：](.*)?/;
            const itemCharacter = [];
            let idx = 1;
            itemsList.forEach(str => {
                str = str.replace(/\s/g, '');
                const result = reg.exec(str);
                if (!result) {
                    return;
                }
                const name = result[1];
                const value = result[2] || '';
                const eigenvalue = value ? [{ value, isSelected: true }] : [];
                itemCharacter.push({
                    character: name,
                    isChecked: true,
                    serialNo: idx++,
                    eigenvalue
                });
            });
            return itemCharacter;
        }
        // 获取清单工作内容窗口数据
        function getJobContent(workElementSrc) {
            return arrayValue(workElementSrc, ['WorkContent']).map((workContent, idx) => ({
                content: getValue(workContent, ['_Name']),
                serialNo: idx + 1,
                isChecked: true
            }));
        }
    }
    // 提取工程量明细(清单、定额底下都可能会有)
    function extractQuantityDetails(src) {
        return arrayValue(src, ['ExpressElement']).map(itemSrc => ({
            seq: getValue(itemSrc, ['_OrderNumber']),
            regex: getValue(itemSrc, ['_Express']),
            result: getValue(itemSrc, ['_Quantity']),
            isSummation: summationMap[getValue(itemSrc, ['_Kind'])]
        }));
    }
    // 提取定额
    function extractRations(workElementSrc) {
        if (!isFullyImport) {
            return [];
        }
        // 正常情况下定额肯定是在WorkContent内的,但是也做定额不在WorkContent内的处理（外层定额）
        const rations = [];
        let serialNo = 1;
        const workContents = arrayValue(workElementSrc, ['WorkContent']);
        workContents.forEach(workContent => {
            const innerRationsSrc = arrayValue(workContent, ['Norm']);
            const jobContentText = getValue(workContent, ['_Name']);
            const innerRations = innerRationsSrc.map(rationSrc => extractRation(rationSrc, jobContentText));
            rations.push(...innerRations);
        });
        const outerRationsSrc = arrayValue(workElementSrc, ['Norm']);
        const outerRations = outerRationsSrc.map(rationSrc => extractRation(rationSrc, ''));
        rations.push(...outerRations);
        return rations;
        // 获取类型
        function getType(rationSrc) {
            const overHeightMap = {
                '1': true,
                '2': false
            };
            const isOverHeight = overHeightMap[getValue(rationSrc, ['EfficiencyKind'])];
            if (isOverHeight) {
                return rationType.overHeight;
            }
            const itemIncreaseMap = {
                '1': true,
                '2': false
            };
            const isItemIncrease = itemIncreaseMap[getValue(rationSrc, ['IncFeeKind'])];
            if (isItemIncrease) {
                return rationType.itemIncrease;
            }
            return rationType.ration;
        }
        // 根据定额计算程序子目，获取量价类型
        function getVolumePriceTypeByCalcItems(rationSrc) {
            const calculationOfItems = arrayValue(rationSrc, ['UnitPriceCalculationOfItem']);
            for (const item of calculationOfItems) {
                const code = getValue(item, ['_Code']);
                const total = +getValue(item, ['_Total']);
                if (code === 'RGF' && total) {
                    return { type: rationType.volumePrice, subType: 1 };
                }
                if (code === 'CLF' && total) {
                    return { type: rationType.volumePrice, subType: 201 };
                }
                if (code === 'JXF' && total) {
                    return { type: rationType.volumePrice, subType: 301 };
                }
            }
            return null;
        }
        // 提取计算程序费率数据
        function extractCalculationOfItems(rationSrc) {
            // 需要提取费率的字段映射
            const codeFiedNameMap = {
                'RGF': 'labour',
                'CLF': 'material',
                'JXF': 'machine',
                'GLF': 'manage',
                'LR': 'profit',
                'DJ': 'common',
            };
            const calculationOfItems = arrayValue(rationSrc, ['UnitPriceCalculationOfItem']);
            const rst = [];
            calculationOfItems.forEach(item => {
                const fieldName = codeFiedNameMap[getValue(item, ['_Code'])];
                const feeRate = +getValue(item, ['_Rate']);
                if (fieldName && !isNaN(feeRate)) {
                    rst.push({ fieldName, feeRate });
                }
            });
            return rst;
        }
        // 从定额的计算程序提取价格
        function getFeesFromCalculationOfItem(rationSrc) {
            const calculationOfItems = arrayValue(rationSrc, ['UnitPriceCalculationOfItem']);
            const fees = [];
            const codeFiedNameMap = {
                'ZJF': 'direct',
                'RGF': 'labour',
                'CLF': 'material',
                'JXF': 'machine',
                'ZCF': 'mainMaterial',
                'SBF': 'equipment',
                'LR': 'profit',
                //'DJ': 'common',定额本身已经有单价这个字段，不需要从这读取
            };
            calculationOfItems.forEach(item => {
                const totalFee = +getValue(item, ['_Total']);
                const fieldName = codeFiedNameMap[getValue(item, ['_Code'])];
                if (totalFee && fieldName) {
                    fees.push({ fieldName, totalFee });
                }
            });
            return fees;
        }
        // 提取定额人材机
        function extractRationGLJs(rationSrc) {
            const rationGLJsSrc = arrayValue(rationSrc, ['LabourMaterialsEquipmentsMachinesElement']);
            return rationGLJsSrc.map(src => ({
                code: getValue(src, ['_Number']),
                quantity: getValue(src, ['_Quantity'])
            }));
        }
        // TODO 目前无法确定定额的取费专业programID，暂时用专业类别Specialty
        function extractRation(rationSrc, jobContentText) {
            // 需要处理定额编码如易达的某文件，定额编码有“E1-3-52*3”,需要截取“*”之前的作为定额编码
            let code = getValue(rationSrc, ['_Number']);
            const codeReg = /([^*]+)\*.*/;
            code = code.replace(codeReg, '$1');
            const ration = {
                serialNo: serialNo++,
                code,
                name: getValue(rationSrc, ['_Name']),
                unit: getValue(rationSrc, ['_Unit']),
                quantity: getValue(rationSrc, ['_Quantity']),
                type: getType(rationSrc),
                programID: getValue(rationSrc, ['_Specialty']),
                libCode: getValue(rationSrc, ['_NormIdentity']),
                jobContentText,
                remark: getValue(rationSrc, ['_Remark'])
            };
            const fees = [{ fieldName: 'common', unitFee: getValue(rationSrc, ['_Price']), totalFee: getValue(rationSrc, ['_Total']) }]
            const feesFromCalcItem = getFeesFromCalculationOfItem(rationSrc);
            ration.fees = mergeFees(fees, feesFromCalcItem);
            ration.rationGLJs = extractRationGLJs(rationSrc);
            // 没有定额人材机则当作是增加费定额、同时导入后成为量价
            if (!ration.rationGLJs.length) {
                const typeData = getVolumePriceTypeByCalcItems(rationSrc);
                if (typeData) {
                    Object.assign(ration, typeData);
                    ration.setFakeProgramID = true;
                }
            }
            ration.quantityDetails = extractQuantityDetails(rationSrc);
            ration.calculationItems = extractCalculationOfItems(rationSrc);
            return ration;
        }
    }
    // 提取措施项目
    function extractCSXM(tenderSrc) {
        const csxmSrc = getValue(tenderSrc, ['Preliminaries']);
        const fields = [['DivisionalWorks'], ['WorkElement']];
        const fees = getFeesFromBasicCost(csxmSrc); // 措施项目的汇总价
        const items = extractItemsRecur(csxmSrc, fields, (itemSrc, curField) => {
            if (curField[0] === fields[0][0]) {
                return extractDivisionalWorks(itemSrc, billType.BILL);
            } else {
                return extractWorkElement(itemSrc, billType.BILL);
            }
        });
        return {
            fees,
            items
        };
    }
    // 提取额外的暂估材料表
    // 除了人材机汇总中的is_evaluate = true的材料为暂估材料外，还需要处理ProvisionalMaterialEquipmentItem的数据
    // 如易达的一些文件，ProvisionalMaterialEquipmentItem中存在人材机汇总没有的数据
    function extractProvisionalMaterialEquipmentItem(tenderSrc) {
        const evalGLJs = arrayValue(tenderSrc, ['Sundry', 'ProvisionalMaterialEquipment', 'ProvisionalMaterialEquipmentItem']);
        return evalGLJs.map(glj => ({
            code: getValue(glj, ['_Number']),
            name: getValue(glj, ['_Name']),
            unit: getValue(glj, ['_Unit']),
            quantity: getValue(glj, ['_Quantity']),
            market_price: getValue(glj, ['_Price']),
            remark: getValue(glj, ['_Remark'])
        }));

    }
    // 提取其他项目
    function extractOther(tenderSrc) {
        const sundry = getValue(tenderSrc, ['Sundry']);
        // SundryCosts 其他项目 ,对于我们软件是其他项目下完整数据，但是广联达的并不是，因此需要Sundry的其他节点来补充这份数据
        const sundryCosts = getValue(sundry, ['SundryCosts']);
        // 其他项目汇总费用
        const sundryFees = [{ fieldName: 'common', totalFee: getValue(sundryCosts, ['_Total']) }];
        const provisionalMaterialEquipment = getValue(sundry, ['ProvisionalMaterialEquipment']);
        return {
            fees: isFullyImport ? sundryFees : [],
            sundryCosts: extractData(sundry, 'SundryCosts', [['SundryCostsGroup'], ['SundryCostsItem']]),
            provisional: extractData(sundry, 'ProvisionalSums', [['ProvisionalSumsGroup'], ['ProvisionalSumsItem']]),
            ProvisionalMaterialEquipment: {
                name: getValue(provisionalMaterialEquipment, ['_Name']),
                feeCode: getValue(provisionalMaterialEquipment, ['_Code']),
                remark: getValue(provisionalMaterialEquipment, ['_Remark']),
                fees: [{ fieldName: 'common', totalFee: getValue(provisionalMaterialEquipment, ['_Total']) }],
            },
            specialty: extractData(sundry, 'SpecialtyProvisionalPrice', [['SpecialtyProvisionalPriceGroup'], ['SpecialtyProvisionalPriceItem']],
                { engineeringContent: ['_Content'] }, { engineeringContent: ['_Content'] }),
            dayWork: extractData(sundry, 'DayWorkRate', [['DayWorkRateGroup'], ['DayWorkRateItem']]),
            mainContractor: extractData(sundry, 'MainContractorAttendance', [['MainContractorAttendanceGroup'], ['MainContractorAttendanceItem']], { serviceContent: ['_Content'] }, { serviceContent: ['_Content'] }),
            claim: extractData(sundry, 'ClaimsCost', [['ClaimsCostGroup'], ['ClaimsCostItem']], { claimVisa: ['_Reason'] }, { claimVisa: ['_Reason'] }),
            visa: extractData(sundry, 'SiteInstructionCost', [['SiteInstructionCostGroup'], ['SiteInstructionCostItem']], { claimVisa: ['_Reason'] }, { claimVisa: ['_Reason'] }),
        };
        // 提取标题和明细
        function extractGroupOrItem(itemSrc, isGroup = true, extendAttrs = null, field) {
            let source;
            if (isGroup) {
                source = {
                    name: getValue(itemSrc, ['_Name']),
                    fees: isFullyImport ? [{ fieldName: 'common', totalFee: getValue(itemSrc, ['_Total']) }] : [],
                    feeCode: getValue(itemSrc, ['_Code']),
                    remark: getValue(itemSrc, ['_Remark'])
                };
            } else {
                const feeRate = getValue(itemSrc, ['_Rate']);
                source = {
                    name: getValue(itemSrc, ['_Name']),
                    unit: getValue(itemSrc, ['_Unit']),
                    calcBase: getValue(itemSrc, ['_QtyFormula']),
                    feeRate: +feeRate !== 100 && feeRate || undefined,
                    fees: isFullyImport ? [{ fieldName: 'common', unitFee: getValue(itemSrc, ['_Price']), totalFee: getValue(itemSrc, ['_Total']) }] : [],
                    feeCode: getValue(itemSrc, ['_Code']),
                    remark: getValue(itemSrc, ['_Remark'])
                };
                if (field !== 'MainContractorAttendanceItem') { // MainContractorAttendanceItem的quantity比较特殊，是清单基数计算后的值而不是工程量，不需要导入
                    source.quantity = getValue(itemSrc, ['_Quantity']);
                }
            }
            return extendAttrs ? Object.assign(source, extendAttrs) : source;
        }
        // 提取数据
        function extractData(sundry, srcName, fields, groupExtendMap, itemExtendMap) {
            const src = getValue(sundry, [srcName]);
            return {
                fees: isFullyImport ? [{ fieldName: 'common', totalFee: getValue(src, ['_Total']) }] : [],
                feeCode: getValue(src, ['_Code']),
                items: extractItemsRecur(src, fields, (itemSrc, curField) => {
                    const groupExtend = groupExtendMap
                        ? Object.keys(groupExtendMap).reduce((acc, key) => {
                            acc[key] = getValue(itemSrc, groupExtendMap[key]);
                            return acc;
                        }, {})
                        : null;
                    const itemExtend = itemExtendMap
                        ? Object.keys(itemExtendMap).reduce((acc, key) => {
                            acc[key] = getValue(itemSrc, itemExtendMap[key]);
                            return acc;
                        }, {})
                        : null;
                    return curField[0] === fields[0][0]
                        ? extractGroupOrItem(itemSrc, true, groupExtend, curField[0])
                        : extractGroupOrItem(itemSrc, false, itemExtend, curField[0]);
                })
            };
        }
    }
    // 提取税金
    function extractTax(tenderSrc) {
        const taxSrc = getValue(tenderSrc, ['Tax']);
        const itemsSrc = arrayValue(taxSrc, ['TaxItem']);
        return {
            ...extractTaxItem(taxSrc),
            items: itemsSrc.map(extractTaxItem)
        };
        function extractTaxItem(src) {
            const feeRate = getValue(src, ['_Rate']);
            return {
                name: getValue(src, ['_Name']),
                calcBase: getValue(src, ['_QtyFormula']),
                feeRate: +feeRate !== 100 && feeRate || undefined,
                fees: isFullyImport ? [{ fieldName: 'common', totalFee: getValue(src, ['_Total']) }] : [],
                feeCode: getValue(src, ['_Code']),
                remark: getValue(src, ['_Remark'])
            };
        }
    }

    // 供料方式
    const Provider = {
        '1': commonConstants.supplyType.ZXCG, // 自行采购
        '2': commonConstants.supplyType.JGCL, // 甲供材料
        '3': commonConstants.supplyType.JDYG // 甲定乙供
    };

    // 获取原始编码
    function getOriginalCode(code) {
        //编码后面有-\d+的形式，去掉-\d+取前面的字符串作为原始代码
        return code.replace(/(.*)-\d+$/, '$1');
    }

    // 提取人材机汇总相关(人材机汇总表、承包材料表)
    function extractGLJSummary(tenderSrc) {
        const initData = { gljSummary: [], differentiaSummary: [], exponentialSummary: [] };
        if (!isFullyImport) {
            return initData;
        }
        const taxType = getValue(tenderSrc, ['_TaxModel']); // 计税方式
        const gljListSrc = arrayValue(tenderSrc, ['LabourMaterialsEquipmentsMachinesSummary']);
        return gljListSrc.reduce((acc, gljSrc) => {
            acc.gljSummary.push(extractGLJ(gljSrc));
            const differentiaGLJ = extractDifferentiaGLJ(gljSrc);
            if (differentiaGLJ) {
                acc.differentiaSummary.push(differentiaGLJ);
            }
            const exponentialGLJ = extractExponentialGLJ(gljSrc);
            if (exponentialGLJ) {
                acc.exponentialSummary.push(exponentialGLJ);
            }
            return acc;
        }, initData);

        // TODO (不靠谱) 获取人材机类型数据
        // 导入的源文件没有细化区分人材机类型，这里只是暂时大概给个值。后续会在后端与标准人材机进行配对。也因此无法准确处理补充人材机
        function getTypeData(gljSrc) {
            const concrete = getBool(getValue(gljSrc, ['_Concrete']));
            if (concrete) {
                return { type: 205, shorName: '商砼' };
            }
            const type = getValue(gljSrc, ['_Kind']);
            switch (type) {
                case '1':
                    return { type: 1, shorName: '人' };
                case '4':
                    return { type: 4, shortName: '主' };
                case '5':
                    return { type: 5, shortName: '设' }
                case '6':
                    return { type: 202, shorName: '砼' };
                case '3':
                case '7':
                    return { type: 301, shorName: '机' };
                case '15':
                    return { type: 6, shortName: '管' };
                default:
                    return { type: 201, shortName: '材' };
            }
        }
        // 根据计税方式获取价格，一般计税对应不含税，简易对应含税
        function getPriceByTaxType(gljSrc, taxType) {
            return +taxType === TaxType.NORMAL
                ? {
                    base_price: getValue(gljSrc, ['_NoTaxOrgPrice']),
                    market_price: getValue(gljSrc, ['_NoTaxPrice'])
                }
                : {
                    base_price: getValue(gljSrc, ['_TaxOrgPrice']),
                    market_price: getValue(gljSrc, ['_TaxPrice'])
                };
        }
        // 提取组成物
        function extractRatios(gljSrc) {
            const ratiosSrc = arrayValue(gljSrc, ['LabourMaterialsEquipmentsMachinesElement']);
            countData.ratioCount += ratiosSrc.length;
            return ratiosSrc.map(src => ({
                code: getValue(src, ['_Number']),
                consumption: getValue(src, ['_Quantity'])
            }));
        }
        // 提取项目人材机
        function extractGLJ(gljSrc) {
            countData.projectGLJCount++;
            const code = getValue(gljSrc, ['_Number']);
            /* if (code.includes('99450680')) {
                debugger;
            } */
            return {
                code,
                original_code: getOriginalCode(code),
                name: getValue(gljSrc, ['_Name']),
                specs: getValue(gljSrc, ['_Specification']),
                unit: getValue(gljSrc, ['_Unit']),
                ...getTypeData(gljSrc),
                ...getPriceByTaxType(gljSrc, taxType), // 软件中项目人材机是没有价格的，只是为了后续处理单价文件
                is_main_material: getBool(getValue(gljSrc, ['_MainMaterial'])),
                is_evaluate: getBool(getValue(gljSrc, ['_ProvisionalMaterial'])),
                supply: Provider[getValue(gljSrc, ['_Provider'])],
                delivery: getValue(gljSrc, ['_Delivery']),
                delivery_address: getValue(gljSrc, ['_Location']),
                originPlace: getValue(gljSrc, ['_ProducingArea']),
                vender: getValue(gljSrc, ['_Supplier']),
                qualityGrace: getValue(gljSrc, ['_Character']),
                remark: getValue(gljSrc, ['_Remark']),
                ratios: extractRatios(gljSrc)
            };
        }
        // 提取造价信息差额调整法
        function extractDifferentiaGLJ(gljSrc) {
            // 风险系数或者基准单价有值，才算一条差额数据
            const riskCoe = getValue(gljSrc, ['_ProviderRate']);
            const standardPrice = getValue(gljSrc, ['_ProviderBase']);
            if ((riskCoe && riskCoe !== '0') || (standardPrice && standardPrice !== '0')) {
                return {
                    code: getValue(gljSrc, ['_Number']),
                    name: getValue(gljSrc, ['_Name']),
                    specs: getValue(gljSrc, ['_Specification']),
                    unit: getValue(gljSrc, ['_Unit']),
                    quantity: getValue(gljSrc, ['_Quantity']),
                    riskCoe,
                    standardPrice,
                    market_price: getPriceByTaxType(gljSrc, taxType).market_price,
                    remark: getValue(gljSrc, ['_Remark']),
                };
            }
            return null;
        }
        // 提取价格指数调整法
        function extractExponentialGLJ(gljSrc) {
            // 变值权重或基本、现行价格指数有值，才算一条指数数据
            const varWeight = getValue(gljSrc, ['_Weight']);
            const FO = getValue(gljSrc, ['_BasicPrice']);
            const FI = getValue(gljSrc, ['_CurrentPrice']);
            if ((varWeight && varWeight !== '0') || (FO && FO !== '0') || (FI && FI !== '0')) {
                return {
                    code: getValue(gljSrc, ['_Number']),
                    name: getValue(gljSrc, ['_Name']),
                    specs: getValue(gljSrc, ['_Specification']),
                    varWeight: varWeight,
                    FO: FO,
                    FI: FI,
                    remark: getValue(gljSrc, ['_Remark'])
                };
            }
            return null;
        }
    }
    // 提取评标材料
    function extractBidEvaluationSummary(tenderSrc) {
        if (!isFullyImport) {
            return [];
        }
        const srcList = arrayValue(tenderSrc, ['BidEvaluationMainMaterial']);
        return srcList.map(gljSrc => ({
            seq: getValue(gljSrc, ['_Code']),
            code: getValue(gljSrc, ['_Number']),
            name: getValue(gljSrc, ['_Name']),
            specs: getValue(gljSrc, ['_Specification']),
            unit: getValue(gljSrc, ['_Unit']),
            quantity: getValue(gljSrc, ['_Quantity']),
            market_price: getValue(gljSrc, ['_Price']),
            originPlace: getValue(gljSrc, ['_ProducingArea']),
            vender: getValue(gljSrc, ['_Supplier']),
            remark: getValue(gljSrc, ['_Remark'])
        }));
    }


    /* 
     * 转换从xml提取的数据，转换成软件所需的数据结构
     *
    */
    async function transformData(extractedData) {
        const IDPlaceholder = await ajaxPost('/pm/import/getProjectPlaceholder', countData);
        extractedData.ID = IDPlaceholder.project++;
        // 最终上传到服务器的转换后的数据
        const transformedData = {
            ID: extractedData.ID,
            ParentID: extractedData.ParentID,
            NextSiblingID: extractedData.NextSiblingID,
            preID: extractedData.preID,
            name: extractedData.name,
            basicInformation: extractedData.basicInformation,
            projType: extractedData.projType,
            property: extractedData.property,
            shareInfo: [],
            engs: []
        };
        // 所有的清单模板库ID
        const allTemplateLibIDs = [];
        extractedData.engs.forEach(eng => {
            eng.tenders.forEach(tender => {
                let templateLibID = tender.property.templateLibID;
                if (!allTemplateLibIDs.includes(templateLibID)) {
                    allTemplateLibIDs.push(templateLibID);
                }
            });
        });
        // 模板映射：{[templateLibID]: data}
        const templateMapping = await ajaxPost('/template/bills/api/getNeedfulTemplate', { allTemplateLibIDs });
        for (let i = 0; i < extractedData.engs.length; i++) {
            const curEng = extractedData.engs[i];
            const preEng = transformedData.engs[i - 1];
            curEng.ID = IDPlaceholder.project++;
            curEng.ParentID = extractedData.ID;
            curEng.NextSiblingID = -1;
            if (preEng) {
                preEng.NextSiblingID = curEng.ID;
            }
            const engData = {
                ID: curEng.ID,
                ParentID: curEng.ParentID,
                NextSiblingID: curEng.NextSiblingID,
                code: curEng.code,
                name: curEng.name,
                projType: curEng.projType,
                shareInfo: [],
                tenders: []
            };
            transformedData.engs.push(engData);
            for (let j = 0; j < curEng.tenders.length; j++) {
                const curTender = curEng.tenders[j];
                const preTender = engData.tenders[j - 1];
                curTender.ID = IDPlaceholder.project++;
                curTender.ParentID = curEng.ID;
                curTender.NextSiblingID = -1;
                curTender.property.unitPriceFile.id = IDPlaceholder.unitPriceFile++;
                if (preTender) {
                    preTender.tender.NextSiblingID = curTender.ID;
                }
                // 项目清单模板(后台清单模板中提取含有固定类别的数据)
                const billsTemplate = _.cloneDeep(templateMapping[curTender.property.templateLibID]);
                // 重设模板树结构数据
                BILLS_UTIL.resetTreeData(billsTemplate, uuid.v1);
                console.log(billsTemplate);
                const tenderData = transformTender(curTender, IDPlaceholder, billsTemplate);
                tenderData.tender.property.rootProjectID = transformedData.ID;
                engData.tenders.push(tenderData);
            }
        }
        console.log(`transformedData`);
        console.log(transformedData);
        return transformedData;
    }
    // 获取承包人材料类型
    function getAdjustType(tenderData) {
        if (tenderData.differentiaSummary.length) {
            return AdjustType.info;
        } else if (tenderData.exponentialSummary.length) {
            return AdjustType.coe;
        } else {
            return AdjustType.info;
        }
    }
    // 获取当前文件
    // 转换单位工程的数据(直接可插入数据库的数据)，返回{tender: {}, bills: [], ration: [], rationGLJ: [], projectGLJ: [], unitPrice: [], mixRatio: []}
    function transformTender(tenderData, IDPlaceholder, billsTemplate) {
        const detailData = transformDetail(tenderData, IDPlaceholder, billsTemplate);
        // 提取需要插入的单位工程数据
        const transformedTender = {
            ID: tenderData.ID,
            ParentID: tenderData.ParentID,
            NextSiblingID: tenderData.NextSiblingID,
            code: tenderData.code,
            name: tenderData.name,
            projType: tenderData.projType,
            property: tenderData.property,
            projectFeature: tenderData.projectFeature,
            engineering: tenderData.engineering,
            defaultRationLib: tenderData.defaultRationLib,
            rationLibIDs: tenderData.rationLibIDs,
            gljLibIDs: tenderData.gljLibIDs,
            shareInfo: []
        };
        transformedTender.property.compilationIllustration = tenderData.compilationIllustration;
        transformedTender.property.gljAdjustType = getAdjustType(tenderData);
        transformedTender.property.decimal = tenderPropertyDecimal;
        transformedTender.property.billsQuantityDecimalValue = tenderPropertyBillsQuantityDecimal;
        detailData.tender = transformedTender;
        return detailData;
    }
    // 过滤掉相关费用代号的数据(递归地)
    // 返回被过滤掉的数据
    function filterDataByFeeCodes(billsData, feeCodes) {
        const filterd = [];
        billsData.forEach(bills => {
            if (bills.items && bills.items.length) {
                const newItems = [];
                bills.items.forEach(subBills => {
                    if (!feeCodes.includes(subBills.feeCode)) {
                        newItems.push(subBills);
                    } else {
                        filterd.push(subBills);
                    }
                });
                bills.items = newItems;
                // bills.items = bills.items.filter(subBills => !feeCodes.includes(subBills.feeCode));
                const subFilterd = filterDataByFeeCodes(bills.items, feeCodes);
                filterd.push(...subFilterd);
            }
        });
        return filterd;
    }
    // 合并清单，将提取出来的清单相关数据，合并进项目清单(模板)
    function mergeBills(tenderData, billsTemplate) {
        const mergedBills = [...billsTemplate];
        const roots = billsTemplate.filter(bills => bills.ParentID === -1);
        let lastRoot = roots.find(root => root.NextSiblingID === -1);
        mergeWorkSummary(tenderData.workSummary);
        mergeFBFX(tenderData.fbfx);
        mergeCSXM(tenderData.csxm);
        mergeOther(tenderData.other);
        mergeTax(tenderData.tax);
        // 需要清空各种原因导入的父项的工程量
        /* const parentMap = {};
        mergedBills.forEach(bills => {
            if (bills.ParentID !== -1) {
                parentMap[bills.ParentID] = 1;
            }
        })
        mergedBills.forEach(bills => {
            if (parentMap[bills.ID] && commonUtil.isDef(bills.quantity)) {
                delete bills.quantity;
            }
        }) */
        return mergedBills;

        // 合并清单数据
        function mergeitems(fixedBills, items) {
            const daryWorkMap = {
                '人工': fixedFlag.LABOUR,
                '材料': fixedFlag.MATERIAL,
                '机械': fixedFlag.MACHINE,
                '施工机械': fixedFlag.MACHINE,
                '施工机具': fixedFlag.MACHINE
            };
            const fixedBillsFlag = getFlag(fixedBills);
            let preBills = billsTemplate.find(bills => bills.ParentID === fixedBills.ID && bills.NextSiblingID === -1);
            items.forEach(bills => {
                // 特殊处理绿色施工,否则可能会重复导入(易达绿色施工安全防护措施费的code为AQWMSGF,feeCode为空)
                if (bills.name === '绿色施工安全防护措施费' && bills.code === 'AQWMSGF') {
                    bills.feeCode = 'AQWMSGF';
                }
                // 计日工需要特殊处理，兼容广联达的数据，广联达的计日工下的人工、材料、机械的费用字典不对，因此用名称匹配
                let matched;
                if (fixedBillsFlag === fixedFlag.DAYWORK) {
                    const dayWorkSubFlag = daryWorkMap[bills.name];
                    matched = dayWorkSubFlag
                        ? billsTemplate.find(templateBills => getFlag(templateBills) === dayWorkSubFlag)
                        : null;
                } else {
                    matched = billsTemplate.find(templateBills => FlagFeeCodeMap[getFlag(templateBills)] === bills.feeCode);
                }
                if (matched) {
                    assignAttr(matched, bills, ['items'], true);
                    if (commonUtil.isDef(bills.feeRate) && commonUtil.isDef(matched.feeRateID)) {
                        delete matched.feeRateID; // 不清除的话，赋值过去的feeRate会被后端覆盖
                    }
                    if (bills.items && bills.items.length) {
                        mergeitems(matched, bills.items);
                        //mergedBills.push(...mergeDataRecur(fixedBills, [bills]))    
                    }
                    preBills = matched;
                } else {
                    const nextID = preBills && preBills.NextSiblingID || -1;
                    mergedBills.push(...mergeDataRecur(fixedBills, [bills]));
                    bills.NextSiblingID = nextID;
                    if (preBills) {
                        preBills.NextSiblingID = bills.ID;
                    }
                    preBills = bills;
                }
            });
        }
        // 简单地合并: 设置某清单的金额,数据递归插入到某清单的子项
        function simpleMerge(flag, src) {
            const fixedBills = billsTemplate.find(bills => getFlag(bills) === flag);
            if (fixedBills) {
                assignAttr(fixedBills, src, ['fees', 'feeCode']);
                mergedBills.push(...mergeDataRecur(fixedBills, src.items));
            }
        }
        // 合并大项费用
        function mergeWorkSummary(workSummary) {
            // 首层数据与清单模板根据费用字典进行匹配，匹配到的则赋值一些属性，匹配不到则将其及其所有子数据插入到模板最末大项费用
            workSummary.forEach(summaryBills => {
                const matched = roots.find(root => FlagFeeCodeMap[getFlag(root)] === summaryBills.feeCode);
                if (matched) {
                    if (!summaryBills.calcBase) {
                        summaryBills.calcBase = matched.calcBase;
                        matched.ignoreValidator = true; // 用了模板的基数，基数无视基数验证
                    }
                    assignAttr(matched, summaryBills, ['items'], true);
                } else {
                    mergedBills.push(...mergeDataRecur(null, [summaryBills]));
                    if (lastRoot) {
                        lastRoot.NextSiblingID = summaryBills.ID;
                        lastRoot = summaryBills;
                    }
                }
            });
        }
        // 合并分部分项
        function mergeFBFX(fbfxSrc) {
            simpleMerge(fixedFlag.SUB_ENGINERRING, fbfxSrc);
        }
        // 合并措施项目
        function mergeCSXM(csxmSrc) {
            const fixedBills = billsTemplate.find(bills => getFlag(bills) === fixedFlag.MEASURE);
            fixedBills.fees = mergeFees(fixedBills.fees, csxmSrc.fees);
            mergeitems(fixedBills, csxmSrc.items);
        }
        // 合并其他项目
        function mergeOther(otherSrc) {
            const fixedBills = billsTemplate.find(bills => getFlag(bills) === fixedFlag.OTHER);
            fixedBills.fees = mergeFees(fixedBills.fees, otherSrc.fees);
            // 合并其他项目费

            // SundryCosts其他项目费会包含暂列金额等后续会再次出现的数据，因此合并SundryCosts的数据时，过滤掉一部分数据
            const feeCodeFilter = [
                FlagFeeCodeMap[fixedFlag.PROVISIONAL], // 暂列金额
                FlagFeeCodeMap[fixedFlag.MATERIAL_PROVISIONAL], // 材料暂估
                FlagFeeCodeMap[fixedFlag.ENGINEERING_ESITIMATE], // 专业工程暂估
                FlagFeeCodeMap[fixedFlag.DAYWORK], // 计日工
                FlagFeeCodeMap[fixedFlag.TURN_KEY_CONTRACT], // 总承包服务费
                FlagFeeCodeMap[fixedFlag.CLAIM], // 索赔
                FlagFeeCodeMap[fixedFlag.VISA], // 现场签证
            ];
            const filterdBills = filterDataByFeeCodes([otherSrc.sundryCosts], feeCodeFilter);
            /**
             * 有些源xml文件中，被过滤掉SundryCosts中的子项，相对于Sundry的子项会多一些属性
             * 比如：Sundry->SunryCosts->SundryCostsGroup->SundryCostsItem中的材料暂估价，含有QtyFormula属性，但是Sundry->ProvisionalMaterialEquipment中不含有QtyFormula
             * 因此，即使后续Sundry的子项会赋值给模板，也要将被过滤掉的子项的属性先赋给模板
             */
            filterdBills.forEach(filterdItem => {
                const matched = billsTemplate.find(templateBills => FlagFeeCodeMap[getFlag(templateBills)] === filterdItem.feeCode);
                if (matched) {
                    assignAttr(matched, filterdItem, ['items'], true); // 不拷贝item属性
                    matched.ignoreA
                }
            });

            const sundryCostsBills = otherSrc.sundryCosts.items;
            mergeitems(fixedBills, sundryCostsBills);
            // 合并暂列金额
            simpleMerge(fixedFlag.PROVISIONAL, otherSrc.provisional);
            // 合并材料暂估，由于材料暂估的子元素是暂估人材机，因此对清单来说，只需处理材料暂估根元素的属性
            const zgcFixedBills = billsTemplate.find(bills => getFlag(bills) === fixedFlag.MATERIAL_PROVISIONAL);
            assignAttr(zgcFixedBills, otherSrc.ProvisionalMaterialEquipment);
            // 合并专业工程暂估价
            simpleMerge(fixedFlag.ENGINEERING_ESITIMATE, otherSrc.specialty);
            // 合并计日工
            const dayWorkFixedBills = billsTemplate.find(bills => getFlag(bills) === fixedFlag.DAYWORK);
            assignAttr(dayWorkFixedBills, otherSrc.dayWork, ['fees', 'feeCode']);
            mergeitems(dayWorkFixedBills, otherSrc.dayWork.items);
            // 合并总承包服务费
            simpleMerge(fixedFlag.TURN_KEY_CONTRACT, otherSrc.mainContractor);
            // 合并索赔
            simpleMerge(fixedFlag.CLAIM, otherSrc.claim);
            // 合并现场签证
            simpleMerge(fixedFlag.VISA, otherSrc.visa);
        }
        // 合并税金
        function mergeTax(taxSrc) {
            const taxFixedBills = billsTemplate.find(bills => getFlag(bills) === fixedFlag.TAX);
            if (!taxSrc.calcBase) {
                taxSrc.calcBase = taxFixedBills.calcBase;
                taxFixedBills.ignoreValidator = true; // 用了模板的基数，基数无视基数验证
            } else {
                taxFixedBills.ignoreValidator = false;
            }
            assignAttr(taxFixedBills, taxSrc);
        }
    }
    // 给固定清单设置上费用字典，因为在合并清单的时候，清单模板中可能有一些数据应该有规定的费用字典，但是没有被设置上费用字典。
    function setupFeeCode(billsData) {
        billsData.forEach(bills => {
            if (bills.feeCode) {
                return;
            }
            bills.feeCode = FlagFeeCodeMap[getFlag(bills)] || '';
        })
    }
    // 检查清单是否引用了自身，比如广联达导出文件中 材料保管费基数为CLBGF
    function isCalcBaseCycle(bills) {
        const matched = bills.calcBase.match(new RegExp(`\\b${bills.feeCode}\\b`)); // \b: 匹配前一个字符和后一个字符不全为\w的位置
        return !!(matched && matched[0]);
    }
    // 转换计算基数
    // 1.有子项数据，则清空基数
    // 2.引用的基数造成自身循环，比如分部分项部分引用了QDF
    // 3.费用代号匹配清单基数的代号
    // 4.费用代号匹配不到清单基数的代号，则匹配此费用代号的清单，作为行引用（即ID引用）
    // 5.对应字典里找不到则设置成金额
    function transformCalcBase(billsData) {
        // 费用代号 - 计算基数映射
        const feeCodeFormulaMap = {}
        Object
            .entries(FormulaFeeCodeMap)
            .forEach(([formula, feeCode]) => {
                feeCodeFormulaMap[feeCode] = formula;
            });
        // 费用代号 - ID映射
        const feeCodeIDMap = {};
        billsData.forEach(data => {
            if (data.feeCode) {
                feeCodeIDMap[data.feeCode] = data.ID;
            }
        });
        for (const bills of billsData) {
            if (!bills.calcBase || bills.ignoreValidator) {
                continue;
            }
            const sub = billsData.find(data => data.ParentID === bills.ID);
            // 有子项数据，则清空基数，费率
            if (sub) {
                bills.calcBase = '';
                bills.feeRate = '';
                continue;
            }
            if (typeof bills.calcBase !== 'string') {
                bills.calcBase = String(bills.calcBase);
            }
            // 引用的基数造成自循环，清空基数
            const isCycle = isCalcBaseCycle(bills);
            if (isCycle) {
                bills.calcBase = '';
                continue;
            }
            // 提取基数中的费用代号
            bills.calcBase = bills.calcBase.replace(/\s/g, '');
            const feeCodes = bills.calcBase.split(/[\+\-\*\/]/g);
            // 提取操作符
            const oprs = bills.calcBase.match(/[\+\-\*\/]/g);
            // 转换后的基数
            const newBase = [];
            let illegal = false;    //不合法
            for (const feeCode of feeCodes) {
                const formula = feeCodeFormulaMap[feeCode];
                const refID = feeCodeIDMap[feeCode];
                if (formula) { // 匹配到公式
                    newBase.push(formula);
                } else if (refID) { // 匹配到行引用
                    newBase.push(`@${refID}`) // 转换成ID引用
                } else { // 无法识别
                    illegal = true;
                    break;
                }
            }
            if (illegal) {
                const fee = getFee(bills.fees, ['common', 'totalFee']);
                const feeRate = bills.feeRate && parseFloat(bills.feeRate) !== 0 ? parseFloat(bills.feeRate) : 0;
                if (fee && parseFloat(fee) !== 0 && feeRate) {
                    bills.calcBase = scMathUtil.roundForObj(parseFloat(fee) * 100 / feeRate, Decimal.FEE);
                } else {
                    bills.calcBase = fee !== '0' ? fee : '';
                }
            } else {
                let newCalcBase = '';
                for (let i = 0; i < newBase.length; i++) {
                    newCalcBase += newBase[i];
                    if (oprs && oprs[i]) {
                        newCalcBase += oprs[i];
                    }
                }
                bills.calcBase = newCalcBase;
            }
        }
    }
    // 转换清单
    function transformBills(tenderData, billsTemplate) {
        const rst = {
            bills: [],
            billsQuantityDetails: []
        };
        const billsData = mergeBills(tenderData, billsTemplate);
        setupFeeCode(billsData);
        transformCalcBase(billsData);
        // 处理综合单价
        // 没有子清单，且没有综合单价，且有综合合价，则综合单价 = 综合合价 / 工程量
        billsData.forEach(bills => {
            const hasChild = billsData.some(data => data.ParentID === bills.ID);
            let unitFee = getFee(bills.fees, ['common', 'unitFee']);
            const totalFee = getFee(bills.fees, ['common', 'totalFee']);
            // 如果费率为0、同时合价不为0，则需要清空计算基数和费率（当作数量单价的形式。易达某文件出现了费率为0、有金额且有基数的数据）
            // 不这么处理的话，导入后造价计算，此清单金额算出来会是0
            if (+bills.feeRate === 0 && +totalFee > 0) {
                bills.calcBase = '';
                bills.feeRate = null;
            }
            if (!hasChild && !parseFloat(unitFee) && totalFee && parseFloat(totalFee)) {
                // 不存工程量
                if (!bills.quantity || !parseFloat(bills.quantity)) {
                    unitFee = totalFee;
                    // 源不存在工程量，没有计算基数、但是却有综合合价，工程量要设置为1
                    if (!bills.calcBase) {
                        bills.quantity = '1';
                    }
                } else {
                    // 综合合价的小数位数
                    //const totalFeeDecimal = totalFee.match(/\.\d+/);
                    //unitFee = scMathUtil.roundForObj(totalFee / bills.quantity, totalFeeDecimal ? totalFeeDecimal[0] : 0);
                    unitFee = scMathUtil.roundForObj(totalFee / bills.quantity, Decimal.FEE);
                }
                const commonFee = bills.fees.find(fee => fee.fieldName === 'common');
                if (commonFee) {
                    commonFee.unitFee = unitFee;
                }
            }
            bills.projectID = tenderData.ID;
            // 工程量明细
            transformQuantityDetails(tenderData.ID, bills.quantityDetails, bills.ID, 'billID');
            bills.quantityEXP = bills.quantityDetails && bills.quantityDetails.length ? GCLMXHj : bills.quantity;
            if (bills.quantityDetails) {
                rst.billsQuantityDetails.push(...bills.quantityDetails);
            }
            rst.bills.push(bills);
        });

        return rst;
    }
    // 转换工程量明细数据
    function transformQuantityDetails(tenderID, quantityDetails, refID, typeKey) {
        if (quantityDetails) {
            quantityDetails.forEach(item => {
                item.ID = uuid.v1();
                item[typeKey] = refID; // billID、rationID
                item.projectID = tenderID;
            });
        }
    }
    // 转换人材机汇总相关数据（人材机汇总、承包人材料、评标材料、暂估价材料、组成物、单价文件）
    function transformGLJList(tenderData, IDPlaceholder, projectGLJMap) {
        const rst = {
            projectGLJ: [], // 项目人材机
            contractorList: [], // 承包人材料
            bidEvaluationList: [], // 评标材料
            evaluationList: [], // 暂估材料
            unitPrice: [], // 单价文件
            mixRatio: [] // 组成物
        };
        transformGLJSummary();
        transformContractorSummary();
        transformRelatedGLJList(tenderData.bidEvaluationSummary, rst.bidEvaluationList, 'is_eval_material');
        transformEvalSummary();
        return rst;

        // 转换项目人材机、组成物
        function transformGLJSummary() {
            // 项目人材机
            tenderData.gljSummary.forEach(projectGLJ => {
                projectGLJ.project_id = tenderData.ID;
                projectGLJ.id = IDPlaceholder.projectGLJ++;
                projectGLJMap[projectGLJ.code] = projectGLJ;
            });
            tenderData.gljSummary.forEach(projectGLJ => {
                //组成物数据
                projectGLJ.ratios.forEach(ratio => {
                    const matched = projectGLJMap[ratio.code];
                    ratio.code = matched.code;
                    // 为了后端匹配标准数据，如易达有组成物"99450680-0001"
                    // 项目人材机、单价文件根据original_code匹配上了标准人材机，修改了gljType等
                    // mixRatio根据code匹配标准数据匹配不上标准数据，因此type与项目人材机的type不同，导致组成物丢失
                    ratio.original_code = getOriginalCode(ratio.code);
                    ratio.projectGLJID = projectGLJ.id;
                    ratio.id = IDPlaceholder.ratio++;
                    ratio.unit_price_file_id = tenderData.property.unitPriceFile.id;
                    ratio.unit = matched && matched.unit || '';
                    ratio.name = matched && matched.name || '';
                    ratio.specs = matched && matched.specs || '';
                    ratio.type = matched && matched.type || 1;
                    ratio.connect_key = [projectGLJ.code || 'null', projectGLJ.name || 'null', projectGLJ.specs || 'null', projectGLJ.unit || 'null', projectGLJ.type].join('|-|');
                    rst.mixRatio.push(ratio);
                });
                rst.projectGLJ.push(projectGLJ);
                // 单价文件数据：
                rst.unitPrice.push(generateUnitPrice(projectGLJ));
            });
        }
        // 转换与项目人材机关联的材料
        function transformRelatedGLJList(list, container, relatedType) {
            list.forEach(glj => {
                glj.ID = uuid.v1();
                glj.projectID = tenderData.ID;
                glj.projectGLJID = -1;
                if (typeof glj.seq === 'undefined') {
                    glj.seq = glj.code;
                }
                const projectGLJ = projectGLJMap[glj.code];
                if (projectGLJ) {
                    projectGLJ[relatedType] = 1;
                    glj.is_related = 1;
                    glj.projectGLJID = projectGLJ.id;
                }
                container.push(glj);
            });
        }
        // 转换承包人材料
        function transformContractorSummary() {
            // 由于承包人差额数据和指数数据共用承包人材料表，因此需要合并差额数据和指数数据
            const mergedList = [];
            tenderData.exponentialSummary.forEach(eGLJ => {
                const matched = tenderData.differentiaSummary.find(dGLJ => dGLJ.code === eGLJ.code);
                if (matched) {
                    assignAttr(matched, eGLJ, ['varWeight', 'FO', 'FI']);
                } else {
                    mergedList.push(eGLJ);
                }
            });
            mergedList.push(...tenderData.differentiaSummary);
            transformRelatedGLJList(mergedList, rst.contractorList, 'is_contractor_material');
        }
        // 转换暂估价材料
        function transformEvalSummary() {
            //evalGLJFromOther
            const existMap = {};
            const evalItems = [];
            tenderData.gljSummary.forEach(glj => {
                if (glj.is_evaluate) {
                    existMap[glj.code] = true;
                    evalItems.push(glj);
                }
            });
            tenderData.evalGLJFromOther.forEach(glj => {
                if (!existMap[glj.code]) {
                    evalItems.push(glj);
                }
            });
            const evalGLJList = evalItems.map(glj => {
                const evalGLJ = {};
                assignAttr(evalGLJ, glj, [
                    'code', 'name', 'specs', 'unit', 'quantity', 'market_price',
                    'originPlace', 'vender', 'remark'
                ]);
                evalGLJ.seq = evalGLJ.code;
                return evalGLJ;
            });
            /* const evalGLJList = tenderData.gljSummary
                .filter(glj => glj.is_evaluate)
                .map(glj => {
                    const evalGLJ = {};
                    assignAttr(evalGLJ, glj, [
                        'code', 'name', 'specs', 'unit', 'quantity', 'market_price',
                        'originPlace', 'vender', 'remark'
                    ]);
                    evalGLJ.seq = evalGLJ.code;
                    return evalGLJ;
                }); */
            transformRelatedGLJList(evalGLJList, rst.evaluationList, 'is_evaluate');
        }
        // 生成单价文件
        function generateUnitPrice(projectGLJ) {
            return {
                projectGLJID: projectGLJ.id,    //做个标记，后端查找标准数据后，方便更新组成物数据
                unit_price_file_id: tenderData.property.unitPriceFile.id,
                id: IDPlaceholder.unitPrice++,
                code: projectGLJ.code,
                original_code: projectGLJ.original_code,
                name: projectGLJ.name,
                unit: projectGLJ.unit,
                specs: projectGLJ.specs,
                type: projectGLJ.type,
                short_name: projectGLJ.shortName,
                base_price: projectGLJ.base_price,
                market_price: projectGLJ.market_price
            };
        }
    }
    // 转换定额、定额人材机、定额系数
    // @param {Object} tenderData - 提取的单位工程数据
    // @param {Array} billsData - 转换后的清单数据
    // @param {Object} projectGLJMap - 转换项目人材机时生成的项目人材机编码-数据的映射
    function transformRations(tenderData, billsData, projectGLJMap) {
        const rst = {
            ration: [],
            rationGLJ: [],
            rationCoe: [],
            rationQuantityDetails: [],
        };
        billsData
            .forEach(bills => {
                if (bills.rations && bills.rations.length) {
                    bills.rations.forEach(ration => {
                        // TODO 取费专业不知如何取，暂时取单位工程取费专业（导出的文件没有可用的取费专业字段，因此需要后端匹配标准定额）
                        // 如果标准定额有取费专业，则会替换掉这里设置的取费专业
                        ration.programID = tenderData.property.projectEngineering;
                        ration.ID = uuid.v1();
                        ration.projectID = tenderData.ID;
                        ration.billsItemID = bills.ID;
                        ration.contain = getContain(bills, ration);
                        // 工程量明细
                        transformQuantityDetails(tenderData.ID, ration.quantityDetails, ration.ID, 'rationID');
                        ration.quantityEXP = getQuantityEXP(bills, ration);
                        if (ration.quantityDetails) {
                            rst.rationQuantityDetails.push(...ration.quantityDetails);
                        }
                        rst.ration.push(ration);
                        // 定额人材机
                        rst.rationGLJ.push(...transformRationGLJs(bills, ration));
                        // 定额系数
                        rst.rationCoe.push(generateRationCoe(ration));
                    });
                } else if (importFileKind !== FileKind.tender && bills.type === billType.FX) {
                    // 如果导入招标、控制价文件、每个分项底下自动生成一条空定额
                    const emptyRation = {
                        projectID: tenderData.ID,
                        ID: uuid.v1(),
                        billsItemID: bills.ID,
                        serialNo: 1,
                        type: rationType.ration,
                        jobContentText: bills.jobContent && bills.jobContent[0] && bills.jobContent[0].content || ''
                    };
                    rst.ration.push(emptyRation);
                }
            })
        return rst;

        // 含量：定额工程量/清单工程量
        function getContain(bills, ration) {
            if (!bills.quantity || !ration.quantity) {
                return '0';
            } else {
                const tempQuantity = ration.quantity / bills.quantity;
                return isFinite(tempQuantity) ? scMathUtil.roundForObj(tempQuantity, Decimal.PROCESS) : '0';
            }
        }
        // 转换定额人材机
        function transformRationGLJs(bills, ration) {
            return ration.rationGLJs.map(rationGLJ => {
                const matched = projectGLJMap[rationGLJ.code];
                if (matched) {
                    rationGLJ.projectGLJID = matched.id;
                    rationGLJ.code = matched.code;
                    rationGLJ.type = matched.type;
                    rationGLJ.shortName = matched.shortName;
                    rationGLJ.name = matched.name;
                    rationGLJ.original_code = matched.original_code;
                    rationGLJ.unit = matched.unit;
                    rationGLJ.specs = matched.specs;
                }
                rationGLJ.ID = uuid.v1();
                rationGLJ.projectID = tenderData.ID;
                rationGLJ.billsItemID = bills.ID;
                rationGLJ.rationID = ration.ID;
                rationGLJ.rationCode = ration.code;  // 暂时跟定额编码关联，后端好匹配标准数据
                rationGLJ.rationItemQuantity = rationGLJ.quantity;  // 定额消耗，暂时取消耗量，需要后端匹配标准数据后更新
                return rationGLJ;
            });
        }
        // 生成定额系数（项目数据处理的方法很有可能无法共用，因此不把重庆的方法抽离了）
        function generateRationCoe(ration) {
            return {
                projectID: ration.projectID,
                rationID: ration.ID,
                coeID: -1,
                ID: uuid.v1(),
                seq: 1, // 以前排序有的字段，暂时没什么用，不过还是赋上一个值
                name: '自定义系数',
                content: '人工×1，材料×1，机械×1，主材×1，设备×1',
                coes: [
                    { amount: 1, operator: '*', gljCode: null, coeType: '定额' },
                    { amount: 1, operator: '*', gljCode: null, coeType: '人工' },
                    { amount: 1, operator: '*', gljCode: null, coeType: '材料' },
                    { amount: 1, operator: '*', gljCode: null, coeType: '机械' },
                    { amount: 1, operator: '*', gljCode: null, coeType: '主材' },
                    { amount: 1, operator: '*', gljCode: null, coeType: '设备' },
                ],
                option_list: []
            }
        }
        // 获取定额的工程量表达式
        function getQuantityEXP(bills, ration) {
            if (ration.quantityDetails && ration.quantityDetails.length) {
                return GCLMXHj;
            }
            // 定额根单位与清单根单位相同，则定额工程量表达式为QDL；否则定额工程量表达式为：定额工程量 * 定额单位量词
            // eg: 清单单位：m  定额单位：100m
            const reg = /\d+(\D+.*)/;
            const billsRootUnit = bills.unit && bills.unit.replace(reg, '$1') || '';
            const rationRootUnit = ration.unit && ration.unit.replace(reg, '$1') || '';
            if (billsRootUnit === rationRootUnit) {
                return QDL;
            }
            const numReg = /(\d+)\D+.*/;
            const num = ration.unit && ration.unit.replace(numReg, '$1') || '1';
            return ration.quantity * num;

        }
    }
    // 删除一些无用属性，减少通信数据量
    function clean(detailData) {
        const {
            bills,
            ration,
            projectGLJ
        } = detailData;
        bills.forEach(item => delete item.items && delete item.rations && delete item.feeCode && delete item.quantityDetails);
        ration.forEach(item => delete item.rationGLJs && delete item.quantityDetails);
        projectGLJ.forEach(item => delete item.ratios);
    }
    // 需要给清单、定额价格字段赋上调价数据，不然打开项目后会被重算: project_view.js -> loadProjectData方法内
    function setupTenderFees(detailData) {
        const { bills, ration } = detailData;
        [...bills, ...ration].forEach(item => {
            if (item.fees && item.fees.length) {
                item.fees.forEach(feeItem => {
                    if (commonUtil.isDef(feeItem.unitFee)) {
                        feeItem.tenderUnitFee = feeItem.unitFee;
                    }
                    if (commonUtil.isDef(feeItem.totalFee)) {
                        feeItem.tenderTotalFee = feeItem.totalFee;
                    }
                })
            }
        });
    }
    // 转换详细的项目数据，清单、定额、定额人材机、项目人材机、单价文件等
    function transformDetail(tenderData, IDPlaceholder, billsTemplate) {
        const { bills, billsQuantityDetails } = transformBills(tenderData, billsTemplate);
        // 转换定额的处理依赖转换后的项目人材机数据，因此需要先转换项目人材机相关的数据
        const projectGLJMap = {};
        const relatedljGLJData = transformGLJList(tenderData, IDPlaceholder, projectGLJMap);
        const {
            ration,
            rationGLJ,
            rationCoe,
            rationQuantityDetails
        } = transformRations(tenderData, bills, projectGLJMap);
        const detailData = {
            bills,
            ration,
            rationGLJ,
            rationCoe,
            quantityDetails: [...billsQuantityDetails, ...rationQuantityDetails],
            ...relatedljGLJData
        };
        clean(detailData);
        setupTenderFees(detailData);
        console.log(`detailData`);
        console.log(detailData);
        return detailData;
    }

    function getUnit8Numbers(unit8Array) {
        const numbers = [];
        for (const a of unit8Array) {
            numbers.push(a);
        }
        return numbers;
    }

    /**
     * 解压cos、zip文件
     * @param {File} file - 上传的文件 
     * @return {Object} 解压出来的xml文件名称与xml文件文本内容映射 
     */
    async function unzipFile(file) {
        const jsZip = new JSZip();
        // 广联达导出的cos文件，内部文件的文件名是gbk编码，如果不处理的话无法导入
        let zip;
        if (typeof TextDecoder === 'undefined') {
            const gbkTextDecoder = new TextDecoder('gbk');
            zip = await jsZip.loadAsync(file, {
                // zip文件中有一个标志，用于说明文件名和注释是否使用UTF-8编码。 
                // 如果未设置，则JSZip无法知道所使用的编码（它通常是操作系统的默认编码）
                // 因此若文件名编码非utf-8，则会调用decodeFileName。我们将其认为是gbk编码
                decodeFileName: function (bytes) {
                    if (bytes.length) {
                        return gbkTextDecoder.decode(bytes);
                    } else {
                        return '';
                    }
                }
            });
        } else {
            const fileNameByteList = [];
            let index = 0;
            zip = await jsZip.loadAsync(file, {
                decodeFileName: function (bytes) {
                    if (bytes.length) {
                        fileNameByteList.push(getUnit8Numbers(bytes));
                        return String(index++);
                    } else {
                        return '';
                    }
                }
            });
            const decodedNames = await util.getDecodedData(fileNameByteList, 'gbk');
            // 将zip里的文件名替换成gbk解码后的名称
            decodedNames.forEach((name, index) => {
                if (zip.files[index]) {
                    zip.files[index].name = name;
                    zip.files[name] = zip.files[index];
                    delete zip.files[index];
                }
            });
        }
        const map = {};
        for (const fileName in zip.files) {
            // 将二进制数据转换成字符串
            map[fileName] = await jsZip.file(fileName).async('string');
        }
        return map;
    }

    //从xml文件中提取数据
    async function extractData(file, escape = false) {
        const fileMap = await unzipFile(file);
        const projectXML = fileMap['Project.xml'];
        if (!projectXML) {
            throw '无有效数据';
        }
        const xmlObjMap = {};
        // 导入易达的文件，经过unzipFile的decodeFileName，xml文件头部变成了﻿<?xml version="1.0" encoding="utf-8"?>，需要将前面的乱码去除，否则DOMParser转换后是null
        const reg = /.*<\?xml version="1.0" encoding="utf-8"\?>/i;
        for (const fileName in fileMap) {
            fileMap[fileName] = fileMap[fileName].replace(reg, '<?xml version="1.0" encoding="utf-8"?>');
            // x2js转换xml使用了DomParser接口，会将一些字符实体进行转义。若不想被自动转义，则需要调用escapeXMLEntity
            const xmlStr = escape ? util.escapeXMLEntity(fileMap[fileName]) : fileMap[fileName];
            //将xml格式良好的字符串转换成对象
            const x2js = new X2JS();
            let xmlObj = x2js.xml_str2json(xmlStr);
            xmlObj = JSON.parse(util.restoreXMLEntity(JSON.stringify(xmlObj)));
            if (!xmlObj) {
                throw '无有效数据。';
            }
            xmlObjMap[fileName] = xmlObj;
        }
        //提取数据
        return extractProject(xmlObjMap);
    };

    // 接受上传的文件类型(不同的省份可以上传的文件不同)
    const accept = ['.zip', '.cos'];

    return {
        accept,
        extractData,
        transformData,
    }
})();