'use strict'; const importXML = (() => { // 通用设置和工具 const config = importXMLBase.CONFIG; const util = importXMLBase.UTIL; const { fixedFlag, billType, rationType, projectType, TaxType, } = commonConstants; const { AdjustType } = config; const { getValue, arrayValue, getFee, mergeFees, assignAttr, mergeDataRecur, getFlag, getBool, getItemsRecur, extractItemsRecur, } = util; //导入的文件类型,界面选的文件类型是生成项目的文件类型,这里的文件类型指的是,要导入文件的类型, //导入文件类型不同,导入数据不同 let importFileKind = ''; // 投标文件和控制价文件是完全导入数据的 let isFullyImport = false; // 文件类型 const FileKind = { '6': 1, // 投标 'tender': 1, '4': 2, // 招标 'bid': 2, '5': 3, // 控制价 'control': 3, }; // 地区类别 const AreaKind = { '1': '一类地区', '2': '二类地区', '3': '三类地区', '4': '四类地区', } // 一些数据是需要从后端获取自增数字后赋值的,这里是记录,用完会在extractProject清空 let countData = { projectCount: 0, //项目数量 projectGLJCount: 0, //项目人材机数量 ratioCount: 0, //组成物数量 unitPriceCount: 0, //单价数量 unitPriceFileCount: 0, //单价文件数量 }; // 精度 const Decimal = { // 工料机消耗量、含量、用量类小数精度 GLJ: 4, // 工程量、数量类小数精度 QUANTITY: 3, // 金额、合价、费用类小数精度 FEE: 2, // 费率、指数、比例(%)类小数精度 RATE: 3, // 默认、中间过程 PROCESS: 6 }; // 根据上方精度要求得到的项目属性,小数位数的值(定额工程量小数位数需求改成六位) const tenderPropertyDecimal = { bills: { unitPrice: Decimal.FEE, totalPrice: Decimal.FEE }, ration: { quantity: Decimal.PROCESS, unitPrice: Decimal.FEE, totalPrice: Decimal.FEE }, glj: { quantity: Decimal.GLJ, unitPriceHasMix: Decimal.FEE, unitPrice: Decimal.FEE }, feeRate: Decimal.RATE, quantity_detail: 4, material: 5,//三材系数 process: 6 }; const tenderPropertyBillsQuantityDecimal = Decimal.QUANTITY; // 工程量表达式相关 const GCLMXHj = 'GCLMXHJ'; const QDL = 'QDL'; // 工程量表达式累加映射 const summationMap = { '1': 1, // 累加 '2': 0 // 不累加 }; /* * 从导入的xml文件中提取有用的数据 * */ // 建设项目 function extractProject(xmlObjMap) { Object.keys(countData).forEach(key => countData[key] = 0); // 清缓存 countData.projectCount++; const projectXMLObj = xmlObjMap['Project.xml']; const projectSrc = getValue(projectXMLObj, ['ConstructionProject']); importFileKind = FileKind[getValue(projectSrc, ['_FileKind'])]; // 标记当前导入的文件类型 isFullyImport = [FileKind.tender, FileKind.control].includes(importFileKind); const rst = { projType: projectType.Project, name: getValue(projectSrc, ['_Name']), engs: extractEngs(projectSrc, xmlObjMap), property: { compilationIllustration: getValue(projectSrc, ['_Explains']) }, basicInformation: extractBasicInfo(projectSrc) }; countData.unitPriceCount = countData.projectGLJCount; if (!isFullyImport) { countData = { projectCount: countData.projectCount, unitPriceFileCount: countData.unitPriceFileCount }; } return rst; } // 从xml对象中提取基本信息相关 function extractBasicInfo(projectSrc) { const projectInfo = getValue(projectSrc, ['ConstructionInfo', 'ProjectInfo']); // 估概预算信息 const tendereeInfo = getValue(projectSrc, ['ConstructionInfo', 'TendereeInfo']); // 招标信息 const bidderInfo = getValue(projectSrc, ['ConstructionInfo', 'BidderInfo']); // 投标信息 return [ { key: 'projNum', value: getValue(projectSrc, ['_Number']) }, // 编码 { key: 'projectType', value: getValue(projectSrc, ['_ProjectType']) }, // 工程类型 { key: 'projectCategory', value: getValue(projectSrc, ['_ProjectCategory']) }, // 工程类别 { key: 'constructionType', value: getValue(projectSrc, ['_ConstructionType']) }, // 建设性质 { key: 'regionalCategories', value: AreaKind[getValue(projectSrc, ['_AreaKind'])] }, // 地区类被 { key: 'projLocation', value: getValue(projectSrc, ['_ProjectSite']) }, // 工程地点 { key: 'constructingUnits', value: getValue(projectSrc, ['_BulidUnit']) }, // 建设单位 { key: 'constructingUnitsPerson', value: getValue(projectSrc, ['_BulidAuthorizer']) }, // 建设单位法定代表人或其授权人 { key: 'rangeOfCompilation', value: getValue(projectSrc, ['_RangeOfCompilation']) }, // 建设(编制)范围 { key: 'scale', value: getValue(projectSrc, ['_Scale']) }, // 建设规模 { key: 'unit', value: getValue(projectSrc, ['_Unit']) }, // 建设规模单位 { key: 'designUnits', value: getValue(projectInfo, ['_Designer']) }, // 设计单位 { key: 'constructionUnits', value: getValue(projectInfo, ['_Contractor']) }, // 承包单位 { key: 'establishUnit', value: getValue(projectInfo, ['_CompileCompany']) }, // 编制单位 { key: importFileKind === FileKind.tender ? 'bidCompileDate' : 'tenderCompileDate', value: getValue(projectInfo, ['_CompileDate']) }, // 编制时间 { key: 'authorizer', value: getValue(projectInfo, ['_Authorizer']) }, // 编制单位法定代表人或其授权人 { key: 'tendereeName', value: getValue(tendereeInfo, ['_TendereeName']) }, // 招标人 { key: 'tenderAuthorizer', value: getValue(tendereeInfo, ['_TenderAuthorizer']) }, // 招标单位法定代表人或其授权人 { key: 'tenderCompiler', value: getValue(tendereeInfo, ['_TenderCompiler']) }, // 招标单位编制人 { key: 'tenderCompilerCertNo', value: getValue(tendereeInfo, ['_TenderCompilerCertNo']) }, // 招标单位编制人资格证书编号 { key: 'tenderCompileDate', value: getValue(tendereeInfo, ['_TenderCompileDate']) }, // 招标单位编制时间 { key: 'tenderExaminer', value: getValue(tendereeInfo, ['_TenderExaminer']) }, // 招标单位审核人 { key: 'tenderExaminerCertNo', value: getValue(tendereeInfo, ['_TenderExaminerCertNo']) }, // 招标单位审核人资格证书编号 { key: 'tenderExamineDate', value: getValue(tendereeInfo, ['_TenderExamineDate']) }, // 招标单位审核时间 { key: 'tenderApprover', value: getValue(tendereeInfo, ['_TenderApprover']) }, // 招标单位审定人 { key: 'tenderApproverCertNo', value: getValue(tendereeInfo, ['_TenderApproverCertNo']) }, // 招标单位审定人资格证书编号 { key: 'proxy', value: getValue(tendereeInfo, ['_Proxy']) }, // 招标代理 { key: 'proxyCertNo', value: getValue(tendereeInfo, ['_ProxyCertNo']) }, // 招标代理资质证书编号 { key: 'proxyAuthorizer', value: getValue(tendereeInfo, ['_ProxyAuthorizer']) }, // 招标代理法定代表人或其授权人 { key: 'proxyCompiler', value: getValue(tendereeInfo, ['_ProxyCompiler']) }, // 招标代理编制人员 { key: 'proxyCompilerCertNo', value: getValue(tendereeInfo, ['_ProxyCompilerCertNo']) }, // 招标代理编制人员资格证书编号 { key: 'proxyCompileDate', value: getValue(tendereeInfo, ['_ProxyCompileDate']) }, // 招标代理编制时间 { key: 'proxyExaminer', value: getValue(tendereeInfo, ['_ProxyExaminer']) }, // 招标代理审核人 { key: 'proxyExaminerCertNo', value: getValue(tendereeInfo, ['_ProxyExaminerCertNo']) }, // 招标代理审核人资格证书编号 { key: 'proxyExamineDate', value: getValue(tendereeInfo, ['_ProxyExamineDate']) }, // 招标代理审核时间 { key: 'proxyApprover', value: getValue(tendereeInfo, ['_ProxyApprover']) }, // 招标代理审定人 { key: 'proxyApproverCertNo', value: getValue(tendereeInfo, ['_ProxyApproverCertNo']) }, // 招标代理审定人资格证书编号 { key: 'proxyApproveDate', value: getValue(tendereeInfo, ['_ProxyApproveDate']) }, // 招标代理审定时间 { key: 'consultant', value: getValue(tendereeInfo, ['_Consultant']) }, // 造价咨询 { key: 'consultantCertNo', value: getValue(tendereeInfo, ['_ConsultantCertNo']) }, // 造价咨询资质证书编号 { key: 'consultantCompiler', value: getValue(tendereeInfo, ['_ConsultantCompiler']) }, // 造价咨询编制人 { key: 'consultantCompilerCertNo', value: getValue(tendereeInfo, ['_ConsultantCompilerCertNo']) }, // 造价咨询编制人资格证书 { key: 'consultantCompileDate', value: getValue(tendereeInfo, ['_ConsultantCompileDate']) }, // 造价咨询编制时间 { key: 'consultantExaminer', value: getValue(tendereeInfo, ['_ConsultantExaminer']) }, // 造价咨询审核人 { key: 'consultantExaminerCertNo', value: getValue(tendereeInfo, ['_ConsultantExaminerCertNo']) }, // 造价咨询审核人资格证书编号 { key: 'consultantExamineDate', value: getValue(tendereeInfo, ['_ConsultantExamineDate']) }, // 造价咨询审核时间 { key: 'consultantApprover', value: getValue(tendereeInfo, ['_ConsultantApprover']) }, // 造价咨询审定人 { key: 'consultantApproverCertNo', value: getValue(tendereeInfo, ['_ConsultantApproverCertNo']) }, // 造价咨询审定人资格证书编号 { key: 'consultantApproveDate', value: getValue(tendereeInfo, ['_ConsultantApproveDate']) }, // 造价咨询审定时间 { key: 'bidName', value: getValue(bidderInfo, ['_BidName']) }, // 投标人 { key: 'bidAuthorizer', value: getValue(bidderInfo, ['_BidAuthorizer']) }, // 投标单位法定代表人或其授权人 { key: 'bidCompiler', value: getValue(bidderInfo, ['_BidCompiler']) }, // 投标单位编制人 { key: 'bidCompilerCertNo', value: getValue(bidderInfo, ['_BidCompilerCertNo']) }, // 投标单位编制人资格证书编号 { key: 'bidCompileDate', value: getValue(bidderInfo, ['_BidCompileDate']) }, // 投标单位编制时间 { key: 'bidExaminer', value: getValue(bidderInfo, ['_BidExaminer']) }, // 投标单位审核人 { key: 'bidExaminerCertNo', value: getValue(bidderInfo, ['_BidExaminerCertNo']) }, // 投标单位审核人资格证书编号 { key: 'bidExamineDate', value: getValue(bidderInfo, ['_BidExamineDate']) }, // 投标单位审核时间 { key: 'bidApprover', value: getValue(bidderInfo, ['_BidApprover']) }, // 投标单位审定人 { key: 'bidApproverCertNo', value: getValue(bidderInfo, ['_BidApproverCertNo']) }, // 投标单位审定人资格证书 { key: 'bidApproveDate', value: getValue(bidderInfo, ['_BidApproveDate']) }, // 投标单位审定时间 ]; } // 从xml对象中提取单项工程数据 function extractEngs(projectSrc, xmlObjMap) { const sectionWorks = arrayValue(projectSrc, ['ProjectInstallationWorkCost', 'SectionalWorks']); return sectionWorks.map(src => { countData.projectCount++; return { projType: projectType.Engineering, name: getValue(src, ['_Name']), code: getValue(src, ['_Number']), tenders: extractTenders(src, xmlObjMap) } }); } // 从xml对象中提取单位工程数据 // 额外新增的数据(文件里没有,需要额外生成) function extractTenders(sectionWorkSrc, xmlObjMap) { const unitWorks = arrayValue(sectionWorkSrc, ['UnitWorks']); return unitWorks.map(unitWorckSrc => { countData.projectCount++; countData.unitPriceFileCount++; const fileName = getValue(unitWorckSrc, ['_FileName']); const tenderXMLObj = xmlObjMap[fileName]; const src = getValue(tenderXMLObj, ['UnitWorks']); // 单位工程文件的数据 return { projType: projectType.Tender, name: getValue(src, ['_Name']), code: getValue(src, ['_Number']), compilationIllustration: getValue(src, ['_Explains']), engineering: getValue(src, ['_ProjectType']), projectFeature: extractProjectFeature(src), workSummary: extractWorkSummary(src), fbfx: extractFBFX(src), csxm: extractCSXM(src), other: extractOther(src), tax: extractTax(src), evalGLJFromOther: extractProvisionalMaterialEquipmentItem(src), ...extractGLJSummary(src), bidEvaluationSummary: extractBidEvaluationSummary(src) }; }); } // 提取工程特征数据 function extractProjectFeature(tenderSrc) { // 支持嵌套子项 const attrInfoSrc = getValue(tenderSrc, ['AttrInfo']); const featureData = extractItemsRecur(attrInfoSrc, [['AttrInfoItem']], (src) => ({ dispName: getValue(src, ['_Name']), value: getValue(src, ['_Value']) })); clearEmptyItems(featureData); console.log(featureData); // 特殊处理几个工程特征(有的文件AttrInfo中不包含一些工程特征) const hasProjectType = featureData.some(item => item.dipName === '工程类型'); const hasScale = featureData.some(item => item.dipName === '建设规模'); const hasScaleUnit = featureData.some(item => item.dipName === '建设规模单位'); if (!hasProjectType) { featureData.push({ dispName: '工程类型', value: getValue(tenderSrc, ['_ProjectType']) }); } if (!hasScale) { featureData.push({ dispName: '建设规模', value: getValue(tenderSrc, ['_Scale']) }); } if (!hasScaleUnit) { featureData.push({ dispName: '建设规模单位', value: getValue(tenderSrc, ['_Unit']) }); } return featureData; function clearEmptyItems(items) { items.forEach(item => { if (!item.items) { return; } if (!item.items.length) { delete item.items; } else { clearEmptyItems(item.items); } }) } } // 提取大项费用 function extractWorkSummary(tenderSrc) { const summarySrc = getValue(tenderSrc, ['UnitWorksSummary']); const fields = [['UnitWorksSummaryGroup'], ['UnitWorksSummaryItem']]; return extractItemsRecur(summarySrc, fields, (src, curField) => { const item = { code: getValue(src, ['_Number']), name: getValue(src, ['_Name']), quantity: getValue(src, ['_Quantity']), remark: getValue(src, ['_Remark']), feeCode: getValue(src, ['_Code']), //费用字典 fees: [{ fieldName: 'common', totalFee: getValue(src, ['_Total']) || '0', unitFee: getValue(src, ['_TechnicalAndEconomicIndex']) || '0' }] }; if (curField[0] === fields[1][0]) { item.calcBase = getValue(src, ['_QtyFormula']); const feeRate = getValue(src, ['_Rate']); item.feeRate = +feeRate !== 100 && feeRate || undefined; } if (!isFullyImport) { delete item.feeRate delete item.fees; } return item; }); } // 从src子项的SummaryOfBasicCost中获取一部分费用 function getFeesFromBasicCost(src) { const summaryCost = getValue(src, ['SummaryOfBasicCost']); if (!summaryCost) { return []; } // SummaryOfBasicCost中有用的价格只有:人工费、材料费、主材费、机械费、管理费、利润 return [ { fieldName: 'labour', totalFee: getValue(summaryCost, ['_Labor']) || '0' }, { fieldName: 'material', totalFee: getValue(summaryCost, ['_Material']) || '0' }, { fieldName: 'mainMaterial', totalFee: getValue(summaryCost, ['_MainMaterial']) || '0' }, { fieldName: 'machine', totalFee: getValue(summaryCost, ['_Machine']) || '0' }, { fieldName: 'manage', totalFee: getValue(summaryCost, ['_Overhead']) || '0' }, { fieldName: 'profit', totalFee: getValue(summaryCost, ['_Profit']) || '0' }, ]; } // 提取分部分项 function extractFBFX(tenderSrc) { const fbfxSrc = getValue(tenderSrc, ['DivisionalAndElementalWorks']); const fields = [['DivisionalWorks'], ['WorkElement']]; const fees = getFeesFromBasicCost(fbfxSrc); // 分部分项的汇总价 const items = getItemsRecur(fbfxSrc, fields, (itemSrc, curField) => { if (curField[0] === fields[0][0]) { // 分部 return extractDivisionalWorks(itemSrc, billType.FB); } else { return extractWorkElement(itemSrc, billType.FX); } }); return { fees, items }; } // 提取分部 function extractDivisionalWorks(divisionalSrc, type) { // 分部不需要基数,因此不用把基数导入 const item = { type, code: getValue(divisionalSrc, ['_Number']), name: getValue(divisionalSrc, ['_Name']), unit: getValue(divisionalSrc, ['_Unit']), quantity: getValue(divisionalSrc, ['_Quantity']), feeCode: getValue(divisionalSrc, ['_Code']), remark: getValue(divisionalSrc, ['_Remark']), }; // 特殊处理措施项目的费用代号(广联达这两条数据没有导出费用代号,跟我们的清单模板不匹配,从而导致从合并变成了新增),详看mergeBills中的mergeitems放啊 if (type === billType.BILL) { if (item.code === 'AQFHWMSG') { item.feeCode = FlagFeeCodeMap[fixedFlag.SAFETY_CONSTRUCTION]; } else if (item.code === 'QTCSF') { item.feeCode = FlagFeeCodeMap[fixedFlag.OTHER_MEASURE_FEE]; } } if (isFullyImport) { const summaryFees = getFeesFromBasicCost(divisionalSrc); const fees = [{ fieldName: 'common', totalFee: getValue(divisionalSrc, ['_Total']), unitFee: getValue(divisionalSrc, ['_TechnicalAndEconomicIndex']) }]; item.fees = mergeFees(fees, summaryFees); } return item; } // 提取分项清单数据(包含定额、定额人材机) function extractWorkElement(workElementSrc, type) { const itemCharacterText = getItemCharacterText(getValue(workElementSrc, ['_Attr'])); const bills = { type: type, // 清单类型 code: getValue(workElementSrc, ['_Number']), name: getValue(workElementSrc, ['_Name']), itemCharacterText, itemCharacter: getItemCharacter(itemCharacterText), jocContentText: getValue(workElementSrc, ['_WorkContent']), jobContent: getJobContent(workElementSrc), unit: getValue(workElementSrc, ['_Unit']), quantity: getValue(workElementSrc, ['_Quantity']), mainBills: getBool(getValue(workElementSrc, ['_Major'])), feeCode: getValue(workElementSrc, ['_Code']), remark: getValue(workElementSrc, ['_Remark']), quantityDetails: extractQuantityDetails(workElementSrc), rations: extractRations(workElementSrc) }; if (type === billType.BILL) { // 分项不需要基数、费率 bills.calcBase = getValue(workElementSrc, ['_QtyFormula']); const feeRate = getValue(workElementSrc, ['_Rate']); bills.feeRate = +feeRate !== 100 && feeRate || undefined; } // 投标和控制价,需要导入最高限价 if (isFullyImport) { const maxPrice = getValue(workElementSrc, ['_PriceHigh']); //不为0才输出 if (maxPrice && maxPrice !== '0') { bills.outPutMaxPrice = true; bills.maxPrice = maxPrice; } const summaryFees = getFeesFromBasicCost(workElementSrc); const fees = [ { fieldName: 'common', unitFee: getValue(workElementSrc, ['_Price']), totalFee: getValue(workElementSrc, ['_Total']) }, { fieldName: 'equipment', unitFee: getValue(workElementSrc, ['_EquipmentPrice']) }, ]; bills.fees = mergeFees(fees, summaryFees); } return bills; // 获取项目特征文本 function getItemCharacterText(attr) { // 去掉除换行符以外的空白符 const newLine = '{newLine}'; attr = attr .replace(/[\r\n]/g, newLine) .replace(/\s/g, '') .replace(new RegExp(newLine, 'g'), '\n'); const matchStrs = [' ', ';']; const matchStr = matchStrs.find(str => attr.includes(str)); if (matchStr) { attr = attr.replace(new RegExp(matchStr, 'g'), '\n'); } return attr; } // 获取清单项目特征窗口数据 function getItemCharacter(itemText) { const itemsList = itemText.split('\n'); const reg = /\d+\.([^:]+)[::](.*)?/; const itemCharacter = []; let idx = 1; itemsList.forEach(str => { str = str.replace(/\s/g, ''); const result = reg.exec(str); if (!result) { return; } const name = result[1]; const value = result[2] || ''; const eigenvalue = value ? [{ value, isSelected: true }] : []; itemCharacter.push({ character: name, isChecked: true, serialNo: idx++, eigenvalue }); }); return itemCharacter; } // 获取清单工作内容窗口数据 function getJobContent(workElementSrc) { return arrayValue(workElementSrc, ['WorkContent']).map((workContent, idx) => ({ content: getValue(workContent, ['_Name']), serialNo: idx + 1, isChecked: true })); } } // 提取工程量明细(清单、定额底下都可能会有) function extractQuantityDetails(src) { return arrayValue(src, ['ExpressElement']).map(itemSrc => ({ seq: getValue(itemSrc, ['_OrderNumber']), regex: getValue(itemSrc, ['_Express']), result: getValue(itemSrc, ['_Quantity']), isSummation: summationMap[getValue(itemSrc, ['_Kind'])] })); } // 提取定额 function extractRations(workElementSrc) { if (!isFullyImport) { return []; } // 正常情况下定额肯定是在WorkContent内的,但是也做定额不在WorkContent内的处理(外层定额) const rations = []; let serialNo = 1; const workContents = arrayValue(workElementSrc, ['WorkContent']); workContents.forEach(workContent => { const innerRationsSrc = arrayValue(workContent, ['Norm']); const jobContentText = getValue(workContent, ['_Name']); const innerRations = innerRationsSrc.map(rationSrc => extractRation(rationSrc, jobContentText)); rations.push(...innerRations); }); const outerRationsSrc = arrayValue(workElementSrc, ['Norm']); const outerRations = outerRationsSrc.map(rationSrc => extractRation(rationSrc, '')); rations.push(...outerRations); return rations; // 获取类型 function getType(rationSrc) { const overHeightMap = { '1': true, '2': false }; const isOverHeight = overHeightMap[getValue(rationSrc, ['EfficiencyKind'])]; if (isOverHeight) { return rationType.overHeight; } const itemIncreaseMap = { '1': true, '2': false }; const isItemIncrease = itemIncreaseMap[getValue(rationSrc, ['IncFeeKind'])]; if (isItemIncrease) { return rationType.itemIncrease; } return rationType.ration; } // 根据定额计算程序子目,获取量价类型 function getVolumePriceTypeByCalcItems(rationSrc) { const calculationOfItems = arrayValue(rationSrc, ['UnitPriceCalculationOfItem']); for (const item of calculationOfItems) { const code = getValue(item, ['_Code']); const total = +getValue(item, ['_Total']); if (code === 'RGF' && total) { return { type: rationType.volumePrice, subType: 1 }; } if (code === 'CLF' && total) { return { type: rationType.volumePrice, subType: 201 }; } if (code === 'JXF' && total) { return { type: rationType.volumePrice, subType: 301 }; } } return null; } // 提取计算程序费率数据 function extractCalculationOfItems(rationSrc) { // 需要提取费率的字段映射 const codeFiedNameMap = { 'RGF': 'labour', 'CLF': 'material', 'JXF': 'machine', 'GLF': 'manage', 'LR': 'profit', 'DJ': 'common', }; const calculationOfItems = arrayValue(rationSrc, ['UnitPriceCalculationOfItem']); const rst = []; calculationOfItems.forEach(item => { const fieldName = codeFiedNameMap[getValue(item, ['_Code'])]; const feeRate = +getValue(item, ['_Rate']); if (fieldName && !isNaN(feeRate)) { rst.push({ fieldName, feeRate }); } }); return rst; } // 从定额的计算程序提取价格 function getFeesFromCalculationOfItem(rationSrc) { const calculationOfItems = arrayValue(rationSrc, ['UnitPriceCalculationOfItem']); const fees = []; const codeFiedNameMap = { 'ZJF': 'direct', 'RGF': 'labour', 'CLF': 'material', 'JXF': 'machine', 'ZCF': 'mainMaterial', 'SBF': 'equipment', 'LR': 'profit', //'DJ': 'common',定额本身已经有单价这个字段,不需要从这读取 }; calculationOfItems.forEach(item => { const totalFee = +getValue(item, ['_Total']); const fieldName = codeFiedNameMap[getValue(item, ['_Code'])]; if (totalFee && fieldName) { fees.push({ fieldName, totalFee }); } }); return fees; } // 提取定额人材机 function extractRationGLJs(rationSrc) { const rationGLJsSrc = arrayValue(rationSrc, ['LabourMaterialsEquipmentsMachinesElement']); return rationGLJsSrc.map(src => ({ code: getValue(src, ['_Number']), quantity: getValue(src, ['_Quantity']) })); } // TODO 目前无法确定定额的取费专业programID,暂时用专业类别Specialty function extractRation(rationSrc, jobContentText) { // 需要处理定额编码如易达的某文件,定额编码有“E1-3-52*3”,需要截取“*”之前的作为定额编码 let code = getValue(rationSrc, ['_Number']); const codeReg = /([^*]+)\*.*/; code = code.replace(codeReg, '$1'); const ration = { serialNo: serialNo++, code, name: getValue(rationSrc, ['_Name']), unit: getValue(rationSrc, ['_Unit']), quantity: getValue(rationSrc, ['_Quantity']), type: getType(rationSrc), programID: getValue(rationSrc, ['_Specialty']), libCode: getValue(rationSrc, ['_NormIdentity']), jobContentText, remark: getValue(rationSrc, ['_Remark']) }; const fees = [{ fieldName: 'common', unitFee: getValue(rationSrc, ['_Price']), totalFee: getValue(rationSrc, ['_Total']) }] const feesFromCalcItem = getFeesFromCalculationOfItem(rationSrc); ration.fees = mergeFees(fees, feesFromCalcItem); ration.rationGLJs = extractRationGLJs(rationSrc); // 没有定额人材机则当作是增加费定额、同时导入后成为量价 if (!ration.rationGLJs.length) { const typeData = getVolumePriceTypeByCalcItems(rationSrc); if (typeData) { Object.assign(ration, typeData); ration.setFakeProgramID = true; } } ration.quantityDetails = extractQuantityDetails(rationSrc); ration.calculationItems = extractCalculationOfItems(rationSrc); return ration; } } // 提取措施项目 function extractCSXM(tenderSrc) { const csxmSrc = getValue(tenderSrc, ['Preliminaries']); const fields = [['DivisionalWorks'], ['WorkElement']]; const fees = getFeesFromBasicCost(csxmSrc); // 措施项目的汇总价 const items = extractItemsRecur(csxmSrc, fields, (itemSrc, curField) => { if (curField[0] === fields[0][0]) { return extractDivisionalWorks(itemSrc, billType.BILL); } else { return extractWorkElement(itemSrc, billType.BILL); } }); return { fees, items }; } // 提取额外的暂估材料表 // 除了人材机汇总中的is_evaluate = true的材料为暂估材料外,还需要处理ProvisionalMaterialEquipmentItem的数据 // 如易达的一些文件,ProvisionalMaterialEquipmentItem中存在人材机汇总没有的数据 function extractProvisionalMaterialEquipmentItem(tenderSrc) { const evalGLJs = arrayValue(tenderSrc, ['Sundry', 'ProvisionalMaterialEquipment', 'ProvisionalMaterialEquipmentItem']); return evalGLJs.map(glj => ({ code: getValue(glj, ['_Number']), name: getValue(glj, ['_Name']), unit: getValue(glj, ['_Unit']), quantity: getValue(glj, ['_Quantity']), market_price: getValue(glj, ['_Price']), remark: getValue(glj, ['_Remark']) })); } // 提取其他项目 function extractOther(tenderSrc) { const sundry = getValue(tenderSrc, ['Sundry']); // SundryCosts 其他项目 ,对于我们软件是其他项目下完整数据,但是广联达的并不是,因此需要Sundry的其他节点来补充这份数据 const sundryCosts = getValue(sundry, ['SundryCosts']); // 其他项目汇总费用 const sundryFees = [{ fieldName: 'common', totalFee: getValue(sundryCosts, ['_Total']) }]; const provisionalMaterialEquipment = getValue(sundry, ['ProvisionalMaterialEquipment']); return { fees: isFullyImport ? sundryFees : [], sundryCosts: extractData(sundry, 'SundryCosts', [['SundryCostsGroup'], ['SundryCostsItem']]), provisional: extractData(sundry, 'ProvisionalSums', [['ProvisionalSumsGroup'], ['ProvisionalSumsItem']]), ProvisionalMaterialEquipment: { name: getValue(provisionalMaterialEquipment, ['_Name']), feeCode: getValue(provisionalMaterialEquipment, ['_Code']), remark: getValue(provisionalMaterialEquipment, ['_Remark']), fees: [{ fieldName: 'common', totalFee: getValue(provisionalMaterialEquipment, ['_Total']) }], }, specialty: extractData(sundry, 'SpecialtyProvisionalPrice', [['SpecialtyProvisionalPriceGroup'], ['SpecialtyProvisionalPriceItem']], { engineeringContent: ['_Content'] }, { engineeringContent: ['_Content'] }), dayWork: extractData(sundry, 'DayWorkRate', [['DayWorkRateGroup'], ['DayWorkRateItem']]), mainContractor: extractData(sundry, 'MainContractorAttendance', [['MainContractorAttendanceGroup'], ['MainContractorAttendanceItem']], { serviceContent: ['_Content'] }, { serviceContent: ['_Content'] }), claim: extractData(sundry, 'ClaimsCost', [['ClaimsCostGroup'], ['ClaimsCostItem']], { claimVisa: ['_Reason'] }, { claimVisa: ['_Reason'] }), visa: extractData(sundry, 'SiteInstructionCost', [['SiteInstructionCostGroup'], ['SiteInstructionCostItem']], { claimVisa: ['_Reason'] }, { claimVisa: ['_Reason'] }), }; // 提取标题和明细 function extractGroupOrItem(itemSrc, isGroup = true, extendAttrs = null, field) { let source; if (isGroup) { source = { name: getValue(itemSrc, ['_Name']), fees: isFullyImport ? [{ fieldName: 'common', totalFee: getValue(itemSrc, ['_Total']) }] : [], feeCode: getValue(itemSrc, ['_Code']), remark: getValue(itemSrc, ['_Remark']) }; } else { const feeRate = getValue(itemSrc, ['_Rate']); source = { name: getValue(itemSrc, ['_Name']), unit: getValue(itemSrc, ['_Unit']), calcBase: getValue(itemSrc, ['_QtyFormula']), feeRate: +feeRate !== 100 && feeRate || undefined, fees: isFullyImport ? [{ fieldName: 'common', unitFee: getValue(itemSrc, ['_Price']), totalFee: getValue(itemSrc, ['_Total']) }] : [], feeCode: getValue(itemSrc, ['_Code']), remark: getValue(itemSrc, ['_Remark']) }; if (field !== 'MainContractorAttendanceItem') { // MainContractorAttendanceItem的quantity比较特殊,是清单基数计算后的值而不是工程量,不需要导入 source.quantity = getValue(itemSrc, ['_Quantity']); } } return extendAttrs ? Object.assign(source, extendAttrs) : source; } // 提取数据 function extractData(sundry, srcName, fields, groupExtendMap, itemExtendMap) { const src = getValue(sundry, [srcName]); return { fees: isFullyImport ? [{ fieldName: 'common', totalFee: getValue(src, ['_Total']) }] : [], feeCode: getValue(src, ['_Code']), items: extractItemsRecur(src, fields, (itemSrc, curField) => { const groupExtend = groupExtendMap ? Object.keys(groupExtendMap).reduce((acc, key) => { acc[key] = getValue(itemSrc, groupExtendMap[key]); return acc; }, {}) : null; const itemExtend = itemExtendMap ? Object.keys(itemExtendMap).reduce((acc, key) => { acc[key] = getValue(itemSrc, itemExtendMap[key]); return acc; }, {}) : null; return curField[0] === fields[0][0] ? extractGroupOrItem(itemSrc, true, groupExtend, curField[0]) : extractGroupOrItem(itemSrc, false, itemExtend, curField[0]); }) }; } } // 提取税金 function extractTax(tenderSrc) { const taxSrc = getValue(tenderSrc, ['Tax']); const itemsSrc = arrayValue(taxSrc, ['TaxItem']); return { ...extractTaxItem(taxSrc), items: itemsSrc.map(extractTaxItem) }; function extractTaxItem(src) { const feeRate = getValue(src, ['_Rate']); return { name: getValue(src, ['_Name']), calcBase: getValue(src, ['_QtyFormula']), feeRate: +feeRate !== 100 && feeRate || undefined, fees: isFullyImport ? [{ fieldName: 'common', totalFee: getValue(src, ['_Total']) }] : [], feeCode: getValue(src, ['_Code']), remark: getValue(src, ['_Remark']) }; } } // 供料方式 const Provider = { '1': commonConstants.supplyType.ZXCG, // 自行采购 '2': commonConstants.supplyType.JGCL, // 甲供材料 '3': commonConstants.supplyType.JDYG // 甲定乙供 }; // 获取原始编码 function getOriginalCode(code) { //编码后面有-\d+的形式,去掉-\d+取前面的字符串作为原始代码 return code.replace(/(.*)-\d+$/, '$1'); } // 提取人材机汇总相关(人材机汇总表、承包材料表) function extractGLJSummary(tenderSrc) { const initData = { gljSummary: [], differentiaSummary: [], exponentialSummary: [] }; if (!isFullyImport) { return initData; } const taxType = getValue(tenderSrc, ['_TaxModel']); // 计税方式 const gljListSrc = arrayValue(tenderSrc, ['LabourMaterialsEquipmentsMachinesSummary']); return gljListSrc.reduce((acc, gljSrc) => { acc.gljSummary.push(extractGLJ(gljSrc)); const differentiaGLJ = extractDifferentiaGLJ(gljSrc); if (differentiaGLJ) { acc.differentiaSummary.push(differentiaGLJ); } const exponentialGLJ = extractExponentialGLJ(gljSrc); if (exponentialGLJ) { acc.exponentialSummary.push(exponentialGLJ); } return acc; }, initData); // TODO (不靠谱) 获取人材机类型数据 // 导入的源文件没有细化区分人材机类型,这里只是暂时大概给个值。后续会在后端与标准人材机进行配对。也因此无法准确处理补充人材机 function getTypeData(gljSrc) { const concrete = getBool(getValue(gljSrc, ['_Concrete'])); if (concrete) { return { type: 205, shorName: '商砼' }; } const type = getValue(gljSrc, ['_Kind']); switch (type) { case '1': return { type: 1, shorName: '人' }; case '4': return { type: 4, shortName: '主' }; case '5': return { type: 5, shortName: '设' } case '6': return { type: 202, shorName: '砼' }; case '3': case '7': return { type: 301, shorName: '机' }; case '15': return { type: 6, shortName: '管' }; default: return { type: 201, shortName: '材' }; } } // 根据计税方式获取价格,一般计税对应不含税,简易对应含税 function getPriceByTaxType(gljSrc, taxType) { return +taxType === TaxType.NORMAL ? { base_price: getValue(gljSrc, ['_NoTaxOrgPrice']), market_price: getValue(gljSrc, ['_NoTaxPrice']) } : { base_price: getValue(gljSrc, ['_TaxOrgPrice']), market_price: getValue(gljSrc, ['_TaxPrice']) }; } // 提取组成物 function extractRatios(gljSrc) { const ratiosSrc = arrayValue(gljSrc, ['LabourMaterialsEquipmentsMachinesElement']); countData.ratioCount += ratiosSrc.length; return ratiosSrc.map(src => ({ code: getValue(src, ['_Number']), consumption: getValue(src, ['_Quantity']) })); } // 提取项目人材机 function extractGLJ(gljSrc) { countData.projectGLJCount++; const code = getValue(gljSrc, ['_Number']); /* if (code.includes('99450680')) { debugger; } */ return { code, original_code: getOriginalCode(code), name: getValue(gljSrc, ['_Name']), specs: getValue(gljSrc, ['_Specification']), unit: getValue(gljSrc, ['_Unit']), ...getTypeData(gljSrc), ...getPriceByTaxType(gljSrc, taxType), // 软件中项目人材机是没有价格的,只是为了后续处理单价文件 is_main_material: getBool(getValue(gljSrc, ['_MainMaterial'])), is_evaluate: getBool(getValue(gljSrc, ['_ProvisionalMaterial'])), supply: Provider[getValue(gljSrc, ['_Provider'])], delivery: getValue(gljSrc, ['_Delivery']), delivery_address: getValue(gljSrc, ['_Location']), originPlace: getValue(gljSrc, ['_ProducingArea']), vender: getValue(gljSrc, ['_Supplier']), qualityGrace: getValue(gljSrc, ['_Character']), remark: getValue(gljSrc, ['_Remark']), ratios: extractRatios(gljSrc) }; } // 提取造价信息差额调整法 function extractDifferentiaGLJ(gljSrc) { // 风险系数或者基准单价有值,才算一条差额数据 const riskCoe = getValue(gljSrc, ['_ProviderRate']); const standardPrice = getValue(gljSrc, ['_ProviderBase']); if ((riskCoe && riskCoe !== '0') || (standardPrice && standardPrice !== '0')) { return { code: getValue(gljSrc, ['_Number']), name: getValue(gljSrc, ['_Name']), specs: getValue(gljSrc, ['_Specification']), unit: getValue(gljSrc, ['_Unit']), quantity: getValue(gljSrc, ['_Quantity']), riskCoe, standardPrice, market_price: getPriceByTaxType(gljSrc, taxType).market_price, remark: getValue(gljSrc, ['_Remark']), }; } return null; } // 提取价格指数调整法 function extractExponentialGLJ(gljSrc) { // 变值权重或基本、现行价格指数有值,才算一条指数数据 const varWeight = getValue(gljSrc, ['_Weight']); const FO = getValue(gljSrc, ['_BasicPrice']); const FI = getValue(gljSrc, ['_CurrentPrice']); if ((varWeight && varWeight !== '0') || (FO && FO !== '0') || (FI && FI !== '0')) { return { code: getValue(gljSrc, ['_Number']), name: getValue(gljSrc, ['_Name']), specs: getValue(gljSrc, ['_Specification']), varWeight: varWeight, FO: FO, FI: FI, remark: getValue(gljSrc, ['_Remark']) }; } return null; } } // 提取评标材料 function extractBidEvaluationSummary(tenderSrc) { if (!isFullyImport) { return []; } const srcList = arrayValue(tenderSrc, ['BidEvaluationMainMaterial']); return srcList.map(gljSrc => ({ seq: getValue(gljSrc, ['_Code']), code: getValue(gljSrc, ['_Number']), name: getValue(gljSrc, ['_Name']), specs: getValue(gljSrc, ['_Specification']), unit: getValue(gljSrc, ['_Unit']), quantity: getValue(gljSrc, ['_Quantity']), market_price: getValue(gljSrc, ['_Price']), originPlace: getValue(gljSrc, ['_ProducingArea']), vender: getValue(gljSrc, ['_Supplier']), remark: getValue(gljSrc, ['_Remark']) })); } /* * 转换从xml提取的数据,转换成软件所需的数据结构 * */ async function transformData(extractedData) { const IDPlaceholder = await ajaxPost('/pm/import/getProjectPlaceholder', countData); extractedData.ID = IDPlaceholder.project++; // 最终上传到服务器的转换后的数据 const transformedData = { ID: extractedData.ID, ParentID: extractedData.ParentID, NextSiblingID: extractedData.NextSiblingID, preID: extractedData.preID, name: extractedData.name, basicInformation: extractedData.basicInformation, projType: extractedData.projType, property: extractedData.property, shareInfo: [], engs: [] }; // 所有的清单模板库ID const allTemplateLibIDs = []; extractedData.engs.forEach(eng => { eng.tenders.forEach(tender => { let templateLibID = tender.property.templateLibID; if (!allTemplateLibIDs.includes(templateLibID)) { allTemplateLibIDs.push(templateLibID); } }); }); // 模板映射:{[templateLibID]: data} const templateMapping = await ajaxPost('/template/bills/api/getNeedfulTemplate', { allTemplateLibIDs }); for (let i = 0; i < extractedData.engs.length; i++) { const curEng = extractedData.engs[i]; const preEng = transformedData.engs[i - 1]; curEng.ID = IDPlaceholder.project++; curEng.ParentID = extractedData.ID; curEng.NextSiblingID = -1; if (preEng) { preEng.NextSiblingID = curEng.ID; } const engData = { ID: curEng.ID, ParentID: curEng.ParentID, NextSiblingID: curEng.NextSiblingID, code: curEng.code, name: curEng.name, projType: curEng.projType, shareInfo: [], tenders: [] }; transformedData.engs.push(engData); for (let j = 0; j < curEng.tenders.length; j++) { const curTender = curEng.tenders[j]; const preTender = engData.tenders[j - 1]; curTender.ID = IDPlaceholder.project++; curTender.ParentID = curEng.ID; curTender.NextSiblingID = -1; curTender.property.unitPriceFile.id = IDPlaceholder.unitPriceFile++; if (preTender) { preTender.tender.NextSiblingID = curTender.ID; } // 项目清单模板(后台清单模板中提取含有固定类别的数据) const billsTemplate = _.cloneDeep(templateMapping[curTender.property.templateLibID]); // 重设模板树结构数据 BILLS_UTIL.resetTreeData(billsTemplate, uuid.v1); console.log(billsTemplate); const tenderData = transformTender(curTender, IDPlaceholder, billsTemplate); tenderData.tender.property.rootProjectID = transformedData.ID; engData.tenders.push(tenderData); } } console.log(`transformedData`); console.log(transformedData); return transformedData; } // 获取承包人材料类型 function getAdjustType(tenderData) { if (tenderData.differentiaSummary.length) { return AdjustType.info; } else if (tenderData.exponentialSummary.length) { return AdjustType.coe; } else { return AdjustType.info; } } // 获取当前文件 // 转换单位工程的数据(直接可插入数据库的数据),返回{tender: {}, bills: [], ration: [], rationGLJ: [], projectGLJ: [], unitPrice: [], mixRatio: []} function transformTender(tenderData, IDPlaceholder, billsTemplate) { const detailData = transformDetail(tenderData, IDPlaceholder, billsTemplate); // 提取需要插入的单位工程数据 const transformedTender = { ID: tenderData.ID, ParentID: tenderData.ParentID, NextSiblingID: tenderData.NextSiblingID, code: tenderData.code, name: tenderData.name, projType: tenderData.projType, property: tenderData.property, projectFeature: tenderData.projectFeature, engineering: tenderData.engineering, defaultRationLib: tenderData.defaultRationLib, rationLibIDs: tenderData.rationLibIDs, gljLibIDs: tenderData.gljLibIDs, shareInfo: [] }; transformedTender.property.compilationIllustration = tenderData.compilationIllustration; transformedTender.property.gljAdjustType = getAdjustType(tenderData); transformedTender.property.decimal = tenderPropertyDecimal; transformedTender.property.billsQuantityDecimalValue = tenderPropertyBillsQuantityDecimal; detailData.tender = transformedTender; return detailData; } // 过滤掉相关费用代号的数据(递归地) // 返回被过滤掉的数据 function filterDataByFeeCodes(billsData, feeCodes) { const filterd = []; billsData.forEach(bills => { if (bills.items && bills.items.length) { const newItems = []; bills.items.forEach(subBills => { if (!feeCodes.includes(subBills.feeCode)) { newItems.push(subBills); } else { filterd.push(subBills); } }); bills.items = newItems; // bills.items = bills.items.filter(subBills => !feeCodes.includes(subBills.feeCode)); const subFilterd = filterDataByFeeCodes(bills.items, feeCodes); filterd.push(...subFilterd); } }); return filterd; } // 合并清单,将提取出来的清单相关数据,合并进项目清单(模板) function mergeBills(tenderData, billsTemplate) { const mergedBills = [...billsTemplate]; const roots = billsTemplate.filter(bills => bills.ParentID === -1); let lastRoot = roots.find(root => root.NextSiblingID === -1); mergeWorkSummary(tenderData.workSummary); mergeFBFX(tenderData.fbfx); mergeCSXM(tenderData.csxm); mergeOther(tenderData.other); mergeTax(tenderData.tax); // 需要清空各种原因导入的父项的工程量 /* const parentMap = {}; mergedBills.forEach(bills => { if (bills.ParentID !== -1) { parentMap[bills.ParentID] = 1; } }) mergedBills.forEach(bills => { if (parentMap[bills.ID] && commonUtil.isDef(bills.quantity)) { delete bills.quantity; } }) */ return mergedBills; // 合并清单数据 function mergeitems(fixedBills, items) { const daryWorkMap = { '人工': fixedFlag.LABOUR, '材料': fixedFlag.MATERIAL, '机械': fixedFlag.MACHINE, '施工机械': fixedFlag.MACHINE, '施工机具': fixedFlag.MACHINE }; const fixedBillsFlag = getFlag(fixedBills); let preBills = billsTemplate.find(bills => bills.ParentID === fixedBills.ID && bills.NextSiblingID === -1); items.forEach(bills => { // 特殊处理绿色施工,否则可能会重复导入(易达绿色施工安全防护措施费的code为AQWMSGF,feeCode为空) if (bills.name === '绿色施工安全防护措施费' && bills.code === 'AQWMSGF') { bills.feeCode = 'AQWMSGF'; } // 计日工需要特殊处理,兼容广联达的数据,广联达的计日工下的人工、材料、机械的费用字典不对,因此用名称匹配 let matched; if (fixedBillsFlag === fixedFlag.DAYWORK) { const dayWorkSubFlag = daryWorkMap[bills.name]; matched = dayWorkSubFlag ? billsTemplate.find(templateBills => getFlag(templateBills) === dayWorkSubFlag) : null; } else { matched = billsTemplate.find(templateBills => FlagFeeCodeMap[getFlag(templateBills)] === bills.feeCode); } if (matched) { assignAttr(matched, bills, ['items'], true); if (commonUtil.isDef(bills.feeRate) && commonUtil.isDef(matched.feeRateID)) { delete matched.feeRateID; // 不清除的话,赋值过去的feeRate会被后端覆盖 } if (bills.items && bills.items.length) { mergeitems(matched, bills.items); //mergedBills.push(...mergeDataRecur(fixedBills, [bills])) } preBills = matched; } else { const nextID = preBills && preBills.NextSiblingID || -1; mergedBills.push(...mergeDataRecur(fixedBills, [bills])); bills.NextSiblingID = nextID; if (preBills) { preBills.NextSiblingID = bills.ID; } preBills = bills; } }); } // 简单地合并: 设置某清单的金额,数据递归插入到某清单的子项 function simpleMerge(flag, src) { const fixedBills = billsTemplate.find(bills => getFlag(bills) === flag); if (fixedBills) { assignAttr(fixedBills, src, ['fees', 'feeCode']); mergedBills.push(...mergeDataRecur(fixedBills, src.items)); } } // 合并大项费用 function mergeWorkSummary(workSummary) { // 首层数据与清单模板根据费用字典进行匹配,匹配到的则赋值一些属性,匹配不到则将其及其所有子数据插入到模板最末大项费用 workSummary.forEach(summaryBills => { const matched = roots.find(root => FlagFeeCodeMap[getFlag(root)] === summaryBills.feeCode); if (matched) { if (!summaryBills.calcBase) { summaryBills.calcBase = matched.calcBase; matched.ignoreValidator = true; // 用了模板的基数,基数无视基数验证 } assignAttr(matched, summaryBills, ['items'], true); } else { mergedBills.push(...mergeDataRecur(null, [summaryBills])); if (lastRoot) { lastRoot.NextSiblingID = summaryBills.ID; lastRoot = summaryBills; } } }); } // 合并分部分项 function mergeFBFX(fbfxSrc) { simpleMerge(fixedFlag.SUB_ENGINERRING, fbfxSrc); } // 合并措施项目 function mergeCSXM(csxmSrc) { const fixedBills = billsTemplate.find(bills => getFlag(bills) === fixedFlag.MEASURE); fixedBills.fees = mergeFees(fixedBills.fees, csxmSrc.fees); mergeitems(fixedBills, csxmSrc.items); } // 合并其他项目 function mergeOther(otherSrc) { const fixedBills = billsTemplate.find(bills => getFlag(bills) === fixedFlag.OTHER); fixedBills.fees = mergeFees(fixedBills.fees, otherSrc.fees); // 合并其他项目费 // SundryCosts其他项目费会包含暂列金额等后续会再次出现的数据,因此合并SundryCosts的数据时,过滤掉一部分数据 const feeCodeFilter = [ FlagFeeCodeMap[fixedFlag.PROVISIONAL], // 暂列金额 FlagFeeCodeMap[fixedFlag.MATERIAL_PROVISIONAL], // 材料暂估 FlagFeeCodeMap[fixedFlag.ENGINEERING_ESITIMATE], // 专业工程暂估 FlagFeeCodeMap[fixedFlag.DAYWORK], // 计日工 FlagFeeCodeMap[fixedFlag.TURN_KEY_CONTRACT], // 总承包服务费 FlagFeeCodeMap[fixedFlag.CLAIM], // 索赔 FlagFeeCodeMap[fixedFlag.VISA], // 现场签证 ]; const filterdBills = filterDataByFeeCodes([otherSrc.sundryCosts], feeCodeFilter); /** * 有些源xml文件中,被过滤掉SundryCosts中的子项,相对于Sundry的子项会多一些属性 * 比如:Sundry->SunryCosts->SundryCostsGroup->SundryCostsItem中的材料暂估价,含有QtyFormula属性,但是Sundry->ProvisionalMaterialEquipment中不含有QtyFormula * 因此,即使后续Sundry的子项会赋值给模板,也要将被过滤掉的子项的属性先赋给模板 */ filterdBills.forEach(filterdItem => { const matched = billsTemplate.find(templateBills => FlagFeeCodeMap[getFlag(templateBills)] === filterdItem.feeCode); if (matched) { assignAttr(matched, filterdItem, ['items'], true); // 不拷贝item属性 matched.ignoreA } }); const sundryCostsBills = otherSrc.sundryCosts.items; mergeitems(fixedBills, sundryCostsBills); // 合并暂列金额 simpleMerge(fixedFlag.PROVISIONAL, otherSrc.provisional); // 合并材料暂估,由于材料暂估的子元素是暂估人材机,因此对清单来说,只需处理材料暂估根元素的属性 const zgcFixedBills = billsTemplate.find(bills => getFlag(bills) === fixedFlag.MATERIAL_PROVISIONAL); assignAttr(zgcFixedBills, otherSrc.ProvisionalMaterialEquipment); // 合并专业工程暂估价 simpleMerge(fixedFlag.ENGINEERING_ESITIMATE, otherSrc.specialty); // 合并计日工 const dayWorkFixedBills = billsTemplate.find(bills => getFlag(bills) === fixedFlag.DAYWORK); assignAttr(dayWorkFixedBills, otherSrc.dayWork, ['fees', 'feeCode']); mergeitems(dayWorkFixedBills, otherSrc.dayWork.items); // 合并总承包服务费 simpleMerge(fixedFlag.TURN_KEY_CONTRACT, otherSrc.mainContractor); // 合并索赔 simpleMerge(fixedFlag.CLAIM, otherSrc.claim); // 合并现场签证 simpleMerge(fixedFlag.VISA, otherSrc.visa); } // 合并税金 function mergeTax(taxSrc) { const taxFixedBills = billsTemplate.find(bills => getFlag(bills) === fixedFlag.TAX); if (!taxSrc.calcBase) { taxSrc.calcBase = taxFixedBills.calcBase; taxFixedBills.ignoreValidator = true; // 用了模板的基数,基数无视基数验证 } else { taxFixedBills.ignoreValidator = false; } assignAttr(taxFixedBills, taxSrc); } } // 给固定清单设置上费用字典,因为在合并清单的时候,清单模板中可能有一些数据应该有规定的费用字典,但是没有被设置上费用字典。 function setupFeeCode(billsData) { billsData.forEach(bills => { if (bills.feeCode) { return; } bills.feeCode = FlagFeeCodeMap[getFlag(bills)] || ''; }) } // 检查清单是否引用了自身,比如广联达导出文件中 材料保管费基数为CLBGF function isCalcBaseCycle(bills) { const matched = bills.calcBase.match(new RegExp(`\\b${bills.feeCode}\\b`)); // \b: 匹配前一个字符和后一个字符不全为\w的位置 return !!(matched && matched[0]); } // 转换计算基数 // 1.有子项数据,则清空基数 // 2.引用的基数造成自身循环,比如分部分项部分引用了QDF // 3.费用代号匹配清单基数的代号 // 4.费用代号匹配不到清单基数的代号,则匹配此费用代号的清单,作为行引用(即ID引用) // 5.对应字典里找不到则设置成金额 function transformCalcBase(billsData) { // 费用代号 - 计算基数映射 const feeCodeFormulaMap = {} Object .entries(FormulaFeeCodeMap) .forEach(([formula, feeCode]) => { feeCodeFormulaMap[feeCode] = formula; }); // 费用代号 - ID映射 const feeCodeIDMap = {}; billsData.forEach(data => { if (data.feeCode) { feeCodeIDMap[data.feeCode] = data.ID; } }); for (const bills of billsData) { if (!bills.calcBase || bills.ignoreValidator) { continue; } const sub = billsData.find(data => data.ParentID === bills.ID); // 有子项数据,则清空基数,费率 if (sub) { bills.calcBase = ''; bills.feeRate = ''; continue; } if (typeof bills.calcBase !== 'string') { bills.calcBase = String(bills.calcBase); } // 引用的基数造成自循环,清空基数 const isCycle = isCalcBaseCycle(bills); if (isCycle) { bills.calcBase = ''; continue; } // 提取基数中的费用代号 bills.calcBase = bills.calcBase.replace(/\s/g, ''); const feeCodes = bills.calcBase.split(/[\+\-\*\/]/g); // 提取操作符 const oprs = bills.calcBase.match(/[\+\-\*\/]/g); // 转换后的基数 const newBase = []; let illegal = false; //不合法 for (const feeCode of feeCodes) { const formula = feeCodeFormulaMap[feeCode]; const refID = feeCodeIDMap[feeCode]; if (formula) { // 匹配到公式 newBase.push(formula); } else if (refID) { // 匹配到行引用 newBase.push(`@${refID}`) // 转换成ID引用 } else { // 无法识别 illegal = true; break; } } if (illegal) { const fee = getFee(bills.fees, ['common', 'totalFee']); const feeRate = bills.feeRate && parseFloat(bills.feeRate) !== 0 ? parseFloat(bills.feeRate) : 0; if (fee && parseFloat(fee) !== 0 && feeRate) { bills.calcBase = scMathUtil.roundForObj(parseFloat(fee) * 100 / feeRate, Decimal.FEE); } else { bills.calcBase = fee !== '0' ? fee : ''; } } else { let newCalcBase = ''; for (let i = 0; i < newBase.length; i++) { newCalcBase += newBase[i]; if (oprs && oprs[i]) { newCalcBase += oprs[i]; } } bills.calcBase = newCalcBase; } } } // 转换清单 function transformBills(tenderData, billsTemplate) { const rst = { bills: [], billsQuantityDetails: [] }; const billsData = mergeBills(tenderData, billsTemplate); setupFeeCode(billsData); transformCalcBase(billsData); // 处理综合单价 // 没有子清单,且没有综合单价,且有综合合价,则综合单价 = 综合合价 / 工程量 billsData.forEach(bills => { const hasChild = billsData.some(data => data.ParentID === bills.ID); let unitFee = getFee(bills.fees, ['common', 'unitFee']); const totalFee = getFee(bills.fees, ['common', 'totalFee']); // 如果费率为0、同时合价不为0,则需要清空计算基数和费率(当作数量单价的形式。易达某文件出现了费率为0、有金额且有基数的数据) // 不这么处理的话,导入后造价计算,此清单金额算出来会是0 if (+bills.feeRate === 0 && +totalFee > 0) { bills.calcBase = ''; bills.feeRate = null; } if (!hasChild && !parseFloat(unitFee) && totalFee && parseFloat(totalFee)) { // 不存工程量 if (!bills.quantity || !parseFloat(bills.quantity)) { unitFee = totalFee; // 源不存在工程量,没有计算基数、但是却有综合合价,工程量要设置为1 if (!bills.calcBase) { bills.quantity = '1'; } } else { // 综合合价的小数位数 //const totalFeeDecimal = totalFee.match(/\.\d+/); //unitFee = scMathUtil.roundForObj(totalFee / bills.quantity, totalFeeDecimal ? totalFeeDecimal[0] : 0); unitFee = scMathUtil.roundForObj(totalFee / bills.quantity, Decimal.FEE); } const commonFee = bills.fees.find(fee => fee.fieldName === 'common'); if (commonFee) { commonFee.unitFee = unitFee; } } bills.projectID = tenderData.ID; // 工程量明细 transformQuantityDetails(tenderData.ID, bills.quantityDetails, bills.ID, 'billID'); bills.quantityEXP = bills.quantityDetails && bills.quantityDetails.length ? GCLMXHj : bills.quantity; if (bills.quantityDetails) { rst.billsQuantityDetails.push(...bills.quantityDetails); } rst.bills.push(bills); }); return rst; } // 转换工程量明细数据 function transformQuantityDetails(tenderID, quantityDetails, refID, typeKey) { if (quantityDetails) { quantityDetails.forEach(item => { item.ID = uuid.v1(); item[typeKey] = refID; // billID、rationID item.projectID = tenderID; }); } } // 转换人材机汇总相关数据(人材机汇总、承包人材料、评标材料、暂估价材料、组成物、单价文件) function transformGLJList(tenderData, IDPlaceholder, projectGLJMap) { const rst = { projectGLJ: [], // 项目人材机 contractorList: [], // 承包人材料 bidEvaluationList: [], // 评标材料 evaluationList: [], // 暂估材料 unitPrice: [], // 单价文件 mixRatio: [] // 组成物 }; transformGLJSummary(); transformContractorSummary(); transformRelatedGLJList(tenderData.bidEvaluationSummary, rst.bidEvaluationList, 'is_eval_material'); transformEvalSummary(); return rst; // 转换项目人材机、组成物 function transformGLJSummary() { // 项目人材机 tenderData.gljSummary.forEach(projectGLJ => { projectGLJ.project_id = tenderData.ID; projectGLJ.id = IDPlaceholder.projectGLJ++; projectGLJMap[projectGLJ.code] = projectGLJ; }); tenderData.gljSummary.forEach(projectGLJ => { //组成物数据 projectGLJ.ratios.forEach(ratio => { const matched = projectGLJMap[ratio.code]; ratio.code = matched.code; // 为了后端匹配标准数据,如易达有组成物"99450680-0001" // 项目人材机、单价文件根据original_code匹配上了标准人材机,修改了gljType等 // mixRatio根据code匹配标准数据匹配不上标准数据,因此type与项目人材机的type不同,导致组成物丢失 ratio.original_code = getOriginalCode(ratio.code); ratio.projectGLJID = projectGLJ.id; ratio.id = IDPlaceholder.ratio++; ratio.unit_price_file_id = tenderData.property.unitPriceFile.id; ratio.unit = matched && matched.unit || ''; ratio.name = matched && matched.name || ''; ratio.specs = matched && matched.specs || ''; ratio.type = matched && matched.type || 1; ratio.connect_key = [projectGLJ.code || 'null', projectGLJ.name || 'null', projectGLJ.specs || 'null', projectGLJ.unit || 'null', projectGLJ.type].join('|-|'); rst.mixRatio.push(ratio); }); rst.projectGLJ.push(projectGLJ); // 单价文件数据: rst.unitPrice.push(generateUnitPrice(projectGLJ)); }); } // 转换与项目人材机关联的材料 function transformRelatedGLJList(list, container, relatedType) { list.forEach(glj => { glj.ID = uuid.v1(); glj.projectID = tenderData.ID; glj.projectGLJID = -1; if (typeof glj.seq === 'undefined') { glj.seq = glj.code; } const projectGLJ = projectGLJMap[glj.code]; if (projectGLJ) { projectGLJ[relatedType] = 1; glj.is_related = 1; glj.projectGLJID = projectGLJ.id; } container.push(glj); }); } // 转换承包人材料 function transformContractorSummary() { // 由于承包人差额数据和指数数据共用承包人材料表,因此需要合并差额数据和指数数据 const mergedList = []; tenderData.exponentialSummary.forEach(eGLJ => { const matched = tenderData.differentiaSummary.find(dGLJ => dGLJ.code === eGLJ.code); if (matched) { assignAttr(matched, eGLJ, ['varWeight', 'FO', 'FI']); } else { mergedList.push(eGLJ); } }); mergedList.push(...tenderData.differentiaSummary); transformRelatedGLJList(mergedList, rst.contractorList, 'is_contractor_material'); } // 转换暂估价材料 function transformEvalSummary() { //evalGLJFromOther const existMap = {}; const evalItems = []; tenderData.gljSummary.forEach(glj => { if (glj.is_evaluate) { existMap[glj.code] = true; evalItems.push(glj); } }); tenderData.evalGLJFromOther.forEach(glj => { if (!existMap[glj.code]) { evalItems.push(glj); } }); const evalGLJList = evalItems.map(glj => { const evalGLJ = {}; assignAttr(evalGLJ, glj, [ 'code', 'name', 'specs', 'unit', 'quantity', 'market_price', 'originPlace', 'vender', 'remark' ]); evalGLJ.seq = evalGLJ.code; return evalGLJ; }); /* const evalGLJList = tenderData.gljSummary .filter(glj => glj.is_evaluate) .map(glj => { const evalGLJ = {}; assignAttr(evalGLJ, glj, [ 'code', 'name', 'specs', 'unit', 'quantity', 'market_price', 'originPlace', 'vender', 'remark' ]); evalGLJ.seq = evalGLJ.code; return evalGLJ; }); */ transformRelatedGLJList(evalGLJList, rst.evaluationList, 'is_evaluate'); } // 生成单价文件 function generateUnitPrice(projectGLJ) { return { projectGLJID: projectGLJ.id, //做个标记,后端查找标准数据后,方便更新组成物数据 unit_price_file_id: tenderData.property.unitPriceFile.id, id: IDPlaceholder.unitPrice++, code: projectGLJ.code, original_code: projectGLJ.original_code, name: projectGLJ.name, unit: projectGLJ.unit, specs: projectGLJ.specs, type: projectGLJ.type, short_name: projectGLJ.shortName, base_price: projectGLJ.base_price, market_price: projectGLJ.market_price }; } } // 转换定额、定额人材机、定额系数 // @param {Object} tenderData - 提取的单位工程数据 // @param {Array} billsData - 转换后的清单数据 // @param {Object} projectGLJMap - 转换项目人材机时生成的项目人材机编码-数据的映射 function transformRations(tenderData, billsData, projectGLJMap) { const rst = { ration: [], rationGLJ: [], rationCoe: [], rationQuantityDetails: [], }; billsData .filter(bills => bills.rations && bills.rations.length) .forEach(bills => { bills.rations.forEach(ration => { // TODO 取费专业不知如何取,暂时取单位工程取费专业(导出的文件没有可用的取费专业字段,因此需要后端匹配标准定额) // 如果标准定额有取费专业,则会替换掉这里设置的取费专业 ration.programID = tenderData.property.projectEngineering; ration.ID = uuid.v1(); ration.projectID = tenderData.ID; ration.billsItemID = bills.ID; ration.contain = getContain(bills, ration); // 工程量明细 transformQuantityDetails(tenderData.ID, ration.quantityDetails, ration.ID, 'rationID'); ration.quantityEXP = getQuantityEXP(bills, ration); if (ration.quantityDetails) { rst.rationQuantityDetails.push(...ration.quantityDetails); } rst.ration.push(ration); // 定额人材机 rst.rationGLJ.push(...transformRationGLJs(bills, ration)); // 定额系数 rst.rationCoe.push(generateRationCoe(ration)); }); }); return rst; // 含量:定额工程量/清单工程量 function getContain(bills, ration) { if (!bills.quantity || !ration.quantity) { return '0'; } else { const tempQuantity = ration.quantity / bills.quantity; return isFinite(tempQuantity) ? scMathUtil.roundForObj(tempQuantity, Decimal.PROCESS) : '0'; } } // 转换定额人材机 function transformRationGLJs(bills, ration) { return ration.rationGLJs.map(rationGLJ => { const matched = projectGLJMap[rationGLJ.code]; if (matched) { rationGLJ.projectGLJID = matched.id; rationGLJ.code = matched.code; rationGLJ.type = matched.type; rationGLJ.shortName = matched.shortName; rationGLJ.name = matched.name; rationGLJ.original_code = matched.original_code; rationGLJ.unit = matched.unit; rationGLJ.specs = matched.specs; } rationGLJ.ID = uuid.v1(); rationGLJ.projectID = tenderData.ID; rationGLJ.billsItemID = bills.ID; rationGLJ.rationID = ration.ID; rationGLJ.rationCode = ration.code; // 暂时跟定额编码关联,后端好匹配标准数据 rationGLJ.rationItemQuantity = rationGLJ.quantity; // 定额消耗,暂时取消耗量,需要后端匹配标准数据后更新 return rationGLJ; }); } // 生成定额系数(项目数据处理的方法很有可能无法共用,因此不把重庆的方法抽离了) function generateRationCoe(ration) { return { projectID: ration.projectID, rationID: ration.ID, coeID: -1, ID: uuid.v1(), seq: 1, // 以前排序有的字段,暂时没什么用,不过还是赋上一个值 name: '自定义系数', content: '人工×1,材料×1,机械×1,主材×1,设备×1', coes: [ { amount: 1, operator: '*', gljCode: null, coeType: '定额' }, { amount: 1, operator: '*', gljCode: null, coeType: '人工' }, { amount: 1, operator: '*', gljCode: null, coeType: '材料' }, { amount: 1, operator: '*', gljCode: null, coeType: '机械' }, { amount: 1, operator: '*', gljCode: null, coeType: '主材' }, { amount: 1, operator: '*', gljCode: null, coeType: '设备' }, ], option_list: [] } } // 获取定额的工程量表达式 function getQuantityEXP(bills, ration) { if (ration.quantityDetails && ration.quantityDetails.length) { return GCLMXHj; } // 定额根单位与清单根单位相同,则定额工程量表达式为QDL;否则定额工程量表达式为:定额工程量 * 定额单位量词 // eg: 清单单位:m 定额单位:100m const reg = /\d+(\D+.*)/; const billsRootUnit = bills.unit && bills.unit.replace(reg, '$1') || ''; const rationRootUnit = ration.unit && ration.unit.replace(reg, '$1') || ''; if (billsRootUnit === rationRootUnit) { return QDL; } const numReg = /(\d+)\D+.*/; const num = ration.unit && ration.unit.replace(numReg, '$1') || '1'; return ration.quantity * num; } } // 删除一些无用属性,减少通信数据量 function clean(detailData) { const { bills, ration, projectGLJ } = detailData; bills.forEach(item => delete item.items && delete item.rations && delete item.feeCode && delete item.quantityDetails); ration.forEach(item => delete item.rationGLJs && delete item.quantityDetails); projectGLJ.forEach(item => delete item.ratios); } // 需要给清单、定额价格字段赋上调价数据,不然打开项目后会被重算: project_view.js -> loadProjectData方法内 function setupTenderFees(detailData) { const { bills, ration } = detailData; [...bills, ...ration].forEach(item => { if (item.fees && item.fees.length) { item.fees.forEach(feeItem => { if (commonUtil.isDef(feeItem.unitFee)) { feeItem.tenderUnitFee = feeItem.unitFee; } if (commonUtil.isDef(feeItem.totalFee)) { feeItem.tenderTotalFee = feeItem.totalFee; } }) } }); } // 转换详细的项目数据,清单、定额、定额人材机、项目人材机、单价文件等 function transformDetail(tenderData, IDPlaceholder, billsTemplate) { const { bills, billsQuantityDetails } = transformBills(tenderData, billsTemplate); // 转换定额的处理依赖转换后的项目人材机数据,因此需要先转换项目人材机相关的数据 const projectGLJMap = {}; const relatedljGLJData = transformGLJList(tenderData, IDPlaceholder, projectGLJMap); const { ration, rationGLJ, rationCoe, rationQuantityDetails } = transformRations(tenderData, bills, projectGLJMap); const detailData = { bills, ration, rationGLJ, rationCoe, quantityDetails: [...billsQuantityDetails, ...rationQuantityDetails], ...relatedljGLJData }; clean(detailData); setupTenderFees(detailData); console.log(`detailData`); console.log(detailData); return detailData; } function getUnit8Numbers(unit8Array) { const numbers = []; for (const a of unit8Array) { numbers.push(a); } return numbers; } /** * 解压cos、zip文件 * @param {File} file - 上传的文件 * @return {Object} 解压出来的xml文件名称与xml文件文本内容映射 */ async function unzipFile(file) { const jsZip = new JSZip(); // 广联达导出的cos文件,内部文件的文件名是gbk编码,如果不处理的话无法导入 let zip; if (typeof TextDecoder === 'undefined') { const gbkTextDecoder = new TextDecoder('gbk'); zip = await jsZip.loadAsync(file, { // zip文件中有一个标志,用于说明文件名和注释是否使用UTF-8编码。 // 如果未设置,则JSZip无法知道所使用的编码(它通常是操作系统的默认编码) // 因此若文件名编码非utf-8,则会调用decodeFileName。我们将其认为是gbk编码 decodeFileName: function (bytes) { if (bytes.length) { return gbkTextDecoder.decode(bytes); } else { return ''; } } }); } else { const fileNameByteList = []; let index = 0; zip = await jsZip.loadAsync(file, { decodeFileName: function (bytes) { if (bytes.length) { fileNameByteList.push(getUnit8Numbers(bytes)); return String(index++); } else { return ''; } } }); const decodedNames = await util.getDecodedData(fileNameByteList, 'gbk'); // 将zip里的文件名替换成gbk解码后的名称 decodedNames.forEach((name, index) => { if (zip.files[index]) { zip.files[index].name = name; zip.files[name] = zip.files[index]; delete zip.files[index]; } }); } const map = {}; for (const fileName in zip.files) { // 将二进制数据转换成字符串 map[fileName] = await jsZip.file(fileName).async('string'); } return map; } //从xml文件中提取数据 async function extractData(file, escape = false) { const fileMap = await unzipFile(file); const projectXML = fileMap['Project.xml']; if (!projectXML) { throw '无有效数据'; } const xmlObjMap = {}; // 导入易达的文件,经过unzipFile的decodeFileName,xml文件头部变成了,需要将前面的乱码去除,否则DOMParser转换后是null const reg = /.*<\?xml version="1.0" encoding="utf-8"\?>/i; for (const fileName in fileMap) { fileMap[fileName] = fileMap[fileName].replace(reg, ''); // x2js转换xml使用了DomParser接口,会将一些字符实体进行转义。若不想被自动转义,则需要调用escapeXMLEntity const xmlStr = escape ? util.escapeXMLEntity(fileMap[fileName]) : fileMap[fileName]; //将xml格式良好的字符串转换成对象 const x2js = new X2JS(); let xmlObj = x2js.xml_str2json(xmlStr); xmlObj = JSON.parse(util.restoreXMLEntity(JSON.stringify(xmlObj))); if (!xmlObj) { throw '无有效数据。'; } xmlObjMap[fileName] = xmlObj; } //提取数据 return extractProject(xmlObjMap); }; // 接受上传的文件类型(不同的省份可以上传的文件不同) const accept = ['.zip', '.cos']; return { accept, extractData, transformData, } })();