Bladeren bron

feat: 根据清单精灵库,生成清单分类库

vian 3 jaren geleden
bovenliggende
commit
4da62fea17

+ 16 - 0
modules/all_models/bill_class.js

@@ -0,0 +1,16 @@
+// 清单分类
+const mongoose = require('mongoose');
+const Schema = mongoose.Schema;
+
+
+const billClass = new Schema({
+    compilationID: String,
+    name: String,
+    code: String,
+    itemCharacter: String,
+    class: { type: String, index: true }, // 分类
+    classCode: { type: String, index: true }, // 类别别名
+}, {versionKey: false});
+
+
+mongoose.model('billClass', billClass, 'billClass');

+ 12 - 0
modules/std_billsGuidance_lib/controllers/libController.js

@@ -125,6 +125,18 @@ class BillsGuideLibController extends BaseController{
             callback(req, res, 1, err.message, []);
         }
     }
+    
+    async generateClassData(req, res) {
+        try{
+            const data = JSON.parse(req.body.data);
+            await billsGuidanceFacade.generateClassData(data.libID);
+            callback(req, res, 0, '', []);
+        }
+        catch(err){
+            console.log(err);
+            callback(req, res, 1, err.message, []);
+        }
+    }
 
     async testItems(req, res){
         try{

+ 97 - 0
modules/std_billsGuidance_lib/facade/facades.js

@@ -23,6 +23,8 @@ 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';
@@ -37,6 +39,7 @@ module.exports = {
     updateItems,
     getBillMaterials,
     editBillMaterials,
+    generateClassData,
     testItems
 };
 
@@ -443,6 +446,100 @@ async function editBillMaterials(libID, billID, gljCodes, compilationID) {
     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 getItemCharacterData(nodes, prefix) {
+    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 => {
+            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);
+    });
+    // 拼接上一文本
+    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]}`);
+            }
+        }
+        classGroups.splice(0, 2, mergedClassItems);
+    }
+    return classGroups[0] || [];
+}
+
+// 生成清单分类
+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 = [];
+    leaves.forEach(billNode => {
+        const guidanceItems = guidanceMap[billNode.data.ID];
+        if (!guidanceItems || !guidanceItems.length) {
+            return;
+        }
+        const guidanceTree = idTree.createNew({id: 'ID', pid: 'ParentID', nid: 'NextSiblingID', rootId: -1, autoUpdate: true});
+        guidanceTree.loadDatas(guidanceItems);
+        const itemCharacterData = getItemCharacterData(guidanceTree.roots);
+        itemCharacterData.forEach(itemCharacter => {
+            billClassData.push({
+                itemCharacter,
+                class: classNum++,
+                classCode: `${billNode.data.code}@${itemCharacter}`,
+                compilationID: lib.compilationId,
+                name: billNode.data.name,
+                code: billNode.data.code,
+            });
+        });
+    });
+    // 清空旧的分类数据
+    await billClassModel.deleteMany({ compilationID: lib.compilationId });
+    if (billClassData.length) {
+        await billClassModel.insertMany(billClassData);
+    }
+}
+
 async function testItems(libID) {
     let items = await billsGuideItemsModel.find({libID: libID});
     //删除垃圾数据

+ 1 - 0
modules/std_billsGuidance_lib/routes/routes.js

@@ -25,6 +25,7 @@ module.exports = function (app) {
     router.post('/updateItems', billsGuideLibController.auth, billsGuideLibController.init, billsGuideLibController.updateItems);
     router.post('/getBillMaterials', billsGuideLibController.auth, billsGuideLibController.init, billsGuideLibController.getBillMaterials);
     router.post('/editBillMaterials', billsGuideLibController.auth, billsGuideLibController.init, billsGuideLibController.editBillMaterials);
+    router.post('/generateClassData', billsGuideLibController.auth, billsGuideLibController.init, billsGuideLibController.generateClassData);
     //test
     //router.post('/testItems', billsGuideLibController.auth, billsGuideLibController.init, billsGuideLibController.testItems);
 

+ 12 - 0
public/id_tree.js

@@ -0,0 +1,12 @@
+const fs = require('fs');
+let scTree = null;
+
+let getSCTree = function() {
+    if (!(scTree)) {
+        let data = fs.readFileSync(__dirname + '/web/id_tree.js', 'utf8', 'r');
+        eval(data + ' scTree = idTree;');
+    }
+    return scTree;
+};
+
+exports.getTree = getSCTree;

+ 21 - 0
web/maintain/billsGuidance_lib/html/main.html

@@ -76,6 +76,7 @@
                     <th width="100">类型</th>
                     <th width="160">添加时间</th>
                     <th width="70">操作</th>
+                    <%- manager.isTemporary ? '' : '<th width="120">生成清单分类</th>' %>
                   </tr>
                 </thead>
                 <tbody>
@@ -199,6 +200,26 @@
       </div>
     </div>
   </div>
+  <!-- 生成清单分类 -->
+  <div class="modal fade" id="generate-class-modal" data-backdrop="static" style="display: none;" aria-hidden="true">
+    <div class="modal-dialog" role="document">
+      <div class="modal-content">
+        <div class="modal-header">
+          <h5 class="modal-title">生成清单分类</h5>
+          <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+            <span aria-hidden="true">×</span>
+          </button>
+        </div>
+        <div class="modal-body">
+          <div id="generate-class-info" style="font-weight: 700;"></div>
+        </div>
+        <div class="modal-footer">
+          <a id="generate-class-confirm" href="javascript:void(0);" class="btn btn-danger">确认</a>
+          <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
+        </div>
+      </div>
+    </div>
+  </div>
   <!-- JS. -->
   <script src="/lib/jquery/jquery.min.js"></script>
   <script src="/lib/tether/tether.min.js"></script>

+ 1 - 0
web/maintain/billsGuidance_lib/html/zhiyin.html

@@ -97,6 +97,7 @@
                                   <a id="downMove" href="javascript:void(0);" class="btn btn-sm lock-btn-control" data-toggle="tooltip" data-placement="bottom" title="下移"><i class="fa fa-arrow-down" aria-hidden="true"></i></a>
                                   <a id="upMove" href="javascript:void(0);" class="btn btn-sm lock-btn-control" data-toggle="tooltip" data-placement="bottom" title="上移"><i class="fa fa-arrow-up" aria-hidden="true"></i></a>
                                   <a id="expandContract" href="javascript:void(0);" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title="收起定额"><i class="fa fa-minus-square-o" aria-hidden="true"></i> 收起定额</a>
+                                  <!-- <a id="generate-class" href="javascript:void(0);" class="btn btn-sm lock-btn-control" data-toggle="tooltip" data-placement="bottom" title="生成分类">生成分类</a> -->
                               </div>
                           </div>
                           <div class="main-top-content">

+ 48 - 0
web/maintain/billsGuidance_lib/js/billsGuidance.js

@@ -804,6 +804,7 @@ const billsGuidance = (function () {
         }
         //全部设为无效
         $('.tools-btn').children().addClass('disabled');
+        $('#generate-class').removeClass('disabled');
         $('#insertRation').addClass('disabled');
         $('#insertAll').addClass('disabled');
         $('.main-bottom-content').find('textarea').attr('readonly', true);
@@ -2540,6 +2541,53 @@ const billsGuidance = (function () {
     }
 
 
+    /* 生成特征分类: 测试使用 */
+    function getItemCharacterData(nodes, prefix) {
+        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);
+        });
+        // 拼接上一文本
+        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]}`);
+                }
+            }
+            classGroups.splice(0, 2, mergedClassItems);
+        }
+        return classGroups[0] || [];
+    }
+
+    $('#generate-class').click(() => {
+        /* if (bills.tree.selected && bills.tree.selected.guidance.tree) {
+            const classData = getItemCharacterData(bills.tree.selected.guidance.tree.roots);
+            console.log(classData);
+        } */
+    });
+
+
     return {initViews, initSlideSize};
 })();
 

+ 33 - 1
web/maintain/billsGuidance_lib/js/main.js

@@ -56,7 +56,13 @@ const billsGuidanceMain = (function () {
             <a class="lock-btn-control disabled text-danger" href="javascript:void(0);" data-toggle="modal" data-target="#del" title="删除"><i class="fa fa-remove"></i></a>
             ` : '' }
             <a class="lock" data-locked="true" href="javascript:void(0);" title="解锁"><i class="fa fa-unlock-alt"></i></a>
-            </td></tr>`;
+            </td>
+            ${ isTemporary !== 'true' ? `
+            <td style="text-align: center;">
+            <a class="btn btn-secondary btn-sm generate-class lock-btn-control disabled" href="javascript:void(0);" data-id="${lib.ID}" title="生成清单分类"><i class="fa fa-sign-in fa-rotate-90"></i>生成</a>
+            </td>
+            ` : '' }
+            </tr>`;
         tbody.append(tr);
     }
     //获取清单指引库
@@ -145,6 +151,32 @@ const billsGuidanceMain = (function () {
         $('#add').on('shown.bs.modal', function () {
             $('#createName').focus();
         });
+        // 生成清单分类
+        $('.main').find('tbody').on('click', '.generate-class', function () {
+            let tr = $(this).parent().parent();
+            let selLib = existLib({k: 'ID', v: tr.attr('id')}, guidanceLibs);
+            curLib = selLib;
+            $('#generate-class-modal').modal('show');
+            $('#generate-class-info').html(`
+            <p>确认根据库“${curLib.name}”生成清单分类?</p>
+            <p style="color: #d9534f">注意,原有分类数据将被清空</p>
+            `)
+            console.log(curLib);
+        });
+        $('#generate-class-confirm').click(async () => {
+            if (!curLib || !curLib.ID) {
+                return;
+            }
+            $.bootstrapLoading.start();
+            try {
+                await ajaxPost('/billsGuidance/api/generateClassData', { libID: curLib.ID }, 1000 * 60 * 10);
+                $('#generate-class-modal').modal('hide');
+            } catch (error) {
+                console.log(error);
+            } finally {
+                $.bootstrapLoading.end();
+            }
+        });
         //所有编辑按钮
         $('.main').find('tbody').on('click', '[data-target="#edit"]', function () {
             let tr = $(this).parent().parent();