'use strict'; /** * * * @author Zhong * @date 2018/6/1 * @version */ import mongoose from 'mongoose'; import CompilationModel from "../../users/models/compilation_model"; import moment from 'moment'; const uuidV1 = require('uuid/v1'); const billsLibModel = mongoose.model('std_bills_lib_list'); const billsGuideLibModel = mongoose.model('std_billsGuidance_lib'); const billsGuideItemsModel = mongoose.model('std_billsGuidance_items'); const stdBillsLibModel = mongoose.model('std_bills_lib_list'); const stdBillsModel = mongoose.model('std_bills_lib_bills'); const stdBillsJobsModel = mongoose.model('std_bills_lib_jobContent'); const stdRationModel = mongoose.model('std_ration_lib_ration_items'); const engLibModel = mongoose.model('engineering_lib'); const compilationModel = mongoose.model('compilation'); const billMaterialModel = mongoose.model('std_billsGuidance_materials'); const gljModel = mongoose.model('std_glj_lib_gljList'); const gljLibModel = mongoose.model('std_glj_lib_map'); const billClassModel = mongoose.model('billClass'); const idTree = require('../../../public/id_tree').getTree(); const _ = require('lodash'); const zhLibID = 'cf851660-3534-11ec-9641-2da8021b8e4e'; const cqLibID = '90c51220-a740-11e8-a354-ab5db7d42428'; module.exports = { handleCopyItems, getComBillsLibInfo, getBillsGuideLibs, initBillsGuideLib, updateBillsGuideLib, getLibWithBills, getItemsBybills, updateItems, getBillMaterials, editBillMaterials, generateClassData, getClassExcelData, testItems }; function setChildren(bill, parentMap) { let children = parentMap[bill.ID]; if (children) { for (let c of children) { setChildren(c, parentMap); } bill.children = children; } else { bill.children = []; } } function sortChildren(lists) { let IDMap = {}, nextMap = {}, firstNode = null, newList = []; for (let l of lists) { if (l.children && l.children.length > 0) l.children = sortChildren(l.children); //递规排序 IDMap[l.ID] = l; if (l.NextSiblingID != -1) nextMap[l.NextSiblingID] = l; } for (let t of lists) { if (!nextMap[t.ID]) { //如果在下一节点映射没找到,则是第一个节点 firstNode = t; break; } } if (firstNode) { newList.push(firstNode); delete IDMap[firstNode.ID]; setNext(firstNode, newList); } //容错处理,如果链断了的情况,直接添加到后面 for (let key in IDMap) { if (IDMap[key]) newList.push(IDMap[key]) } return newList; function setNext(node, array) { if (node.NextSiblingID != -1) { let next = IDMap[node.NextSiblingID]; if (next) { array.push(next); delete IDMap[next.ID]; setNext(next, array); } } } } function getItemFromChildren(children, allItems) { for (let i = 0; i < children.length; i++) { const cur = children[i]; const next = children[i + 1]; cur.NextSiblingID = next && next.ID || -1; allItems.push(cur); if (cur.children && cur.children.length) { getItemFromChildren(cur.children, allItems); } } } function resetTreeData(billsList) { const idMapping = {}; idMapping['-1'] = -1; // 建立新ID-旧ID映射 billsList.forEach(bills => idMapping[bills.ID] = uuidV1()); billsList.forEach(function (bills) { bills.ID = idMapping[bills.ID] ? idMapping[bills.ID] : -1; bills.ParentID = idMapping[bills.ParentID] ? idMapping[bills.ParentID] : -1; bills.NextSiblingID = idMapping[bills.NextSiblingID] ? idMapping[bills.NextSiblingID] : -1; }); } // 从某库中根据清单编号拷贝工艺 async function handleCopyItems(sourceGuideLibID, sourceBillsLibID, targetGuideLibID, targetBillsLibID) { const sourceBills = await stdBillsModel.find({ billsLibId: sourceBillsLibID, deleted: false }, '-_id ID code').lean(); const targetBills = await stdBillsModel.find({ billsLibId: targetBillsLibID, deleted: false }, '-_id ID code').lean(); const sourceItems = await billsGuideItemsModel.find({ libID: sourceGuideLibID, type: 0, deleted: false }, '-_id').lean(); // 过滤掉定额项 const targetCodeIDMap = {}; targetBills.forEach(bills => targetCodeIDMap[bills.code] = bills.ID); const toDeleteIDList = []; const insertBulks = []; sourceBills.forEach(bills => { const matchedItems = sourceItems.filter(item => item.billsID === bills.ID); // 重新将断缺的树结构数据整理好 if (matchedItems.length) { const parentMap = {}; matchedItems.forEach(item => { (parentMap[item.ParentID] || (parentMap[item.ParentID] = [])).push(item); }); const roots = parentMap['-1']; roots.forEach(root => setChildren(root, parentMap)); const sorted = sortChildren(roots); const newItems = []; getItemFromChildren(sorted, newItems); resetTreeData(newItems); const targetBillsID = targetCodeIDMap[bills.code]; if (targetBillsID) { toDeleteIDList.push(targetBillsID); newItems.forEach(newItem => { newItem.libID = targetGuideLibID; newItem.billsID = targetBillsID; insertBulks.push({ insertOne: { document: newItem } }); }) } } }); if (toDeleteIDList.length) { await billsGuideItemsModel.remove({ libID: targetGuideLibID, billsID: { $in: toDeleteIDList } }); } if (insertBulks.length) { await billsGuideItemsModel.bulkWrite(insertBulks); } } async function getCompilationList() { let compilationModel = new CompilationModel(); return await compilationModel.getCompilationList(); } async function getComBillsLibInfo() { let rst = { compilationList: [], billsLibs: [] }; let compilationList = await getCompilationList(); if (compilationList.length <= 0) { throw '没有数据'; } else { for (let compilation of compilationList) { rst.compilationList.push({ _id: compilation._id, name: compilation.name }); } rst.billsLibs = await billsLibModel.find({ deleted: false }, '-_id billsLibId billsLibName'); return rst; } } async function getBillsGuideLibs(findData, isTemporary) { if (isTemporary) { const libs = await billsGuideLibModel.find({ ID: { $in: [zhLibID, cqLibID] } }).lean(); return libs; } return await billsGuideLibModel.find(findData); } // 如果是“重庆费用定额(2018)”,则默认叶子清单的项目指引窗口只有一条数据,取清单名称。 async function genGuidanceItems_cq18(guidanceLibId, billsLibId) { let bills = await stdBillsModel.find({ billsLibId: billsLibId, deleted: false, 'jobs.0': { $exists: true } }); let insertArr = []; for (let bill of bills) { let newItem = { libID: guidanceLibId, ID: uuidV1(), ParentID: -1, NextSiblingID: -1, name: bill.name, type: 0, billsID: bill.ID }; insertArr.push({ insertOne: { document: newItem } }); } await billsGuideItemsModel.bulkWrite(insertArr); } //拷贝工作内容并转化为树结构,形成项目指引数据 async function genGuidanceItems(guidanceLibId, billsLibId) { let bills = await stdBillsModel.find({ billsLibId: billsLibId, deleted: false, 'jobs.0': { $exists: true } }); //设置工作内容数据 let jobIds = []; let totalJobs = []; for (let bill of bills) { for (let job of bill.jobs) { jobIds.push(job.id); } } jobIds = Array.from(new Set(jobIds)); if (jobIds.length > 0) { totalJobs = await stdBillsJobsModel.find({ deleted: false, id: { $in: jobIds } }); } if (totalJobs.length > 0) { let jobIdIndex = {};//id索引 for (let job of totalJobs) { jobIdIndex[job.id] = job; } let insertArr = []; for (let bill of bills) { //排序后根据serialNo转换成NextSiblingID,倒序 bill.jobs.sort(function (a, b) { let rst = 0; if (a.serialNo > b.serialNo) { rst = -1; } else if (a.serialNo < b.serialNo) { rst = 1; } return rst; }); let jobNoIndex = {};//下标索引 for (let i = 0; i < bill.jobs.length; i++) { let newItem = { libID: guidanceLibId, ID: uuidV1(), ParentID: -1, NextSiblingID: jobNoIndex[i - 1] ? jobNoIndex[i - 1]['ID'] : -1, name: jobIdIndex[bill.jobs[i]['id']]['content'], type: 0, billsID: bill.ID }; jobNoIndex[i] = newItem; insertArr.push({ insertOne: { document: newItem } }); } } await billsGuideItemsModel.bulkWrite(insertArr); } } async function initBillsGuideLib(updateData) { await billsGuideLibModel.create(updateData); let compilation = await compilationModel.findOne({ _id: mongoose.Types.ObjectId(updateData.compilationId) }); if (compilation && compilation.overWriteUrl && compilation.overWriteUrl === '/web/over_write/js/chongqing_2018.js') { await genGuidanceItems_cq18(updateData.ID, updateData.billsLibId); } else { await genGuidanceItems(updateData.ID, updateData.billsLibId); } } async function updateBillsGuideLib(data) { if (data.updateType === 'delete') { //删除所有条目 await billsGuideLibModel.remove(data.findData); await billsGuideItemsModel.remove({ libID: data.findData.ID }); } else { await billsGuideLibModel.update(data.findData, { $set: data.updateData }); await engLibModel.update({ 'billsGuidance_lib.id': data.findData.ID }, { $set: { 'billsGuidance_lib.$.name': data.updateData.name } }, { multi: true }); } } async function getLibWithBills(libID) { let guidanceLib = await getBillsGuideLibs({ ID: libID }); if (guidanceLib.length === 0) { throw '不存在此指引库!'; } let billsLib = await stdBillsLibModel.findOne({ billsLibId: guidanceLib[0].billsLibId }); if (!billsLib) { throw '引用的清单规则库不存在!'; } let bills = await stdBillsModel.find({ billsLibId: billsLib.billsLibId }, '-_id code name ID NextSiblingID ParentID jobs items comment').lean(); const guideItems = await billsGuideItemsModel.find({ libID: guidanceLib[0].ID }, '-_id billsID').lean(); const billsMap = {}; for (const item of guideItems) { billsMap[item.billsID] = true; } for (const item of bills) { if (billsMap[item.ID]) { item.hasGuide = true; } } return { guidanceLib: guidanceLib[0], bills }; } function getAttrs(field, datas) { let rst = []; for (let data of datas) { if (data[field]) { rst.push(data[field]); } } return rst; } //定额项目指所引用定额是否被删除 function rationAllExist(rationItems, stdRationIdx) { for (let item of rationItems) { if (!stdRationIdx[item.rationID]) { return false; } } return true; } //将同层树结构转为顺序数组 function chainToArr(nodes) { let rst = []; let tempIdx = {}; let nodeIdx = {}; //建索引 for (let node of nodes) { tempIdx[node.ID] = { ID: node.ID, NextSiblingID: node.NextSiblingID, preSibling: null, nextSibling: null }; nodeIdx[node.ID] = node; } //建链 for (let i in tempIdx) { let temp = tempIdx[i]; if (temp.NextSiblingID != -1) { let next = tempIdx[temp.NextSiblingID]; temp.nextSibling = next; next.preSibling = temp; } } let firstNode = null; for (let i in tempIdx) { if (!tempIdx[i].preSibling) { firstNode = tempIdx[i]; break; } } //获得顺序队列 while (firstNode) { rst.push(nodeIdx[firstNode.ID]); firstNode = firstNode.nextSibling; } return rst; } async function getItemsBybills(guidanceLibID, billsID) { const type = { job: 0, ration: 1 }; let items = await billsGuideItemsModel.find({ libID: guidanceLibID, billsID: billsID, deleted: false }); let rationItems = _.filter(items, { type: type.ration }); let rationIds = getAttrs('rationID', rationItems); let stdRations = await stdRationModel.find({ ID: { $in: rationIds }, $or: [{ isDeleted: null }, { isDeleted: false }] }); let stdRationIndex = {}; for (let stdRation of stdRations) { stdRationIndex[stdRation.ID] = stdRation; } //判断定额完整性 if (!rationAllExist(rationItems, stdRationIndex)) { //建定额链, 排序后再清除不存在的定额,保证顺序正确性 rationItems = chainToArr(rationItems); //清除已被删除的定额 let removeIds = []; _.remove(rationItems, function (item) { if (!stdRationIndex[item.rationID]) { removeIds.push(item.ID); return true; } return false; }); _.remove(items, function (item) { return removeIds.includes(item.ID); }); await billsGuideItemsModel.remove({ ID: { $in: removeIds } }); //重组树结构 let bulkArr = []; for (let i = 0, len = rationItems.length; i < len; i++) { rationItems[i].NextSiblingID = rationItems[i + 1] ? rationItems[i + 1].ID : -1; bulkArr.push({ updateOne: { filter: { ID: rationItems[i].ID }, update: { $set: { NextSiblingID: rationItems[i].NextSiblingID } } } }); } await billsGuideItemsModel.bulkWrite(bulkArr); } return items; } async function updateItems(updateDatas) { let bulkArr = []; for (let updateData of updateDatas) { if (updateData.updateType === 'create') { bulkArr.push({ insertOne: { document: updateData.updateData } }); } else if (updateData.updateType === 'update') { bulkArr.push({ updateOne: { filter: updateData.findData, update: { $set: updateData.updateData } } }); } else { bulkArr.push({ deleteOne: { filter: updateData.findData } }); } } if (bulkArr.length > 0) { await billsGuideItemsModel.bulkWrite(bulkArr); } } // 获取清单材料数据 async function getBillMaterials(libID, billID) { // 指引下已有定额人材机(材料大类) let allGljList = []; const rationItems = await billsGuideItemsModel.find({ libID, billsID: billID, rationID: { $ne: null } }, '-_id rationID').lean(); if (rationItems.length) { const rationIDs = rationItems.map(item => item.rationID); const rations = await stdRationModel.find({ ID: { $in: rationIDs } }, '-_id rationGljList'); const gljIDs = []; rations.forEach(ration => { if (ration.rationGljList && ration.rationGljList.length) { gljIDs.push(...ration.rationGljList.map(rGlj => rGlj.gljId)); } }); if (gljIDs.length) { allGljList = await gljModel.find({ ID: { $in: [...new Set(gljIDs)] } }, '-_id ID code name specs gljType').lean(); allGljList = allGljList.filter(glj => /^2/.test(glj.gljType) || [4, 5].includes(glj.gljType)); } } // 清单材料 let billMaterials = []; const billMaterial = await billMaterialModel.findOne({ libID, billID }).lean(); if (!billMaterial || !billMaterial.materials) { return { billMaterials, allGljList }; } const gljIDs = billMaterial.materials.map(m => m.gljID); const gljList = await gljModel.find({ ID: { $in: gljIDs } }, '-_id ID code name specs').lean(); billMaterials = gljList.map(glj => ({ gljID: glj.ID, code: glj.code, name: glj.name, specs: glj.specs })); return { billMaterials, allGljList } } // 编辑清单材料数据,返回清单材料数据 async function editBillMaterials(libID, billID, gljCodes, compilationID) { const gljLib = await gljLibModel.findOne({ compilationId: compilationID }, '-_id ID').lean(); const gljList = await gljModel.find({ repositoryId: gljLib.ID, code: { $in: gljCodes }, }, '-_id ID code name specs').lean(); const gljMap = {}; const materials = []; gljList.forEach(glj => { materials.push({ gljID: glj.ID }); gljMap[glj.code] = 1; }); const missCodes = gljCodes.filter(code => !gljMap[code]); if (missCodes.length) { throw new Error(`没有找到人材机:
${missCodes.join('
')}`); } const billMaterial = await billMaterialModel.findOne({ libID, billID }, '-_id ID').lean(); if (billMaterial) { await billMaterialModel.update({ ID: billMaterial.ID }, { $set: { materials } }); } else { await billMaterialModel.create({ libID, billID, ID: uuidV1(), materials }); } return gljList.map(glj => ({ gljID: glj.ID, code: glj.code, name: glj.name, specs: glj.specs })); } // 是否为工序行 function isProcessNode(node) { return node && node.depth() % 2 === 0 && node.data.type === 0 } // 是否是选项行 function isOptionNode(node) { return node && node.depth() % 2 === 1 && node.data.type === 0 } // 判断蓝色子项和孙子项是否打勾了必填 function hasRequireData(node) { const requiredList = node.getPosterity().filter(subNode => subNode.data.required === true ); if (requiredList.length) { return true } return false; } // 这里判断有无无定额的情况,有的就返回true,没有就返回false function isAllRationData(node) { let isRequired = true; if (node.children && node.children.length) { node.children.forEach(subNode => { if (!subNode.children || !subNode.children.length) { isRequired = false; } }) } //这里是兼容第一层直接是定额的白色节点 if (node.children && node.children.length) { node.children.forEach(subNode => { if (subNode.data.rationID) { isRequired = true; } }) } return isRequired; } // 获取选套定额 function getOptionalData(node, list = []) { if (isProcessNode(node)) { node.children.forEach(element => { if (element.children && element.children.length) { element.children.forEach(item => { if (item.data.rationID) { list.push(item.data.rationID); } else if (isProcessNode(item)) { getOptionalData(item, list) } }) } }); } else { node.children.forEach(element => { if (element.data.rationID) { list.push(element.data.rationID); } }); } return list; } // 获取必填项下的ID和name的键值对 function getClassCodeStrData(nodes,data={}){ nodes.forEach(node=>{ if (isProcessNode(node)&&node.data.required) { node.children.forEach(subNode=>{ data[subNode.data.ID]=subNode.data.name; }) } getClassCodeStrData(node.children,data); }) return data; } //获取定额数据 // requireRationData必套定额对象 // optionalRationData 选逃定额对象 // classGroups classCode文字和id键值对 // classCodeList 各个classCode的pID和ID的关系 function getItemData(nodes, requireRationData = {}, optionalRationData = {}, classGroups = {}, prefixID = '', prefixSonID = '', IDData = {}) { const processNodes = nodes.filter(node => isProcessNode(node)); // const classGroups = []; // 同层必填选项的数组(二维数组) processNodes.forEach(processNode => { // 蓝色节点,必填 if (processNode.data.required) { // 白色节点 const optionNodes = processNode.children.filter(node => isOptionNode(node)); optionNodes.forEach(optionNode => { if (!requireRationData[optionNode.data.ID]) requireRationData[optionNode.data.ID] = []; if (!optionalRationData[optionNode.data.ID]) optionalRationData[optionNode.data.ID] = []; //白色节点下没有蓝色节点,就是到底了 if (!optionNode.children.some(subOptionNode => isProcessNode(subOptionNode))) { // 是否必套定额 if (isAllRationData(optionNode)) { optionNode.children.forEach(subOptionNode => { requireRationData[optionNode.data.ID].push([subOptionNode.data.rationID]); }) } else { optionalRationData[optionNode.data.ID] = getOptionalData(optionNode); } const kV = {}; kV[optionNode.data.ID] = optionNode.data.name; Object.assign(classGroups, kV); } else { const kV = {}; kV[optionNode.data.ID] = optionNode.data.name; Object.assign(classGroups, kV); // 后代项是否有必填 if (hasRequireData(optionNode)) { //后代项有必填 prefixSonID = ''; getItemData(optionNode.children, requireRationData, optionalRationData, classGroups, optionNode.data.ID, prefixSonID, IDData); } else { //后代项无必填 optionNode.children.forEach(subOptionNode => { // 是否必套定额 if (isAllRationData(optionNode)) { if (!requireRationData[subOptionNode.parent.data.ID]) requireRationData[subOptionNode.parent.data.ID] = []; requireRationData[subOptionNode.parent.data.ID].push(getOptionalData(subOptionNode)); } else { if (!optionalRationData[subOptionNode.parent.data.ID]) optionalRationData[subOptionNode.parent.data.ID] = []; optionalRationData[subOptionNode.parent.data.ID].push(...getOptionalData(subOptionNode)); } }) } } }) } else { // 蓝色节点,非必填 if (hasRequireData(processNode)) { //后代项有必填 if (isProcessNode(processNode)) { //蓝色 // 是否必套定额 if (isAllRationData(processNode)) { processNode.children.forEach((subProcessNode) => { subProcessNode.children.forEach((sSubProcessNode) => { //这里是特殊处理,因为原来的逻辑是直接把定额绑到必填白色选项中下面的,每个蓝色的一组,但是这样是不对的,需要绑定在必填白色选项下的蓝色节点,所以这里就需要传入蓝色节点的id const requireChildrenID = sSubProcessNode.parent.data.required ? prefixSonID : sSubProcessNode.data.ID; IDData[sSubProcessNode.data.ID] = prefixID; getItemData( [sSubProcessNode], requireRationData, optionalRationData, classGroups, prefixID, requireChildrenID, IDData ) }) }) } else { // 全部选套就不用走循环了,直接按照选套执行 let key = processNode.data.ID; if (prefixID) key = prefixID; if (prefixSonID) key = prefixSonID; if (!optionalRationData[key]) optionalRationData[key] = []; optionalRationData[key].push(...getOptionalData(processNode)); // 因为这里没有按照走整个流程,所以文字和ID的关系需要获取补充 if(hasRequireData(processNode)) Object.assign(classGroups,getClassCodeStrData(processNode.children)) ; } } } else { let key = processNode.data.ID; if (prefixID) key = prefixID; if (prefixSonID) key = prefixSonID; // 是否必套定额 if (isAllRationData(processNode)) { if (!requireRationData[key]) requireRationData[key] = []; requireRationData[key].push(getOptionalData(processNode)); } else { if (!optionalRationData[key]) optionalRationData[key] = []; optionalRationData[key].push(...getOptionalData(processNode)); } } } }) return { requireRationData, optionalRationData, classGroups, IDData } } // 从指引节点,获取分类特征、必套定额数据 function getItemClassData(nodes, prefix, prefixID) { const processNodes = nodes.filter(node => isProcessNode(node)); const classGroups = []; // 同层必填选项的数组(二维数组) processNodes.forEach(processNode => { const classItems = []; const optionNodes = processNode.children.filter(node => isOptionNode(node)); optionNodes.forEach(optionNode => { // const name = prefix ? `${prefix}@${optionNode.data.name}` : optionNode.data.name; if (optionNode.parent && optionNode.parent.data.required && (!optionNode.children || !optionNode.children.length || (optionNode.children[0].data && optionNode.children[0].data.rationID) || !optionNode.children.some(node => isProcessNode(node)))) { // 必套定额 classItems.push({ name: optionNode.data.name, ID: optionNode.data.ID }); } else { // classItems.push(...getItemCharacterData(optionNode.children, optionNode.parent && optionNode.parent.data.required ? optionNode.data.name : '')); const childrenClassItem = getItemClassData( optionNode.children, optionNode.parent && optionNode.parent.data.required ? optionNode.data.name : '', optionNode.parent && optionNode.parent.data.required ? optionNode.data.ID : '' ); //如果返回的子项为空,但是父项又勾选了必填,则要把本身存入数组 if (optionNode.parent && optionNode.parent.data.required && childrenClassItem.length === 0 ) { classItems.push({ name: optionNode.data.name, ID: optionNode.data.ID }); } else { classItems.push(...childrenClassItem); } } }); if (classItems.length) { classGroups.push(classItems); } }); // 拼接上一文本 if (classGroups[0] && classGroups[0].length) { // classGroups[0] = classGroups[0].map(name => prefix ? `${prefix}@${name}` : name); classGroups[0] = classGroups[0].map(item => { item.name = prefix ? `${prefix}@${item.name}` : item.name item.ID = prefixID ? `${prefixID}@${item.ID}` : item.ID return item; }); } // 二维数组内容排列组合 while (classGroups.length > 1) { const prevClassItems = classGroups[0]; const nextClassItems = classGroups[1]; const mergedClassItems = []; for (let i = 0; i < prevClassItems.length; i++) { for (let j = 0; j < nextClassItems.length; j++) { // 拼接文本 const mergedName = `${prevClassItems[i].name}@${nextClassItems[j].name}`; const mergedID = `${prevClassItems[i].ID}@${nextClassItems[j].ID}`; mergedClassItems.push({ name: mergedName, ID: mergedID }); } } classGroups.splice(0, 2, mergedClassItems); } // 去重(类别别名要唯一) const items = classGroups[0] || []; const nameMap = {}; const rst = []; items.forEach(item => { if (!nameMap[item.name]) { rst.push(item); } nameMap[item.name] = true; }); return rst; } // 获取选套定额:把所有分类数据的必套定额确定好了先。选套定额就是清单下所有定额除了必套的 function getOptionalRationIDs(optionalRationData) { const optionalRationIDs = []; if(optionalRationData){ Object.values(optionalRationData).forEach(optionalRation => { if (optionalRation.length) optionalRationIDs.push(...optionalRation); }) } return [...new Set(optionalRationIDs)]; } // 获取错套定额:清单下所有定额,除了分类对应的必套、选套定额 function getErrorRationIDs(requiredRationIDList, optionalRationIDs, allRationIDs) { const finRequireData = []; requiredRationIDList.forEach(requiredRationIDs => { finRequireData.push(...requiredRationIDs); }) const errorRationIDs = []; allRationIDs.forEach(rationID => { if (!finRequireData.includes(rationID) && !optionalRationIDs.includes(rationID)) { errorRationIDs.push(rationID); } }); return [...new Set(errorRationIDs)]; } //把classcode和必套选套定额结合在一起 function combineData(codeData, requireRationData, optionalRationData, classGroups, IDData) { // 这里要记录下已经被绑定的选套定额,因为没有被用的定额需要绑定到各个classcode下 const matchRationList = []; //这里需要把绑定在子节点的定额更新到必填的白色选项中(classcode的值) const requireCombineData = {}; const optionCombineData = {}; Object.keys(IDData).forEach(key => { if (!requireCombineData[IDData[key]]) requireCombineData[IDData[key]] = new Set(); if (!optionCombineData[IDData[key]]) optionCombineData[IDData[key]] = new Set(); if (requireRationData[key]) { requireRationData[key].forEach(subData => { subData.forEach(subItem => { requireCombineData[IDData[key]].add(subItem); }) }) } if (optionalRationData[key]) { optionalRationData[key].forEach(subData => { optionCombineData[IDData[key]].add(subData); }) } }) const finData = codeData.map(classCodeData => { const errorRationIDs = []; const optionalRationIDs = []; const requiredRationIDs = []; let name = ''; const classCodeIDs = classCodeData.ID; if (/@/.test(classCodeIDs)) { classCodeIDs.split('@').forEach((classCodeID) => { if (name) { name = name + '@' + classGroups[classCodeID] } else { name = classGroups[classCodeID] }; // 一组的必套定额,先去重 const unitRation = []; // 这里是必填选项下绑定必套定额 if (requireRationData[classCodeID] && requireRationData[classCodeID].length) { requireRationData[classCodeID].forEach(subItem => { unitRation.push(...new Set(subItem)); }) requiredRationIDs.push(unitRation); // 这里也要把用过的必套定额先存起来 matchRationList.push(...unitRation); } //这里是必填选项下绑定在蓝色节点下的必套定额 if (requireCombineData[classCodeID] && requireCombineData[classCodeID].size) { requiredRationIDs.push([...requireCombineData[classCodeID]]); // 这里也要把用过的必套定额先存起来 matchRationList.push(...requireCombineData[classCodeID]); } // 这里是必填选项下绑定选套定额 if (optionalRationData[classCodeID] && optionalRationData[classCodeID].length) { optionalRationIDs.push(...optionalRationData[classCodeID]); matchRationList.push(...optionalRationData[classCodeID]); } //这里是必填选项下绑定在蓝色节点下的选套定额,下同 if (optionCombineData[classCodeID] && optionCombineData[classCodeID].size) { optionalRationIDs.push(...optionCombineData[classCodeID]); matchRationList.push(...optionCombineData[classCodeID]); } }) return { name, requiredRationIDs, optionalRationIDs: [...new Set(optionalRationIDs)], errorRationIDs } } else { const unitRation = []; name = classGroups[classCodeIDs]; if (requireRationData[classCodeIDs] && requireRationData[classCodeIDs].length){ requireRationData[classCodeIDs].forEach(subItem => { unitRation.push(...new Set(subItem)); }) requiredRationIDs.push(unitRation); // 这里也要把用过的必套定额先存起来 matchRationList.push(...unitRation); } if (requireCombineData[classCodeIDs] && requireCombineData[classCodeIDs].size) { requiredRationIDs.push([...requireCombineData[classCodeIDs]]) // 这里也要把用过的必套定额先存起来 matchRationList.push(...requireCombineData[classCodeIDs]); }; if (optionalRationData[classCodeIDs] && optionalRationData[classCodeIDs].length) { optionalRationIDs.push(...optionalRationData[classCodeIDs]); matchRationList.push(...optionalRationData[classCodeIDs]); } if (optionCombineData[classCodeIDs] && optionCombineData[classCodeIDs].size) { optionalRationIDs.push(...optionCombineData[classCodeIDs]); matchRationList.push(...optionalRationData[classCodeIDs]); } return { name, requiredRationIDs, optionalRationIDs: [...new Set(optionalRationIDs)], errorRationIDs } } }) const unMatchRation = []; Object.values(optionalRationData).forEach(data => { data.forEach((rationID) => { if (!matchRationList.includes(rationID)) { unMatchRation.push(rationID); } }) }) // 这里把没有使用过的必套定额也丢到了选套里面 Object.values(requireRationData).forEach(data => { data.forEach((rationData) => { rationData.forEach(rationID=>{ if (!matchRationList.includes(rationID)) { unMatchRation.push(rationID); } }) }) }) return { itemClassData: finData, unMatchRation }; } // 生成清单分类 async function generateClassData(libID) { const lib = await billsGuideLibModel.findOne({ ID: libID }).lean(); if (!lib) { throw new Error('无有效精灵库'); } const guidanceItems = await billsGuideItemsModel.find({ libID }, '-_id').lean(); // 清单ID - 指引数据映射 const guidanceMap = {}; guidanceItems.forEach(item => { (guidanceMap[item.billsID] || (guidanceMap[item.billsID] = [])).push(item); }); const bills = await stdBillsModel.find({ billsLibId: lib.billsLibId }, '-_id ID ParentID NextSiblingID name code').lean(); const billTree = idTree.createNew({ id: 'ID', pid: 'ParentID', nid: 'NextSiblingID', rootId: -1, autoUpdate: true }); billTree.loadDatas(bills); // 叶子清单 const leaves = billTree.items.filter(node => !node.children || !node.children.length); // 获取分类数据 let classNum = 1; const billClassData = []; for (let billNode of leaves) { const guidanceItems = guidanceMap[billNode.data.ID]; if (!guidanceItems || !guidanceItems.length) { continue; } if (billNode.data.code.startsWith('03')) continue;//先屏蔽掉03开头的清单 const guidanceTree = idTree.createNew({ id: 'ID', pid: 'ParentID', nid: 'NextSiblingID', rootId: -1, autoUpdate: true }); guidanceTree.loadDatas(guidanceItems); //console.log('getItemClassData start',billNode.data.name,billNode.data.code,billNode.data.ID); const classCodeData = getItemClassData(guidanceTree.roots); // 必套定额在这个方法内就获取了,避免重复执行递归方法 const { requireRationData, optionalRationData, classGroups, IDData } = getItemData(guidanceTree.roots); const { itemClassData, unMatchRation } = combineData(classCodeData, requireRationData, optionalRationData, classGroups, IDData); const allRationIDs = guidanceTree.items.filter(node => !!node.data.rationID).map(node => node.data.rationID); // 选套定额ID const optionalRationIDs = getOptionalRationIDs(itemClassData, allRationIDs); //if(itemClassData.length > 1000) console.log('getItemClassData end',billNode.data.name,billNode.data.code,billNode.data.ID,itemClassData.length) itemClassData.forEach(item => { // 错套定额 item.optionalRationIDs.push(...unMatchRation); const errorRationIDs = getErrorRationIDs(item.requiredRationIDs, item.optionalRationIDs, allRationIDs); if (billNode.data.ID == '1d32fa34-0a9b-11ea-a33d-5388f9783b09') console.log(item.name) billClassData.push({ itemCharacter: item.name, class: classNum++, classCode: `${billNode.data.code}@${item.name}`, compilationID: lib.compilationId, name: billNode.data.name, code: billNode.data.code, requiredRationIDs: item.requiredRationIDs || [], optionalRationIDs: item.optionalRationIDs || [], errorRationIDs, }); }); //新增无必填时的情况 if (itemClassData.length === 0) { Object.keys(requireRationData).forEach((key) => { requireRationData[key].forEach(data => { if (data.length) optionalRationIDs.push(...data); }) }) const errorRationIDs = getErrorRationIDs([], optionalRationIDs, allRationIDs); billClassData.push({ itemCharacter: '', class: classNum++, classCode: `${billNode.data.code}`, compilationID: lib.compilationId, name: billNode.data.name, code: billNode.data.code, requiredRationIDs: [], optionalRationIDs: optionalRationIDs || [], errorRationIDs, }); } } console.log(`billClassData.length`); console.log(billClassData.length); // 清空旧的分类数据 await billClassModel.deleteMany({ compilationID: lib.compilationId }); // 之前遇到一次性插入40w条数据的情况,会卡死,循环插入速度快很多 while (billClassData.length > 10000) { const list = billClassData.splice(0, 10000); await billClassModel.insertMany(list); } await billClassModel.insertMany(billClassData); } // 获取分类excel数据 async function getClassExcelData(libID) { const lib = await billsGuideLibModel.findOne({ ID: libID }, '-_id compilationId').lean(); if (!lib) { throw new Error('无有效精灵库'); } console.log('start'); const date = Date.now(); const classData = await billClassModel.find({ compilationID: lib.compilationId }).lean(); console.log('end'); console.log(Date.now() - date); const excelData = [['类别', '编码', '清单名称', '必填特征排列组合', '类别别名', '必套定额', '选套定额', '错套定额']]; classData.forEach(item => { const excelItem = [ item.class, item.code, item.name, item.itemCharacter, item.classCode, (item.requiredRationIDs || []).join('@'), (item.optionalRationIDs || []).join('@'), (item.errorRationIDs || []).join('@'), ]; excelData.push(excelItem); }); return excelData; } async function testItems(libID) { let items = await billsGuideItemsModel.find({ libID: libID }); //删除垃圾数据 let delBulk = []; let itemsMapping = {}; for (let item of items) { itemsMapping[item.ID] = true; } for (let item of items) { if (item.ParentID != -1 && !itemsMapping[item.ParentID]) { delBulk.push({ deleteOne: { filter: { ID: item.ID } } }); } } if (delBulk.length > 0) { console.log(`delBulk.length`); console.log(delBulk.length); await billsGuideItemsModel.bulkWrite(delBulk); } /* //查找同层节点含有相同NextSiblingID的节点 let rst = []; let billsGroup = {}; for(let item of items){ if(!billsGroup[item.billsID]){ billsGroup[item.billsID] = [item]; } else { billsGroup[item.billsID].push(item); } } for(let bGroup in billsGroup){ let group = billsGroup[bGroup]; let parentGroup = {}; for(let gItem of group){ if(!parentGroup[gItem.ParentID]){ parentGroup[gItem.ParentID] = [gItem] } else { parentGroup[gItem.ParentID].push(gItem); } } for(let pGroup in parentGroup){ let pGroupData = parentGroup[pGroup]; let nextGroup = {}; for(let nItem of pGroupData){ let sameNext = _.filter(pGroupData, {NextSiblingID: nItem.NextSiblingID}); if(sameNext.length > 1){ console.log(`sameNext`); console.log(sameNext); if(!nextGroup[nItem.ParentID + nItem.NextSiblingID]){ rst.push({NextSiblingID: nItem.NextSiblingID, ParentID: nItem.ParentID}); nextGroup[nItem.ParentID + nItem.NextSiblingID] = 1; } } } } }*/ return delBulk.length; }