Browse Source

feat: 生成清单分类库的时候,增加生成定额检测需要的数据(必套、选套、错套定额)

vian 3 years ago
parent
commit
0113f7d70e

+ 4 - 1
modules/all_models/bill_class.js

@@ -4,12 +4,15 @@ const Schema = mongoose.Schema;
 
 
 const billClass = new Schema({
-    compilationID: String,
+    compilationID: { type: String, index: true},
     name: String,
     code: String,
     itemCharacter: String,
     class: { type: Number, index: true }, // 分类
     classCode: { type: String, index: true }, // 类别别名
+    requiredRationIDs: { type: [Number], default: [] }, // 必套定额ID
+    optionalRationIDs: { type: [Number], default: [] }, // 选套定额ID
+    errorRationIDs: { type: [Number], default: [] }, // 错套定额ID
 }, {versionKey: false});
 
 

+ 4 - 1
modules/std_billsGuidance_lib/controllers/libController.js

@@ -131,6 +131,7 @@ class BillsGuideLibController extends BaseController{
     
     async generateClassData(req, res) {
         try{
+            res.setTimeout(1000 * 60 * 10); // 不设置的话,处理时间过长,会触发默认的响应超时,报错(前端报错,后台还继续在处理)
             const data = JSON.parse(req.body.data);
             await billsGuidanceFacade.generateClassData(data.libID);
             callback(req, res, 0, '', []);
@@ -144,7 +145,9 @@ class BillsGuideLibController extends BaseController{
     async exportClassExcel(req, res) {
         try{
             const excelData = await billsGuidanceFacade.getClassExcelData(req.query.libID);
-            const buffer = excel.build([{name: "清单分类库", data: excelData}], {'!cols': [{wch:6}, {wch:12},{wch:14},{wch:24}, {wch:45}]});
+            console.log('start-build');
+            const buffer = excel.build([{name: "清单分类库", data: excelData}], {'!cols': [{wch:6}, {wch:12}, {wch:14}, {wch:24}, {wch:45}, {wch:20}, {wch:30}, {wch:30}]});
+            console.log('end-build');
             const filePath = './public/export.xlsx';
             fs.writeFileSync(filePath, buffer, 'binary');
             const stats = fs.statSync(filePath);

+ 79 - 27
modules/std_billsGuidance_lib/facade/facades.js

@@ -483,22 +483,24 @@ function isOptionNode(node) {
     return node && node.depth() % 2 === 1 && node.data.type === 0
 }
 
-// 从指引节点,获取分类特征数据
-function getItemCharacterData(nodes, prefix) {
+// 从指引节点,获取分类特征、必套定额数据
+function getItemClassData(nodes, prefix) {
     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(optionNode.data.name);
+                // 必套定额
+                const requiredRationIDs = optionNode.children && optionNode.children.length ?
+                    optionNode.children.filter(node => !!node.data.rationID).map(node => node.data.rationID) : [];
+                classItems.push({ name: optionNode.data.name, requiredRationIDs });
             } else {
-                classItems.push(...getItemCharacterData(optionNode.children, optionNode.parent && optionNode.parent.data.required ? optionNode.data.name : ''));
+                classItems.push(...getItemClassData(optionNode.children, optionNode.parent && optionNode.parent.data.required ? optionNode.data.name : ''));
             }
         });
         if (classItems.length) {
@@ -507,7 +509,10 @@ function getItemCharacterData(nodes, prefix) {
     });
     // 拼接上一文本
     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
+            return item;
+        });
     }
     // 二维数组内容排列组合
     while (classGroups.length > 1) {
@@ -516,12 +521,57 @@ function getItemCharacterData(nodes, prefix) {
         const mergedClassItems = [];
         for (let i = 0; i < prevClassItems.length; i++) {
             for (let j = 0; j < nextClassItems.length; j++) {
-                mergedClassItems.push(`${prevClassItems[i]}@${nextClassItems[j]}`);
+                // 拼接文本
+                const mergedName = `${prevClassItems[i].name}@${nextClassItems[j].name}`;
+                // 拼接必套定额
+                const mergedRationIDs = [...prevClassItems[i].requiredRationIDs, ...nextClassItems[j].requiredRationIDs];
+                mergedClassItems.push({name: mergedName, requiredRationIDs: mergedRationIDs});
             }
         }
         classGroups.splice(0, 2, mergedClassItems);
     }
-    return classGroups[0] ? [...new Set(classGroups[0])] : []; // 去重
+    // 去重(类别别名要唯一)
+    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(itemClassData, allRationIDs) {
+    // 所有必套定额
+    let requiredRationIDs = [];
+    itemClassData.forEach(item => {
+        if (item.requiredRationIDs) {
+            requiredRationIDs.push(...item.requiredRationIDs);
+        }
+    });
+    requiredRationIDs = [...new Set(requiredRationIDs)];
+    // 选套定额就是清单下所有定额除了必套的
+    const optionalRationIDs = [];
+    allRationIDs.forEach(rationID => {
+        if (!requiredRationIDs.includes(rationID)) {
+            optionalRationIDs.push(rationID);
+        }
+    });
+    return [...new Set(optionalRationIDs)];
+}
+
+// 获取错套定额:清单下所有定额,除了分类对应的必套、选套定额
+function getErrorRationIDs(requiredRationIDs, optionalRationIDs, allRationIDs) {
+    const errorRationIDs = [];
+    allRationIDs.forEach(rationID => {
+        if (!requiredRationIDs.includes(rationID) && !optionalRationIDs.includes(rationID)) {
+            errorRationIDs.push(rationID);
+        }
+    });
+    return [...new Set(errorRationIDs)];
 }
 
 // 生成清单分类
@@ -551,28 +601,23 @@ async function generateClassData(libID) {
         }
         const guidanceTree = idTree.createNew({id: 'ID', pid: 'ParentID', nid: 'NextSiblingID', rootId: -1, autoUpdate: true});
         guidanceTree.loadDatas(guidanceItems);
-        /* if (!guidanceTree.check(guidanceTree.roots)) {
-            console.log('==================================清单下精灵树结构问题=========================================');
-            console.log('==================================清单下精灵树结构问题=========================================');
-            console.log('==================================清单下精灵树结构问题=========================================');
-            console.log('==================================清单下精灵树结构问题=========================================');
-            console.log('==================================清单下精灵树结构问题=========================================');
-            console.log(billNode.data);
-            console.log('==================================清单下精灵树结构问题=========================================');
-            console.log('==================================清单下精灵树结构问题=========================================');
-            console.log('==================================清单下精灵树结构问题=========================================');
-            console.log('==================================清单下精灵树结构问题=========================================');
-            console.log('==================================清单下精灵树结构问题=========================================');
-        } */
-        const itemCharacterData = getItemCharacterData(guidanceTree.roots);
-        itemCharacterData.forEach(itemCharacter => {
+        const itemClassData = getItemClassData(guidanceTree.roots); // 必套定额在这个方法内就获取了,避免重复执行递归方法
+        const allRationIDs = guidanceTree.items.filter(node => !!node.data.rationID).map(node => node.data.rationID);
+        // 选套定额ID
+        const optionalRationIDs = getOptionalRationIDs(itemClassData, allRationIDs);
+        itemClassData.forEach(item => {
+            // 错套定额
+            const errorRationIDs = getErrorRationIDs(item.requiredRationIDs, optionalRationIDs, allRationIDs);
             billClassData.push({
-                itemCharacter,
+                itemCharacter: item.name,
                 class: classNum++,
-                classCode: `${billNode.data.code}@${itemCharacter}`,
+                classCode: `${billNode.data.code}@${item.name}`,
                 compilationID: lib.compilationId,
                 name: billNode.data.name,
                 code: billNode.data.code,
+                requiredRationIDs: item.requiredRationIDs || [],
+                optionalRationIDs,
+                errorRationIDs,
             });
         });
     });
@@ -595,15 +640,22 @@ async function getClassExcelData(libID) {
     if (!lib) {
         throw new Error('无有效精灵库');
     }
+    console.log('start');
+    const date = Date.now();
     const classData = await billClassModel.find({ compilationID: lib.compilationId }).lean();
-    const excelData = [['类别', '编码', '清单名称', '必填特征排列组合', '类别别名']];
+    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.classCode,
+            (item.requiredRationIDs || []).join('@'),
+            (item.optionalRationIDs || []).join('@'),
+            (item.errorRationIDs || []).join('@'),
         ];
         excelData.push(excelItem);
     });

+ 61 - 41
web/maintain/billsGuidance_lib/js/billsGuidance.js

@@ -2589,7 +2589,7 @@ const billsGuidance = (function () {
     }
 
 
-    /* 生成特征分类: 测试使用 */
+    /* 生成特征分类: 前端测试使用,想要前端测试,需要在zhiyin.html中,将id=generate-classa的按钮放开 */
     function getItemCharacterData(nodes, prefix) {
         const processNodes = nodes.filter(node => isProcessNode(node));
         const classGroups = []; // 同层必填选项的数组(二维数组)
@@ -2602,7 +2602,10 @@ const billsGuidance = (function () {
                     || !optionNode.children.length 
                     || (optionNode.children[0].data && optionNode.children[0].data.rationID) 
                     || !optionNode.children.some(node => isProcessNode(node)))) {
-                    classItems.push(optionNode.data.name);
+                    // 必套定额
+                    const requiredRationIDs = optionNode.children && optionNode.children.length ?
+                        optionNode.children.filter(node => !!node.data.rationID).map(node => node.data.rationID) : [];
+                    classItems.push({ name: optionNode.data.name, requiredRationIDs });
                 } else {
                     classItems.push(...getItemCharacterData(optionNode.children, optionNode.parent && optionNode.parent.data.required ? optionNode.data.name : ''));
                 }
@@ -2613,7 +2616,11 @@ const billsGuidance = (function () {
         });
         // 拼接上一文本
         if (classGroups[0] && classGroups[0].length) {
-            classGroups[0] = classGroups[0].map(name => prefix ? `${prefix}@${name}` : name);
+            // classGroups[0] = classGroups[0].map(name => prefix ? `${prefix}@${name}` : name);
+            classGroups[0] = classGroups[0].map(item => {
+                item.name = prefix ? `${prefix}@${item.name}` : item.name
+                return item;
+            });
         }
         // 二维数组内容排列组合
         while (classGroups.length > 1) {
@@ -2622,55 +2629,68 @@ const billsGuidance = (function () {
             const mergedClassItems = [];
             for (let i = 0; i < prevClassItems.length; i++) {
                 for (let j = 0; j < nextClassItems.length; j++) {
-                    mergedClassItems.push(`${prevClassItems[i]}@${nextClassItems[j]}`);
+                    // 拼接文本
+                    const mergedName = `${prevClassItems[i].name}@${nextClassItems[j].name}`;
+                    // 拼接必套定额
+                    const mergedRationIDs = [...prevClassItems[i].requiredRationIDs, ...nextClassItems[j].requiredRationIDs];
+                    mergedClassItems.push({name: mergedName, requiredRationIDs: mergedRationIDs});
                 }
             }
             classGroups.splice(0, 2, mergedClassItems);
         }
-        return classGroups[0] ? [...new Set(classGroups[0])] : [];
+        // 去重(类别别名要唯一)
+        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 getItemCharacterData(nodes, prefix) {
-        debugger;
-        const processNodes = nodes.filter(node => isProcessNode(node) && node.data.required);
-        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.children 
-                    || !optionNode.children.length 
-                    || (optionNode.children[0].data && optionNode.children[0].data.rationID) 
-                    || !optionNode.children.some(node => isProcessNode(node) && node.data.required)) {
-                    classItems.push(optionNode.data.name);
-                } else {
-                    classItems.push(...getItemCharacterData(optionNode.children, optionNode.data.name));
-                }
-            });
-            classGroups.push(classItems);
+
+    // 获取选套定额:把所有分类数据的必套定额确定好了先。选套定额就是清单下所有定额除了必套的
+    function getOptionalRationIDs(itemClassData, guideNodes) {
+        // 所有必套定额
+        let requiredRationIDs = [];
+        itemClassData.forEach(item => {
+            if (item.requiredRationIDs) {
+                requiredRationIDs.push(...item.requiredRationIDs);
+            }
         });
-        // 拼接上一文本
-        if (classGroups[0] && classGroups[0].length) {
-            classGroups[0] = classGroups[0].map(name => prefix ? `${prefix}@${name}` : name);
-        }
-        // 二维数组内容排列组合
-        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++) {
-                    mergedClassItems.push(`${prevClassItems[i]}@${nextClassItems[j]}`);
-                }
+        requiredRationIDs = [...new Set(requiredRationIDs)];
+        // 选套定额就是清单下所有定额除了必套的
+        const optionalRationIDs = [];
+        guideNodes.forEach(node => {
+            if (node.data.rationID && !requiredRationIDs.includes(node.data.rationID)) {
+                optionalRationIDs.push(node.data.rationID);
             }
-            classGroups.splice(0, 2, mergedClassItems);
-        }
-        return classGroups[0] || [];
-    } */
+        });
+        return [...new Set(optionalRationIDs)];
+    }
+
+    // 获取错套定额:清单下所有定额,除了分类对应的必套、选套定额
+    function getErrorRationIDs(requiredRationIDs, optionalRationIDs, guideNodes) {
+        const errorRationIDs = [];
+        guideNodes.forEach(node => {
+            if (node.data.rationID && !requiredRationIDs.includes(node.data.rationID) && !optionalRationIDs.includes(node.data.rationID)) {
+                errorRationIDs.push(node.data.rationID);
+            }
+        });
+        return [...new Set(errorRationIDs)];
+    }
 
     $('#generate-class').click(() => {
         if (bills.tree.selected && bills.tree.selected.guidance.tree) {
             const classData = getItemCharacterData(bills.tree.selected.guidance.tree.roots);
+            const optionalRationIDs = getOptionalRationIDs(classData, bills.tree.selected.guidance.tree.items);
+            classData.forEach(item => {
+                item.errorRationIDs = getErrorRationIDs(item.requiredRationIDs, optionalRationIDs, bills.tree.selected.guidance.tree.items);
+            })
+            console.log(optionalRationIDs);
             console.log(classData);
         }
     });