'use strict'; /** * * * @author Zhong * @date 2019/5/14 * @version */ /* * 尽可能减少服务器压力,前端提取后端所需数据,提取成更符合我们程序数据结构的数据 * 源数据中,含有前缀"_"的数据,为原xml节点中的属性,不含前缀的数据为原xml节点的节点名 * */ const importXML = (() => { // 通用设置和工具 const config = importXMLBase.CONFIG; const util = importXMLBase.UTIL; const { fixedFlag, billType, rationType, projectType } = commonConstants; const { AdjustType } = config; const { getValue, arrayValue, getFee, getFlag, getItemsRecur, } = util; // 限定取值的取费类别与固定ID对应 const FEE_TYPE_FLAG = { '120201': fixedFlag.CONSTRUCTION_ORGANIZATION, '1204': fixedFlag.SAFETY_CONSTRUCTION, '10041': fixedFlag.PROJECT_COMPLETE_ARCH_FEE, '1206': fixedFlag.HOUSE_QUALITY_ACCEPT_FEE, '800': fixedFlag.CHARGE, '900': fixedFlag.TAX, '9001': fixedFlag.ADDED_VALUE_TAX, '9002': fixedFlag.ADDITIONAL_TAX, '9003': fixedFlag.ENVIRONMENTAL_PROTECTION_TAX }; // 根据费用类别给清单数据设置flags字段 function initFlags(feeType) { let flag = FEE_TYPE_FLAG[feeType]; return flag ? [{ fieldName: 'fixed', flag: flag }] : []; } //导入的文件类型,界面选的文件类型是生成项目的文件类型,这里的文件类型指的是,要导入文件的类型, //导入文件类型不同,导入数据不同 let importFileKind = ''; //文件类型 const FileKind = { '投标': 1, 'tender': 1, '招标': 2, 'bid': 2, '控制价': 3, 'control': 3 }; // 第x册定额名称包含字符与计算程序ID的匹配映射 const NameProgramMapping = { '机械设备': 25, '热力设备': 26, '静置设备与工艺金属结构制作': 27, '电气设备': 28, '建筑智能化': 29, '自动化控制仪表': 30, '通风空调': 31, '工业管道': 32, '消防': 33, '给排水、采暖、燃气': 34, '刷油、防腐蚀、绝热': 35 }; // 获取定额取费专业,根据名称匹配有无固定映射,若没有则取单位工程取费专业(后台配置) function getProgramID(name, mapping, projectEngineering) { for (let mapName in mapping) { const reg = new RegExp(`第.{1,2}册\\s*${mapName}(安装)?工程`); if (name.match(reg)) { return mapping[mapName]; } } return projectEngineering; } let countData = { projectCount: 0, //项目数量 projectGLJCount: 0, //项目人材机数量 ratioCount: 0, //组成物数量 unitPriceCount: 0, //单价数量 unitPriceFileCount: 0, //单价文件数量 }; //标段 function loadProject(source) { Object.keys(countData).forEach(key => countData[key] = 0); // 清缓存 countData.projectCount++; importFileKind = FileKind[getValue(source, ['标段', '_文件类型'])]; let obj = { projType: projectType.Project, name: getValue(source, ['标段', '_项目名称']), engs: loadEng(source), property: { compilationIllustration: getValue(source, ['标段', '编制说明', '_内容']) }, basicInformation: loadBasicInfo(source), }; //统计项目数量 let count = 1 + obj.engs.length; obj.engs.forEach(eng => { count += eng.tenders.length; }); obj.projectCount = count; if (source.isTwoLevel) { obj.isTwoLevel = true; } return obj; } //基本信息相关 function loadBasicInfo(source) { let info = []; info.push({ key: 'projNum', value: getValue(source, ['标段', '_项目编号']) }); info.push({ key: 'constructionUnit', value: getValue(source, ['标段', '_建设单位']) }); //项目信息 let projInfoSrc = getValue(source, ['标段', '项目信息']); if (projInfoSrc) { info.push({ key: 'projectScale', value: getValue(projInfoSrc, ['_工程规模']) }); info.push({ key: 'projLocation', value: getValue(projInfoSrc, ['_工程所在地']) }); info.push({ key: 'projAddress', value: getValue(projInfoSrc, ['_工程地址']) }); info.push({ key: 'buildingUnit', value: getValue(projInfoSrc, ['_施工单位']) }); info.push({ key: 'establishUnit', value: getValue(projInfoSrc, ['_编制单位']) }); info.push({ key: 'auditUnit', value: getValue(projInfoSrc, ['_审核单位']) }); info.push({ key: 'establishUnitAuthor', value: getValue(projInfoSrc, ['_编制人']) }); info.push({ key: 'auditUnitAuditor', value: getValue(projInfoSrc, ['_审核人']) }); info.push({ key: 'commencementDate', value: getValue(projInfoSrc, ['_开工日期']) }); info.push({ key: 'completionDate', value: getValue(projInfoSrc, ['_竣工日期']) }); info.push({ key: 'establishDate', value: getValue(projInfoSrc, ['_编制日期']) }); info.push({ key: 'auditDate', value: getValue(projInfoSrc, ['_审核日期']) }); info.push({ key: 'materialPricePeriod', value: getValue(projInfoSrc, ['_材料价格期']) }); info.push({ key: 'contractPriceType', value: getValue(projInfoSrc, ['_合同价类型']) }); //招标信息 info.push({ key: 'agency', value: getValue(projInfoSrc, ['招标信息', '_招标代理机构']) }); info.push({ key: 'tenderCostEngineer', value: getValue(projInfoSrc, ['招标信息', '_造价工程师']) }); info.push({ key: 'tenderingRegistrationCertificateNumber', value: getValue(projInfoSrc, ['招标信息', '_造价工程师注册证号']) }); info.push({ key: 'tenderingPeriod', value: getValue(projInfoSrc, ['招标信息', '_招标工期']) }); info.push({ key: 'engineeringCost', value: getValue(projInfoSrc, ['招标信息', '_控制总价']) }); //投标信息 info.push({ key: 'bidder', value: getValue(projInfoSrc, ['投标信息', '_投标人']) }); info.push({ key: 'bidCostEngineer', value: getValue(projInfoSrc, ['投标信息', '_造价工程师']) }); info.push({ key: 'bidRegistrationCertificateNumber', value: getValue(projInfoSrc, ['投标信息', '_造价工程师注册证号']) }); info.push({ key: 'projectManager', value: getValue(projInfoSrc, ['投标信息', '_项目经理']) }); info.push({ key: 'bidPeriod', value: getValue(projInfoSrc, ['投标信息', '_投标工期']) }); info.push({ key: 'biddingMargin', value: getValue(projInfoSrc, ['投标信息', '_投标保证金']) }); info.push({ key: 'qualityCommitment', value: getValue(projInfoSrc, ['投标信息', '_质量承诺']) }); info.push({ key: 'guaranteeType', value: getValue(projInfoSrc, ['投标信息', '_担保类型']) }); info.push({ key: 'engineeringCost', value: getValue(projInfoSrc, ['投标信息', '_投标总价']) }); let firstEng = arrayValue(source, ['标段', '单项工程'])[0]; if (firstEng) { let firstTender = arrayValue(firstEng, ['单位工程'])[0]; if (firstTender) { info.push({ key: 'projectCategory', value: getValue(firstTender, ['工程特征', '_工程类别']) }); } } } return info; } //单项工程 function loadEng(source) { const engs = arrayValue(source, ['标段', '单项工程']); // 支持导入无单项工程数据,如果只有没有单项工程,自动生成一个单项工程数据 if (!engs.length) { countData.projectCount++; const eng = { code: '1', name: '单项工程', projType: 'Engineering', tenders: loadTender(source, ['标段', '单位工程']), } // 标记为两层结构,导出需要用到(虽然自动生成了三层,但是导出的时候需要只导出两层) source.isTwoLevel = true; return [eng]; } return arrayValue(source, ['标段', '单项工程']).map(src => { countData.projectCount++; return { projType: projectType.Engineering, name: getValue(src, ['_名称']), code: getValue(src, ['_编号']), tenders: loadTender(src) }; }); } //单位工程 function loadTender(engSrc, path = ['单位工程']) { return arrayValue(engSrc, path).map(src => { countData.projectCount++; countData.unitPriceFileCount++; return { projType: projectType.Tender, name: getValue(src, ['_名称']), code: getValue(src, ['_编号']), engineering: getValue(src, ['_专业']), //可能跟我们软件里的工程专业对应不上 projectFeature: loadProjectFeature(src), feeSummary: loadFeeSummary(src), //单位工程费汇总 fbfx: loadFBFX(src), csxm: loadCSXM(src), other: loadOther(src), chargeTax: loadChargeTax(src), gljSummary: loadGljSummary(src), differentiaSummary: loadDifferentiaSummary(src),//承包人材料差额法表 exponentialSummary: loadExponentialSummary(src),//承包人材料指数法表 evalBidSummary: loadEvalBidSummary(src), //评标材料表 evalSummary: loadEvalSummary(src) // 暂估价材料表 }; }); } //工程特征相关,特征项没有固定的项,不清楚导入的项无法用已有key对应,而且工程特征名称不会有重复,因此用名称与我们软件的工程特征对应 function loadProjectFeature(tenderSrc) { let feature = []; feature.push({ dispName: '建筑分类', value: getValue(tenderSrc, ['工程特征', '_建筑分类']) }); feature.push({ dispName: '工程分类', value: getValue(tenderSrc, ['工程特征', '_工程分类']) }); feature.push({ dispName: '建设规模', value: getValue(tenderSrc, ['工程特征', '_建设规模']) }); let items = arrayValue(tenderSrc, ['工程特征', '特征项']); feature.push(...items.map(item => { return { dispName: getValue(item, ['_名称']), value: getValue(item, ['_内容']) }; })); return feature; } //单位工程费汇总-计价程序费用行 function loadFeeSummary(tenderSrc) { let feeItems = arrayValue(tenderSrc, ['单位工程费汇总', '计价程序费用行']); return feeItems.map(itemSrc => { let obj = { rowCode: getValue(itemSrc, ['_行代号']), name: getValue(itemSrc, ['_项目名称']), calcBase: getValue(itemSrc, ['_计算基础表达式']), feeType: getValue(itemSrc, ['_费用类别']) }; if (importFileKind === FileKind.tender) { // obj.calcBase = getValue(itemSrc, ['_计算基础表达式']); obj.feeRate = getValue(itemSrc, ['_费率']); obj.fees = [{ fieldName: 'common', totalFee: getValue(itemSrc, ['_金额']) || '0' }]; } return obj; }); } //分部分项,两种情况 分部分项-清单分部-清单项目、分部分项-清单项目 //return {Array} function loadFBFX(tenderSrc) { let fbfxSrc = getValue(tenderSrc, ['分部分项清单']), fields = [['清单分部'], ['清单项目']]; return getItemsRecur(fbfxSrc, fields, (itemSrc, curField) => { if (curField[0] === fields[0][0]) { //提取分部所需数据 let obj = { type: billType.FB, code: getValue(itemSrc, ['_编号']), name: getValue(itemSrc, ['_名称']), remark: getValue(itemSrc, ['_备注']), }; if (importFileKind === FileKind.tender) { obj.fees = [ { fieldName: 'common', totalFee: getValue(itemSrc, ['_金额']) || '0' }, { fieldName: 'estimate', totalFee: getValue(itemSrc, ['_其中暂估价']) || '0' }, ]; } return obj; } else { //提取清单项目所需数据 return extractBills(itemSrc, billType.FX); } }); } //提取清单项目数据 function extractBills(billsSrc, type) { let { features, contents } = getFeaturesAndContents(billsSrc); // 特征及内容窗口 let { itemCharacter, jobContent } = featureAndContent(features, contents); let obj = { type: type, //清单类型 code: getValue(billsSrc, ['_项目编码']), name: getValue(billsSrc, ['_项目名称']), unit: getValue(billsSrc, ['_单位']), isEstimate: getValue(billsSrc, ['_暂估清单标志']) === 'true' ? true : false, mainBills: getValue(billsSrc, ['_主要清单标志']) === 'true' ? true : false, quantity: getValue(billsSrc, ['_工程量']) || '0', remark: getValue(billsSrc, ['_备注']), itemCharacterText: featureAndContentText(features, contents), itemCharacter: itemCharacter, // 不导入到清单下方的特征及内容窗口 jobContent: jobContent }; //投标和控制价,需要导入最高限价 if ([FileKind.tender, FileKind.control].includes(importFileKind)) { let maxPrice = getValue(billsSrc, ['_最高限价']); //不为0才输出 if (maxPrice && maxPrice !== '0') { obj.outPutMaxPrice = true; obj.outPutLimitPrice = true; obj.maxPrice = maxPrice; } } if (importFileKind === FileKind.tender) { //投标 obj.fees = [ { fieldName: 'common', unitFee: getValue(billsSrc, ['_综合单价']), totalFee: getValue(billsSrc, ['_综合合价']) }, { fieldName: 'estimate', totalFee: getValue(billsSrc, ['_其中暂估价']) }, ]; //费用组成 obj.fees.push(...extractFeeContent(getValue(billsSrc, ['费用组成']))); //组价内容 obj.rations = extractRation(billsSrc); } return obj; // 获取特征及内容源数据 function getFeaturesAndContents(billsSrc) { let features = arrayValue(billsSrc, ['项目特征', '特征']).map(fSrc => { return { name: getValue(fSrc, ['_特征名称']), eigenvalue: getValue(fSrc, ['_特征描述']) }; }); let contents = arrayValue(billsSrc, ['工程内容', '内容']).map(cSrc => { return { name: getValue(cSrc, ['_内容']) } }); return { features, contents }; } // 特征及内容窗口 function featureAndContent(features, contents) { // 项目特征 let itemCharacter = features.map((feature, idx) => { let obj = { character: feature.name, isChecked: true, serialNo: idx + 1, eigenvalue: [] }; if (feature.eigenvalue) { obj.eigenvalue.push({ value: feature.eigenvalue, isSelected: true }); } return obj; }); // 工作内容 let jobContent = contents.map((content, idx) => { return { content: content.name, serialNo: idx + 1, isChecked: true }; }); return { itemCharacter, jobContent }; } // 特征及内容文本(默认显示在项目特征列) function featureAndContentText(features, contents) { let textArr = []; // 项目特征 textArr.push('[项目特征]'); if (features.length) { textArr.push(...features.map(feature => { //若不存在特征描述,同时特征名称中含有“:”,则直接返回特征名称 (广联达招标文件中,特征名称包含了特征名:值) if (!feature.eigenvalue && /[\:,:]/.test(feature.name)) { return feature.name; } else { return `${feature.name}: ${feature.eigenvalue}`; } })); } // 工作内容 textArr.push('[工作内容]'); if (textArr.length) { textArr.push(...contents.map(content => content.name)); } return textArr.join('\n'); } } //提取定额子目 function extractRation(billsSrc) { let rationSrcs = arrayValue(billsSrc, ['组价内容', '定额子目']), serialNo = 1; return rationSrcs.map(rationSrc => { let rationData = { serialNo: serialNo++, code: getValue(rationSrc, ['_原始定额编号']).replace('换', ''), name: getValue(rationSrc, ['_项目名称']), caption: getValue(rationSrc, ['_项目名称']), unit: getValue(rationSrc, ['_单位']), libCode: getValue(rationSrc, ['_定额库编码']), quantity: getValue(rationSrc, ['_工程量']) || '0', quantityEXP: getValue(rationSrc, ['_工程量计算式']), adjustState: getValue(rationSrc, ['_子目类型']) == '2' ? '1' : '', //子目类型2为换算定额,标记一下我们软件才能显示 isSubcontract: getValue(rationSrc, ['_分包标志']) === 'false' ? false : true, remark: getValue(rationSrc, ['_备注']), programID: parseInt(getValue(rationSrc, ['_单价构成文件ID'])), }; //'6'认为量材,其他都认为是定额 let type = getValue(rationSrc, ['_子目类型']); if (type == '6') { rationData.type = rationType.volumePrice; rationData.subType = rationType.volumePriceMaterial; } else { rationData.type = rationType.ration; } rationData.fees = [ { fieldName: 'rationUnitPrice', unitFee: getValue(rationSrc, ['_定额单价']) || '0', totalFee: getValue(rationSrc, ['_定额合价']) || '0' }, { fieldName: 'common', unitFee: getValue(rationSrc, ['_综合单价']) || '0', totalFee: getValue(rationSrc, ['_综合合价']) || '0' }, ]; rationData.fees.push(...extractFeeContent(getValue(rationSrc, ['费用组成']))); //定额人材机 let rationGljSrc = arrayValue(rationSrc, ['工料分析', '人材机含量']); rationData.rationGljs = rationGljSrc.map(rjSrc => { return { code: getValue(rjSrc, ['_人材机代码']), quantity: getValue(rjSrc, ['_消耗量']) || '0', } }); return rationData; }); } //获取费用组成 function extractFeeContent(src) { let rst = []; //减少数据量 function addFee(feeData) { //有不为空的价格,才会插入 let match = Object.entries(feeData).find(data => { let [k, v] = data; return k !== 'fieldName' && v && v !== '0' && v !== '0.00'; }); if (match) { rst.push(feeData); } } addFee({ fieldName: 'labour', unitFee: getValue(src, ['_基价人工费单价']), totalFee: getValue(src, ['_基价人工费合价']), }); addFee({ fieldName: 'material', unitFee: getValue(src, ['_基价材料费单价']), totalFee: getValue(src, ['_基价材料费合价']), }); addFee({ fieldName: 'machine', unitFee: getValue(src, ['_基价机械费单价']), totalFee: getValue(src, ['_基价机械费合价']), }); addFee({ fieldName: 'unratedMaterial', unitFee: getValue(src, ['_未计材料单价']), totalFee: getValue(src, ['_未计材料合价']), }); addFee({ fieldName: 'gljDiff', unitFee: getValue(src, ['_人材机价差单价']), totalFee: getValue(src, ['_人材机价差合价']), }); addFee({ fieldName: 'estimate', unitFee: getValue(src, ['_暂估材料单价']), totalFee: getValue(src, ['_暂估材料合价']), }); addFee({ fieldName: 'manage', unitFee: getValue(src, ['_管理费单价']), totalFee: getValue(src, ['_管理费合价']), }); addFee({ fieldName: 'profit', unitFee: getValue(src, ['_利润单价']), totalFee: getValue(src, ['_利润合价']), }); addFee({ fieldName: 'risk', unitFee: getValue(src, ['_一般风险单价']), totalFee: getValue(src, ['_一般风险合价']), }); addFee({ fieldName: 'otherRisk', unitFee: getValue(src, ['_其他风险单价']), totalFee: getValue(src, ['_其他风险合价']), }); return rst; } //措施项目 function loadCSXM(tenderSrc) { let target = {}; let zzcsxmSrc = getValue(tenderSrc, ['措施项目清单', '组织措施清单']); target.zzcs = { items: loadZZCS(zzcsxmSrc) }; let jscsxmSrc = getValue(tenderSrc, ['措施项目清单', '技术措施清单']); target.jscs = { items: loadJSCS(jscsxmSrc) }; if (importFileKind === FileKind.tender) { //投标 target.fees = [ { fieldName: 'common', totalFee: getValue(tenderSrc, ['措施项目清单', '_金额']) || '0' }, { fieldName: 'estimate', totalFee: getValue(tenderSrc, ['措施项目清单', '_其中暂估价']) || '0' }, ]; target.zzcs.fees = [{ fieldName: 'common', totalFee: getValue(tenderSrc, ['措施项目清单', '组织措施清单', '_金额']) || '0' }]; target.jscs.fees = [ { fieldName: 'common', totalFee: getValue(tenderSrc, ['措施项目清单', '技术措施清单', '_金额']) || '0' }, { fieldName: 'estimate', totalFee: getValue(tenderSrc, ['措施项目清单', '技术措施清单', '_其中暂估价']) || '0' }, ]; } return target; //组织措施清单 function loadZZCS(zzcsSrc) { let fields = [['组织措施分类'], ['公式计算措施项']]; return getItemsRecur(zzcsSrc, fields, (itemSrc, curField) => { let rstObj; if (curField[0] === fields[0][0]) { //组织措施分类 rstObj = { type: billType.BILLS, code: getValue(itemSrc, ['_编码']), name: getValue(itemSrc, ['_名称']), remark: getValue(itemSrc, ['_备注']) } } else { rstObj = { //公式计算措施项 type: billType.BILLS, code: getValue(itemSrc, ['_序号']), name: getValue(itemSrc, ['_名称']), calcBase: getValue(itemSrc, ['_计算基础表达式']), feeRate: getValue(itemSrc, ['_费率']), isEstimate: getValue(itemSrc, ['_暂估价标志']) === 'true' ? true : false, remark: getValue(itemSrc, ['_备注']), flags: initFlags(getValue(itemSrc, ['_费用类别'])) }; } if (importFileKind === FileKind.tender) { rstObj.fees = [{ fieldName: 'common', totalFee: getValue(itemSrc, ['_金额']) || '0' }]; } return rstObj; }); } //技术措施清单 function loadJSCS(jscsSrc) { let fields = [['技术措施分类'], ['清单项目']]; return getItemsRecur(jscsSrc, fields, (itemSrc, curField) => { if (curField[0] === fields[0][0]) { //技术措施分类 let obj = { type: billType.BILLS, code: getValue(itemSrc, ['_编号']), name: getValue(itemSrc, ['_名称']) }; if (importFileKind === FileKind.tender) { obj.fees = [ { fieldName: 'common', totalFee: getValue(itemSrc, ['_金额']) || '0' }, { fieldName: 'estimate', totalFee: getValue(itemSrc, ['_其中暂估价']) || '0' }, ]; } return obj; } else { //清单项目 return extractBills(itemSrc, billType.BILLS); } }); } } //其他项目 function loadOther(tenderSrc) { let otherSrc = getValue(tenderSrc, ['其他项目清单']); //暂列金额 let provisional = { items: arrayValue(otherSrc, ['暂列金额', '暂列金额明细']).map(src => { let obj = { type: billType.BILLS, code: getValue(src, ['_编号']), name: getValue(src, ['_项目名称']), unit: getValue(src, ['_计量单位']), remark: getValue(src, ['_备注']) }; //if (importFileKind === FileKind.tender) { obj.fees = [{ fieldName: 'common', totalFee: getValue(src, ['_金额']) || '0' },]; //} return obj; }) }; //专业工程暂估价 let engineeringPro = { items: arrayValue(otherSrc, ['专业工程暂估价', '专业工程暂估明细']).map(src => { let obj = { type: billType.BILLS, code: getValue(src, ['_编号']), name: getValue(src, ['_工程名称']), engineeringContent: getValue(src, ['_工程内容']), remark: getValue(src, ['_备注']) }; //if (importFileKind === FileKind.tender) { obj.fees = [{ fieldName: 'common', totalFee: getValue(src, ['_金额']) || '0' },]; //} return obj; }) }; //计日工 function extractDayWorkItem(src) { let obj = { type: billType.BILLS, code: getValue(src, ['_编号']), name: getValue(src, ['_名称']), unit: getValue(src, ['_单位']), quantity: getValue(src, ['_数量']), remark: getValue(src, ['_备注']) }; if (importFileKind === FileKind.tender) { obj.fees = [ { fieldName: 'common', unitFee: getValue(src, ['_综合单价']) || '0', totalFee: getValue(src, ['_综合合价']) || '0' } ]; } return obj; } let labour = arrayValue(otherSrc, ['计日工', '人工', '计日工项目']).map(extractDayWorkItem), material = arrayValue(otherSrc, ['计日工', '材料', '计日工项目']).map(extractDayWorkItem), machine = arrayValue(otherSrc, ['计日工', '施工机械', '计日工项目']).map(extractDayWorkItem); let dayWork = { labour, material, machine }; //总承包服务费 let fields = [['总承包服务费分类'], ['总承包服务费费用项']], serviceSrc = getValue(otherSrc, ['总承包服务费']); let service = { items: getItemsRecur(serviceSrc, fields, (src, curField) => { if (curField[0] === fields[0][0]) { return { //总承包服务费分类 type: billType.BILLS, code: getValue(src, ['_编号']), name: getValue(src, ['_名称']), remark: getValue(src, ['_备注']) } } else { let obj = { //总承包服务费费用项 type: billType.BILLS, code: getValue(src, ['_编号']), name: getValue(src, ['_工程名称']), calcBase: getValue(src, ['_计算基础']), serviceContent: getValue(src, ['_服务内容']), feeRate: getValue(src, ['_费率']), remark: getValue(src, ['_备注']) }; if (importFileKind === FileKind.tender) { obj.fees = [ { fieldName: 'common', totalFee: getValue(src, ['_金额']) || '0' } ]; } return obj; } }) }; //索赔、签证 function extractClaimVisa(src) { let obj = { type: billType.BILLS, code: getValue(src, ['_编号']), name: getValue(src, ['_项目名称']), unit: getValue(src, ['_计量单位']), quantity: getValue(src, ['_数量']), claimVisa: getValue(src, ['_依据']) }; //if (importFileKind === FileKind.tender) { obj.fees = [ { fieldName: 'common', unitFee: getValue(src, ['_单价']) || '0', totalFee: getValue(src, ['_合价']) || '0' } ]; //} return obj; } let claim = { items: arrayValue(otherSrc, ['索赔计价汇总', '签证索赔计价汇总费用项']).map(extractClaimVisa) }, visa = { items: arrayValue(otherSrc, ['现场签证计价汇总', '签证索赔计价汇总费用项']).map(extractClaimVisa) }; //其他 let others = { items: arrayValue(otherSrc, ['其他', '其他列项']).map(src => { let obj = { type: billType.BILLS, code: getValue(src, ['_序号']), name: getValue(src, ['_名称']), calcBase: getValue(src, ['_计算基础']), feeRate: getValue(src, ['_费率']), remark: getValue(src, ['_备注']) }; if (importFileKind === FileKind.tender) { obj.fees = [{ fieldName: 'common', totalFee: getValue(src, ['_金额']) || '0' }]; } return obj; }) }; provisional.fees = [{ fieldName: 'common', totalFee: getValue(otherSrc, ['暂列金额', '_金额']) || '0' }]; engineeringPro.fees = [{ fieldName: 'common', totalFee: getValue(otherSrc, ['专业工程暂估价', '_金额']) || '0' }]; claim.fees = [{ fieldName: 'common', totalFee: getValue(otherSrc, ['索赔计价汇总', '_金额']) || '0' }]; visa.fees = [{ fieldName: 'common', totalFee: getValue(otherSrc, ['现场签证计价汇总', '_金额']) || '0' }]; if (importFileKind === FileKind.tender) { dayWork.fees = [{ fieldName: 'common', totalFee: getValue(otherSrc, ['计日工', '_金额']) || '0' }]; service.fees = [{ fieldName: 'common', totalFee: getValue(otherSrc, ['总承包服务费', '_金额']) || '0' }]; others.fees = [{ fieldName: 'common', totalFee: getValue(otherSrc, ['其他', '_金额']) || '0' }]; } return { provisional, engineeringPro, dayWork, service, claim, visa, others }; } //规费和税金清单 function loadChargeTax(tenderSrc) { // 由于规费和规费子项的费用类别都是800,因此要处理(重复出现的费用类别不设置固定ID,保证没有相同的固定清单); // 记录出现过的费用类别 let occurs = []; return arrayValue(tenderSrc, ['规费和税金清单', '费用项']).map(src => { let feeType = getValue(src, ['_费用类别']); let obj = { type: billType.BILLS, rowCode: getValue(src, ['_行代号']), code: getValue(src, ['_序号']), name: getValue(src, ['_名称']), calcBase: getValue(src, ['_计算基础表达式']), feeRate: getValue(src, ['_费率']), remark: getValue(src, ['_备注']), flags: occurs.includes(feeType) ? [] : initFlags(feeType) }; occurs.push(feeType); if (importFileKind === FileKind.tender) { obj.fees = [{ fieldName: 'common', totalFee: getValue(src, ['_金额']) || '0' }]; } return obj; }); } //根据费用类别、配比类比获取人材机类别(后端匹配不到标准人材机的时候用) function getGljTypeData(feeType, ratioType, name) { // 因为这份文件不太标准,各家也不统一,没有一个靠谱的方法能准备获取人材机类型,所以有些地方需要特殊处理 // 有的人材机类型根据人材机名称特殊获取 const specialMap = { '定额管理费': { type: 6, shortName: '管' } }; if (specialMap[name]) { return specialMap[name]; } // 一些需要特殊处理的动力材料 const powerNameMap = { '柴油': { type: 305, shortName: '动' }, '柴油(机械用)': { type: 305, shortName: '动' }, '柴油(机械用)': { type: 305, shortName: '动' }, '汽油': { type: 305, shortName: '动' }, '汽油(机械用)': { type: 305, shortName: '动' }, '汽油(机械用)': { type: 305, shortName: '动' }, '电': { type: 305, shortName: '动' }, '电(机械用)': { type: 305, shortName: '动' }, '电(机械用)': { type: 305, shortName: '动' }, '机上人工': { type: 303, shortName: '机人' }, }; // 特殊处理机上人工(鹏业有的机上人工输出的feeType是1) if (/机上人工/.test(name)) { return { type: 303, shortName: '机人' }; } // 人、机不需要匹配配比类别 if (feeType === '1') { return { type: 1, shortName: '人' }; } else if (feeType === '3') { // 特殊处理动力材料 return powerNameMap[name] || { type: 301, shortName: '机' }; } const map = { '-': { type: 201, shortName: '材' }, '2-': { type: 201, shortName: '材' }, '2-1': { type: 202, shortName: '砼' }, '2-2': { type: 205, shortName: '商砼' }, '2-3': { type: 203, shortName: '浆' }, '2-4': { type: 206, shortName: '商浆' }, '2-5': { type: 204, shortName: '配比' }, '4-': { type: 4, shortName: '主' }, }; return map[`${feeType}-${ratioType}`] || null; } //主要材料类别-三材类别映射 const MaterialMap = { '100': 1, //钢材钢筋认为钢材,因为文件没细分 '200': 4, //水泥 '300': 3, //木材 '400': 5 //商品砼 }; //人材机汇总 function loadGljSummary(tenderSrc) { let gljsSrc = arrayValue(tenderSrc, ['人材机汇总', '人材机']); return gljsSrc.map(gljSrc => { countData.projectGLJCount++; //编码都取原始编码,如果原始编码为空,都取编码 let sourceCode = getValue(gljSrc, ['_原始代码']), code = getValue(gljSrc, ['_代码']); if (!sourceCode) { sourceCode = code; } // 人材机的真正原始编号应按规矩处理,不能直接读取编码。 // 应取sourceCode,qtf文件中原始代码的最后一个“-”前面的文本;如果没有“-”则直接取原始代码 const orgCodeReg = /.*(?=-\d+$)/; const match = sourceCode.match(orgCodeReg); const orgCode = match ? match[0] : sourceCode; let gljData = { customCode: code, //处理自定义的代码 (有的公司定额人材机代码、人材机代码是用自动生成的代码) code: code.includes(sourceCode) ? code : sourceCode, // 如果自定义代码里包含了原始编码,则编码取自定义编码 original_code: orgCode, name: getValue(gljSrc, ['_名称']), specs: getValue(gljSrc, ['_规格']), unit: getValue(gljSrc, ['_单位']), supply: getValue(gljSrc, ['_供应方式']) == '2' ? 2 : 0, //不是完全甲供就取自行采购 is_evaluate: getValue(gljSrc, ['_暂估材料标志']) === 'true' ? true : false, no_tax_eqp: getValue(gljSrc, ['_不计税设备标志']) === 'true' ? true : false, base_price: getValue(gljSrc, ['_定额价']), market_price: getValue(gljSrc, ['_市场价']), originPlace: getValue(gljSrc, ['_产地']), vender: getValue(gljSrc, ['_厂家']), qualityGrace: getValue(gljSrc, ['_质量等级']), brand: getValue(gljSrc, ['_品牌']), remark: getValue(gljSrc, ['_备注']) }; if (MaterialMap[getValue(gljSrc, ['_主要材料类别'])]) { gljData.materialType = MaterialMap[getValue(gljSrc, ['_主要材料类别'])]; gljData.materialCoe = getValue(gljSrc, ['_主要材料单位系数']); } let typeData = getGljTypeData(getValue(gljSrc, ['_费用类别']), getValue(gljSrc, ['_配比类别']), gljData.name); if (typeData) { gljData.type = typeData.type; gljData.shortName = typeData.shortName; } //人材机配比 let ratioSrc = arrayValue(gljSrc, ['人材机配比']); gljData.ratios = ratioSrc.map(rSrc => { countData.ratioCount++; return { code: getValue(rSrc, ['_明细材料代码']), consumption: getValue(rSrc, ['_数量']) } }); return gljData; }); } //承包人材料差额法表 function loadDifferentiaSummary(tenderSrc) { let gljsSrc = arrayValue(tenderSrc, ['承包人材料差额法表', '承包人材料差额法明细']); return gljsSrc.map(gljSrc => { return { code: getValue(gljSrc, ['_关联材料号']), name: getValue(gljSrc, ['_名称']), specs: getValue(gljSrc, ['_规格']), unit: getValue(gljSrc, ['_单位']), quantity: getValue(gljSrc, ['_数量']), riskCoe: getValue(gljSrc, ['_风险系数']), standardPrice: getValue(gljSrc, ['_基准单价']), market_price: getValue(gljSrc, ['_投标单价']), remark: getValue(gljSrc, ['_备注']), }; }); } //承包人材料指数法表 function loadExponentialSummary(tenderSrc) { let gljsSrc = arrayValue(tenderSrc, ['承包人材料指数法表', '承包人材料指数法明细']); return gljsSrc.map(gljSrc => { return { code: getValue(gljSrc, ['_关联材料号']), name: getValue(gljSrc, ['_名称']), specs: getValue(gljSrc, ['_规格']), varWeight: getValue(gljSrc, ['_变值权重B']), FO: getValue(gljSrc, ['_基本价格指数']), FI: getValue(gljSrc, ['_现行价格指数']), remark: getValue(gljSrc, ['_备注']), }; }); } //评标材料 function loadEvalBidSummary(tenderSrc) { let gljsSrc = arrayValue(tenderSrc, ['评标材料表', '材料明细']); return gljsSrc.map(gljSrc => { return { seq: getValue(gljSrc, ['_序号']), code: getValue(gljSrc, ['_关联材料号']), name: getValue(gljSrc, ['_材料名称']), specs: getValue(gljSrc, ['_规格型号']), unit: getValue(gljSrc, ['_计量单位']), quantity: getValue(gljSrc, ['_数量']), market_price: getValue(gljSrc, ['_单价']), // 由于数据库有这两个字段,暂时也导入(前端隐藏了,目前实际上应该是没用的,以防万一) originPlace: getValue(gljSrc, ['_产地']), vender: getValue(gljSrc, ['_厂家']), remark: getValue(gljSrc, ['_备注']) }; }); } // 暂估价材料 function loadEvalSummary(tenderSrc) { const gljsSrc = arrayValue(tenderSrc, ['暂估价材料表', '材料明细']); return gljsSrc.map(gljSrc => { return { seq: getValue(gljSrc, ['_序号']), code: getValue(gljSrc, ['_关联材料号']), name: getValue(gljSrc, ['_材料名称']), specs: getValue(gljSrc, ['_规格型号']), unit: getValue(gljSrc, ['_计量单位']), quantity: getValue(gljSrc, ['_数量']), market_price: getValue(gljSrc, ['_单价']), // 由于数据库有这两个字段,暂时也导入(前端隐藏了,目前实际上应该是没用的,以防万一) originPlace: getValue(gljSrc, ['_产地']), vender: getValue(gljSrc, ['_厂家']), remark: getValue(gljSrc, ['_备注']) } }); } //-------------------------提取数据后的数据处理 //获取人材机调整法(差额法还是指数法) function getAdjustType(tenderData) { //如果承包人材料指数法里,所有数据的,变值权重B、基本价额指数,现行价格指数全为0,则为指数法,否则为差额法 let isCoe = tenderData.exponentialSummary.length && tenderData.exponentialSummary.every(data => !(data.varWeight === '0' && data.FO === '0' && data.FI === '0') ); return isCoe ? AdjustType.coe : AdjustType.info; } // 个软件公司确定了对应关系的其他项目、税金,使用费用类别进行匹配,其他按照名称关键字匹配 const matchRegs = [ { field: 'feeType', reg: /^1300$/, flag: fixedFlag.OTHER }, { field: 'feeType', reg: /^900$/, flag: fixedFlag.TAX }, { field: 'name', reg: /分部分项/, flag: fixedFlag.SUB_ENGINERRING }, { field: 'name', reg: /^措施项目/, flag: fixedFlag.MEASURE }, { field: 'name', reg: /技术措施/, flag: fixedFlag.CONSTRUCTION_TECH }, { field: 'name', reg: /组织措施/, flag: fixedFlag.CONSTRUCTION_ORGANIZATION }, { field: 'name', reg: /组织措施/, flag: fixedFlag.CONSTRUCTION_ORGANIZATION }, { field: 'name', reg: /安全文明/, flag: fixedFlag.SAFETY_CONSTRUCTION }, // {reg: /其他项目/, flag: fixedFlag.OTHER}, { field: 'name', reg: /暂列金额/, flag: fixedFlag.PROVISIONAL }, { field: 'name', reg: /暂估价/, flag: fixedFlag.ESTIMATE }, { field: 'name', reg: /计日工/, flag: fixedFlag.DAYWORK }, { field: 'name', reg: /总承包服务/, flag: fixedFlag.TURN_KEY_CONTRACT }, { field: 'name', reg: /索赔(?:及|与|和)现场签证/, flag: fixedFlag.TURN_KEY_CONTRACT }, { field: 'name', reg: /规费/, flag: fixedFlag.CHARGE }, // {reg: /税金/, flag: fixedFlag.TAX}, { field: 'name', reg: /增值税/, flag: fixedFlag.ADDED_VALUE_TAX }, { field: 'name', reg: /^附加税/, flag: fixedFlag.ADDITIONAL_TAX }, { field: 'name', reg: /环境保护税/, flag: fixedFlag.ENVIRONMENTAL_PROTECTION_TAX }, { field: 'name', reg: /(?:合\s*价)|(?:工程造价)/, flag: fixedFlag.ENGINEERINGCOST } ]; //处理单位工程费用汇总的清单,这一部分没有靠谱的规则,特殊处理并添加相关清单 function setupFeeSummary(feeSummary, needfulTemplate) { let preDXFY = null, //记录上一条添加的大项费用 preSubTax = null; //记录上一条添加的附加税子项(特殊处理城市xx、教育xx附加...名称的清单) let addtionalTax = needfulTemplate.find(nData => getFlag(nData) === fixedFlag.ADDITIONAL_TAX), //附加税 engineeringCost = needfulTemplate.find(nData => getFlag(nData) === fixedFlag.ENGINEERINGCOST); //工程造价 for (let feeBills of feeSummary) { //匹配固定的附加税子项 if (addtionalTax && (/城市维护建设/.test(feeBills.name) || /教育费附加/.test(feeBills.name) || /地方教育附加/.test(feeBills.name)) ) { let subTaxData = { ID: uuid.v1(), ParentID: addtionalTax.ID, NextSiblingID: -1, name: feeBills.name, rowCode: feeBills.rowCode, feeRate: feeBills.feeRate, calcBase: feeBills.calcBase, }; if (importFileKind === FileKind.tender) { //subTaxData.rowCode = feeBills.rowCode; subTaxData.fees = feeBills.fees; } if (preSubTax) { preSubTax.NextSiblingID = subTaxData.ID; } needfulTemplate.push(subTaxData); preSubTax = subTaxData; } else { let isMatched = false; //匹配固定项 for (let match of matchRegs) { if (!match.reg.test(feeBills[match.field])) { continue; } let findBills = needfulTemplate.find(nData => getFlag(nData) === match.flag); if (!findBills) { //如果模板中没有安全文明施工费,计价汇总行中安全文明施工费,将其设置为组织清单子项 if (/安全文明施工/.test(feeBills.name)) { let fixedCSXM = needfulTemplate.find(nData => getFlag(nData) === fixedFlag.CONSTRUCTION_ORGANIZATION); if (fixedCSXM) { needfulTemplate.push({ ID: uuid.v1(), ParentID: fixedCSXM.ID, NextSiblingID: -1, name: feeBills.name, calcBase: feeBills.calcBase, feeRate: feeBills.feeRate, }); isMatched = true; break; //不添加到大项费用 } } continue; } isMatched = true; // 文件有基数则导入基数,没有则用模板的基数 if (feeBills.calcBase) { findBills.calcBase = feeBills.calcBase; } else { // 用了模板的基数,基数无视基数验证 findBills.ignoreValidator = true; } findBills.feeRate = feeBills.feeRate; findBills.rowCode = feeBills.rowCode; //后台配置的feeRateID优先级比feeRate高,导入数据有费率值时,需要清空这个配置 if (findBills.feeRate && parseFloat(findBills.feeRate) !== 0) { findBills.feeRateID = null; } if (importFileKind === FileKind.tender) { //findBills.rowCode = feeBills.rowCode; findBills.fees = feeBills.fees; } break; } //匹配不到固定项,认为是大项费用 if (!isMatched) { let dxfyData = { ID: uuid.v1(), ParentID: -1, NextSiblingID: -1, name: feeBills.name, rowCode: feeBills.rowCode, calcBase: feeBills.calcBase, feeRate: feeBills.feeRate, }; if (importFileKind === FileKind.tender) { //dxfyData.rowCode = feeBills.rowCode; dxfyData.fees = feeBills.fees; } if (!preDXFY) { preDXFY = engineeringCost; } preDXFY.NextSiblingID = dxfyData.ID; needfulTemplate.push(dxfyData); preDXFY = dxfyData; } } } } //处理、添加清单 function setupBills(tenderData, billsTarget) { //规费和税金 这个地方不靠谱,先特殊处理(无法兼容所有情况) //1.如果在清单中出现(匹配名称相关关键字),则更新数据 //2.否则新增(在税金出现前,全作为规费的子清单,否则作为税金子清单) let charge = null, tax = null, additionalTax = null; for (let i = 0; i < tenderData.chargeTax.length; i++) { let curBills = tenderData.chargeTax[i]; let matchBills = billsTarget.find(d => (d.rowCode && d.rowCode === curBills.rowCode) || d.name === curBills.name); //匹配到,更新 if (matchBills) { if (getFlag(matchBills) === fixedFlag.CHARGE) { charge = matchBills; } else if (getFlag(matchBills) === fixedFlag.TAX) { tax = matchBills; } else if (getFlag(matchBills) === fixedFlag.ADDITIONAL_TAX) { additionalTax = matchBills; } matchBills.calcBase = curBills.calcBase; matchBills.feeRate = curBills.feeRate; matchBills.fees = curBills.fees || []; matchBills.rowCode = curBills.rowCode; if (curBills.flags && curBills.flags.length) { matchBills.flags = curBills.flags; } } else { //新增 if (!charge && !tax) { continue; } //存在规费,还未找到税金,后面数据作为规费的子项 已找到税金,后面数据作为税金子项 let curParent = !tax ? charge : tax; //特殊处理"城市维护建设税" "教育费附加" "地方教育附加",这三项固定设为税金下附加税的子项 if (additionalTax && (/城市维护建设/.test(curBills.name) || /教育费附加/.test(curBills.name) || /地方教育附加/.test(curBills.name))) { curParent = additionalTax; } curBills.ID = uuid.v1(); curBills.NextSiblingID = -1; curBills.ParentID = curParent.ID; let pre = billsTarget.find(d => d.ParentID === curParent.ID && d.NextSiblingID === -1); if (pre) { pre.NextSiblingID = curBills.ID; } billsTarget.push(curBills); if (!additionalTax && /^附加税/.test(curBills.name)) { additionalTax = curBills; } } } //分部分项 addFixedBlock(fixedFlag.SUB_ENGINERRING, tenderData.fbfx); //措施项目 addFixedBlock(fixedFlag.MEASURE, null, tenderData.csxm.fees); //组织措施 //特殊处理子项 //需要添加的组织措施子项 function updateZZCSItem(org, tar) { for (let [k, v] of Object.entries(tar)) { // 如果匹配到的清单自身已有固定ID,不更新固定ID // 如果目标清单没有基数,模板清单基数不更新 if ((k === 'flags' && Array.isArray(tar.flags) && tar.flags.length) || (k === 'calcBase' && !tar.calcBase)) { continue; } org[k] = v; } } let zzcsSubs = []; let fixedOrganization = billsTarget.find(data => /组织措施费/.test(data.name)), fixedSafe = billsTarget.find(data => /安全文明/.test(data.name)), fixedProjectComplete = billsTarget.find(data => /建设工程竣工档案编制/.test(data.name)), fixedHouseQuality = billsTarget.find(data => /住宅工程质量分户验收/.test(data.name)), fixedZZCS = billsTarget.find(data => getFlag(data) === fixedFlag.CONSTRUCTION_ORGANIZATION), lastItem = billsTarget.find(data => data.ParentID === fixedZZCS.ID && data.NextSiblingID === -1); for (let item of tenderData.csxm.zzcs.items) { if (/组织措施费/.test(item.name) && fixedOrganization) { updateZZCSItem(fixedOrganization, item); } else if (/安全文明/.test(item.name) && fixedSafe) { updateZZCSItem(fixedSafe, item); } else if (/建设工程竣工档案/.test(item.name) && fixedProjectComplete) { updateZZCSItem(fixedProjectComplete, item); } else if (/住宅工程质量分户验收/.test(item.name) && fixedHouseQuality) { updateZZCSItem(fixedHouseQuality, item); } else { zzcsSubs.push(item); } } addFixedBlock(fixedFlag.CONSTRUCTION_ORGANIZATION, zzcsSubs, tenderData.csxm.zzcs.fees); //更新原本的最后一个节点 if (zzcsSubs.length && lastItem) { lastItem.NextSiblingID = zzcsSubs[0].ID; } //技术措施 addFixedBlock(fixedFlag.CONSTRUCTION_TECH, tenderData.csxm.jscs.items, tenderData.csxm.jscs.fees); //暂列金额 addFixedBlock(fixedFlag.PROVISIONAL, tenderData.other.provisional.items, tenderData.other.provisional.fees); //专业工程暂估价 addFixedBlock(fixedFlag.ENGINEERING_ESITIMATE, tenderData.other.engineeringPro.items, tenderData.other.engineeringPro.fees); // 专业工程暂估价汇总金额设置为暂估价清单金额 let estimateFixedBills = billsTarget.find(d => getFlag(d) === fixedFlag.ESTIMATE); if (estimateFixedBills) { estimateFixedBills.fees = tenderData.other.engineeringPro.fees; } //计日工 addFixedBlock(fixedFlag.DAYWORK, null, tenderData.other.dayWork.fees); //人工 addFixedBlock(fixedFlag.LABOUR, tenderData.other.dayWork.labour); //材料 addFixedBlock(fixedFlag.MATERIAL, tenderData.other.dayWork.material); //机械 addFixedBlock(fixedFlag.MACHINE, tenderData.other.dayWork.machine); //总承包服务费 addFixedBlock(fixedFlag.TURN_KEY_CONTRACT, tenderData.other.service.items, tenderData.other.service.fees); //索赔 addFixedBlock(fixedFlag.CLAIM, tenderData.other.claim.items, tenderData.other.claim.fees); //签证 addFixedBlock(fixedFlag.VISA, tenderData.other.visa.items, tenderData.other.visa.fees); //其他 if (tenderData.other.others.items.length) { //特殊处理材料(工程设备暂估价) let materialProIdx = tenderData.other.others.items.findIndex(item => item.name === '材料(工程设备)暂估价'); if (~materialProIdx) { let materialPro = tenderData.other.others.items.splice(materialProIdx, 1)[0]; let fixedMaterialPro = billsTarget.find(data => getFlag(data) === fixedFlag.MATERIAL_PROVISIONAL); if (fixedMaterialPro) { fixedMaterialPro.fees = materialPro.fees; fixedMaterialPro.feeRate = materialPro.feeRate; fixedMaterialPro.calcBase = materialPro.calcBase; } } if (tenderData.other.others.items.length) { let othersParent = { ID: uuid.v1(), NextSiblingID: -1, name: '其他', fees: tenderData.other.others.fees || [] }; addFixedBlock(fixedFlag.OTHER, [othersParent]); addFixedBlock(othersParent, tenderData.other.others.items); } } //删掉有feeRate的feeRateID billsTarget.forEach(data => { if (data.feeRate) { data.feeRateID = null; } }); function addFixedBlock(flag, items, fees = null) { let fixedBills = flag; if (typeof flag === 'number') { fixedBills = billsTarget.find(d => getFlag(d) === flag); } if (fixedBills) { if (fees) { fixedBills.fees = fees || []; } if (Array.isArray(items) && items.length) { addBills(fixedBills.ID, items); } } } //设置清单的ID数据并添加清单到目标数组 function addBills(parentID, items) { for (let i = 0; i < items.length; i++) { let curBills = items[i], preBills = items[i - 1]; curBills.ID = uuid.v1(); curBills.ParentID = parentID; curBills.NextSiblingID = -1; if (preBills) { preBills.NextSiblingID = curBills.ID; } billsTarget.push(curBills); if (curBills.items && curBills.items.length) { addBills(curBills.ID, curBills.items); } } } } //计算基数字典 const CalcBaseMap = { FBFXHJ: '{分部分项工程费}', RGF: '{分部分项定额人工费}', CLF: '{分部分项定额材料费}', JXF: '{分部分项定额施工机具使用费}', ZCF: '{分部分项主材费}', GR: '{分部分项人工工日}', CSXMHJ: '{措施项目费}', ZZCSF: '{组织措施项目费}', JSCSF: '{技术措施项目费}', JSCS_RGF: '{技术措施项目定额人工费}', JSCS_CLF: '{技术措施项目定额材料费}', JSCS_JXF: '{技术措施项目定额施工机具使用费}', JSCS_ZCF: '{技术措施项目主材费}', JSCS_GR: '{技术措施项目人工工日}', //JZMJ: '{建筑面积}', RCJJC: '{人材机价差}', RGJC: '{人工价差}', CLJC: '{材料价差}', JXJC: '{机械价差}', JRGLF: '{甲供人工费}', JGCLF: '{甲供材料费}', JGJXF: '{甲供施工机具使用费}', JGZCF: '{甲供主材费}', FBF: '{分包费}', FBRGF: '{分包定额人工费}', FBCLF: '{分包定额材料费}', FBJXF: '{分包定额机械费}', FBZCF: '{分包主材费}', FBSBF: '{分包设备费}', FBGR: '{分包人工工日}', QTXMHJ: '{其他项目费}', GF: '{规费}', SJ: '{税金}', SJHJ: '{税金}', SQGCZJ: '{税前工程造价}' }; // 检查固定清单引用的基数造成自身循环,比如分部分项部分引用了FBFXHJ // 有文件的单位工程费汇总中,含有技术措施项目费清单,含有基数,且无子项。 function isCalcBaseCycle(bills) { const flag = getFlag(bills); if (!flag) { return false; } // 跟固定清单直接相关联的基数,若相关固定清单引用了其下的基数,则造成自身循环 const CycleMap = { [fixedFlag.SUB_ENGINERRING]: ['FBFXHJ', 'RGF', 'CLF', 'JXF', 'ZCF', 'GR'], [fixedFlag.MEASURE]: ['CSXMHJ'], [fixedFlag.CONSTRUCTION_ORGANIZATION]: ['ZZCSF'], [fixedFlag.CONSTRUCTION_TECH]: ['JSCSF', 'JSCS_RGF', 'JSCS_CLF', 'JSCS_JXF', 'JSCS_ZCF', 'JSCS_GR'], [fixedFlag.CHARGE]: ['GF'], [fixedFlag.TAX]: ['SJ', 'SJHJ'] }; const match = CycleMap[flag]; if (!match) { return false; } return match.some(item => bills.calcBase.match(new RegExp(`\\b${item}\\b`))); } //转换计算基数 //1.有子项数据,则清空基数 //2.引用的基数造成自身循环,比如分部分项部分引用了FBFXHJ //3.行代号引用转换为ID引用 //4.对应字典代号转换,对应字典里找不到则设置成金额 function transformCalcBase(billsData) { //行代号 - ID映射 let rowCodeMap = {}; billsData.forEach(data => { if (data.rowCode) { rowCodeMap[data.rowCode] = data.ID; } }); for (let bills of billsData) { if (!bills.calcBase || bills.ignoreValidator) { continue; } let 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, ''); let bases = bills.calcBase.split(/[\+\-\*\/]/g); //提取操作符 let oprs = bills.calcBase.match(/[\+\-\*\/]/g); //转换后的基数 let newBase = []; let illegal = false; //不合法 for (let base of bases) { if (rowCodeMap[base]) { //行引用 newBase.push(`@${rowCodeMap[base]}`); } else if (CalcBaseMap[base]) { //基数字典 newBase.push(CalcBaseMap[base]); } else if (!isNaN(base)) { newBase.push(base); } else { //都没匹配到,说明软件无法识别此基数 illegal = true; break; } }; if (illegal) { let fee = getFee(bills.fees, ['common', 'totalFee']); let 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, 2); } 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; } } } //转换单位工程的数据(直接可插入数据库的数据),返回{tender: {}, bills: [], ration: [], rationGLJ: [], projectGLJ: [], unitPrice: [], mixRatio: []} async function transformTender(tenderData, IDPlaceholder, needfulTemplate) { let detailData = await transformBills(tenderData, IDPlaceholder, needfulTemplate); //提取需要插入的单位工程数据 let tender = { 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: [] }; tender.property.gljAdjustType = getAdjustType(tenderData); detailData.tender = tender; return detailData; } //转换清单数据 async function transformBills(tenderData, IDPlaceholder, needfulTemplate) { //处理单位工程费用汇总的清单,这一部分没有靠谱的规则,特殊处理。 setupFeeSummary(tenderData.feeSummary, needfulTemplate); //处理添加清单数据 setupBills(tenderData, needfulTemplate); //转换清单基数表达式 transformCalcBase(needfulTemplate); //转换提取需要插入的详细数据 let detailData = transformDetail(tenderData, needfulTemplate, IDPlaceholder); //console.log(detailData); return detailData; } //提取转换后的定额、定额人材机、人材机汇总、单价文件、组成物数据 function transformDetail(tenderData, billsData, IDPlaceholder) { let rst = { bills: [], //清单 ration: [], //定额 rationGLJ: [], //定额人材机 rationCoe: [], //定额系数,全为默认 projectGLJ: [], //项目人材机 contractorList: [], //承包人材料 bidEvaluationList: [], //评标材料 evaluationList: [], //暂估材料 unitPrice: [], //单价文件 mixRatio: [] //组成物 }; //工料机汇总code-数据映射 // 项目人材机customCode、code-原始数据映射,有的导入数据中,有一部分关联自生成材料号CX,有的关联代码... // 因此customCode、code都需要跟原始数据映射,通过customCode取不到数据的时候,通过orgCode获取 let projectGLJMap = {}; // 代码映射 let customCodeMap = {}; // 原始代码映射 let originCodeMap = {}; function getGLJByMap(code) { return customCodeMap[code] || originCodeMap[code] || null; } //投标文件才需要导入定额等数据 if (importFileKind === FileKind.tender) { tenderData.gljSummary.forEach(pGLJ => { pGLJ.project_id = tenderData.ID; pGLJ.id = IDPlaceholder.projectGLJ++; // 如果人材机数据的类型为空,默认设置成201先,具体需要后台匹配 pGLJ.type = pGLJ.type || 201; pGLJ.shortName = pGLJ.shortName || '材'; //gljCodeMap[pGLJ.code] = pGLJ; //projectGLJMap[pGLJ.customCode] = pGLJ; //projectGLJMap[pGLJ.code] = pGLJ; customCodeMap[pGLJ.customCode] = pGLJ; originCodeMap[pGLJ.code] = pGLJ; }); //处理项目人材机数据 tenderData.gljSummary.forEach(pGLJ => { //组成物数据 pGLJ.ratios.forEach(ratio => { //let matchData = projectGLJMap[ratio.code]; let matchData = getGLJByMap(ratio.code); ratio.code = matchData.code; ratio.projectGLJID = pGLJ.id; //后端查找标准数据后,方便更新组成物数据 ratio.id = IDPlaceholder.ratio++; ratio.unit_price_file_id = tenderData.property.unitPriceFile.id; ratio.unit = matchData ? matchData.unit : ''; ratio.name = matchData ? matchData.name : ''; ratio.specs = matchData ? matchData.specs : ''; ratio.type = matchData ? matchData.type : 1; ratio.connect_key = [pGLJ.code || 'null', pGLJ.name || 'null', pGLJ.specs || 'null', pGLJ.unit || 'null', pGLJ.type].join('|-|'); rst.mixRatio.push(ratio); }); delete pGLJ.ratios; rst.projectGLJ.push(pGLJ); //单价文件数据 rst.unitPrice.push(getUnitPrice(pGLJ)); }); // 处理承包人材料(差额/指数)法表 handleContractorList(); // 处理评标材料 handleGLJRelatedList(tenderData.evalBidSummary, rst.bidEvaluationList, 'is_eval_material'); //处理定额数据 //获取含有定额数据的清单 handleRation(billsData); } // 处理暂估价材料:变更 :导入招标 handleGLJRelatedList(tenderData.evalSummary, rst.evaluationList, 'is_evaluate'); // 处理与项目人材机关联的承包人材料 function handleContractorList() { const contractorType = getAdjustType(tenderData); const contractorData = contractorType === AdjustType.info ? tenderData.differentiaSummary : tenderData.exponentialSummary; handleGLJRelatedList(contractorData, rst.contractorList, 'is_contractor_material'); } // 处理与项目人材机关联的承包人材料、评标材料、暂估材料 function handleGLJRelatedList(list, container, relatedType) { list.forEach(data => { data.ID = uuid.v1(); data.projectID = tenderData.ID; data.projectGLJID = -1; if (typeof data.seq === 'undefined') { data.seq = data.code; } //const projectGLJ = projectGLJMap[data.code]; const projectGLJ = getGLJByMap(data.code); if (projectGLJ) { projectGLJ[relatedType] = 1; data.is_related = 1; data.projectGLJID = projectGLJ.id; } container.push(data); }); } //处理清单 设置必要数据 删除无用属性 billsData.forEach(bills => { //处理综合单价 //1.没有子清单,且没有综合单价,则综合单价=综合合价/工程量 let children = billsData.find(data => data.ParentID === bills.ID), unitFee = getFee(bills.fees, ['common', 'unitFee']), totalFee = getFee(bills.fees, ['common', 'totalFee']); if (!children && !parseFloat(unitFee) && totalFee && parseFloat(totalFee)) { //不存工程量 if (!bills.quantity || !parseFloat(bills.quantity)) { unitFee = totalFee; //源不存在工程量,没有计算基数、但是却有综合合价,工程量要设置为1 if (!bills.calcBase) { bills.quantity = '1'; } } else { //综合合价的小数位数 let totalFeeDecimal = totalFee.match(/\.\d+/); unitFee = scMathUtil.roundForObj(totalFee / bills.quantity, totalFeeDecimal ? totalFeeDecimal[0] : 0); } let commonFee = bills.fees.find(fee => fee.fieldName === 'common'); if (commonFee) { commonFee.unitFee = unitFee; } } bills.projectID = tenderData.ID; bills.quantityEXP = bills.quantity; delete bills.items; delete bills.rations; rst.bills.push(bills); // 如果导入招标、控制价文件、每个分项底下自动生成一条空定额 if (importFileKind !== FileKind.tender && bills.type === billType.FX) { const emptyRation = { projectID: tenderData.ID, ID: uuid.v1(), billsItemID: bills.ID, serialNo: 1, type: rationType.ration, }; rst.ration.push(emptyRation); } }); return rst; function handleRation(billsData) { let billsHasRations = billsData.filter(bills => Array.isArray(bills.rations) && bills.rations.length); billsHasRations.forEach(bills => { //处理定额 bills.rations.forEach(ration => { ration.programID = getProgramID(ration.name, NameProgramMapping, tenderData.property.projectEngineering); ration.ID = uuid.v1(); ration.projectID = tenderData.ID; ration.billsItemID = bills.ID; //含量:定额工程量/清单工程量 if (!bills.quantity || !ration.quantity) { ration.contain = '0'; } else { let tempV = ration.quantity / bills.quantity; ration.contain = isFinite(tempV) ? scMathUtil.roundForObj(tempV, 6) : '0'; } //如果之前处理是量价,但是又有定额工料机,而且只有一条名称,编号,单位又完全匹配,说明是工料机类型的定额,修改定额类型,去掉定额工料机 if(ration.type===rationType.volumePrice && ration.rationGljs.length==1 ){ let matchGLJ = getGLJByMap(ration.rationGljs[0].code); if(matchGLJ && ration.code===matchGLJ.code && ration.name===matchGLJ.name&& ration.unit===matchGLJ.unit){ ration.type=rationType.gljRation ration.subType=matchGLJ.type; ration.projectGLJID = matchGLJ.id; ration.rationGljs = []; } } //处理定额人材机,添加需要的数据 ration.rationGljs.forEach(rGLJ => { //let matchGLJ = projectGLJMap[rGLJ.code]; let matchGLJ = getGLJByMap(rGLJ.code); if (matchGLJ) { rGLJ.projectGLJID = matchGLJ.id; rGLJ.code = matchGLJ.code; rGLJ.type = matchGLJ.type; rGLJ.shortName = matchGLJ.shortName; rGLJ.name = matchGLJ.name; rGLJ.original_code = matchGLJ.original_code; rGLJ.unit = matchGLJ.unit; rGLJ.specs = matchGLJ.specs; } rGLJ.ID = uuid.v1(); rGLJ.projectID = tenderData.ID; rGLJ.billsItemID = bills.ID; rGLJ.rationID = ration.ID; rGLJ.rationCode = ration.code; //暂时跟定额编码关联,后端好匹配标准数据 rGLJ.rationItemQuantity = rGLJ.quantity; //定额消耗,暂时取消耗量,需要后端匹配标准数据后更新 rst.rationGLJ.push(rGLJ); }); delete ration.rationGljs; rst.ration.push(ration); rst.rationCoe.push(getRationCoe(ration)); }); }); } function getRationCoe(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 getUnitPrice(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 }; } } //从xml文件中提取数据 async function extractData(file, escape = false) { //将二进制文件转换成字符串 let xmlStr = await util.readAsTextSync(file); if (escape) { xmlStr = util.escapeXMLEntity(xmlStr); } //将xml格式良好的字符串转换成对象 let x2js = new X2JS(); let xmlObj = x2js.xml_str2json(xmlStr); xmlObj = JSON.parse(util.restoreXMLEntity(JSON.stringify(xmlObj))); if (!xmlObj) { throw '无有效数据。'; } //console.log(xmlObj); //提取数据 return loadProject(xmlObj); }; //把数据转换成适应项目的数据 async function transformData(xmlObj) { //获取占位ID数据 countData.unitPriceCount = countData.projectGLJCount; if (importFileKind !== FileKind.tender) { countData = { projectCount: countData.projectCount, unitPriceFileCount: countData.unitPriceFileCount }; } let IDPlaceholder = await ajaxPost('/pm/import/getProjectPlaceholder', countData); xmlObj.ID = IDPlaceholder.project++; //获取到的转换后的最终上传数据 let postConstructData = { isTwoLevel: !!xmlObj.isTwoLevel, ID: xmlObj.ID, ParentID: xmlObj.ParentID, NextSiblingID: xmlObj.NextSiblingID, preID: xmlObj.preID, name: xmlObj.name, basicInformation: xmlObj.basicInformation, projType: xmlObj.projType, property: xmlObj.property, shareInfo: [], engs: [] }; // 所有的清单模板库ID let allTemplateLibIDs = []; xmlObj.engs.forEach(eng => { eng.tenders.forEach(tender => { let templateLibID = tender.property.templateLibID; if (!allTemplateLibIDs.includes(templateLibID)) { allTemplateLibIDs.push(templateLibID); } }); }); // 模板映射:{[templateLibID]: data} let templateMapping = await ajaxPost('/template/bills/api/getNeedfulTemplate', { allTemplateLibIDs }); for (let i = 0; i < xmlObj.engs.length; i++) { let curEng = xmlObj.engs[i], preEng = postConstructData.engs[i - 1]; curEng.ID = IDPlaceholder.project++; curEng.ParentID = xmlObj.ID; curEng.NextSiblingID = -1; if (preEng) { preEng.NextSiblingID = curEng.ID; } let postEngData = { ID: curEng.ID, ParentID: curEng.ParentID, NextSiblingID: curEng.NextSiblingID, code: curEng.code, name: curEng.name, projType: curEng.projType, shareInfo: [], tenders: [] }; postConstructData.engs.push(postEngData); for (let j = 0; j < curEng.tenders.length; j++) { let curTender = curEng.tenders[j], preTender = postEngData.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; } //提取详细数据 let needfulTemplate = _.cloneDeep(templateMapping[curTender.property.templateLibID]); // 重设模板树结构数据 BILLS_UTIL.resetTreeData(needfulTemplate, uuid.v1); let postTenderData = await transformTender(curTender, IDPlaceholder, needfulTemplate); postTenderData.tender.property.rootProjectID = postConstructData.ID; postEngData.tenders.push(postTenderData); } } //console.log(postConstructData); return postConstructData; }; // 接受上传的文件类型(不同的省份可以上传的文件不同) const accept = ['.xml', '.qtf']; return { accept, extractData, transformData }; })();