/**
 *
 *
 * @author Zhong
 * @date 2019/6/24
 * @version
 */


/*
* 广东建设工程政府投资项目造价数据标准3.0
* */
const XMLStandard = (function () {
    // 广东18的导出文件中，需要把建设项目和单位工程文件分开
    // 数据类别，辨别提取的数据，哪份是以建设项目为根元素，哪份以单位工程为根元素
    const DATA_TYPE = {
        PROJECT: 1,
        TENDER: 2,
    };
    // 精度
    const Decimal = {
        // 工料机消耗量、含量、用量类小数精度
        GLJ: 4,
        // 工程量、数量类小数精度
        QUANTITY: 3,
        // 金额、合价、费用类小数精度
        FEE: 2,
        // 费率、指数、比例（%）类小数精度
        RATE: 3,
    };
    // 建设性质
    const ConstructionType = {
        '新建': '1',
        '扩建': '2',
        '改建': '3',
        '修缮': '4',
        '修复': '5',
        '维护保养': '6',
        '其他': '9',
    };
    // 造价类型
    const CostType = {
        '投资估算': '1',
        '设计概算': '2',
        '施工图预算': '3',
        '招标工程量清单': '4',
        '招标控制价（最高投标限价）': '5',
        '投标报价': '6',
        '签约合同价': '7',
        '竣工结算价': '8',
    };
    // 软件中的文件类型与造假类型映射
    const FileKind = {
        '1': '6', // 投标
        '2': '4', // 招标
        '3': '5', // 控制价
    };
    // 计价模式
    const ValuationModel = {
        BILL: '1', // 清单计价
        RATION: '2', // 定额计价
    };
    // 地区类别
    const AreaKind = {
        '一类地区': '1',
        '二类地区': '2',
        '三类地区': '3',
        '四类地区': '4',
    };
    // 专业类别
    const Specialty = {
        '房屋建筑与装饰工程': '1',
        '仿古建筑': '2',
        '通用安装工程': '3',
        '市政工程': '4',
        '园林绿化': '5',
        '矿山': '6',
        '构筑物': '7',
        '城市轨道交通工程': '8',
        '爆破': '9',
        '装饰装修': '10',
        '市政交通设施': '11',
        '市政养护': '12',
        '修缮': '13',
        '环卫': '14',
        '古建筑': '15',
        '节能': '16',
        '抗震加固': '17',
        '绿色建筑': '18',
        '工业建筑': '19',
        '地下管廊': '20',
        '装配式建筑': '21',
        '营造林': '22',
        '修复': '23',
        '其他': '99',
    };
    // 费用字典占用列表，普通清单根据首字母获取费用字典时，与下列占用费用字典重复时，需要加上_序号后缀
    const feeCodeList = [
        'QDF', 'QRG', 'QCL', 'QJX', 'ZCSB', 'QZCF', 'QSBF', 'QGL', 'QLR', 'QZGJ', 'CSF', 'AQWMSGF', 'AXSJSCSXMF',
        'QTCSF', 'LSSGCSF', 'SGYCWRFZF', 'YGSMGLF', 'DXJXSBJCCJAC', 'GGCSF', 'CSRG', 'CSCL', 'CSJX', 'CSZCSB',
        'CSZCF', 'CSSBF', 'CSGL', 'CSLR', 'CSZGJ', 'QTF', 'ZGJ', 'ZGC', 'ZLF', 'ZGGC', 'ZCBFWF', 'LXF', 'CLBGF',
        'YSBGF', 'SPFY', 'XCQZFY', 'DLF', 'QTFY', 'YLJ', 'GF', 'SJ', 'ZZJ', 'ZJF', 'RGF', 'JXRG', 'CLF', 'JXF',
        'ZCF', 'SBF', 'ZZCSB', 'JC', 'RGJC', 'CLJC', 'JXJC', 'JGC', 'GLF', 'LR', 'DJ', 'HJ', 'YNZTYSYPFFY',
        'JZYWSHBXF', 'GCFY', 'JZAZGCF', 'SBJGQJGZF', 'GCJSQTFY', 'YBF', 'CLGZF', 'JSQDKLX', 'PDLDZJ'
    ];
    // 计算程序名称-计算基数映射
    const CalculationQtyMap = {
        '直接费': 'RGF+CLF+JXF+ZCF+SBF+GLF',
        '人工费': 'RGF',
        '材料费': 'CLF',
        '机具费': 'JXF',
        '主材费': 'ZCF',
        '设备费': 'SBF',
        '管理费': 'GLF',
        '利润': 'LR',
        '综合单价': 'RGF+CLF+JXF+ZCF+SBF+GLF+LR',
    };
    // 计算程序名称-费用代号映射
    const CalculationCodeMap = {
        '直接费': 'ZJF',
        '人工费': 'RGF',
        '材料费': 'CLF',
        '机具费': 'JXF',
        '主材费': 'ZCF',
        '设备费': 'SBF',
        '管理费': 'GLF',
        '利润': 'LR',
        '综合单价': 'DJ',
    }
    // 章节类型
    const ChapterKind = {
        '章': '1',
        '节': '2',
        '细目': '3',
        '其他': '4',
    };
    // 汇总类型
    const SummrayKind = {
        YES: '1', // 汇总
        NO: '2', // 不汇总
    };
    // 工程量计算累加与汇总类型映射
    const SummationKindMap = {
        '0': SummrayKind.NO,
        '1': SummrayKind.YES,
    }
    // 清单子目类型
    const BillsKind = {
        FBFX: '1', // 分部分项子目
        '模板及支架': '2',
        '脚手架': '3',
        '现场围挡': '4',
        '防尘降噪绿色施工防护': '5',
        MEASURE: '6', // 其他措施子目
    };
    // 取费类型
    const CalcKind = {
        '地上工程': '1',
        '地下明(盖)挖工程': '2',
        '地下暗挖工程': '3',
        '盾构工程': '4',
        '轨道工程': '5',
        '通信工程': '6',
        '信号工程': '7',
        '供电工程': '8',
        '智能与控制系统工程': '9',
        '机电工程': '10',
        '其他': '15',
    };
    // 费用归属
    const CostKind = {
        '建筑工程费': '1',
        '安装工程费': '2',
        '其他': '3',
    };
    // 计算方式
    const CalcType = {
        'quantityMulTotalUnitPrice': '1', // 工程量×综合单价
        'formulaAddFeeRate': '2', // 计算基数×费率
        'quantityMulUnitPrice': '3', // 工程量×单价
    };
    // 降效类型 建筑装饰工程定额超高降效费用计算标记
    const EfficiencyKind = {
        BASE_REDUCTION: '1', // 降效基础定额
        NOT_BASE_REDUCTION: '2', // 非降效定额
        MARKET_PRICE_REDUCTION: '3', // 按市场价计算降效费用定额
        RATION_PRICE_REDUCTION: '4', // 按定额价计算降效费用定额
    };
    // 子目增加费类型 安装工程定额子目增加费的费用计算标记
    const IncFeeKind = {
        SUB_BASE: '1', // 子目增加费基础定额
        NOT_SUB_BASE: '2', // 非子目增加费定额
        SUB_CALC: '3', // 子目增加费用计算定额
    };
    // 工料机类型
    const GLJKind = {
    };
    // 供料方式
    const Provider = {
        [commonConstants.supplyType.ZXCG]: '1',
        [commonConstants.supplyType.BFJG]: '2',
        [commonConstants.supplyType.WQJG]: '3',
    }

    // 通用设置和工具
    const _base = XML_EXPORT_BASE;
    const _config = _base.CONFIG;
    const _type = _config.TYPE;
    const _util = _base.UTIL;
    const _cache = _base.CACHE;

    // 项目汇总字段
    const summaryObj = {
        [fixedFlag.SUB_ENGINERRING]: {
            items: [
                { name: 'subEngineering', feeName: 'common' },
                { name: 'subEngineeringLabour', feeName: 'labour' },
                { name: 'subEngineeringMaterial', feeName: 'material' },
                { name: 'subEngineeringEquipment', feeName: 'equipment' },
                { name: 'subEngineeringMainMaterial', feeName: 'mainMaterial' },
                { name: 'subEngineeringMachine', feeName: 'machine' },
                { name: 'subEngineeringManage', feeName: 'manage' },
                { name: 'subEngineeringProfit', feeName: 'profit' }
            ]
        },
        [fixedFlag.MEASURE]: {
            items: [
                { name: 'measure', feeName: 'common' },
                { name: 'measureLabour', feeName: 'labour' },
                { name: 'measureMaterial', feeName: 'material' },
                { name: 'measureEquipment', feeName: 'equipment' },
                { name: 'measureMainMaterial', feeName: 'mainMaterial' },
                { name: 'measureMachine', feeName: 'machine' },
                { name: 'measureManage', feeName: 'manage' },
                { name: 'measureProfit', feeName: 'profit' }
            ]
        },
        [fixedFlag.SAFETY_CONSTRUCTION]: {
            items: [{ name: 'greenMeasureFee', feeName: 'common' }]
        },
        [fixedFlag.OTHER_MEASURE_FEE]: {
            items: [{ name: 'otherPreliminaries', feeName: 'common' }]
        },
        [fixedFlag.OTHER]: {
            items: [{ name: 'other', feeName: 'common' }]
        },
        [fixedFlag.PROVISIONAL]: {
            items: [{ name: 'provisional', feeName: 'common' }]
        },
        [fixedFlag.MATERIAL_PROVISIONAL]: {
            items: [{ name: 'materialProvisional', feeName: 'common' }]
        },
        [fixedFlag.ENGINEERING_ESITIMATE]: {
            items: [{ name: 'engineeringEstimate', feeName: 'common' }]
        },
        [fixedFlag.DAYWORK]: {
            items: [{ name: 'daywork', feeName: 'common' }]
        },
        [fixedFlag.TURN_KEY_CONTRACT]: {
            items: [{ name: 'turnKeyContract', feeName: 'common' }]
        },
        [fixedFlag.CLAIM]: {
            items: [{ name: 'claim', feeName: 'common' }]
        },
        [fixedFlag.VISA]: {
            items: [{ name: 'visa', feeName: 'common' }]
        },
        [fixedFlag.TAX]: {
            items: [{ name: 'tax', feeName: 'common' }]
        },
        [fixedFlag.ENGINEERINGCOST]: {
            items: [{ name: 'engineeringCost', feeName: 'common' }]
        }
    };

    // 获取工程类型：枚举单位工程的工程专业+费用标准，用“；”分隔
    function getProjectType(projectData, enumerable) {
        // 工程类型 工程专业+费用标准
        if (!enumerable) {
            const engineeringName = projectData.property.engineeringName.replace('工程', '');
            const feeStandardName = projectData.property.feeStandardName;
            return `${engineeringName}${feeStandardName}`;
        }
        return projectData.children.reduce((acc, eng) => {
            const tenderProjectTypeArr = eng.children.reduce((acc, tender) => {
                const engineeringName = tender.property.engineeringName.replace('工程', '');
                const feeStandardName = tender.property.feeStandardName;
                acc.push(`${engineeringName}${feeStandardName}`);
                return acc;
            }, []);
            acc.push(...tenderProjectTypeArr);
            return acc;
        }, []).join(';');
    }
    // 清单名称首字母包含的清单ID eg: 'AABC': ['ID1', 'ID2']
    const firstLetterMap = {};
    // 对已被占用的费用字典进行占位处理
    feeCodeList.forEach(feeCode => firstLetterMap[feeCode] = [null]);
    /**
     * 获取费用字典
     * @param {Object} bills - 清单数据，清单获取费用字典时用
     * @param {String} formula - 文本内容，非ID引用基数获取费用字典时用 eg: {分部分项工程费}
     * @return {String}
     */
    function getFeeCode(bills, formula) {
        let feeCode;
        // 清单获取费用字典，如果根据固定类别找不到对应的费用字典，才用名称首字母获取
        if (bills) {
            const flag = bills.flagsIndex && bills.flagsIndex.fixed ? bills.flagsIndex.fixed.flag : null;
            feeCode = FlagFeeCodeMap[flag];
            if (feeCode) {
                return feeCode;
            }
            const name = bills.name || '';
            // 新需求： 不处理重名问题
            feeCode = pinyinUtil.getFirstLetter(name);
            return feeCode;
            // 出现重名，应以“费用代号_顺序号”规则，顺序号为自然数，从 1 开始(整个建设项目)
            /* const tempFeeCode = pinyinUtil.getFirstLetter(name);
            const sameFeeCodes = firstLetterMap[tempFeeCode] ? firstLetterMap[tempFeeCode] : firstLetterMap[tempFeeCode] = [];
            feeCode = sameFeeCodes.includes(bills.ID)
                ? `${tempFeeCode}${sameFeeCodes.indexOf(bills.ID) === 0 ? '' : '_' + sameFeeCodes.indexOf(bills.ID)}`
                : sameFeeCodes.length
                    ? `${tempFeeCode}_${sameFeeCodes.length}`
                    : tempFeeCode;
            if (!sameFeeCodes.includes(bills.ID)) {
                sameFeeCodes.push(bills.ID);
            }
            return feeCode; */
        } else { // 非ID引用基数获取费用字典
            return FormulaFeeCodeMap[formula] || '';
        }
    }

    // 获取转换后的计算基数
    // 1.有子项，则取固定清单对应基数
    // 2.无子项，有基数，转换为费用字典
    function getQtyFormula(node) {
        let expr = node.data.calcBase || '';
        if (node.children.length) {
            return FlagFeeCodeMap[node.getFlag()] || '';
        }
        if (expr) {
            const normalBase = _util.getNormalBase(expr);
            const idBase = _util.getIDBase(expr);
            //普通基数转费用字典
            normalBase.forEach(base => {
                const feeCode = getFeeCode(null, base);
                if (/[\(\)]/.test(base)) {
                    // 将（）进行处理不然会被当成组
                    base = base.replace(/[\(\)]/g, function (matched) {
                        return matched === '(' ? '\\(' : '\\)'
                    });
                }
                expr = expr.replace(new RegExp(base, 'g'), feeCode);
            });
            //id引用转换为费用字典
            idBase.forEach(base => {
                const id = base.match(/[^@]+/)[0];
                const theNode = node.tree.getNodeByID(id);
                const feeCode = getFeeCode(theNode.data);
                expr = expr.replace(new RegExp(base, 'g'), feeCode);
            });
            return expr;
        }
    }

    // 获取计算计算说明
    function getCalBasis(node) {
        let expr = node.data.calcBase || '';
        expr = expr.replace(new RegExp('{', 'g'), '');
        expr = expr.replace(new RegExp('}', 'g'), '');
        return expr;
    }

    // 获取定额册 取定额编号开头字母后面、-前面的数字，没有则取0
    // 如: A2-1-1 则册为2
    function getVolume(code) {
        if (!code) {
            return '0';
        }
        const reg = /^[A-Za-z](\d+)(?=-)/;
        const rst = reg.exec(code);
        return rst ? rst[1] : '0';
    }

    // 获取定额章 取定额编号第一个-和第二个-之间的数字
    // 如: A1-2-3 则章为2
    function getChapter(code) {
        if (!code) {
            return '0';
        }
        const reg = /^[A-Za-z]\d+-(\d+)(?=-)/;
        const rst = reg.exec(code);
        return rst ? rst[1] : '0';
    }

    // 获取人材机类型
    function getGLJKind(type) {
        // 标准中的定义： 1=人工；2=材料；3=机械；4=主材；5=设备；6=配合比(砂浆、混凝土)；7=机械（具）台班；8=定额材料；15=其他。
        // 处理人材机类型：2=除了配合比、砂浆、混凝土的材料; 3=除了机械台班的机械
        type = +type;
        switch (type) {
            case 202:
            case 203:
            case 204:
                return '6'; // 配合比、砂浆、混凝土
            case 301:
                return '7'; // 机械台班
            case 6:
            case 7:
            case 8:
                return '15'; // 管理费、利润、一般风险费
        }
        const rootTypeReg = /\d/;
        const rootType = rootTypeReg.exec(type)[0];
        return ['1', '2', '3', '4', '5', '6'].includes(rootType) ? rootType : '15';
    }

    /*
     * 提取数据入口
     * @param  {String}userID 用户ID
     *         {Number}exportKind 导出类型（投标1、招标2、控制价3)
     *         {Object}projectData 项目数据（项目自身的数据、建设、单项、单位的数据(projects表的数据)，不包含详细的清单定额等数据）
     * @return {Array} [{data: Object, exportKind: Number, fileName: String}]
     * */
    async function entry(userID, exportKind, projectData) {
        // name为导出的xml节点属性名称 dName为可能需要显示给用户属性时使用的名称，比如自检提示的时候用，这里只有required为true时需要设置dNam

        // 错误列表
        const _failList = _cache.getItem('failList');

        // 建设项目
        function ConstructionProject(projectData, summaryInfo) {
            const basicInformation = projectData.property.basicInformation;
            const attrs = [
                // 项目编号
                {
                    name: 'Number', dName: '工程编号', required: true, minLen: 1,
                    value: _util.getValueByKey(basicInformation, 'projNum')
                },
                // 项目名称
                {
                    name: 'Name', dName: '项目名称', required: true,
                    value: projectData.name
                },
                // 工程类别
                {
                    name: 'ProjectCategory', dName: '工程类别', required: true,
                    value: _util.getValueByKey(basicInformation, 'projectCategory')
                },
                // 工程类型
                {
                    name: 'ProjectType', dName: '工程类型', required: true,
                    value: _util.getValueByKey(basicInformation, 'projectType')
                },
                // 建设性质
                {
                    name: 'ConstructionType', type: _type.INT, enumeration: Object.values(ConstructionType), enumerationHint: Object.keys(ConstructionType),
                    value: ConstructionType[_util.getValueByKey(basicInformation, 'constructionType')]
                },
                // 文件类型
                {
                    name: 'FileKind', dName: '文件类型', type: _type.INT, required: true,
                    value: FileKind[exportKind]
                },
                // 计价模式
                {
                    name: 'ValuationModel', dName: '计价模式', type: _type.INT, required: true,
                    value: ValuationModel.BILL
                },
                // 计税模式
                {
                    name: 'TaxModel', dName: '计税模式', type: _type.INT, required: true,
                    value: projectData.property.taxType
                },
                // 地区类别
                {
                    name: 'AreaKind', dName: '地区类别', type: _type.INT, required: true, enumeration: Object.values(AreaKind), enumerationHint: Object.keys(AreaKind),
                    value: AreaKind[_util.getValueByKey(basicInformation, 'regionalCategories')]
                },
                // 工程地点
                {
                    name: 'ProjectSite',
                    value: _util.getValueByKey(basicInformation, 'projLocation')
                },
                // 建设单位
                {
                    name: 'BulidUnit', dName: '建设单位', required: true, minLen: 1,
                    value: _util.getValueByKey(basicInformation, 'constructingUnits')
                },
                // 建设单位法定代表人或其授权人
                {
                    name: 'BulidAuthorizer',
                    value: _util.getValueByKey(basicInformation, 'constructingUnitsPerson')
                },
                // 数据交换标准名称
                {
                    name: 'StandardName', required: true,
                    value: '建设工程政府投资项目造价数据标准'
                },
                // 数据交换标准编号
                {
                    name: 'StandardNumber', required: true,
                    value: 'DBJ/T 15-145-2018'
                },
                // 建设（编制）范围
                {
                    name: 'RangeOfCompilation',
                    value: _util.getValueByKey(basicInformation, 'rangeOfCompilation')
                },
                // 建设规模
                {
                    name: 'Scale', dName: '工程规模', type: _type.DECIMAL, required: true,
                    value: _util.getValueByKey(basicInformation, 'scale')
                },
                // 建设规模单位
                {
                    name: 'Unit', dName: '建设规模单位', required: true, minLen: 1,
                    value: _util.getValueByKey(basicInformation, 'unit')
                },
                // 技术经济指标（元） 取单方造价
                {
                    name: 'TechnicalAndEconomicIndex', type: _type.DECIMAL,
                    value: summaryInfo.perCost
                },
                // 总说明 编制说明
                {
                    name: 'Explains',
                    value: projectData.property.compilationIllustration
                }
            ];
            //唯一约束
            this.constraints = {
                billsCode: [],  //清单项目项目编码(xsd中没体现，通过跟pm沟通得知)
            };
            _base.Element.call(this, 'ConstructionProject', attrs, '建设项目');
        }
        // 系统信息
        function SystemInfo({ ID1, ID2, makeDate }) {
            const attrs = [
                // 编制软件信息
                { name: 'ID1', value: ID1 },
                // 编制机器硬件信息，不输出
                { name: 'ID2', value: ID2 },
                // 文件生成时间
                { name: 'MakeDate', dName: '文件生成时间', type: _type.DATE_TIME, required: true, value: makeDate }
            ];
            _base.Element.call(this, 'SystemInfo', attrs, '系统信息');
        }
        // 工程信息
        function ConstructionInfo() {
            _base.Element.call(this, 'ConstructionInfo', []);
        }
        // 费用精度
        function Option() {
            const attrs = [
                // 工料机消耗量、含量、用量类小数精度
                { name: 'ResPrecision', type: _type.INT, required: true, value: Decimal.GLJ },
                // 工程量、数量类小数精度
                { name: 'QuantityPrecision', type: _type.INT, required: true, value: Decimal.QUANTITY },
                // 金额、合价、费用类小数精度
                { name: 'CostPrecision', type: _type.INT, required: true, value: Decimal.FEE },
                // 费率、指数、比例（%）类小数精度
                { name: 'RatePrecision', type: _type.INT, required: true, value: Decimal.RATE }
            ];
            _base.Element.call(this, 'Option', attrs, '费用精度');
        }
        // 估（概、预、结）算信息
        // 有内容的属性才输出
        function ProjectInfo(basicInformation, summaryInfo) {
            const attrs = [
                // 设计单位
                { name: 'Designer', mustHasValue: true, value: _util.getValueByKey(basicInformation, 'designUnits') },
                // 承包单位
                { name: 'Contractor', mustHasValue: true, value: _util.getValueByKey(basicInformation, 'constructionUnits') },
                // 编制单位
                {
                    name: 'CompileCompany', dName: '编制单位', required: true,
                    value: _util.getValueByKey(basicInformation, 'establishUnit') || '无'
                },
                // 编制时间,投标文件取bidCompileDate。招标、控制价文件取tenderCompileDate
                {
                    name: 'CompileDate', dName: '编制时间', required: true,
                    value: exportKind === _config.EXPORT_KIND.Tender
                        ? _util.getValueByKey(basicInformation, 'bidCompileDate')
                        : _util.getValueByKey(basicInformation, 'tenderCompileDate')
                },
                // 编制单位法定代表人或其授权人
                { name: 'Authorizer', mustHasValue: true, value: _util.getValueByKey(basicInformation, 'constructingUnitsPerson') },
                // 工程总价（元）
                {
                    name: 'Total', dName: '工程总价', type: _type.DECIMAL, required: true,
                    value: summaryInfo.engineeringCost
                }
            ];
            _base.Element.call(this, 'ProjectInfo', attrs, '建设项目基本信息');
        }
        // 招标信息 工程量清单、招标控制价时输出
        // 有内容的属性才输出
        function TendereeInfo(basicInformation, summaryInfo) {
            const attrs = [
                // 招标人
                {
                    name: 'TendereeName', dName: '招标人', required: true,
                    value: _util.getValueByKey(basicInformation, 'tendereeName')
                },
                // 招标单位法定代表人或其授权人
                {
                    name: 'TenderAuthorizer', mustHasValue: true,
                    value: _util.getValueByKey(basicInformation, 'tenderAuthorizer')
                },
                // 招标单位编制人
                {
                    name: 'TenderCompiler', mustHasValue: true,
                    value: _util.getValueByKey(basicInformation, 'tenderCompiler')
                },
                // 招标单位编制人资格证书编号
                {
                    name: 'TenderCompilerCertNo', mustHasValue: true,
                    value: _util.getValueByKey(basicInformation, 'tenderCompilerCertNo')
                },
                // 招标单位编制时间
                {
                    name: 'TenderCompileDate', mustHasValue: true, required: true,
                    value: _util.getValueByKey(basicInformation, 'tenderCompileDate')
                },
                // 招标单位审核人
                {
                    name: 'TenderExaminer', mustHasValue: true,
                    value: _util.getValueByKey(basicInformation, 'tenderExaminer')
                },
                // 招标单位审核人资格证书编号
                {
                    name: 'TenderExaminerCertNo', mustHasValue: true,
                    value: _util.getValueByKey(basicInformation, 'tenderExaminerCertNo')
                },
                // 招标单位审核时间
                {
                    name: 'TenderExamineDate', mustHasValue: true,
                    value: _util.getValueByKey(basicInformation, 'tenderExamineDate')
                },
                // 招标单位审定人
                {
                    name: 'TenderApprover', mustHasValue: true,
                    value: _util.getValueByKey(basicInformation, 'tenderApprover')
                },
                // 招标单位审定人资格证书编号
                {
                    name: 'TenderApproverCertNo', mustHasValue: true,
                    value: _util.getValueByKey(basicInformation, 'tenderApproverCertNo')
                },
                // 招标单位审定时间
                {
                    name: 'TenderApproveDate', mustHasValue: true,
                    value: _util.getValueByKey(basicInformation, 'tenderApproveDate')
                },
                // 招标代理 不得为空，如无招标代理，则应填写“无”。
                {
                    name: 'Proxy', dName: '招标代理',
                    value: _util.getValueByKey(basicInformation, 'proxy') || '无', required: true
                },
                // 招标代理资质证书编号
                {
                    name: 'ProxyCertNo', mustHasValue: true,
                    value: _util.getValueByKey(basicInformation, 'proxyCertNo')
                },
                // 招标代理法定代表人或其授权人
                {
                    name: 'ProxyAuthorizer', mustHasValue: true,
                    value: _util.getValueByKey(basicInformation, 'proxyAuthorizer')
                },
                // 招标代理编制人员
                {
                    name: 'ProxyCompiler', mustHasValue: true,
                    value: _util.getValueByKey(basicInformation, 'proxyCompiler')
                },
                // 招标代理编制人员资格证书编号
                {
                    name: 'ProxyCompilerCertNo', mustHasValue: true,
                    value: _util.getValueByKey(basicInformation, 'proxyCompilerCertNo')
                },
                // 招标代理编制时间
                {
                    name: 'ProxyCompileDate', mustHasValue: true,
                    value: _util.getValueByKey(basicInformation, 'proxyCompileDate')
                },
                // 招标代理审核人
                {
                    name: 'ProxyExaminer', mustHasValue: true,
                    value: _util.getValueByKey(basicInformation, 'proxyExaminer')
                },
                // 招标代理审核人资格证书编号
                {
                    name: 'ProxyExaminerCertNo', mustHasValue: true,
                    value: _util.getValueByKey(basicInformation, 'proxyExaminerCertNo')
                },
                // 招标代理审核时间
                {
                    name: 'ProxyExamineDate', mustHasValue: true,
                    value: _util.getValueByKey(basicInformation, 'proxyExamineDate')
                },
                // 招标代理审定人
                {
                    name: 'ProxyApprover', mustHasValue: true,
                    value: _util.getValueByKey(basicInformation, 'proxyApprover')
                },
                // 招标代理审定人资格证书编号
                {
                    name: 'ProxyApproverCertNo', mustHasValue: true,
                    value: _util.getValueByKey(basicInformation, 'proxyApproverCertNo')
                },
                // 招标代理审定时间
                {
                    name: 'ProxyApproveDate', mustHasValue: true,
                    value: _util.getValueByKey(basicInformation, 'proxyApproveDate')
                },
                // 造价咨询 、Consultant（造价咨询）：不得为空，如无则应填写“无”。
                {
                    name: 'Consultant', dName: '造价咨询', required: true,
                    value: _util.getValueByKey(basicInformation, 'consultant') || '无'
                },
                // 造价咨询资质证书编号
                {
                    name: 'ConsultantCertNo', mustHasValue: true,
                    value: _util.getValueByKey(basicInformation, 'consultantCertNo')
                },
                // 造价咨询法定代表人或其授权人
                {
                    name: 'ConsultantAuthorizer', mustHasValue: true,
                    value: _util.getValueByKey(basicInformation, 'consultantAuthorizer')
                },
                // 造价咨询编制人
                {
                    name: 'ConsultantCompiler', mustHasValue: true,
                    value: _util.getValueByKey(basicInformation, 'consultantCompiler')
                },
                // 造价咨询编制人资格证书
                {
                    name: 'ConsultantCompilerCertNo', mustHasValue: true,
                    value: _util.getValueByKey(basicInformation, 'consultantCompilerCertNo')
                },
                // 造价咨询编制时间
                {
                    name: 'ConsultantCompileDate', mustHasValue: true,
                    value: _util.getValueByKey(basicInformation, 'consultantCompileDate')
                },
                // 造价咨询审核人
                {
                    name: 'ConsultantExaminer', mustHasValue: true,
                    value: _util.getValueByKey(basicInformation, 'consultantExaminer')
                },
                // 造价咨询审核人资格证书编号
                {
                    name: 'ConsultantExaminerCertNo', mustHasValue: true,
                    value: _util.getValueByKey(basicInformation, 'consultantExaminerCertNo')
                },
                // 造价咨询审核时间
                {
                    name: 'ConsultantExamineDate', mustHasValue: true,
                    value: _util.getValueByKey(basicInformation, 'consultantExamineDate')
                },
                // 造价咨询审定人
                {
                    name: 'ConsultantApprover', mustHasValue: true,
                    value: _util.getValueByKey(basicInformation, 'consultantApprover')
                },
                // 造价咨询审定人资格证书编号
                {
                    name: 'ConsultantApproverCertNo', mustHasValue: true,
                    value: _util.getValueByKey(basicInformation, 'consultantApproverCertNo')
                },
                // 造价咨询审定时间
                {
                    name: 'ConsultantApproveDate', mustHasValue: true,
                    value: _util.getValueByKey(basicInformation, 'consultantApproveDate')
                },
                // 招标控制价（元）
                {
                    name: 'TenderSumLimit', dName: '招标控制价', type: _type.DECIMAL, required: true,
                    value: summaryInfo.engineeringCost
                }
            ];
            _base.Element.call(this, 'TendereeInfo', attrs, '招标信息');
        }
        // 投标信息
        function BidderInfo(basicInformation, summaryInfo) {
            const attrs = [
                // 投标人
                {
                    name: 'BidName', dName: '投标人', required: true,
                    value: _util.getValueByKey(basicInformation, 'bidName') || '无'
                },
                // 投标单位法定代表人或其授权人
                {
                    name: 'BidAuthorizer', mustHasValue: true,
                    value: _util.getValueByKey(basicInformation, 'bidAuthorizer')
                },
                // 投标总价（元）
                {
                    name: 'BidTotal', dName: '投标总价', type: _type.DECIMAL, required: true,
                    value: summaryInfo.engineeringCost
                },
                // 投标单位编制人
                {
                    name: 'BidCompiler', mustHasValue: true,
                    value: _util.getValueByKey(basicInformation, 'bidCompiler')
                },
                // 投标单位编制人资格证书编号
                {
                    name: 'BidCompilerCertNo', mustHasValue: true,
                    value: _util.getValueByKey(basicInformation, 'bidCompilerCertNo')
                },
                // 投标单位编制时间
                {
                    name: 'BidCompileDate', mustHasValue: true, required: true,
                    value: _util.getValueByKey(basicInformation, 'bidCompileDate')
                },
                // 投标单位审核人
                {
                    name: 'BidExaminer', mustHasValue: true,
                    value: _util.getValueByKey(basicInformation, 'bidExaminer')
                },
                // 投标单位审核人资格证书编号
                {
                    name: 'BidExaminerCertNo', mustHasValue: true,
                    value: _util.getValueByKey(basicInformation, 'bidExaminerCertNo')
                },
                // 投标单位审核时间
                {
                    name: 'BidExamineDate', mustHasValue: true,
                    value: _util.getValueByKey(basicInformation, 'bidExamineDate')
                },
                // 投标单位审定人
                {
                    name: 'BidApprover', mustHasValue: true,
                    value: _util.getValueByKey(basicInformation, 'bidApprover')
                },
                // 投标单位审定人资格证书
                {
                    name: 'BidApproverCertNo', mustHasValue: true,
                    value: _util.getValueByKey(basicInformation, 'bidApproverCertNo')
                },
                // 投标单位审定时间
                {
                    name: 'BidApproveDate', mustHasValue: true,
                    value: _util.getValueByKey(basicInformation, 'bidApproveDate')
                }
            ];
            _base.Element.call(this, 'BidderInfo', attrs, '投标信息');
        }
        // 费用汇总
        function SummaryOfCost(summaryInfo) {
            // 人工费、材料费、机械费...: 取“分部分项工程”行+“措施项目”行对应的费用值
            const totalLabour = scMathUtil.roundForObj(+summaryInfo.subEngineeringLabour + (+summaryInfo.measureLabour), Decimal.FEE);
            const totalMaterial = scMathUtil.roundForObj(+summaryInfo.subEngineeringMaterial + (+summaryInfo.measureMaterial), Decimal.FEE);
            const totalMachine = scMathUtil.roundForObj(+summaryInfo.subEngineeringMachine + (+summaryInfo.measureMachine), Decimal.FEE);
            const totalEquipment = scMathUtil.roundForObj(+summaryInfo.subEngineeringEquipment + (+summaryInfo.measureEquipment), Decimal.FEE);
            const totalMainMaterial = scMathUtil.roundForObj(+summaryInfo.subEngineeringMainMaterial + (+summaryInfo.measureMainMaterial), Decimal.FEE);
            const totalMainMaterialEquipment = scMathUtil.roundForObj(totalMainMaterial + totalEquipment, Decimal.FEE);
            const totalManage = scMathUtil.roundForObj(+summaryInfo.subEngineeringManage + (+summaryInfo.measureManage), Decimal.FEE);
            const totalProfit = scMathUtil.roundForObj(+summaryInfo.subEngineeringProfit + (+summaryInfo.measureProfit), Decimal.FEE);
            const attrs = [
                // 工程造价（元）
                {
                    name: 'Total', type: _type.DECIMAL,
                    value: summaryInfo.engineeringCost
                },
                // 分部分项工程费
                {
                    name: 'DivisionalAndElementalWorks', type: _type.DECIMAL,
                    value: summaryInfo.subEngineering
                },
                // 措施项目费
                {
                    name: 'Preliminaries', type: _type.DECIMAL,
                    value: summaryInfo.measure
                },
                // 绿色施工安全防护措施费
                {
                    name: 'CostForHSE', type: _type.DECIMAL,
                    value: summaryInfo.greenMeasureFee
                },
                // 其他措施费
                {
                    name: 'OtherPreliminaries', type: _type.DECIMAL,
                    value: summaryInfo.otherPreliminaries
                },
                // 其他项目费
                {
                    name: 'SundryCosts', type: _type.DECIMAL,
                    value: summaryInfo.other
                },
                // 暂列金额
                {
                    name: 'ProvisionalSums', type: _type.DECIMAL,
                    value: summaryInfo.provisional
                },
                // 暂估价材料
                {
                    name: 'ProvisionalMaterial', type: _type.DECIMAL,
                    value: summaryInfo.materialProvisional
                },
                // 专业工程暂估价
                {
                    name: 'SpecialtyProvisionalPrice', type: _type.DECIMAL,
                    value: summaryInfo.engineeringEstimate
                },
                // 计日工费用
                {
                    name: 'DayWorkRate', type: _type.DECIMAL,
                    value: summaryInfo.daywork
                },
                // 总承包服务费
                {
                    name: 'MainContractorAttendance', type: _type.DECIMAL,
                    value: summaryInfo.turnKeyContract
                },
                // 索赔费用
                {
                    name: 'ClaimForLossAndExpenses', type: _type.DECIMAL,
                    value: summaryInfo.claim
                },
                // 现场签证费用
                {
                    name: 'SiteInstruction', type: _type.DECIMAL,
                    value: summaryInfo.visa
                },
                // 规费
                {
                    name: 'StatutoryFees', type: _type.DECIMAL,
                    value: '0'
                },
                // 税金
                {
                    name: 'Tax', type: _type.DECIMAL,
                    value: summaryInfo.tax
                },
                // 人工费
                {
                    name: 'Labor', type: _type.DECIMAL,
                    value: totalLabour
                },
                // 材料费
                {
                    name: 'Material', type: _type.DECIMAL,
                    value: totalMaterial
                },
                // 设备费
                {
                    name: 'Equipment', type: _type.DECIMAL,
                    value: totalEquipment
                },
                // 主材设备费
                {
                    name: 'MainMaterialEquipment', type: _type.DECIMAL,
                    value: totalMainMaterialEquipment
                },
                // 主材费
                {
                    name: 'MainMaterial', type: _type.DECIMAL,
                    value: totalMainMaterial
                },
                // 机械费
                {
                    name: 'Machine', type: _type.DECIMAL,
                    value: totalMachine
                },
                // 管理费
                {
                    name: 'Overhead', type: _type.DECIMAL,
                    value: totalManage
                },
                // 利润
                {
                    name: 'Profit', type: _type.DECIMAL,
                    value: totalProfit
                },
            ];
            _base.Element.call(this, 'SummaryOfCost', attrs, '费用汇总');
        }
        // 建筑安装工程费
        function ProjectInstallationWorkCost(projectData, summaryInfo) {
            const basicInformation = projectData.property.basicInformation;
            const attrs = [
                // 工程编号
                {
                    name: 'Number',
                    value: _util.getValueByKey(basicInformation, 'projNum')
                },
                // 工程名称
                {
                    name: 'Name', dName: '工程名称', required: true,
                    value: projectData.name
                },
                // 金额 (元)
                {
                    name: 'Total', type: _type.DECIMAL,
                    value: summaryInfo.engineeringCost
                },
                // 建设规模
                {
                    name: 'Scale', dName: '建设规模', type: _type.DECIMAL, required: true,
                    value: _util.getValueByKey(basicInformation, 'scale')
                },
                // 建设规模单位
                {
                    name: 'Unit', dName: '建设规模单位', required: true,
                    value: _util.getValueByKey(basicInformation, 'unit')
                },
                // 占总投资比例（%）
                {
                    name: 'Ratios', type: _type.DECIMAL,
                    value: '0.00'
                },
            ];
            _base.Element.call(this, 'ProjectInstallationWorkCost', attrs, '建筑安装工程费');
        }
        // 单项工程
        function SectionalWorks({ name, total, rate }) {
            const attrs = [
                // 工程编号
                {
                    name: 'Number',
                    value: ''
                },
                // 工程名称
                {
                    name: 'Name', dName: '工程名称', required: true,
                    value: name
                },
                // 金额
                {
                    name: 'Total', type: _type.DECIMAL,
                    value: total
                },
                // 建设规模 单项工程没有填写的地方，输出“0”
                {
                    name: 'Scale', dName: '建设规模', type: _type.DECIMAL, required: true,
                    value: '0'
                },
                // 建设规模单位 单项工程没有填写的地方，输出和"m2"
                {
                    name: 'Unit', dName: '建设规模单位', required: true,
                    value: 'm2'
                },
                // 占总投资比例（%）
                {
                    name: 'Ratios', type: _type.DECIMAL,
                    value: rate
                },
                // 费用代号
                {
                    name: 'Code', type: _type.DECIMAL,
                    value: ''
                },
            ];
            _base.Element.call(this, 'SectionalWorks', attrs, '单项工程');
        }
        // 单位工程
        function UnitWorks(projectData, tenderData, defaultRationLibName, summaryInfo) {
            const basicInformation = projectData.property.basicInformation;
            const projectFeature = tenderData.property.projectFeature;
            const attrs = [
                // 工程编号
                {
                    name: 'Number', dName: '工程编号', required: true,
                    value: ''
                },
                // 工程名称
                {
                    name: 'Name', dName: '工程名称', required: true,
                    value: tenderData.name
                },
                // 标段 取建设项目名称
                /* {
                    name: 'Segment',
                    value: projectData.name
                }, */
                // 工程类别
                {
                    name: 'ProjectCategory', dName: '工程类别', required: true,
                    value: _util.getValueByKey(basicInformation, 'projectCategory')
                },
                // 工程类型
                {
                    name: 'ProjectType', dName: '工程类型', required: true, minLen: 1,
                    value: _util.getValueByKey(projectFeature, 'projType')
                },
                // 计价模式
                {
                    name: 'ValuationModel', dName: '计价模式', type: _type.INT, required: true,
                    value: ValuationModel.BILL
                },
                // 计税模式
                {
                    name: 'TaxModel', dName: '计税模式', type: _type.INT, required: true,
                    value: tenderData.property.taxType
                },
                // 地区类别 取建设项目-基本信息-地区类别，基本信息那已经自检一次，这里不自检 //type: _type.INT, required: true, enumeration: Object.values(AreaKind), enumerationHint: Object.keys(AreaKind)
                {
                    name: 'AreaKind', dName: '地区类别',
                    value: AreaKind[_util.getValueByKey(basicInformation, 'regionalCategories')]
                },
                // 金额
                {
                    name: 'Total', type: _type.DECIMAL,
                    value: summaryInfo.engineeringCost
                },
                // 专业类别
                {
                    name: 'Specialty', type: _type.INT, required: true,
                    value: Specialty[tenderData.property.engineeringName]
                },
                // 工程用途
                {
                    name: 'Purposes',
                    value: ''
                },
                //建设（编制）范围
                {
                    name: 'RangeOfCompilation',
                    value: ''
                },
                // 建设规模 取单位工程-工程特征-建设规模
                {
                    name: 'Scale', dName: '建设规模', type: _type.DECIMAL, required: true,
                    value: _util.getValueByKey(projectFeature, 'buildScale')
                },
                // 建设规模单位 取单位工程-工程特征-建设规模单位
                {
                    name: 'Unit', dName: '建设规模单位', required: true, minLen: 1,
                    value: _util.getValueByKey(projectFeature, 'buildScaleUnit')
                },
                // 占总投资比例（%）
                {
                    name: 'Ratios', type: _type.DECIMAL,
                    value: summaryInfo.rate
                },
                // 清单数据库，没有则填写 “无”
                {
                    name: 'BillDataBase', dName: '清单数据库', required: true,
                    value: '工程量清单规范(2013广东)',
                },
                {
                    // 定额数据库，默认的定额库名称，没有则填写 “无”
                    name: 'NormDataBase', dName: '定额数据库', required: true,
                    value: defaultRationLibName || '无'
                },
                // 人工材料设备价格文件，没有则填写 “无”
                {
                    name: 'ResInfoPricingFile', dName: '人工材料设备价格文件', required: true,
                    value: '无'
                },
                // 总说明
                {
                    name: 'Explains',
                    value: tenderData.property.compilationIllustration
                },
                // 导出XML文件名，工程编号+工程名称.xml, 暂时取“名称.xml”，用户设置完工程编号后修改此值
                {
                    name: 'FileName', required: true,
                    value: `${tenderData.name}.xml`
                },
                // 备注
                {
                    name: 'Remark',
                    value: ''
                },
            ];
            _base.Element.call(this, 'UnitWorks', attrs, '单位工程');
        }
        // 工程特征信息 AttrInfo， 补充信息AddiInfo
        function Info(eleName) {
            _base.Element.call(this, eleName, []);
        }
        // 工程特征信息明细 AttrInfoItem， 补充信息明细AddiInfoItem
        function InfoItem(eleName, attr) {
            const attrs = [
                {
                    name: 'Name', dName: '工程名称', required: true,
                    value: attr.dispName,
                },
                { name: 'Value', value: attr.value },
            ];
            if (attr.code) {
                attrs.push({ name: 'Code', value: attr.code });
            }
            if (attr.remark) {
                attrs.push({ name: 'Remark', value: attr.remark });
            }
            _base.Element.call(this, eleName, attrs, '工程特征');
        }
        // 单位工程费用汇总
        function UnitWorksSummary() {
            _base.Element.call(this, 'UnitWorksSummary', []);
        }
        // 单位工程费用汇总标题
        function UnitWorksSummaryGroup(bills) {
            const node = tenderDetail.mainTree.nodes[`id_${bills.ID}`];
            const row = node.serialNo() + 1;
            const attrs = [
                // 费用编号
                { name: 'Number', value: bills.code },
                // 费用名称
                {
                    name: 'Name', dName: '名称', required: true, minLen: 1, failHint: `第${row}行清单-“项目名称”`,
                    value: bills.name
                },
                // 单位
                { name: 'Unit', value: bills.unit },
                // 工程量
                {
                    name: 'Quantity', type: _type.DECIMAL,
                    value: bills.quantity
                },
                // 金额
                {
                    name: 'Total', type: _type.DECIMAL,
                    value: _util.getFee(bills.fees, 'common.totalFee')
                },
                // 技术经济指标（元） 取综合单价
                {
                    name: 'TechnicalAndEconomicIndex', type: _type.DECIMAL,
                    value: _util.getFee(bills.fees, 'common.unitFee')
                },
                //章节类型
                {
                    name: 'ChapterKind', dName: '章节类型', type: _type.INT, required: true,
                    value: '4'
                },
                // 费用代号
                {
                    name: 'Code', dName: '费用代号', required: true,
                    value: getFeeCode(bills)
                },
                // 汇总类型
                {
                    name: 'Kind', dName: '汇总类型', type: _type.INT, required: true,
                    value: '1'
                },
                // 备注
                { name: 'Remark', value: bills.remark }
            ];
            _base.Element.call(this, 'UnitWorksSummaryGroup', attrs, '单位工程费用汇总标题');
        }
        // 单位工程费汇总明细
        function UnitWorksSummaryItem(node, isEmptyCode = false) {
            const bills = node.data;
            const row = node.serialNo() + 1;
            const attrs = [
                { name: 'Number', value: bills.code },
                {
                    name: 'Name', dName: '名称', required: true, minLen: 1, failHint: `第${row}行清单-“项目名称”`,
                    value: bills.name
                },
                { name: 'Unit', value: bills.unit },
                {
                    name: 'Quantity', type: _type.DECIMAL,
                    value: bills.quantity
                },
                // 计算基数：可由费用代号、数字、四则运算符号、小括号组成
                {
                    name: 'QtyFormula', type: _type.DECIMAL,
                    value: getQtyFormula(node)
                },
                // 费率
                {
                    name: 'Rate', type: _type.DECIMAL,
                    value: !commonUtil.isEmptyVal(bills.feeRate) ? bills.feeRate : '100'
                },
                {
                    name: 'Total', type: _type.DECIMAL,
                    value: _util.getFee(bills.fees, 'common.totalFee')
                },
                // 技术经济指标（元）
                {
                    name: 'TechnicalAndEconomicIndex', type: _type.DECIMAL,
                    value: _util.getFee(bills.fees, 'common.unitFee')
                },
                //章节类型
                {
                    name: 'ChapterKind', dName: '章节类型', type: _type.INT, required: true,
                    value: '4'
                },
                // 费用代号
                {
                    name: 'Code', dName: '费用代号', required: true,
                    value: isEmptyCode ? '' : getFeeCode(bills)
                },
                // 汇总类型
                {
                    name: 'Kind', dName: '汇总类型', type: _type.INT, required: true,
                    value: '1'
                },
                // 备注
                { name: 'Remark', value: bills.remark }
            ];
            _base.Element.call(this, 'UnitWorksSummaryItem', attrs, '单位工程费用汇总明细');
        }
        // 分部分项工程
        function DivisionalAndElementalWorks() {
            _base.Element.call(this, 'DivisionalAndElementalWorks', []);
        }
        // 措施项目
        function Preliminaries() {
            _base.Element.call(this, 'Preliminaries', []);
        }
        // 合计费用
        function SummaryOfBasicCost(items, node) {
            const bills = node.data;
            // 省略了一些
            const mainMaterial = _util.getFee(bills.fees, 'mainMaterial.totalFee');
            const equipment = _util.getFee(bills.fees, 'equipment.totalFee');
            const mainMaterialEquipment = scMathUtil.roundForObj(mainMaterial + equipment, Decimal.FEE); // 主材设备费 = 主材 + 设备 （设备不输出）
            const attrs = [
                // 人工费
                {
                    name: 'Labor', type: _type.DECIMAL,
                    value: _util.getFee(bills.fees, 'labour.totalFee')
                },
                // 材料费
                {
                    name: 'Material', type: _type.DECIMAL,
                    value: _util.getFee(bills.fees, 'material.totalFee')
                },
                // 主材费
                {
                    name: 'MainMaterial', type: _type.DECIMAL,
                    value: mainMaterial
                },
                // 主材设备费
                {
                    name: 'MainMaterialEquipment', type: _type.DECIMAL,
                    value: mainMaterialEquipment
                },
                // 暂估价
                {
                    name: 'Appraisal', type: _type.DECIMAL,
                    value: _util.getFee(bills.fees, 'estimate.totalFee')
                },
                // 机械费
                {
                    name: 'Machine', type: _type.DECIMAL,
                    value: _util.getFee(bills.fees, 'machine.totalFee')
                },
                // 管理费
                {
                    name: 'Overhead', type: _type.DECIMAL,
                    value: _util.getFee(bills.fees, 'manage.totalFee')
                },
                // 利润
                {
                    name: 'Profit', type: _type.DECIMAL,
                    value: _util.getFee(bills.fees, 'profit.totalFee')
                },
                // 分部分项工程费
                {
                    name: 'DivisionalAndElementalWorks', type: _type.DECIMAL,
                    // TODO value的处理速度可能需要优化
                    value: node.isBelongToFlags([fixedFlag.SUB_ENGINERRING]) ? _util.getFeeByFlag(items, fixedFlag.SUB_ENGINERRING, 'common.totalFee') : '0'
                },
                // 措施项目费
                {
                    name: 'Preliminaries', type: _type.DECIMAL,
                    value: node.isBelongToFlags([fixedFlag.MEASURE]) ? _util.getFeeByFlag(items, fixedFlag.MEASURE, 'common.totalFee') : '0'
                },
                // 绿色施工安全防护措施费
                {
                    name: 'CostForHSE', type: _type.DECIMAL,
                    value: node.isBelongToFlags([fixedFlag.SAFETY_CONSTRUCTION]) ? _util.getFeeByFlag(items, fixedFlag.SAFETY_CONSTRUCTION, 'common.totalFee') : '0'
                },
                // 其他项目费
                {
                    name: 'SundryCosts', type: _type.DECIMAL,
                    value: node.isBelongToFlags([fixedFlag.OTEHER]) ? _util.getFeeByFlag(items, fixedFlag.OTEHER, 'common.totalFee') : '0'
                },
                // 暂列金额
                {
                    name: 'ProvisionalSums', type: _type.DECIMAL,
                    value: '0'
                    //value: node.isBelongToFlags([fixedFlag.fixedFlag.PROVISIONAL]) ? _util.getFeeByFlag(items, fixedFlag.PROVISIONAL, 'common.totalFee') : '0'
                },
                // 规费
                {
                    name: 'StatutoryFees', type: _type.DECIMAL,
                    value: '0'
                },
                // 税金
                {
                    name: 'Tax', type: _type.DECIMAL,
                    value: '0'
                    //value: node.isBelongToFlags([fixedFlag.fixedFlag.TAX]) ? _util.getFeeByFlag(items, fixedFlag.TAX, 'common.totalFee') : '0'
                },
            ];
            _base.Element.call(this, 'SummaryOfBasicCost', attrs);
        }
        // 分部工程
        function DivisionalWorks(bills) {
            const attrs = [
                // 编码
                { name: 'Number', value: bills.code },
                // 名称
                {
                    name: 'Name', dName: '名称', minLen: 1,
                    value: bills.name
                },
                // 特征
                { name: 'Attr', value: '' },
                // 单位
                { name: 'Unit', value: bills.unit },
                // 工程量
                {
                    name: 'Quantity', type: _type.DECIMAL,
                    value: bills.quantity
                },
                // 综合合价
                {
                    name: 'Total', type: _type.DECIMAL,
                    value: _util.getFee(bills.fees, 'common.totalFee')
                },
                // 综合单价
                {
                    name: 'TechnicalAndEconomicIndex', type: _type.DECIMAL,
                    value: _util.getFee(bills.fees, 'common.unitFee')
                },
                // 章节类型
                {
                    name: 'ChapterKind', dName: '章节类型', type: _type.INT, required: true,
                    value: '4'
                },
                // 费用字典，拼音首字母
                //{ name: 'Code', value: getFeeCode(bills) },
                { name: 'Code', value: '' },
                // 备注
                { name: 'Remark', value: bills.remark }
            ];
            _base.Element.call(this, 'DivisionalWorks', attrs, '分部工程');
        }
        // 清单项目
        function WorkElement(node, kind) {
            const bills = node.data;
            const row = node.serialNo() + 1;
            const programID = getBillsProgramID(bills);
            const attrs = [
                // 项目编码
                { name: 'Number', dName: '项目编码', required: true, value: bills.code || '', minLen: 1, failHint: `第${row}行清单-“项目编码”` },
                // 项目名称
                { name: 'Name', dName: '项目名称', required: true, value: bills.name, minLen: 1, failHint: `第${row}行清单-“项目名称”` },
                // 项目特征
                { name: 'Attr', value: bills.itemCharacterText },
                // 工作内容
                { name: 'WorkContent', value: bills.jocContentText },
                // 计量单位
                { name: 'Unit', dName: '计量单位', required: true, value: bills.unit, minLen: 1, failHint: `第${row}行清单-“计量单位”` },
                // 工程量
                { name: 'Quantity', type: _type.DECIMAL, value: bills.quantity },
                // 计算基数
                { name: 'QtyFormula', value: getQtyFormula(node) },
                // 单价（元）
                //{ name: 'Price', type: _type.DECIMAL, value: _util.getFee(bills.fees, 'common.unitFee') },
                // 设备单价（元）指清单项目所采用设备的综合单价
                { name: 'EquipmentPrice', type: _type.DECIMAL, value: _util.getFee(bills.fees, 'equipment.unitFee') },
                // 最低限价（元）
                //{ name: 'PriceLow', type: _type.DECIMAL, value: 'todo' },
                // 最高限价（元）
                { name: 'PriceHigh', type: _type.DECIMAL, value: bills.maxPrice },
                // 费率（%）
                //{ name: 'Rate', type: _type.DECIMAL, value: !commonUtil.isEmptyVal(bills.feeRate) ? bills.feeRate : '100' },
                // 合价（元）
                { name: 'Total', type: _type.DECIMAL, value: _util.getFee(bills.fees, 'common.totalFee') },
                // 主要清单
                { name: 'Major', type: _type.BOOL, value: !!bills.mainBills },
                // 子目类型
                { name: 'Kind', dName: '子目类型', type: _type.INT, required: true, value: kind },
                // 取费类型
                { name: 'CalculationKind', dName: '取费类型', type: _type.INT, required: true, value: '1' },
                // 费用归属
                { name: 'CostKind', dName: '费用归属', type: _type.INT, required: true, value: '1' },
                // 计算方式
                { name: 'CalcType', dName: '计算方式', type: _type.INT, required: true, value: '1' },
                // 专业类型
                { name: 'Specialty', type: _type.INT, value: programID },
                // 清单标识
                { name: 'ListingIdentity', value: 'GB50500-2013 1 GD' },
                // 费用代号
                //{ name: 'Code', value: getFeeCode(bills) },
                { name: 'Code', value: '' },
                // 备注
                { name: 'Remark', value: bills.remark }
            ];
            // 单价费率，单价与费率在同一个费用项目中不允许同时存在。（对于套定额的清单，则输出单价不输出费率rate；对于基数计算的，则输出费率不输出单价）
            const showPrice = !!(node.children && node.children.length && node.children[0].sourceType === commonConstants.SourceType.RATION)
            if (showPrice) {
                attrs.push({ name: 'Price', type: _type.DECIMAL, value: _util.getFee(bills.fees, 'common.unitFee') });
            } else {
                attrs.push({ name: 'Rate', type: _type.DECIMAL, value: !commonUtil.isEmptyVal(bills.feeRate) ? bills.feeRate : '100' });
            }
            _base.Element.call(this, 'WorkElement', attrs, '清单');
        }
        // 工程量计算表
        function ExpressElement(quantityDetail) {
            const attrs = [
                // 序号
                { name: 'OrderNumber', dName: '序号', required: true, value: quantityDetail.seq + 1 }, // 软件中是从0开始，导出需要从1开始
                // 工程量计算式
                { name: 'Express', dName: '工程量计算式', required: true, value: quantityDetail.regex },
                // 工程量
                { name: 'Quantity', dName: '工程量', required: true, value: quantityDetail.result },
                // 汇总类型
                { name: 'Kind', dName: '汇总类型', type: _type.INT, required: true, value: SummationKindMap[quantityDetail.isSummation] },
            ];
            _base.Element.call(this, 'ExpressElement', attrs, '工程量计算表');
        }
        // 工序内容
        function WorkContent(contentText, fee) {
            const attrs = [
                // 定额工作内容
                { name: 'Name', dName: '定额工作内容', required: true, value: contentText },
                // 取此工作内容下定额子目/量价/定额同级人材机的综合合价之和
                { name: 'Total', type: _type.DECIMAL, value: fee },
                { name: 'Remark', value: '' }
            ];
            _base.Element.call(this, 'WorkContent', attrs, '工序内容');
        }
        // 定额子目
        function Norm(node, libCode, kind) {
            const ration = node.data;
            const row = node.serialNo() + 1;
            // 通过定额库编码，读取编码中间的数字，如房建定额库编码是“GD 1 2018"，工程专业取“1”。
            const reg = /GD (\d+)/;
            const matched = libCode.match(reg);
            const specialty = matched && matched[1] || '1';
            const attrs = [
                // 定额编码
                { name: 'Number', dName: '定额编码', required: true, value: ration.code, minLen: 1, failHint: `第${row}行定额-“定额编码”` },
                { name: 'Name', dName: '定额名称', required: true, value: ration.name, minLen: 1, failHint: `第${row}行定额-“定额名称”` },
                { name: 'Unit', dName: '计量单位', required: true, value: ration.unit, minLen: 1, failHint: `第${row}行定额-“计量单位”` },
                { name: 'Quantity', type: _type.DECIMAL, value: ration.quantity },
                // 单价
                { name: 'Price', type: _type.DECIMAL, value: _util.getFee(ration.fees, 'common.unitFee') },
                // 合价
                { name: 'Total', type: _type.DECIMAL, value: _util.getFee(ration.fees, 'common.totalFee') },
                // 是否定额
                { name: 'IsNorm', dName: '是否定额', type: _type.Bool, required: true, value: 'true' },
                // 子目类型
                { name: 'Kind', dName: '子目类型', type: _type.INT, required: true, value: kind },
                // 取费类型
                { name: 'CalculationKind', dName: '取费类型', type: _type.INT, required: true, value: '1' },
                // 费用归属
                { name: 'CostKind', dName: '费用归属', type: _type.INT, required: true, value: '1' },
                // 降效类型
                {
                    name: 'EfficiencyKind', type: _type.INT,
                    value: ration.type === rationType.overHeight ? EfficiencyKind.BASE_REDUCTION : EfficiencyKind.NOT_BASE_REDUCTION
                },
                // 子目增加费类型itemIncrease
                {
                    name: 'IncFeeKind', type: _type.INT,
                    value: ration.type === rationType.overHeight ? IncFeeKind.SUB_BASE : IncFeeKind.NOT_SUB_BASE
                },
                // 换算说明
                { name: 'Conversion', value: ration.adjustState },
                // 专业类别
                { name: 'Specialty', type: _type.INT, value: specialty },
                //定额标识（定额库编码）
                { name: 'NormIdentity', value: libCode },
                // 册 定额书没有分册填写0，有分册的按册号填写
                { name: 'Volume', type: _type.INT, value: getVolume(ration.code) },
                // 章
                { name: 'Chapter', type: _type.INT, value: getChapter(ration.code) },
                { name: 'Remark', value: ration.remark }
            ];
            _base.Element.call(this, 'Norm', attrs, '定额子目');
        }
        // 子目管理费
        function ManageFees(calcTemplate) {
            const calcItems = (calcTemplate || {}).calcItems;
            const manageItem = (calcItems || []).find(item => item.name === '管理费');
            const fee1 = manageItem ? manageItem.unitFee : '0';
            const rate = manageItem && commonUtil.isDef(manageItem.feeRate) ? manageItem.feeRate : '100';
            const attrs = [
                // 一类地区管理费 输出定额计算程序的管理费单价
                { name: 'Fee1', dName: '一类地区管理费', type: _type.DECIMAL, required: true, value: fee1 },
                // 二类地区管理费
                { name: 'Fee2', dName: '二类地区管理费', type: _type.DECIMAL, required: true, value: '0' },
                // 三类地区管理费
                { name: 'Fee3', dName: '三类地区管理费', type: _type.DECIMAL, required: true, value: '0' },
                // 四类地区管理费
                { name: 'Fee4', dName: '四类地区管理费', type: _type.DECIMAL, required: true, value: '0' },
                // 管理费换算比例
                { name: 'Rate', dName: '管理费换算比例', type: _type.DECIMAL, required: true, value: rate },
            ];
            _base.Element.call(this, 'ManageFees', attrs, '子目管理费');
        }
        // 工料机含量明细
        function LabourMaterialsEquipmentsMachinesElement(glj, quantity, noCost) {
            const attrs = [
                // 工料机编码
                { name: 'Number', value: glj.code },
                // 消耗量
                { name: 'Quantity', type: _type.DECIMAL, value: (+quantity).toFixed(Decimal.GLJ) },
                // 不计价材料
                { name: 'NOCost', typ: _type.BOOL, value: noCost },
                // 备注
                { name: 'Remark', value: '' }
            ];
            _base.Element.call(this, 'LabourMaterialsEquipmentsMachinesElement', attrs);
        }
        //子目单价计算
        function UnitPriceCalculationOfItem(calcItem) {
            const feeRate = !commonUtil.isEmptyVal(calcItem.feeRate) ? calcItem.feeRate : '100';
            const attrs = [
                // 费用名称
                { name: 'Name', dName: '费用名称', required: true, value: calcItem.name },
                // 计算基数
                { name: 'QtyFormula', value: CalculationQtyMap[calcItem.name] },
                // 费率
                { name: 'Rate', type: _type.DECIMAL, value: feeRate },
                // 金额
                { name: 'Total', type: _type.DECIMAL, value: calcItem.unitFee },
                // 费用代号
                { name: 'Code', required: true, value: CalculationCodeMap[calcItem.name] },
                // 备注
                { name: 'Remark', value: '' },
            ];
            _base.Element.call(this, 'UnitPriceCalculationOfItem', attrs, '子目单价计算');
        }
        // 其他项目
        function Sundry() {
            _base.Element.call(this, 'Sundry', []);
        }
        // 其他项目汇总子目
        function SundryItem(eleName, bills) {
            const node = tenderDetail.mainTree.nodes[`id_${bills.ID}`];
            const row = node.serialNo() + 1;
            const attrs = [
                // 名称
                { name: 'Name', dName: '名称', required: true, minLen: 1, value: bills.name, failHint: `第${row}行清单-“项目名称”` },
                // 金额
                { name: 'Total', type: _type.DECIMAL, value: _util.getFee(bills.fees, 'common.totalFee') },
                // 费用代号
                { name: 'Code', dName: '费用代号', required: true, value: getFeeCode(bills) },
                // 备注
                { name: 'Remark', value: bills.remark }
            ];
            _base.Element.call(this, eleName, attrs, '其他项目汇总项');
        }
        // 其他项目费标题
        function SundryCostsGroup(node) {
            const bills = node.data;
            // 材料暂估价取不汇总，其他为汇总
            const kind = node.getFlag() === fixedFlag.MATERIAL_PROVISIONAL ? SummrayKind.NO : SummrayKind.YES;
            const row = node.serialNo() + 1;
            const attrs = [
                // 名称
                { name: 'Name', dName: '名称', required: true, minLen: 1, value: bills.name, failHint: `第${row}行清单-“项目名称”` },
                // 金额
                { name: 'Total', type: _type.DECIMAL, value: _util.getFee(bills.fees, 'common.totalFee') },
                // 费用代号
                { name: 'Code', dName: '费用代号', required: true, value: getFeeCode(bills) },
                // 汇总类型
                { name: 'Kind', dName: '汇总类型', type: _type.INT, required: true, value: kind },
                // 备注
                { name: 'Remark', value: bills.remark }
            ];
            _base.Element.call(this, 'SundryCostsGroup', attrs, '其他项目费标题');
        }
        // 其他项目费明细
        function SundryCostsItem(node) {
            const bills = node.data;
            // 材料暂估价取不汇总，其他为汇总
            const kind = node.getFlag() === fixedFlag.MATERIAL_PROVISIONAL ? SummrayKind.NO : SummrayKind.YES;
            const row = node.serialNo() + 1;
            const attrs = [
                // 名称
                { name: 'Name', dName: '名称', required: true, minLen: 1, value: bills.name, failHint: `第${row}行清单-“项目名称”` },
                // 单位
                { name: 'Unit', value: bills.unit },
                // 工程量
                { name: 'Quantity', type: _type.DECIMAL, value: bills.quantity },
                // 计算基数
                { name: 'QtyFormula', value: getQtyFormula(node) },
                // 单价
                //{ name: 'Price', type: _type.DECIMAL, value: _util.getFee(bills.fees, 'common.unitFee') },
                // 费率
                //{ name: 'Rate', type: _type.DECIMAL, value: !commonUtil.isEmptyVal(bills.feeRate) ? bills.feeRate : '100' },
                // 金额
                { name: 'Total', type: _type.DECIMAL, value: _util.getFee(bills.fees, 'common.totalFee') },
                // 费用代号
                { name: 'Code', dName: '费用代号', required: true, value: getFeeCode(bills) },
                // 汇总类型
                { name: 'Kind', dName: '汇总类型', type: _type.INT, required: true, value: kind },
                // 备注
                { name: 'Remark', value: bills.remark }
            ];
            // 单价费率，单价与费率在同一个费用项目中不允许同时存在。（对于套定额的清单，则输出单价不输出费率rate；对于基数计算的，则输出费率不输出单价）
            const showPrice = !!(node.children && node.children.length && node.children[0].sourceType === commonConstants.SourceType.RATION);
            if (showPrice) {
                attrs.push({ name: 'Price', type: _type.DECIMAL, value: _util.getFee(bills.fees, 'common.unitFee') });
            } else {
                attrs.push({ name: 'Rate', type: _type.DECIMAL, value: !commonUtil.isEmptyVal(bills.feeRate) ? bills.feeRate : '100' });
            }
            _base.Element.call(this, 'SundryCostsItem', attrs, '清单');
        }
        // 暂列金额标题
        function ProvisionalSumsGroup(node) {
            const bills = node.data;
            const row = node.serialNo() + 1;
            const attrs = [
                // 名称
                { name: 'Name', dName: '名称', required: true, minLen: 1, value: bills.name, failHint: `第${row}行清单-“项目名称”` },
                // 金额
                { name: 'Total', type: _type.DECIMAL, value: _util.getFee(bills.fees, 'common.totalFee') },
                // 汇总类型
                { name: 'Kind', dName: '汇总类型', type: _type.INT, required: true, value: SummrayKind.YES },
                // 备注
                { name: 'Remark', value: bills.remark }
            ];
            _base.Element.call(this, 'ProvisionalSumsGroup', attrs, '暂列金额标题');
        }
        // 暂列金额明细
        function ProvisionalSumsItem(node) {
            const bills = node.data;
            const row = node.serialNo() + 1;
            const attrs = [
                // 名称
                { name: 'Name', dName: '名称', required: true, minLen: 1, value: bills.name, failHint: `第${row}行清单-“项目名称”` },
                // 单位
                { name: 'Unit', value: bills.unit },
                // 工程量
                { name: 'Quantity', type: _type.DECIMAL, value: bills.quantity },
                // 计算基数
                { name: 'QtyFormula', value: getQtyFormula(node) },
                // 单价
                //{ name: 'Price', type: _type.DECIMAL, value: _util.getFee(bills.fees, 'common.unitFee') },
                // 费率
                // { name: 'Rate', type: _type.DECIMAL, value: !commonUtil.isEmptyVal(bills.feeRate) ? bills.feeRate : '100' },
                // 金额
                { name: 'Total', type: _type.DECIMAL, value: _util.getFee(bills.fees, 'common.totalFee') },
                // 汇总类型
                { name: 'Kind', dName: '汇总类型', type: _type.INT, required: true, value: SummrayKind.YES },
                // 备注
                { name: 'Remark', value: bills.remark }
            ];
            // 单价费率，单价与费率在同一个费用项目中不允许同时存在。（对于套定额的清单，则输出单价不输出费率rate；对于基数计算的，则输出费率不输出单价）
            const showPrice = !!(node.children && node.children.length && node.children[0].sourceType === commonConstants.SourceType.RATION);
            if (showPrice) {
                attrs.push({ name: 'Price', type: _type.DECIMAL, value: _util.getFee(bills.fees, 'common.unitFee') });
            } else {
                attrs.push({ name: 'Rate', type: _type.DECIMAL, value: !commonUtil.isEmptyVal(bills.feeRate) ? bills.feeRate : '100' });
            }
            _base.Element.call(this, 'ProvisionalSumsItem', attrs, '暂列金额明细');
        }
        // 材料设备暂估价明细
        // 读取单位工程-其他项目费的材料暂估价的组成数据，业务上这个清单是使用清单基数计算得来，所以此部分读取整个单位工程下打勾暂估的材料
        function ProvisionalMaterialEquipmentItem(glj, price) {
            const attrs = [
                // 编码
                { name: 'Number', value: glj.code },
                // 名称
                { name: 'Name', dName: '名称', required: true, minLen: 1, value: glj.name },
                // 型号规格
                { name: 'Specification', value: glj.specs },
                // 单位
                { name: 'Unit', dName: '单位', required: true, value: glj.unit },
                // 工程量(总消耗量)
                { name: 'Quantity', type: _type.DECIMAL, value: glj.quantity },
                // 单价 取市场价
                { name: 'Price', type: _type.DECIMAL, value: price.marketPrice },
                // 金额 round（总消耗量*市场价，2）
                { name: 'Total', type: _type.DECIMAL, value: scMathUtil.roundForObj(glj.quantity * price.marketPrice, 2) },
                // 备注
                { name: 'Remark', value: glj.remark }
            ];
            _base.Element.call(this, 'ProvisionalMaterialEquipmentItem', attrs, '材料设备暂估价明细');
        }
        // 专业工程暂估价标题
        function SpecialtyProvisionalPriceGroup(node) {
            const bills = node.data;
            const row = node.serialNo() + 1;
            const attrs = [
                // 名称
                { name: 'Name', dName: '名称', required: true, minLen: 1, value: bills.name, failHint: `第${row}行清单-“项目名称”` },
                // 工程内容
                { name: 'Content', value: bills.engineeringContent },
                // 金额
                { name: 'Total', type: _type.DECIMAL, value: _util.getFee(bills.fees, 'common.totalFee') },
                // 汇总类型
                { name: 'Kind', dName: '汇总类型', type: _type.INT, required: true, value: SummrayKind.YES },
                // 备注
                { name: 'Remark', value: bills.remark }
            ];
            _base.Element.call(this, 'SpecialtyProvisionalPriceGroup', attrs, '专业工程暂估价标题');
        }
        // 专业工程暂估价明细
        function SpecialtyProvisionalPriceItem(node) {
            const bills = node.data;
            const row = node.serialNo() + 1;
            const attrs = [
                // 名称
                { name: 'Name', dName: '名称', required: true, minLen: 1, value: bills.name, failHint: `第${row}行清单-“项目名称”` },
                // 工程内容
                { name: 'Content', value: bills.engineeringContent },
                // 单位
                { name: 'Unit', value: bills.unit },
                // 工程量
                { name: 'Quantity', type: _type.DECIMAL, value: bills.quantity },
                // 计算基数
                { name: 'QtyFormula', value: getQtyFormula(node) },
                // 单价
                //{ name: 'Price', type: _type.DECIMAL, value: _util.getFee(bills.fees, 'common.unitFee') },
                // 费率
                //{ name: 'Rate', type: _type.DECIMAL, value: !commonUtil.isEmptyVal(bills.feeRate) ? bills.feeRate : '100' },
                // 金额
                { name: 'Total', type: _type.DECIMAL, value: _util.getFee(bills.fees, 'common.totalFee') },
                // 汇总类型
                { name: 'Kind', dName: '汇总类型', type: _type.INT, required: true, value: SummrayKind.YES },
                // 备注
                { name: 'Remark', value: bills.remark }
            ];
            const showPrice = !!(node.children && node.children.length && node.children[0].sourceType === commonConstants.SourceType.RATION);
            if (showPrice) {
                attrs.push({ name: 'Price', type: _type.DECIMAL, value: _util.getFee(bills.fees, 'common.unitFee') });
            } else {
                attrs.push({ name: 'Rate', type: _type.DECIMAL, value: !commonUtil.isEmptyVal(bills.feeRate) ? bills.feeRate : '100' });
            }
            _base.Element.call(this, 'SpecialtyProvisionalPriceItem', attrs, '专业工程暂估价明细');
        }
        // 计日工标题
        function DayWorkRateGroup(node) {
            const bills = node.data;
            const row = node.serialNo() + 1;
            const attrs = [
                // 名称
                { name: 'Name', dName: '名称', required: true, minLen: 1, value: bills.name, failHint: `第${row}行清单-“项目名称”` },
                // 金额
                { name: 'Total', type: _type.DECIMAL, value: _util.getFee(bills.fees, 'common.totalFee') },
                // 费用代号
                { name: 'Code', value: getFeeCode(bills) },
                // 备注
                { name: 'Remark', value: bills.remark }
            ];
            _base.Element.call(this, 'DayWorkRateGroup', attrs, '计日工标题');
        }
        // 计日工明细
        function DayWorkRateItem(node) {
            const bills = node.data;
            const row = node.serialNo() + 1;
            const attrs = [
                // 编码
                { name: 'Number', value: bills.code },
                // 名称
                { name: 'Name', dName: '名称', required: true, minLen: 1, value: bills.name, failHint: `第${row}行清单-“项目名称”` },
                // 型号规格
                { name: 'Specification', value: '' },
                // 单位
                { name: 'Unit', dName: '单位', required: true, value: bills.unit },
                // 工程量
                { name: 'Quantity', type: _type.DECIMAL, value: bills.quantity },
                // 单价
                { name: 'Price', type: _type.DECIMAL, value: _util.getFee(bills.fees, 'common.unitFee') },
                // 金额
                { name: 'Total', type: _type.DECIMAL, value: _util.getFee(bills.fees, 'common.totalFee') },
                // 备注
                { name: 'Remark', value: bills.remark }
            ];
            _base.Element.call(this, 'DayWorkRateItem', attrs, '计日工明细');
        }
        // 总承包服务费标题
        function MainContractorAttendanceGroup(node) {
            const bills = node.data;
            const row = node.serialNo() + 1;
            const attrs = [
                // 名称
                { name: 'Name', dName: '名称', required: true, minLen: 1, value: bills.name, failHint: `第${row}行清单-“项目名称”` },
                // 金额
                { name: 'Total', type: _type.DECIMAL, value: _util.getFee(bills.fees, 'common.totalFee') },
                // 汇总类型
                { name: 'Kind', dName: '汇总类型', type: _type.INT, required: true, value: SummrayKind.YES },
                // 备注
                { name: 'Remark', value: bills.remark }
            ];
            _base.Element.call(this, 'MainContractorAttendanceGroup', attrs, '总承包服务费标题');
        }
        // 总承包服务费明细
        function MainContractorAttendanceItem(node) {
            const bills = node.data;
            const row = node.serialNo() + 1;
            const attrs = [
                // 名称
                { name: 'Name', dName: '名称', required: true, minLen: 1, value: bills.name, failHint: `第${row}行清单-“项目名称”` },
                // 项目价值，计算基数对应的金额
                { name: 'Quantity', type: _type.DECIMAL, value: bills.tenderCalcBaseValue },
                // 服务内容
                { name: 'Content', value: bills.serviceContent },
                // 计算基数
                { name: 'QtyFormula', value: getQtyFormula(node) },
                // 费率
                { name: 'Rate', type: _type.DECIMAL, value: !commonUtil.isEmptyVal(bills.feeRate) ? bills.feeRate : '100' },
                // 金额
                { name: 'Total', type: _type.DECIMAL, value: _util.getFee(bills.fees, 'common.totalFee') },
                // 汇总类型
                { name: 'Kind', dName: '汇总类型', type: _type.INT, required: true, value: SummrayKind.YES },
                // 备注
                { name: 'Remark', value: bills.remark }
            ];
            _base.Element.call(this, 'MainContractorAttendanceItem', attrs, '总承包服务费明细');
        }
        // 索赔、签证费用标题
        function ClaimVisaCostGroup(eleName, node) {
            const bills = node.data;
            const row = node.serialNo() + 1;
            const attrs = [
                // 名称
                { name: 'Name', dName: '名称', required: true, minLen: 1, value: bills.name, failHint: `第${row}行清单-“项目名称”` },
                // 金额
                { name: 'Total', type: _type.DECIMAL, value: _util.getFee(bills.fees, 'common.totalFee') },
                // 汇总类型
                { name: 'Kind', dName: '汇总类型', type: _type.INT, required: true, value: SummrayKind.YES },
                // 备注
                { name: 'Remark', value: bills.remark }
            ];
            _base.Element.call(this, eleName, attrs, '索赔、签证费用标题');
        }
        // 索赔费用明细
        function ClaimVisaCostItem(eleName, node) {
            const bills = node.data;
            const row = node.serialNo() + 1;
            const attrs = [
                // 名称
                { name: 'Name', dName: '名称', required: true, minLen: 1, value: bills.name, failHint: `第${row}行清单-“项目名称”` },
                // 单位
                { name: 'Unit', value: bills.unit },
                // 数量
                { name: 'Quantity', type: _type.DECIMAL, value: bills.quantity },
                // 计算基数
                { name: 'QtyFormula', value: getQtyFormula(node) },
                // 单价
                //{ name: 'Price', type: _type.DECIMAL, value: _util.getFee(bills.fees, 'common.unitFee') },
                // 费率
                //{ name: 'Rate', type: _type.DECIMAL, value: !commonUtil.isEmptyVal(bills.feeRate) ? bills.feeRate : '100' },
                // 金额
                { name: 'Total', type: _type.DECIMAL, value: _util.getFee(bills.fees, 'common.totalFee') },
                // 依据
                { name: 'Reason', value: bills.claimVisa },
                // 汇总类型
                { name: 'Kind', dName: '汇总类型', type: _type.INT, required: true, value: SummrayKind.YES },
                // 备注
                { name: 'Remark', value: bills.remark }
            ];
            const showPrice = !!(node.children && node.children.length && node.children[0].sourceType === commonConstants.SourceType.RATION);
            if (showPrice) {
                attrs.push({ name: 'Price', type: _type.DECIMAL, value: _util.getFee(bills.fees, 'common.unitFee') });
            } else {
                attrs.push({ name: 'Rate', type: _type.DECIMAL, value: !commonUtil.isEmptyVal(bills.feeRate) ? bills.feeRate : '100' });
            }
            _base.Element.call(this, eleName, attrs, '索赔费用明细');
        }
        // 规费
        function StatutoryFees() {
            const attrs = [
                { name: 'Name', dName: '名称', required: true, minLen: 1, value: '规费' },
                { name: 'Code', dName: '费用代号', required: true, value: 'GF' }
            ];
            _base.Element.call(this, 'StatutoryFees', attrs, '规费');
        }
        // 税金
        function Tax(eleName, node) {
            const bills = node.data;
            const row = node.serialNo() + 1;
            const attrs = [
                // 名称
                { name: 'Name', dName: '名称', required: true, minLen: 1, value: bills.name, failHint: `第${row}行清单-“项目名称”` },
                // 计算基数说明
                { name: 'CalBasis', value: getCalBasis(node) },
                // 计算基数
                { name: 'QtyFormula', value: getQtyFormula(node) },
                // 费率
                { name: 'Rate', type: _type.DECIMAL, value: !commonUtil.isEmptyVal(bills.feeRate) ? bills.feeRate : '100' },
                // 金额
                { name: 'Total', type: _type.DECIMAL, value: _util.getFee(bills.fees, 'common.totalFee') },
                // 费用代号
                { name: 'Code', required: true, value: getFeeCode(bills) },
                // 备注
                { name: 'Remark', value: bills.remark }
            ];
            _base.Element.call(this, eleName, attrs, '税金');
        }
        // 人材机汇总条目对象
        function getGLJSummaryItem(glj, price, infoData, coeData) {
            return {
                code: glj.code || '',
                name: glj.name || '',
                specs: glj.specs || '',
                unit: glj.unit || '',
                quantity: glj.quantity || '0',
                riskCoe: infoData && infoData.riskCoe || '0',
                standardPrice: infoData && infoData.standardPrice || '0',
                basePrice: price.basePrice || '0',
                marketPrice: price.marketPrice || '0',
                varWeight: coeData && coeData.varWeight || '0',
                FO: coeData && coeData.FO || '0',
                FI: coeData && coeData.FI || '0',
                kind: getGLJKind(glj.type),
                isConcrete: [205, 206].includes(glj.type),
                isMainMaterial: !!glj.is_main_material,
                isProvisionalMaterial: !!glj.is_evaluate,
                provider: Provider[glj.supply],
                delivery: glj.delivery || '',
                location: glj.delivery_address || '',
                producingArea: glj.originPlace || '',
                supplier: glj.vender || '',
                character: glj.qualityGrace || '',
                remark: glj.remark || ''
            };
        }
        // 人材机汇总
        function LabourMaterialsEquipmentsMachinesSummary(glj) {
            const attrs = [
                // 编码
                { name: 'Number', dName: '编码', required: true, value: glj.code },
                // 名称
                { name: 'Name', dName: '名称', required: true, value: glj.name },
                // 规格型号
                { name: 'Specification', value: glj.specs },
                // 单位
                { name: 'Unit', dName: '单位', required: true, value: glj.unit },
                // 数量(总消耗量) 用toFixed来补零没问题，因为补的位数是大于计算取舍位数，不会有toFixed的精度问题(2.35.toFixed(2) = 2.3)
                { name: 'Quantity', type: _type.DECIMAL, value: (+glj.quantity).toFixed(Decimal.GLJ) },
                // 风险系数 从承包人主要材料设备-造价信息差额调整法读取，取不到则输出"0"
                { name: 'ProviderRate', type: _type.DECIMAL, value: glj.riskCoe },
                // 基准单价 从承包人主要材料设备-造价信息差额调整法读取，取不到则输出"0"
                { name: 'ProviderBase', type: _type.DECIMAL, value: glj.standardPrice },
                // 除税定额价（定额价）
                { name: 'NoTaxOrgPrice', type: _type.DECIMAL, value: glj.basePrice },
                // 除税编制价（市场价）
                { name: 'NoTaxPrice', type: _type.DECIMAL, value: glj.marketPrice },
                // 含税定额价 含税与不含税是对应项目的计税方式（一般、简易）
                // 人材机含税和不含税的数据在项目创建时就确定，并且都赋在一个属性上
                // 因此导出都取项目中的basePriceh额marketPrice就行
                { name: 'TaxOrgPrice', type: _type.DECIMAL, value: glj.basePrice },
                // 含税编制价 
                { name: 'TaxPrice', type: _type.DECIMAL, value: glj.marketPrice },
                // 引进部分
                { name: 'ForeignCurrency', type: _type.DECIMAL, value: '0' },
                // 折合人民币
                { name: 'ConvertedIntoRMB', type: _type.DECIMAL, value: '0' },
                // 除税定额价合价 取Round(定额价*总消耗量,2）
                { name: 'NoTaxOrgTotal', type: _type.DECIMAL, value: scMathUtil.roundForObj(glj.basePrice * glj.quantity, 2) },
                // 除税编制价合价(元) 取Round(不含税市场价*总消耗量,2）
                { name: 'NoTaxTotal', type: _type.DECIMAL, value: scMathUtil.roundForObj(glj.marketPrice * glj.quantity, 2) },
                // 含税定额价合价
                { name: 'TaxOrgTotal', type: _type.DECIMAL, value: '0' },
                // 含税编制价合价 取Round(含税市场价*总消耗量,2） (暂时用市场价)
                { name: 'TaxTotal', type: _type.DECIMAL, value: scMathUtil.roundForObj(glj.marketPrice * glj.quantity, 2) },
                // 变值权重 从承包人主要材料设备-价格指数调整法读取，取不到则输出“0”
                { name: 'Weight', type: _type.DECIMAL, value: glj.varWeight },
                // 基本价格指数 从承包人主要材料设备-价格指数调整法读取，取不到则输出“0”
                { name: 'BasicPrice', type: _type.DECIMAL, value: glj.FO },
                // 现行价格指数 从承包人主要材料设备-价格指数调整法读取，取不到则输出“0”
                { name: 'CurrentPrice', type: _type.DECIMAL, value: glj.FI },
                // 工料机类型
                { name: 'Kind', type: _type.INT, required: true, value: glj.kind },
                // 工料机归属
                { name: 'Class', value: '' },
                // 扩展属性
                { name: 'ExtKind', type: _type.INT, value: 1 },
                // 商品砼 如果工料机类型是“商品混凝土”、“商品砂浆”、“商品配合比”(软件中没有)，则取“true”；否则取“false”
                { name: 'Concrete', type: _type.BOOL, value: glj.isConcrete },
                // 主要材料
                { name: 'MainMaterial', type: _type.BOOL, value: glj.isMainMaterial },
                // 是否暂估价材料
                { name: 'ProvisionalMaterial', type: _type.BOOL, value: glj.isProvisionalMaterial },
                // 供料方式
                { name: 'Provider', type: _type.INT, value: glj.provider },
                // 价格来源
                { name: 'PriceSource', value: '' },
                // 交货方式
                { name: 'Delivery', value: glj.delivery },
                // 送达地点
                { name: 'Location', value: glj.location },
                // 产地
                { name: 'ProducingArea', value: glj.producingArea },
                // 供应商
                { name: 'Supplier', value: glj.supplier },
                // 质量要求
                { name: 'Character', value: glj.character },
                // 备注
                { name: 'Remark', value: glj.remark }
            ];
            _base.Element.call(this, 'LabourMaterialsEquipmentsMachinesSummary', attrs, '人材机汇总');
        }
        // 评标主要材料
        function BidEvaluationMainMaterial(glj) {
            const attrs = [
                // 序号
                { name: 'Code', dName: '序号', required: true, value: glj.seq },
                // 编码
                { name: 'Number', value: glj.code },
                // 名称
                { name: 'Name', dName: '名称', required: true, value: glj.name },
                // 规格型号
                { name: 'Specification', value: glj.specs },
                // 单位
                { name: 'Unit', dName: '单位', required: true, value: glj.unit },
                // 市场价
                { name: 'Price', type: _type.DECIMAL, value: glj.marketPrice },
                // 最高限价
                { name: 'LimitedPrice', type: _type.DECIMAL, value: '0' },
                // 总消耗量
                { name: 'Quantity', type: _type.DECIMAL, value: glj.quantity },
                // 金额  Round(市场价*总消耗量,2)
                { name: 'Total', type: _type.DECIMAL, value: scMathUtil.roundForObj(glj.marketPrice * glj.quantity, 2) },
                // 交货方式
                { name: 'Delivery', type: _type.DECIMAL, value: glj.delivery },
                // 送达地点
                { name: 'DeliPlace', value: glj.delivery_address },
                // 产地
                { name: 'ProducingArea', value: glj.originPlace },
                // 供应商
                { name: 'Supplier', value: glj.vender },
                // 质量要求
                { name: 'Character', value: glj.qualityGrace },
                // 备注
                { name: 'Remark', value: glj.remark }
            ];
            _base.Element.call(this, 'BidEvaluationMainMaterial', attrs, '评标主要材料');
        }


        // 建设项目节点
        let constructionProjectEle = null;

        /*
        * 加载数据，分两种数据类型
        * 1.建设项目
        * 2.建设项目下单位工程
        * @param  {Object}项目数据
        * @return {Array}
        * */
        async function loadData(projectData) {
            // 标记自检提示的开始（一次性多出多个文件类型，会导出多次）
            _failList.push(_config.HINT_START);
            // 提取出的数据
            const extractData = [];
            // 建设项目部分
            constructionProjectEle = await loadConstructionProject(projectData);
            extractData.push({
                data: constructionProjectEle,
                dataType: DATA_TYPE.PROJECT,
                exportKind,
                fileName: 'Project.xml'
            });
            // 单位工程部分
            // 提取出单位工程级别的项目数据
            const tenderData = projectData.children.reduce((acc, cur) => {
                if (Array.isArray(cur.children)) {
                    acc.push(...cur.children)
                }
                return acc;
            }, []);
            // 获取单位工程详细结构
            const isPlainTender = false;
            const tenders = await loadTenders(projectData.summaryInfo, tenderData, isPlainTender);
            tenders.forEach(tender => {
                // 单位工程文件名为@_单位工程编码_单位工程名称.xml
                // 因为单位工程编码后续用户可能会改变，这里暂时将fileName设置成单位工程名称
                const name = tender.attrs.find(attr => attr.name === 'Name');
                extractData.push({
                    data: tender,
                    dataType: DATA_TYPE.TENDER,
                    exportKind,
                    fileName: name
                });
            });
            // 建设项目表的人材机汇总、评标材料汇总
            const constructionGLJSummary = loadConstructionGLJSummary();
            const constructionBidEvaluations = loadConstructionBidEval();
            const projectInstallationWorkCost = constructionProjectEle.children.find(ele => ele.name === 'ProjectInstallationWorkCost');
            projectInstallationWorkCost.children.push(...constructionGLJSummary, ...constructionBidEvaluations);
            return extractData;
        }

        /*
        * 加载建设项目
        * @param  {Object}项目数据
        * @return {Array}
        * */
        async function loadConstructionProject(projectData) {
            const summaryInfo = projectData.summaryInfo[projectData.ID];
            // 建设项目
            const constructionProject = new ConstructionProject(projectData, summaryInfo);
            // 系统信息
            // 软件相关信息进行base64编码(gbk to base64)
            const hardwareID = _util.generateHardwareId();
            const encodedData = await encodingUtil.getEncodedData([projectData.softInfo, hardwareID], 'gbk', true);
            const ID1 = encodedData[projectData.softInfo];
            const ID2 = encodedData[hardwareID];
            const systemInfo = new SystemInfo({
                ID1,
                ID2,
                makeDate: moment(Date.now()).format('YYYY-MM-DDTHH:mm:ss'),
            });
            // 工程信息
            const constructionInfo = loadConstructionInfo(projectData);
            // 费用汇总
            const summaryCost = new SummaryOfCost(summaryInfo);
            // 建筑安装工程费
            const projectInstall = new ProjectInstallationWorkCost(projectData, summaryInfo);
            // 单项工程
            const sectionalWorks = await loadEngineering(projectData.summaryInfo, projectData.children);
            // 人材机汇总
            projectInstall.children.push(...sectionalWorks);
            constructionProject.children.push(
                systemInfo,
                constructionInfo,
                summaryCost,
                projectInstall
            );
            return constructionProject;
        }

        // 加载工程信息相关元素
        function loadConstructionInfo(projectData) {
            const basicInformation = projectData.property.basicInformation;
            const summaryInfo = projectData.summaryInfo[projectData.ID];
            // 工程信息
            const constructionInfo = new ConstructionInfo();
            // 费用精度
            const option = new Option();
            // 估（概、预、结）算信息
            const projectInfo = new ProjectInfo(basicInformation, summaryInfo);
            let exportInfo;
            if (exportKind === _config.EXPORT_KIND.Tender) {
                // 投标文件输出招标、投标信息
                exportInfo = [
                    new TendereeInfo(basicInformation, summaryInfo),
                    new BidderInfo(basicInformation, summaryInfo)
                ];
            } else {
                // 招标或控制价文件只输出招标信息
                exportInfo = [new TendereeInfo(basicInformation, summaryInfo)];
            }
            constructionInfo.children.push(option, projectInfo, ...exportInfo);
            return constructionInfo;
        }

        /*
        * 加载所有单项工程元素
        * @param  {Object}summaryInfoMap 总的汇总价格映射表(与项目ID映射)
        *         {Array}engineeringData 所有单项工程项目数据
        * @return {Array}
        * */
        async function loadEngineering(summaryInfoMap, engineeringData) {
            const isPlainTender = true;
            const sectionalWorks = [];
            for (const engData of engineeringData) {
                const summaryInfo = summaryInfoMap[engData.ID];
                // 单项工程
                const sectionalWork = new SectionalWorks({
                    name: engData.name,
                    total: summaryInfo.engineeringCost,
                    rate: summaryInfo.rate,
                });
                // 费用汇总
                const summaryCost = new SummaryOfCost(summaryInfo);
                // 简单结构的单位工程
                const tenders = await loadTenders(summaryInfoMap, engData.children, isPlainTender);
                sectionalWork.children.push(summaryCost, ...tenders);
                sectionalWorks.push(sectionalWork);
            }
            return sectionalWorks;
        }

        // 项目人材机code-data映射，减少循环次数，方便组成物定额人材机等数据可通过编码获取完整数据
        let projectGLJCodeMap;
        function setupprojectGLJCodeMap(projectGLJList) {
            const map = {};
            projectGLJList.forEach(glj => map[glj.code] = glj);
            return map;
        }

        /*
         * 加载所有单位工程元素
         * @param  {Object}summaryInfoMap 总的汇总价格映射表(与项目ID映射)
         *         {Array}tenderData 所有单项工程项目数据
         *         {Boolean}isPlain 是否简单结构(简单结构只含有UnitWorks，其下不含子元素)
         * @return {Array}
         * */
        let tenderDetail = null;
        let usedStdBills = []; // 造价书里使用到的清单库里的标准清单
        async function loadTenders(summaryInfoMap, tenderData, isPlain) {
            const unitWorks = [];
            for (const tData of tenderData) {
                // 需要请求项目详细数据的时候，间隔一段时间再初始单位工程数据，减少服务器压力
                const tenderDetailMap = _cache.getItem('tenderDetailMap');
                if (!tenderDetailMap[tData.ID]) {
                    await _util.setTimeoutSync(() => { }, _config.TIMEOUT_TIME);
                }
                // 获取单位工程详细数据
                tenderDetail = await _util.getTenderDetail(tData.ID, userID);
                // 先计算人材机总消耗量，以供后面需要
                gljUtil.calcProjectGLJQuantity(tenderDetail.projectGLJ.datas,
                    tenderDetail.ration_glj.datas, tenderDetail.Ration.datas, tenderDetail.Bills.datas, Decimal.GLJ, _, scMathUtil);
                const summaryInfo = summaryInfoMap[tData.ID];
                const defaultRationLib = tenderDetail.projectInfo.engineeringInfo.ration_lib.find(lib => lib.isDefault);
                const defaultRationLibName = defaultRationLib.name;
                // 单位工程
                const unitWork = new UnitWorks(projectData, tData, defaultRationLibName, summaryInfo);
                // 简单结构，不需要继续添加子元素
                if (isPlain) {
                    unitWorks.push(unitWork);
                    continue;
                }
                projectGLJCodeMap = setupprojectGLJCodeMap(tenderDetail.projectGLJ.datas.gljList);
                // 不是简单结构，继续添加各种子元素
                // 方便用户看错误来自哪个单位工程
                _failList.push(`<span style="font-weight: bold">单位工程“${tData.name}”下：</span>`);
                const orgLen = _failList.length;
                // 工程特征信息
                const attrInfo = loadAttrInfo(tData.property.projectFeature);
                // 基本信息
                const addiInfo = loadAddiInfo(projectData.property.basicInformation);
                // 费用汇总
                const summaryOfCost = new SummaryOfCost(summaryInfo);
                /* // 获取单位工程详细数据
                tenderDetail = await _util.getTenderDetail(tData.ID, userID);
                // 先计算人材机总消耗量，以供后面需要
                gljUtil.calcProjectGLJQuantity(tenderDetail.projectGLJ.datas,
                    tenderDetail.ration_glj.datas, tenderDetail.Ration.datas, tenderDetail.Bills.datas, Decimal.GLJ, _, scMathUtil); */

                // 单位工程费用汇总
                const unitWorksSummary = loadUnitWorksSummary(tenderDetail);
                // 获取标准清单编码-取费类别映射表
                usedStdBills = await getUsedStdBills(tenderDetail.Bills.datas);
                // 分部分项工程
                const divisionalElements = loadDivisionalAndElementalWorks();
                // 措施项目
                const preliminaries = loadPreliminaries();
                // 其他项目
                const sundry = loadSundry();
                // 规费
                const statutoryFees = new StatutoryFees();
                // 税金
                const tax = loadTax();
                // 人材机汇总
                const gljSummary = loadGLJSummary();
                // 评标材料
                const bidEvaluations = loadBidEvaluation();
                unitWork.children.push(
                    attrInfo,
                    addiInfo,
                    summaryOfCost,
                    unitWorksSummary,
                    divisionalElements,
                    preliminaries,
                    sundry,
                    statutoryFees,
                    tax,
                    ...gljSummary,
                    ...bidEvaluations
                );
                // 只有方便用户看错误来自哪个单位工程的提示，说明该单位工程没有报错
                if (_failList.length === orgLen) {
                    _failList.pop();
                }
                unitWorks.push(unitWork);
            }
            return unitWorks;
        }

        // 获取清单的取费类别，对应的是标准清单的工程专业
        function getBillsProgramID(bills) {
            if (bills.programID) {
                return bills.programID;
            }
            const defaultProgramID = 1;
            const reg = /^\d{12}$/;
            if (!bills.billsLibId || !reg.test(bills.code)) {
                return defaultProgramID;
            } else {
                const nineCode = bills.code.substr(0, 9);
                const matched = usedStdBills.find(stdItem => stdItem.code === nineCode && stdItem.billsLibId === bills.billsLibId);
                return matched && matched.engineering || defaultProgramID;
            }
        }

        // 获取造价书中使用到的标准清单
        async function getUsedStdBills(allBills) {
            // 标准清单添加到造价书后应该是12位编码
            const reg = /^\d{12}$/;
            const map = allBills
                .filter(bills => !bills.programID && bills.billsLibId && reg.test(bills.code))
                .reduce((acc, bills) => {
                    if (!acc[bills.billsLibId]) {
                        acc[bills.billsLibId] = [];
                    }
                    const nineCode = bills.code.substr(0, 9);
                    acc[bills.billsLibId].push(nineCode);
                    return acc;
                }, {});
            const queries = Object
                .entries(map)
                .map(([billsLibId, codes]) => ({ billsLibId, code: { $in: codes } }));
            const occupancy = 'code engineering billsLibId'
            return await ajaxPost('/stdBillsEditor/getBillsByQueries', { queries, occupancy });
        }

        // 加载工程特征信息
        // attrList 单位工程的工程特征，来自property.projectFeature
        function loadAttrInfo(attrList) {
            const attrInfo = new Info('AttrInfo');
            attrInfo.children = loadItems(attrList);
            return attrInfo;

            // 递归导出子项
            function loadItems(items) {
                const children = [];
                items.forEach(item => {
                    const infoItem = new InfoItem('AttrInfoItem', item);
                    children.push(infoItem);
                    if (item.items && item.items.length) {
                        infoItem.children = loadItems(item.items);
                    }
                });
                return children;
            }
        }

        // 加载补充信息（基本信息所有条目）
        function loadAddiInfo(infoList) {
            const addiInfo = new Info('AddiInfo');
            addiInfo.children = infoList.map(rootInfoItem => {
                const rootInfo = new InfoItem('AddiInfoItem', rootInfoItem);
                rootInfo.children = rootInfoItem.items.map(infoItem => new InfoItem('AddiInfoItem', infoItem));
                return rootInfo;
            });
            return addiInfo;
        }

        // 加载单位工程费用汇总
        // tenderDetail 单位工程详细数据
        function loadUnitWorksSummary() {
            const unitWorksSummary = new UnitWorksSummary();
            // 读取单位工程中的固定类别为“分部分项工程”、“措施项目”、“其他项目”的大项费用
            // 分部分项
            const fbfx = tenderDetail.mainTree.roots.find(node => node.getFlag() === fixedFlag.SUB_ENGINERRING);
            const fbfxGroup = new UnitWorksSummaryGroup(fbfx.data);
            // 分部分项工程的子项，取项目名称的首字母拼接
            const fbfxChildren = fbfx.children.map(node => new UnitWorksSummaryItem(node, true));
            fbfxGroup.children.push(...fbfxChildren);
            // 措施项目
            const csxm = tenderDetail.mainTree.roots.find(node => node.getFlag() === fixedFlag.MEASURE);
            const csxmGroup = new UnitWorksSummaryGroup(csxm.data);
            // 措施项目的子项，“绿色施工安全防护措施费”、“其他措施费”
            const csxmChildren = csxm.getPosterity()
                .filter(node => [fixedFlag.SAFETY_CONSTRUCTION, fixedFlag.OTHER_MEASURE_FEE].includes(node.getFlag()))
                .map(node => new UnitWorksSummaryItem(node));
            csxmGroup.children.push(...csxmChildren);
            // 其他项目
            const other = tenderDetail.mainTree.roots.find(node => node.getFlag() === fixedFlag.OTHER);
            const otherGroup = new UnitWorksSummaryGroup(other.data);
            // 其他项目子项，暂列金额；暂估价；材料（工程设备）暂估价；专业工程暂估价；计日工；总承包服务费；预算包干费；工程优质费；概算幅度差；索赔费用；现场签证费用；其他费用
            const otherChildrenFlags = [
                fixedFlag.PROVISIONAL,
                fixedFlag.ESTIMATE,
                fixedFlag.MATERIAL_PROVISIONAL,
                fixedFlag.ENGINEERING_ESITIMATE,
                fixedFlag.DAYWORK,
                fixedFlag.TURN_KEY_CONTRACT,
                fixedFlag.BUDGET_INCLUDE_WORK_FEE,
                fixedFlag.PROJECT_HIGH_QUALITY_FEE,
                fixedFlag.BUDGET_ESTIMATE_DIFF,
                fixedFlag.CLAIM,
                fixedFlag.VISA,
                fixedFlag.OTHER_FEE
            ];
            const otherChildren = other.getPosterity()
                .filter(node => otherChildrenFlags.includes(node.getFlag()))
                .map(node => new UnitWorksSummaryItem(node));
            otherGroup.children.push(...otherChildren);
            // 除固定类别为“分部分项工程”、“措施项目”、“其他项目”以外的大项费用，通常无子项，所以显示到Item中。（即使造价书有子项，也显示到Item中，不再列出其子项数据）
            const flags = [fixedFlag.SUB_ENGINERRING, fixedFlag.MEASURE, fixedFlag.OTHER];
            const otherRoots = tenderDetail.mainTree.roots
                .filter(node => !flags.includes(node.getFlag()))
                .map(node => new UnitWorksSummaryItem(node));
            unitWorksSummary.children.push(fbfxGroup, csxmGroup, otherGroup, ...otherRoots);
            return unitWorksSummary;
        }

        // 加载分部分项工程
        function loadDivisionalAndElementalWorks() {
            const division = new DivisionalAndElementalWorks();
            const subEngineering = tenderDetail.mainTree.roots.find(node => node.getFlag() === fixedFlag.SUB_ENGINERRING);
            // 合计费用
            const summaryCost = new SummaryOfBasicCost(tenderDetail.mainTree.items, subEngineering);
            // 分部、分项
            const fbfx = loadFBFX(subEngineering.children, BillsKind.FBFX);
            division.children.push(summaryCost, ...fbfx);
            return division;
        }

        // 加载措施项目
        function loadPreliminaries() {
            const preliminaries = new Preliminaries();
            const measure = tenderDetail.mainTree.roots.find(node => node.getFlag() === fixedFlag.MEASURE);
            // 合计费用
            const summaryCost = new SummaryOfBasicCost(tenderDetail.mainTree.items, measure);
            // 分部、分项
            const fbfx = loadFBFX(measure.children, BillsKind.MEASURE);
            preliminaries.children.push(summaryCost, ...fbfx);
            return preliminaries;
        }

        // 加载分部分项清单，这部分是分部分项工程和措施项目共用的
        function loadFBFX(nodes, kind) {
            return nodes.map(node => {
                // 措施项目是叶子：如果无单位、且无定额、且综合合价=0时，判断为DivisionalWorks；否则，判断为WorkElement。
                const isDivisionalMeasure = kind === BillsKind.MEASURE && !node.children.length && !node.data.unit && !+_util.getFee(node.data.fees, 'common.totalFee');
                let ele;
                // 有子清单的是分部
                if (node.source.children.length || isDivisionalMeasure) {
                    ele = new DivisionalWorks(node.data);
                    const summaryCost = new SummaryOfBasicCost(tenderDetail.mainTree.items, node);
                    // 递归获取子元素
                    ele.children = [summaryCost, ...loadFBFX(node.children, kind)];
                } else { // 无子清单的是分项(分部分项部分)
                    ele = loadBills(node, kind);
                }
                return ele;
            });
        }

        // 加载清单
        function loadBills(node, kind) {
            const workElement = new WorkElement(node, kind);
            // 合计费用
            const summaryCost = new SummaryOfBasicCost(tenderDetail.mainTree.items, node);
            workElement.children.push(summaryCost);
            // 工程量计算表
            const expressElement = loadQuantityExpressions(tenderDetail.quantity_detail.datas, true, node.data.ID);
            workElement.children.push(...expressElement);
            // 相同工作内容的定额进行分组。若工作内容为空，则定额直接下挂到清单下
            const workMap = _.groupBy(node.children, node => node.data.jobContentText || '');
            const valueWorks = [];
            const emptyWorkRationNodes = [];
            Object
                .entries(workMap)
                .forEach(([contentText, rationNodes]) => {
                    if (contentText) {
                        valueWorks.push({ contentText, rationNodes });
                    } else {
                        emptyWorkRationNodes.push(...rationNodes);
                    }
                });
            const workContents = valueWorks.map(({ contentText, rationNodes }) => {
                const workContent = new WorkContent(contentText, _util.getAggregateFee(rationNodes));
                workContent.children = rationNodes.map(node => loadRation(node, kind));
                return workContent;
            });
            workElement.children.push(...workContents);
            const rations = emptyWorkRationNodes.map(node => loadRation(node, kind));
            workElement.children.push(...rations);
            return workElement
        }

        // 加载工程量计算表
        function loadQuantityExpressions(quantityDetails, isBills, ID) {
            if (!quantityDetails.length) {
                return [];
            }
            quantityDetails.sort((a, b) => a.seq - b.seq);
            const key = isBills ? 'billID' : 'rationID';
            return quantityDetails
                .filter(quantityDetail => quantityDetail[key] === ID)
                .map(quantityDetail => new ExpressElement(quantityDetail));
        }

        // 获取定额的定额库编码
        function getLibCode(ration, rationLibs) {
            const theLib = rationLibs.find(lib => lib.id === ration.libID);
            return theLib ? theLib.libCode : '';
        }
        // 获取NoCost 如果类型是主材或者设备，输出=“true”，否则为"false"。
        function getNoCost(projectGLJCodeMap, code) {
            const noCostType = [4, 5];
            const glj = projectGLJCodeMap[code];
            return glj && noCostType.includes(glj.type) ? 'true' : 'false';
        }
        // 加载定额
        function loadRation(node, kind) {
            // 定额
            const libCode = getLibCode(node.data, tenderDetail.projectInfo.engineeringInfo.ration_lib);
            const norm = new Norm(node, libCode, kind);
            // 工程量计算表
            const expressElement = loadQuantityExpressions(tenderDetail.quantity_detail.datas, false, node.data.ID);
            if (expressElement.length) {
                norm.children.push(...expressElement);
            }
            // 触发计算程序计算，否则定额calcTemplate数据必为空
            calcProgramObj.refreshCalcProgram(node, 4);
            // 子目管理费
            const manageFees = new ManageFees(node.data.calcTemplate);
            norm.children.push(manageFees);
            // 定额人材机含量明细
            let rationGLJs = tenderDetail.ration_glj.datas.filter(rGLJ => rGLJ.rationID === node.data.ID);
            rationGLJs = gljUtil.sortRationGLJ(rationGLJs); // 定额人材机排序
            const rationGLJElements = rationGLJs
                .filter(rGLJ => {
                    // 总消耗量为0的，不输出
                    const totalQuantity = gljUtil.getTotalQuantity(rGLJ, node.data, Decimal.QUANTITY, Decimal.GLJ);
                    const parsedTotalQuantity = parseFloat(totalQuantity);
                    return parsedTotalQuantity;
                })
                .map(rGLJ => {
                    //const totalQuantity = gljUtil.getTotalQuantity(rGLJ, node.data, Decimal.QUANTITY, Decimal.GLJ);
                    const noCost = getNoCost(projectGLJCodeMap, rGLJ.code);
                    return new LabourMaterialsEquipmentsMachinesElement(rGLJ, rGLJ.quantity, noCost);
                });
            norm.children.push(...rationGLJElements);
            // 子目单价计算（定额计算程序）
            if (node.data.calcTemplate) {
                const unitPriceCalculationOfItems = node.data.calcTemplate.calcItems.map(item => new UnitPriceCalculationOfItem(item));
                norm.children.push(...unitPriceCalculationOfItems);
            }
            return norm;
        }

        // 递归加载标题和子目
        function loadGroupAndItems(nodes, groupFactory, itemFactory) {
            return nodes.map(node => {
                let ele;
                // 有子清单的是标题
                if (node.source.children.length) {
                    ele = groupFactory(node);
                    // 递归获取子元素
                    ele.children = loadGroupAndItems(node.children, groupFactory, itemFactory);
                } else { // 无子清单的是子目（除了计日工，计日工最底层节点也需要是标题，否则检测平台会报错）
                    ele = itemFactory(node);
                }
                return ele;
            });
        }

        // 加载其他项目
        function loadSundry() {
            // 加载材料设备暂估价明细
            // 业务上这个清单是使用清单基数计算得来，所以此部分读取整个单位工程下打勾暂估的材料。is_evaluate = 1 
            function loadProvisionalMaterialDetail(gljList) {
                return gljList
                    .filter(glj => glj.is_evaluate)
                    .map(glj => {
                        const price = gljUtil.getGLJPrice(glj, tenderDetail.projectGLJ.datas, // 价格信息
                            tenderDetail.projectInfo.property.calcOptions, tenderDetail.labourCoe.datas, tenderDetail.projectInfo.property.decimal, false, _, scMathUtil);
                        return new ProvisionalMaterialEquipmentItem(glj, price);
                    });
            }

            const sundry = new Sundry();
            // 其他项目费
            const other = tenderDetail.mainTree.roots.find(node => node.getFlag() === fixedFlag.OTHER);
            const sundryCosts = new SundryItem('SundryCosts', other.data);
            sundryCosts.children = loadGroupAndItems(
                other.children,
                (node) => new SundryCostsGroup(node),
                (node) => new SundryCostsItem(node)
            );
            // 暂列金额
            const provisional = tenderDetail.mainTree.items.find(node => node.getFlag() === fixedFlag.PROVISIONAL);
            const provisionalSums = new SundryItem('ProvisionalSums', provisional.data);
            provisionalSums.children = loadGroupAndItems(
                provisional.children,
                (node) => new ProvisionalSumsGroup(node),
                (node) => new ProvisionalSumsItem(node)
            );
            // 材料设备暂估价
            const provisionalMaterial = tenderDetail.mainTree.items.find(node => node.getFlag() === fixedFlag.MATERIAL_PROVISIONAL);
            const provisionalMaterialEle = new SundryItem('ProvisionalMaterialEquipment', provisionalMaterial.data);
            provisionalMaterialEle.children = loadProvisionalMaterialDetail(tenderDetail.projectGLJ.datas.gljList);
            // 专业工程暂估价
            const specialtyProvisionalNode = tenderDetail.mainTree.items.find(node => node.getFlag() === fixedFlag.ENGINEERING_ESITIMATE);
            const specialtyProvisional = new SundryItem('SpecialtyProvisionalPrice', specialtyProvisionalNode.data);
            specialtyProvisional.children = loadGroupAndItems(
                specialtyProvisionalNode.children,
                (node) => new SpecialtyProvisionalPriceGroup(node),
                (node) => new SpecialtyProvisionalPriceItem(node)
            );
            // 计日工
            const daywork = tenderDetail.mainTree.items.find(node => node.getFlag() === fixedFlag.DAYWORK);
            const dayworkRate = new SundryItem('DayWorkRate', daywork.data);
            // 计日工最多只能有两层子项
            const isValidDepth = _util.validDepth(2, daywork);
            if (!isValidDepth) {
                _failList.push('计日工子项超过两层')
            } else {
                // 计日工最底层节点也需要是标题，否则检测平台会报错(不过这样的话xsd)
                dayworkRate.children = loadGroupAndItems(
                    daywork.children,
                    (node) => new DayWorkRateGroup(node),
                    (node) => new DayWorkRateGroup(node)
                );
            }
            // 总承包服务费
            const turnKeyContract = tenderDetail.mainTree.items.find(node => node.getFlag() === fixedFlag.TURN_KEY_CONTRACT);
            const mainContractorAttendance = new SundryItem('MainContractorAttendance', turnKeyContract.data);
            mainContractorAttendance.children = loadGroupAndItems(
                turnKeyContract.children,
                (node) => new MainContractorAttendanceGroup(node),
                (node) => new MainContractorAttendanceItem(node)
            );
            // 索赔费用
            const claim = tenderDetail.mainTree.items.find(node => node.getFlag() === fixedFlag.CLAIM);
            const claimsCost = new SundryItem('ClaimsCost', claim.data);
            claimsCost.children = loadGroupAndItems(
                claim.children,
                (node) => new ClaimVisaCostGroup('ClaimsCostGroup', node),
                (node) => new ClaimVisaCostItem('ClaimsCostItem', node)
            );
            // 签证费用
            const visa = tenderDetail.mainTree.items.find(node => node.getFlag() === fixedFlag.VISA);
            const siteInstructionCost = new SundryItem('SiteInstructionCost', visa.data);
            siteInstructionCost.children = loadGroupAndItems(
                visa.children,
                (node) => new ClaimVisaCostGroup('SiteInstructionCostGroup', node),
                (node) => new ClaimVisaCostItem('SiteInstructionCostItem', node)
            );
            sundry.children.push(sundryCosts, provisionalSums, provisionalMaterialEle, specialtyProvisional, dayworkRate, mainContractorAttendance, claimsCost, siteInstructionCost);
            return sundry;
        }

        // 加载税金
        function loadTax() {
            const tax = tenderDetail.mainTree.roots.find(node => node.getFlag() === fixedFlag.TAX);
            const taxEle = new Tax('Tax', tax);
            // 加载税金子项，若无税金子项，税金本身也作为子项
            const taxChildren = tax.source.children.length ? tax.source.children : [tax];
            taxEle.children = taxChildren.map(taxItem => new Tax('TaxItem', taxItem));
            return taxEle;
        }

        // 建设项目人材机汇总的映射表
        // 映射除了消耗量的字段，字段与字段间用特殊符号隔开，并作为映射的key，消耗量作为value
        // 当除了消耗量和用消耗量关联计算的数据以外的字段都相等（包括组成物数据）时，认为是相同的人材机，不汇总消耗量，否则汇总消耗量
        const allGLJMap = {};
        const splitStr = '{@}'; // 字段间分割符
        const splitKeys = [
            'code', 'name', 'specs', 'unit', 'riskCoe', 'standardPrice',
            'basePrice', 'marketPrice', 'varWeight', 'FO', 'FI', 'kind', 'isConcrete',
            'isMainMaterial', 'isProvisionalMaterial', 'provider', 'delivery', 'location',
            'producingArea', 'supplier', 'character', 'remark'
        ];
        // 合并人材机汇总数据（合并消耗量）
        function mergeGLJSummary(gljSummaryItem) {
            const connectKey = splitKeys.reduce((acc, key, index) => {
                return `${acc}${index === 0 ? '' : splitStr}${gljSummaryItem[key]}`;
            }, '');
            gljSummaryItem.quantity = +gljSummaryItem.quantity; // 防止汇总的时候变成字符串拼接
            if (!allGLJMap[connectKey]) {
                // 不需要合并组成物，以第一条人材机的组成物为准
                allGLJMap[connectKey] = { serialNo: Object.keys(allGLJMap).length, quantity: gljSummaryItem.quantity, ratios: gljSummaryItem.ratios };
            } else {
                allGLJMap[connectKey].quantity = scMathUtil.roundForObj(allGLJMap[connectKey].quantity + gljSummaryItem.quantity, Decimal.GLJ);
            }
        }
        // 加载建设项目人材机汇总数据
        function loadConstructionGLJSummary() {
            const gljList = [];
            Object
                .entries(allGLJMap)
                .forEach(([connectKey, { serialNo, quantity, ratios }]) => {
                    const glj = connectKey
                        .split(splitStr)
                        .reduce((obj, value, index) => {
                            const attr = splitKeys[index];
                            obj[attr] = value;
                            return obj;
                        }, {});
                    glj.quantity = quantity;
                    glj.serialNo = serialNo;
                    glj.ratios = ratios;
                    gljList.push(glj);
                });
            return gljList
                .sort((a, b) => a.serialNo - b.serialNo)
                .map(gljSummaryItem => {
                    const gljEle = new LabourMaterialsEquipmentsMachinesSummary(gljSummaryItem);
                    gljEle.children = gljSummaryItem.ratios.map(ratio => {
                        const noCost = getNoCost(projectGLJCodeMap, ratio.code);
                        return new LabourMaterialsEquipmentsMachinesElement(ratio, ratio.consumption, noCost);
                    });
                    return gljEle;
                });
        }

        // 加载人材机汇总
        function loadGLJSummary() {
            // 所有人材机
            const gljList = gljUtil.sortRationGLJ(tenderDetail.projectGLJ.datas.gljList); // 汇总排序
            const contractorList = tenderDetail.contractor_list.datas;
            const decimalObj = tenderDetail.projectInfo.property.decimal;
            // 总承包人造价信息差额调整法数据
            const infoDatas = materialAdjustObj.getPirceInfoDatas(gljList, contractorList, decimalObj)
            // 总承包人价格指数调整法数据
            const engineeringCostNode = tenderDetail.Bills.tree.roots.find(node => node.getFlag() === fixedFlag.ENGINEERINGCOST);
            const ecTotalFee = _util.getFee(engineeringCostNode.data.fees, 'common.totalFee');
            const coeDatas = materialAdjustObj.getPriceCoeDatas(gljList, contractorList, ecTotalFee, tenderDetail.projectInfo.property);
            return gljList
                .filter(glj => !!+glj.quantity) // 总消耗量为0不导出
                .map(glj => {
                    // 人材机
                    const price = gljUtil.getGLJPrice(glj, tenderDetail.projectGLJ.datas, // 价格信息
                        tenderDetail.projectInfo.property.calcOptions, tenderDetail.labourCoe.datas, tenderDetail.projectInfo.property.decimal, false, _, scMathUtil);
                    // 人材机对应的造价信息差额数据
                    const infoData = infoDatas.find(item => item.projectGLJID === glj.id);
                    // 人材机对应的价格指数数据
                    const coeData = coeDatas.find(item => item.projectGLJID === glj.id);
                    const gljSummaryItem = getGLJSummaryItem(glj, price, infoData, coeData);
                    const gljEle = new LabourMaterialsEquipmentsMachinesSummary(gljSummaryItem);
                    // 人材机组成物
                    const connectKey = gljUtil.getIndex(glj, gljKeyArray);
                    const ratios = tenderDetail.projectGLJ.datas.mixRatioMap[connectKey] || [];
                    gljSummaryItem.ratios = ratios;
                    gljEle.children = ratios.map(ratio => {
                        const noCost = getNoCost(projectGLJCodeMap, ratio.code);
                        return new LabourMaterialsEquipmentsMachinesElement(ratio, ratio.consumption, noCost);
                    });
                    mergeGLJSummary(gljSummaryItem); // 合并人材机消耗量，为输出建设项目表下的人材机汇总做准备
                    return gljEle;
                });
        }

        // 建设项目评标材料的映射表
        // 映射除了消耗量的字段，字段与字段间用特殊符号隔开，并作为映射的key，消耗量作为value
        const allBidEvalMap = {};
        const bidEvalSplitKeys = [
            'seq', 'code', 'name', 'specs', 'unit', 'marketPrice', 'delivery',
            'delivery_address', 'originPlace', 'vender', 'qualityGrace', 'remark'
        ];
        // 合并数据（合并消耗量）
        function mergeBidEval(glj) {
            const connectKey = bidEvalSplitKeys.reduce((acc, key, index) => {
                return `${acc}${index === 0 ? '' : splitStr}${glj[key]}`;
            }, '');
            glj.quantity = +glj.quantity; // 防止汇总的时候变成字符串拼接
            if (!allBidEvalMap[connectKey]) {
                allBidEvalMap[connectKey] = { serialNo: Object.keys(allBidEvalMap).length, quantity: glj.quantity };
            } else {
                allBidEvalMap[connectKey].quantity = scMathUtil.roundForObj(allBidEvalMap[connectKey].quantity + glj.quantity, Decimal.GLJ);
            }
        }
        // 加载建设项目评标材料数据
        function loadConstructionBidEval() {
            const gljList = [];
            Object
                .entries(allBidEvalMap)
                .forEach(([connectKey, { serialNo, quantity }]) => {
                    const glj = connectKey
                        .split(splitStr)
                        .reduce((obj, value, index) => {
                            const attr = bidEvalSplitKeys[index];
                            obj[attr] = value;
                            return obj;
                        }, {});
                    glj.quantity = quantity;
                    glj.serialNo = serialNo;
                    gljList.push(glj);
                });
            return gljList
                .sort((a, b) => a.serialNo - b.serialNo)
                .map(glj => new BidEvaluationMainMaterial(glj));
        }

        // 加载评标材料
        function loadBidEvaluation() {
            const gljList = tenderDetail.projectGLJ.datas.gljList;
            const bidEvaluationSrc = tenderDetail.bid_evaluation_list.datas;
            const decimalObj = tenderDetail.projectInfo.property.decimal;
            const bidEvaluationList = configMaterialObj.getBidMaterialDatas(gljList, bidEvaluationSrc, decimalObj);
            return bidEvaluationList.map(glj => {
                const projectGLJ = gljList.find(pGLJ => pGLJ.id === glj.projectGLJID);
                if (projectGLJ) {
                    glj.delivery = projectGLJ.delivery;
                    glj.delivery_address = projectGLJ.delivery_address;
                    glj.originPlace = projectGLJ.originPlace;
                    glj.vender = projectGLJ.vender;
                    glj.qualityGrace = projectGLJ.qualityGrace;
                }
                const toMergeGLJ = _.cloneDeep(glj);
                // 需要提前处理空值的情况，将合并数据需要判断的字段值进行空值统一处理，防止空值值不同（null, undefined, ''）造成连接key判断错误
                toMergeGLJ.seq = toMergeGLJ.seq || '';
                toMergeGLJ.code = toMergeGLJ.code || '';
                toMergeGLJ.name = toMergeGLJ.name || '';
                toMergeGLJ.specs = toMergeGLJ.specs || '';
                toMergeGLJ.unit = toMergeGLJ.unit || '';
                toMergeGLJ.marketPrice = toMergeGLJ.marketPrice || '0';
                toMergeGLJ.delivery = toMergeGLJ.delivery || '';
                toMergeGLJ.delivery_address = toMergeGLJ.delivery_address || '';
                toMergeGLJ.originPlace = toMergeGLJ.originPlace || '';
                toMergeGLJ.vender = toMergeGLJ.vender || '';
                toMergeGLJ.qualityGrace = toMergeGLJ.qualityGrace || '';
                toMergeGLJ.remark = toMergeGLJ.remark || '';
                mergeBidEval(toMergeGLJ);
                return new BidEvaluationMainMaterial(glj);
            });
        }

        // 返回提取的数据
        return await loadData(projectData);
    }

    // 导出名称为建设项目名称
    let zipFileBaseName = '';

    /*
     * 重置工程编号，广东18 3.0的接口，导出时的文件会用到这个编号
     * 因此重置工程编号后，还需要重置文件名数据
     * @param  {Array}codes 工程编号
     *         {Array}ungroupedExtractData 提取的数据(可能存在不同导出类型，需要分组。比如同时导出招标文件和控制价文件)
     * @return {void}
     * */
    function resetContentCode(codes, ungroupedExtractData) {
        // 提取到的数据分两种类型：
        // 1.根元素为建设项目：需要修改建设项目下的单位工程元素的编码和文件名
        // 2.根元素为单位工程：需要修改单位工程的编码和文件名
        // 获取建设项目数据下的单位工程元素
        const groupedData = _.groupBy(ungroupedExtractData, 'exportKind');
        for (const exportKind in groupedData) {
            const extractData = groupedData[exportKind];
            const extractProject = extractData.find(data => data.dataType === DATA_TYPE.PROJECT);
            zipFileBaseName = _util.getAttr(extractProject.data, 'Name');
            const extractTenders = extractData.filter(data => data.dataType === DATA_TYPE.TENDER)
            // 建设项目元素下建筑安装工程费元素
            const projectInstallations = _util.getElementFromSrc(extractProject.data, 'ProjectInstallationWorkCost');
            // 建筑安装工程费元素下单项工程元素
            const sectionalWorks = projectInstallations.reduce((acc, cur) => {
                const sections = _util.getElementFromSrc(cur, 'SectionalWorks');
                acc.push(...sections);
                return acc;
            }, []);
            let idx = 0;
            // 单项工程
            sectionalWorks.forEach(sectionalWork => {
                _util.setAttr(sectionalWork, 'Number', codes[idx++]);
            });
            // 建筑安装工程费元素下单位工程元素, 顺序与提取的单位工程文件(extractTenders)是一一对应的
            const unitWorks = sectionalWorks.reduce((acc, cur) => {
                const tenders = _util.getElementFromSrc(cur, 'UnitWorks');
                acc.push(...tenders);
                return acc;
            }, []);
            unitWorks.forEach((unitWork, index) => {
                // 更改编号
                const code = codes[idx++];
                _util.setAttr(unitWork, 'Number', code);
                // 更改文件名:@_单位工程编码_单位工程名称.xml
                const name = _util.getAttr(unitWork, 'Name');
                const fileName = `@_${code}_${name}.xml`;
                _util.setAttr(unitWork, 'FileName', fileName);
                // 更改提取的单位工程详细文件的导出名称、单位工程节点编号、文件名
                const extractTender = extractTenders[index];
                extractTender.fileName = fileName;
                const extractUnitWork = extractTender.data;
                _util.setAttr(extractUnitWork, 'Number', code);
                _util.setAttr(extractUnitWork, 'FileName', fileName);
            });
        }
    }

    /*
    * 导出文件的结构逻辑，每个费用定额导出的最终文件结构要求可能不同，需要各自定义
    * 广东18费用定额，由建设项目文件和若干个单位工程文件组成。存储成 XML 文件时，
    * 建设项目文件应与单位工程文件分开，然后用ZIP算法技术压缩成一个后缀名为COS文件。
    * 同一类型（投标、招标、控制价）的文件被包含在一个cos文件中，有多个类型的文件则多个cos文件一个个导出
    * @param  {Array}fileData 文件数据
    * @return {void}
    * */
    async function saveAsFile(fileData) {
        // 根据导出类型分组
        const groupedData = _.groupBy(fileData, 'exportKind');
        for (const exportKind in groupedData) {
            const files = groupedData[exportKind];
            const zip = new JSZip();
            for (const file of files) {
                zip.file(file.fileName, file.blob, { binary: true });
            }
            // 导出的文件名需要用gbk编码，否则广联达导入不了
            // encodeFileName不支持异步，先调用一次generateAsync，获取string与Unit8Array的数据源映射            
            // 不用TextEncoder的原因：https://developer.mozilla.org/zh-CN/docs/Web/API/TextEncoder/TextEncoder
            // - 在Firefox 48和Chrome 53之前，编码类型标签被接受为TextEncoder对象的参数
            // - 现在这两个浏览器已经删除了除utf-8之外的任何编码器类型的支持，以符合规范。 传入TextEncoder构造函数的任何类型标签现在都将被忽略，并且将创建一个utf-8 TextEncoder。
            const source = new Set();
            await zip.generateAsync({
                type: 'uint8array',
                encodeFileName: (string) => {
                    source.add(string);
                    return string;
                }
            });
            const encodedData = await encodingUtil.getEncodedData([...source], 'gbk');
            const zipFile = await zip.generateAsync({
                type: 'uint8array',
                encodeFileName: (string) => {
                    const abSource = encodedData[string].data;
                    const ab = Uint8Array.from(abSource);
                    return ab;
                }
            });
            // TypedBuffer to ArrayBuffer to blob
            const saveAsBlob = new Blob([zipFile.buffer]);
            const exportKindName = _config.EXPORT_KIND_NAME[exportKind];
            saveAs(saveAsBlob, `${zipFileBaseName}(${exportKindName}).COS`);
        }
    }

    return {
        summaryObj,
        entry,
        resetContentCode,
        saveAsFile
    };

})();