Files
AutoBOM/VBA/combined_output.md
Misaka_Company 40a9fe99f4 feat: track and return missing material categories in BOM
- Update GetValidMaterialsByModel to aggregate and return missing
  category names under the "MissingCategories" key.
- Remove debug print statements and unused variables to improve
  code cleanliness.
- Enhance result visualization to display completeness status
  with color formatting and detailed material tables.
2026-01-21 09:51:37 +08:00

2877 lines
98 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 目录
- [ClassModules\clsBOMManager.cls](#classmodules-clsbommanager-cls)
- [ClassModules\clsCategory.cls](#classmodules-clscategory-cls)
- [ClassModules\clsConditionExtractor.cls](#classmodules-clsconditionextractor-cls)
- [ClassModules\clsConditionMatcher.cls](#classmodules-clsconditionmatcher-cls)
- [ClassModules\clsMaterialItem.cls](#classmodules-clsmaterialitem-cls)
- [ClassModules\clsModelParser.cls](#classmodules-clsmodelparser-cls)
- [Modules\modBOMProcessor.bas](#modules-modbomprocessor-bas)
- [Modules\modModelParserExamples.bas](#modules-modmodelparserexamples-bas)
- [Modules\modModelParserTest.bas](#modules-modmodelparsertest-bas)
---
## ClassModules\clsBOMManager.cls
<a id="classmodules-clsbommanager-cls"></a>
```
' ========================================
' 类模块: clsBOMManager
' 用途: 管理整个BOM数据结构
' 功能:
' 1. 加载和组织BOM数据
' 2. 建立类别层级关系
' 3. 提供物料查询接口
' 4. 支持领料逻辑处理
' ========================================
Option Explicit
' ========================================
' 私有成员变量
' ========================================
Private dictCategories As Object ' Dictionary对象: 类别名称 -> clsCategory对象
' 作用: 快速查找任意类别
Private dictAllMaterials As Object ' Dictionary对象: 物料代号 -> clsMaterialItem对象
' 作用: 快速查找任意物料
Private rootCategories As collection ' Collection: 存储所有顶层类别(无父类别的类别)
' 作用: 遍历完整的类别树结构
' ========================================
' 类初始化
' 说明: 创建BOMManager实例时自动调用
' ========================================
Private Sub Class_Initialize()
Set dictCategories = CreateObject("Scripting.Dictionary")
Set dictAllMaterials = CreateObject("Scripting.Dictionary")
Set rootCategories = New collection
End Sub
' ========================================
' LoadData 方法
' 功能: 从Excel工作表加载BOM数据并构建数据结构
' 参数:
' wsConfig - [领料配置]工作表对象,包含类别层级和需要领料的物料
' wsPlatform - [平台配置清单]工作表对象,包含完整的物料信息(代号/名称/数量/条件)
' 处理步骤:
' 1. 从[平台配置清单]加载所有物料的基础信息到dictAllMaterials
' 2. 从[领料配置]加载类别信息,筛选需要领料的物料
' 3. 建立类别的父子关系,构建层级树
' 注意:
' - 只有出现在[领料配置]中的物料才会被添加到类别中
' - 未在[领料配置]中的物料表示不需要领料
' ========================================
Public Sub LoadData(wsConfig As Worksheet, wsPlatform As Worksheet)
Dim i As Long, lastRow As Long
Dim mat As clsMaterialItem
Dim cat As clsCategory
' ========================================
' 第一步: 从平台配置清单加载所有物料的基础信息
' 说明:
' - 读取C列(代号)、D列(名称)、E列(数量)、F列(选择条件)
' - 从第4行开始读取(前3行是标题)
' - 所有物料存入dictAllMaterials字典,以代号为键
' 目的: 建立完整的物料信息库,供后续按代号查询
' ========================================
lastRow = wsPlatform.Cells(wsPlatform.Rows.Count, "C").End(xlUp).row
For i = 4 To lastRow ' 从第4行开始(跳过标题)
Set mat = New clsMaterialItem
mat.code = Trim(wsPlatform.Cells(i, "C").value & "") ' 物料代号
mat.Name = Trim(wsPlatform.Cells(i, "D").value & "") ' 物料名称
On Error Resume Next
mat.Quantity = CDbl(wsPlatform.Cells(i, "E").value) ' 物料数量
On Error GoTo 0
mat.Condition = Trim(wsPlatform.Cells(i, "F").value & "") ' 选择条件
' 只保存代号非空的物料
If mat.code <> "" Then
Set dictAllMaterials(mat.code) = mat
End If
Next i
' ========================================
' 第二步: 从领料配置加载类别信息并建立层级
' 说明:
' - 读取A列(代号)、C列(类别)、D列(上层类别)
' - 从第2行开始读取(第1行是标题)
' - 只有出现在此表中的物料才需要领料
' 处理逻辑:
' 1. 为每个类别创建clsCategory对象
' 2. 从dictAllMaterials中查找对应的物料信息
' 3. 将物料添加到对应的类别中
' ========================================
lastRow = wsConfig.Cells(wsConfig.Rows.Count, "A").End(xlUp).row
For i = 2 To lastRow ' 从第2行开始
Dim code As String, catName As String, parentCatName As String
code = Trim(wsConfig.Cells(i, "A").value & "") ' 物料代号
catName = Trim(wsConfig.Cells(i, "C").value & "") ' 类别名称
parentCatName = Trim(wsConfig.Cells(i, "D").value & "") ' 上层类别名称
' 跳过空行
If code = "" Then GoTo NextRow
' 确保类别对象存在(如果类别不存在则创建)
If Not dictCategories.Exists(catName) Then
Set cat = New clsCategory
cat.categoryName = catName
cat.ParentCategoryName = parentCatName
Set dictCategories(catName) = cat
End If
' 将物料添加到类别
' 注意: 必须先在dictAllMaterials中查找到完整的物料信息
If dictAllMaterials.Exists(code) Then
Set mat = dictAllMaterials(code)
mat.Category = catName ' 设置物料所属类别
mat.ParentCategory = parentCatName ' 设置物料的上层类别
dictCategories(catName).AddMaterial mat ' 将物料添加到类别对象中
End If
NextRow:
Next i
' ========================================
' 第三步: 建立类别层级关系
' 说明:
' - 遍历所有类别,根据ParentCategoryName建立父子关系
' - 如果类别有父类别,将自己添加到父类别的SubCategories中
' - 如果类别没有父类别,则为根类别,添加到rootCategories中
' 结果:
' - 构建完整的树形结构
' - rootCategories包含所有顶层类别
' - 每个类别的SubCategories包含其直接子类别
' ========================================
Dim key As Variant
For Each key In dictCategories.Keys
Set cat = dictCategories(key)
If cat.ParentCategoryName <> "" Then
' 有父类别,建立父子关系
If dictCategories.Exists(cat.ParentCategoryName) Then
Dim parentCat As clsCategory
Set parentCat = dictCategories(cat.ParentCategoryName)
parentCat.AddSubCategory cat ' 将当前类别添加为父类别的子类别
End If
Else
' 无父类别,是根类别
rootCategories.Add cat, cat.categoryName
End If
Next key
End Sub
' ========================================
' GetRootCategories 方法
' 功能: 获取所有顶层类别的集合
' 返回: Collection对象,包含所有无父类别的clsCategory对象
' 用途:
' - 遍历整个BOM结构时的入口点
' - 生成领料清单时遍历所有根类别
' 示例:
' Dim cats As Collection
' Set cats = bomMgr.GetRootCategories()
' For i = 1 To cats.Count
' Debug.Print cats(i).CategoryName
' Next i
' ========================================
Public Function GetRootCategories() As collection
Set GetRootCategories = rootCategories
End Function
' ========================================
' GetCategory 方法
' 功能: 根据类别名称获取类别对象
' 参数:
' categoryName - 要查询的类别名称(字符串)
' 返回:
' clsCategory对象 - 如果找到
' Nothing - 如果未找到
' 用途: 快速查找特定类别及其下的物料
' 示例:
' Dim cat As clsCategory
' Set cat = bomMgr.GetCategory("部件")
' If Not cat Is Nothing Then
' Debug.Print cat.Materials.Count & " 个物料"
' End If
' ========================================
Public Function GetCategory(categoryName As String) As clsCategory
If dictCategories.Exists(categoryName) Then
Set GetCategory = dictCategories(categoryName)
Else
Set GetCategory = Nothing
End If
End Function
' ========================================
' GetMaterialsForPicking 方法
' 功能: 获取某类别下需要领料的物料清单(考虑层级逻辑)
' 参数:
' categoryName - 类别名称
' useParent - 可选参数,默认True
' True: 使用父类别物料(默认领料方式)
' False: 使用子类别物料(库存不足时的替代方案)
' 返回: Collection对象,包含clsMaterialItem对象
'
' 业务逻辑说明:
' 1. 默认领取父类别的物料(如"低压接头部件")
' 2. 当父类别库存不足时,才领取子类别的物料(如"接头"+"弹性元件")
' 3. 如果useParent=True但类别有子类别,仍返回父类别物料
' 4. 如果useParent=False,递归获取所有子类别的物料
'
' 示例1: 获取"部件"类别的物料(父类别)
' Set mats = bomMgr.GetMaterialsForPicking("部件", True)
' ' 返回: 低压接头部件、高压接头部件等组装好的部件
'
' 示例2: 获取"部件"类别的物料(子类别展开)
' Set mats = bomMgr.GetMaterialsForPicking("部件", False)
' ' 返回: 径向低压接头、弹簧管、螺旋管等零件
' ========================================
Public Function GetMaterialsForPicking(categoryName As String, _
Optional useParent As Boolean = True) As collection
Dim result As collection
Set result = New collection
' 查找指定类别
Dim cat As clsCategory
Set cat = GetCategory(categoryName)
If cat Is Nothing Then
' 类别不存在,返回空集合
Set GetMaterialsForPicking = result
Exit Function
End If
Dim i As Long
If useParent Then
' ========================================
' 使用父类别物料(默认领料方式)
' 说明:
' - 直接返回当前类别下的所有物料
' - 即使该类别有子类别,也仍然返回父类别物料
' - 这是正常情况下的领料方式(领取组装好的部件)
' ========================================
Dim m As clsMaterialItem
For i = 1 To cat.materials.Count
Set m = cat.materials(i)
result.Add m
Next i
Else
' ========================================
' 使用子类别物料(库存不足时的替代方案)
' 说明:
' - 如果当前类别有子类别,递归获取所有子类别的物料
' - 如果当前类别是叶子类别(无子类别),返回本类别物料
' - 这用于父类别库存不足,需要领取零件自行组装的情况
' 示例:
' 当"低压接头部件"库存不足时
' 改为领取"径向低压接头"+"弹簧管"零件
' ========================================
If cat.HasSubCategories Then
' 有子类别,递归获取所有子类别的物料
Dim subCat As clsCategory
Dim j As Long
For j = 1 To cat.SubCategories.Count
Set subCat = cat.SubCategories(j)
Dim subMats As collection
' 递归调用,继续展开子类别
Set subMats = GetMaterialsForPicking(subCat.categoryName, False)
Dim k As Long
For k = 1 To subMats.Count
result.Add subMats(k)
Next k
Next j
Else
' 叶子类别,返回本类别物料
For i = 1 To cat.materials.Count
Set m = cat.materials(i)
result.Add m
Next i
End If
End If
Set GetMaterialsForPicking = result
End Function
' ========================================
' PrintCategoryTree 方法
' 功能: 将类别树结构打印到工作表(用于调试和查看)
' 参数:
' ws - 输出的目标工作表对象
' 输出格式:
' - 第一列: 类别名称(带缩进显示层级)
' - 第二列: 物料数量信息
' - 第三列: 选择条件
' 说明:
' - 使用缩进显示类别层级(每层2个空格)
' - 递归打印所有子类别和物料
' - 便于验证数据结构是否正确
' 示例输出:
' 表壳 (物料数:1)
' - 01091004312 表壳(本色) 数量:1 条件:
' 部件 (物料数:20)
' - 01011019001 低压接头部件 数量:1 条件:lcfw=M02...
' 接头 (物料数:18)
' - 01081013833 径向低压接头 数量:1 条件:gclj=Z12...
' ========================================
Public Sub PrintCategoryTree(ws As Worksheet)
Dim row As Long
row = 1
ws.Cells(row, 1).value = "类别层级结构"
row = row + 1
' 遍历所有根类别,递归打印整个树
Dim rootCat As clsCategory
Dim i As Long
For i = 1 To rootCategories.Count
Set rootCat = rootCategories(i)
Call PrintCategory(ws, rootCat, row, 0)
Next i
End Sub
' ========================================
' PrintCategory 方法 (私有方法)
' 功能: 递归打印单个类别及其子类别(供PrintCategoryTree调用)
' 参数:
' ws - 输出的工作表对象
' cat - 要打印的类别对象
' row - 当前输出行号(ByRef,会被修改)
' level - 当前层级深度(0=根类别,1=一级子类别...)
' 说明:
' - 使用递归方式遍历整个类别树
' - 根据level参数计算缩进空格数
' - 先打印类别名,再打印该类别的所有物料,最后递归打印子类别
' ========================================
Private Sub PrintCategory(ws As Worksheet, cat As clsCategory, _
ByRef row As Long, level As Integer)
' 计算缩进(每层2个空格)
Dim indent As String
indent = String(level * 2, " ")
' 打印类别名称和物料数量统计
ws.Cells(row, 1).value = indent & cat.categoryName & _
" (物料数:" & cat.materials.Count & ")"
row = row + 1
' 打印该类别下的所有物料
Dim mat As clsMaterialItem
Dim i As Long
For i = 1 To cat.materials.Count
Set mat = cat.materials(i)
' 物料行额外缩进2个空格,并加上"- "前缀
ws.Cells(row, 1).value = indent & " - " & mat.code & " " & mat.Name
ws.Cells(row, 2).value = "数量:" & mat.Quantity
ws.Cells(row, 3).value = "条件:" & mat.Condition
row = row + 1
Next i
' 递归打印所有子类别
Dim subCat As clsCategory
Dim j As Long
For j = 1 To cat.SubCategories.Count
Set subCat = cat.SubCategories(j)
' 递归调用,层级加1
Call PrintCategory(ws, subCat, row, level + 1)
Next j
End Sub
' ========================================
' GetMaterialsByModel 方法
'
' 【功能概述】
' 根据产品型号字符串自动解析规格参数,并返回符合这些规格的所有物料清单。
' 这是BOM系统的核心方法,实现了从"产品型号"到"物料清单"的智能转换。
'
' 【工作流程】
' 1. 型号解析 (clsModelParser) → 将产品型号字符串拆解为结构化参数
' 2. 条件提取 (clsConditionExtractor) → 从型号参数中提取匹配条件变量
' 3. 物料筛选 (clsConditionMatcher) → 根据条件从物料库中筛选符合条件的物料
'
' 【参数说明】
' modelStr - 产品型号字符串
' 格式: [型号]-[口径].[安装].[表壳].[连接].[量程]|[表盘]|[附件]|[法兰]
' 示例: "YTHN-100.A0.532.M203.M16.Y3|BP-095.2312.M16.PA3"
' 说明: 必须是完整的型号字符串,不能为空
'
' categoryName - 类别名称(可选参数,默认为空字符串)
' 为空字符串 "": 返回所有类别的符合条件物料(默认行为)
' 指定类别名: 仅返回该类别下的符合条件物料
' 示例: "部件"、"表壳"、"接头"、"机芯"
'
' autoFallback - 是否自动降级到子类别(可选参数,默认为True)
' True (默认): 当某个类别无匹配物料时,自动降级到其子类别继续查找
' False: 仅在当前类别查找,不降级到子类别
' 示例场景:
' 型号 "YTHN-100.A0.532.M203.M17.Y3" 的量程是M17
' 类别"部件"下的物料条件是 "lcfw=M02"
' autoFallback=True: 自动降级到子类别"接头"、"弹性元件"等查找
' autoFallback=False: "部件"类别返回空集合
'
' 【返回值】
' 返回类型: Collection对象
' 元素类型: Collection中的每个元素都是 clsMaterialItem 对象
'
' clsMaterialItem 对象属性:
' .code - String - 物料代号(如: "01011019001")
' .Name - String - 物料名称(如: "低压接头部件")
' .Quantity - Double - 物料数量(如: 1, 2, 0.5)
' .Condition - String - 选择条件(如: "lcfw=M02 AND gclj=Z12")
' .Category - String - 所属类别名称
' .ParentCategory - String - 上层类别名称
'
' 【注意事项】
' 1. 调用前必须先执行 LoadData() 方法加载BOM数据
' 2. 型号字符串必须完整且符合格式要求
' 3. 返回的Collection可能为空(没有符合条件的物料),需要判断Count属性
' 4. 调试信息会输出到VBA的"立即窗口"(Ctrl+G查看)
' 5. 物料的Condition属性为空表示该物料无条件限制(所有型号都使用)
' 6. autoFallback 参数影响查找范围:
' - True (默认): 会返回父类别和子类别的物料,更全面但可能包含不需要的物料
' - False: 仅返回指定类别的物料,更精确但可能遗漏子类别的替代物料
'
' 【相关方法】
' - GetMaterialsByCategoryAndModel: 结合了类别层级逻辑的物料获取
' - GetMaterialsForPicking: 纯粹按类别获取物料(不进行型号匹配)
' - ParseModelAndExtractConditions: 仅解析型号并返回条件字典
' ========================================
Public Function GetMaterialsByModel(modelStr As String, _
Optional categoryName As String = "", _
Optional autoFallback As Boolean = True) As collection
Dim result As collection
Set result = New collection
' ========================================
' 第1步: 解析型号并提取条件
' ========================================
Dim parser As New clsModelParser
If Not parser.ParseModel(modelStr) Then
Debug.Print "型号解析失败: " & parser.ErrorMessage
Set GetMaterialsByModel = result
Exit Function
End If
Dim extractor As New clsConditionExtractor
Dim conditions As Object
Set conditions = extractor.ExtractConditions(parser)
' 调试输出
Debug.Print "【型号】: " & modelStr
Debug.Print "【提取的条件】:"
Dim key As Variant
For Each key In conditions.Keys
Debug.Print " " & key & " = " & conditions(key)
Next key
Debug.Print ""
' ========================================
' 第2步: 按类别筛选物料(根据参数决定是否自动降级到子类别)
' ========================================
Dim matcher As New clsConditionMatcher
Dim totalMatchCount As Long
Dim catResult As collection ' 提前声明避免在If/Else中重复声明
Dim cat As clsCategory
Dim rootCat As clsCategory
Dim i As Long, j As Long
totalMatchCount = 0
If autoFallback Then
Debug.Print "【降级模式】启用自动降级到子类别"
Else
Debug.Print "【降级模式】禁用自动降级,仅查找当前类别"
End If
Debug.Print ""
If categoryName <> "" Then
' ========================================
' 情况A: 指定了类别名称
' 只在该类别及其子类别中查找
' ========================================
Set cat = GetCategory(categoryName)
If Not cat Is Nothing Then
' 从该类别开始查找(根据参数决定是否启用子类别降级逻辑)
Set catResult = FilterCategoryWithSubcategories(cat, matcher, conditions, autoFallback)
' 合并结果
For i = 1 To catResult.Count
result.Add catResult(i)
Next i
totalMatchCount = catResult.Count
End If
Else
' ========================================
' 情况B: 未指定类别名称
' 遍历所有根类别,对每个类别应用子类别降级逻辑
' ========================================
For j = 1 To rootCategories.Count
Set rootCat = rootCategories(j)
' 对每个根类别应用筛选(根据参数决定是否启用子类别降级逻辑)
Set catResult = FilterCategoryWithSubcategories(rootCat, matcher, conditions, autoFallback)
' 合并结果
For i = 1 To catResult.Count
result.Add catResult(i)
Next i
totalMatchCount = totalMatchCount + catResult.Count
Next j
End If
Debug.Print "共匹配 " & totalMatchCount & " 个物料"
Debug.Print String(60, "=")
Set GetMaterialsByModel = result
End Function
' ========================================
' FilterCategoryWithSubcategories 方法 (私有)
' 功能: 对指定类别进行筛选,根据参数决定是否自动降级到子类别
' 参数:
' cat - 类别对象
' matcher - 条件匹配器对象
' conditions - 提取的条件字典
' autoFallback - 是否自动降级到子类别默认True
' True: 当前类别无匹配时,自动降级到子类别查找
' False: 仅在当前类别查找,不降级到子类别
' 返回: Collection对象包含匹配的物料
'
' 工作逻辑:
' 1. 先尝试在当前类别下筛选物料
' 2. 如果当前类别有匹配结果,直接返回
' 3. 如果当前类别无匹配结果且 autoFallback=True检查是否有子类别
' 4. 如果有子类别且允许降级,递归对所有子类别进行筛选
' 5. 如果无子类别或不允许降级,返回当前结果
'
' 示例场景:
' 型号: "YTHN-100.A0.532.M203.M16.Y3"
' 类别"部件"下有物料"低压接头部件"(条件: lcfw=M02
' 如果该型号的量程不是M02则"部件"类别无匹配
' 当 autoFallback=True 时,自动降级到子类别"接头"、"弹性元件"等查找
' 当 autoFallback=False 时,返回空集合,不查找子类别
' ========================================
Private Function FilterCategoryWithSubcategories(ByVal cat As clsCategory, _
ByVal matcher As clsConditionMatcher, _
ByVal conditions As Object, _
ByVal autoFallback As Boolean) As collection
Dim result As collection
Set result = New collection
' 类别不存在,返回空集合
If cat Is Nothing Then
Set FilterCategoryWithSubcategories = result
Exit Function
End If
' ========================================
' 第一阶段: 尝试在当前类别下筛选
' ========================================
Dim mat As clsMaterialItem
Dim i As Long
Dim matchCount As Long
matchCount = 0
' 遍历当前类别的所有物料进行筛选
For i = 1 To cat.materials.Count
Set mat = cat.materials(i)
If matcher.IsMatch(mat.Condition, conditions) Then
result.Add mat
matchCount = matchCount + 1
' 调试输出
Debug.Print "【匹配】" & cat.categoryName & " > " & _
mat.code & " - " & mat.Name & _
" | 条件: " & IIf(mat.Condition = "", "(无)", mat.Condition)
End If
Next i
' ========================================
' 第二阶段: 如果当前类别无匹配且允许降级,检查子类别
' ========================================
If matchCount = 0 And cat.HasSubCategories And autoFallback Then
Debug.Print "【降级】类别 """ & cat.categoryName & """ 无匹配物料,降级到子类别查找..."
' 递归处理所有子类别
Dim subCat As clsCategory
Dim j As Long
For j = 1 To cat.SubCategories.Count
Set subCat = cat.SubCategories(j)
' 递归调用,获取子类别的匹配结果(传递相同的 autoFallback 参数)
Dim subResult As collection
Set subResult = FilterCategoryWithSubcategories(subCat, matcher, conditions, autoFallback)
' 合并子类别的结果
Dim k As Long
For k = 1 To subResult.Count
result.Add subResult(k)
Next k
Next j
ElseIf matchCount = 0 And cat.HasSubCategories And Not autoFallback Then
Debug.Print "【跳过】类别 """ & cat.categoryName & """ 无匹配物料,但降级已禁用,不查找子类别"
ElseIf matchCount > 0 Then
Debug.Print "【成功】类别 """ & cat.categoryName & """ 匹配 " & matchCount & " 个物料"
End If
Set FilterCategoryWithSubcategories = result
End Function
' ========================================
' CollectAllMaterials 方法 (私有)
' 功能: 递归收集类别及其所有子类别的物料
' 参数:
' cat - 类别对象
' collection - 用于存储物料的Collection
' ========================================
Private Sub CollectAllMaterials(cat As clsCategory, collection As collection)
Dim i As Long
' 添加当前类别的所有物料
For i = 1 To cat.materials.Count
collection.Add cat.materials(i)
Next i
' 递归处理子类别
Dim subCat As clsCategory
Dim j As Long
For j = 1 To cat.SubCategories.Count
Set subCat = cat.SubCategories(j)
Call CollectAllMaterials(subCat, collection)
Next j
End Sub
' ========================================
' GetMaterialsByCategoryAndModel 方法
' 功能: 根据类别和型号获取物料(使用父类别或子类别逻辑)
' 参数:
' modelStr - 产品型号字符串
' categoryName - 类别名称
' useParent - True=使用父类别物料False=使用子类别物料
' 返回: Collection对象包含符合条件的clsMaterialItem对象
' 说明: 这是 GetMaterialsForPicking 和 GetMaterialsByModel 的结合
' ========================================
Public Function GetMaterialsByCategoryAndModel(modelStr As String, _
categoryName As String, _
Optional useParent As Boolean = True) As collection
Dim result As collection
Set result = New collection
' 1. 解析型号并提取条件
Dim parser As New clsModelParser
If Not parser.ParseModel(modelStr) Then
Set GetMaterialsByCategoryAndModel = result
Exit Function
End If
Dim extractor As New clsConditionExtractor
Dim conditions As Object
Set conditions = extractor.ExtractConditions(parser)
' 2. 获取类别的物料(根据 useParent 参数)
Dim materialsToFilter As collection
Set materialsToFilter = GetMaterialsForPicking(categoryName, useParent)
' 3. 筛选符合条件的物料
Dim matcher As New clsConditionMatcher
Dim mat As clsMaterialItem
For Each mat In materialsToFilter
If matcher.IsMatch(mat.Condition, conditions) Then
result.Add mat
End If
Next mat
Set GetMaterialsByCategoryAndModel = result
End Function
' ========================================
' ParseModelAndExtractConditions 方法
' 功能: 解析型号并返回条件字典(工具方法)
' 参数: modelStr - 产品型号字符串
' 返回: Dictionary对象包含提取的条件
' 用途: 供外部调用,用于查看提取的条件
' ========================================
Public Function ParseModelAndExtractConditions(modelStr As String) As Object
Dim parser As New clsModelParser
Dim extractor As New clsConditionExtractor
Dim conditions As Object
If parser.ParseModel(modelStr) Then
Set conditions = extractor.ExtractConditions(parser)
Else
Set conditions = CreateObject("Scripting.Dictionary")
End If
Set ParseModelAndExtractConditions = conditions
End Function
' ========================================
' GetValidMaterialsByModel 方法
' 功能: 根据产品型号获取物料,并判断是否为完整的物料集合
' 参数:
' modelStr - 产品型号字符串
' 返回: Collection对象
' Collection中包含两个元素:
' (1) "Materials" - Collection对象,包含所有匹配的 clsMaterialItem 对象
' (2) "IsComplete" - Boolean值,指示物料是否完整
'
' 完整性判断规则:
' - 对于每个需要领料的类别(顶层类别或其需要领取的子类别)
' - 必须有且仅有一个物料被匹配
' - 如果某个类别有0个或多于1个物料,则视为不完整
'
' 示例:
' Dim result As Collection
' Set result = bomMgr.GetValidMaterialsByModel("YTHN-100.A0.532.M203.M16.Y3")
' Dim materials As Collection
' Dim isComplete As Boolean
' Set materials = result("Materials")
' isComplete = result("IsComplete")
' ========================================
Public Function GetValidMaterialsByModel(modelStr As String) As collection
Dim result As New collection
Dim allMaterials As New collection
Dim isComplete As Boolean
' 解析型号并提取条件
Dim parser As New clsModelParser
If Not parser.ParseModel(modelStr) Then
Debug.Print "型号解析失败: " & parser.ErrorMessage
isComplete = False
result.Add allMaterials, "Materials"
result.Add isComplete, "IsComplete"
Set GetValidMaterialsByModel = result
Exit Function
End If
Dim extractor As New clsConditionExtractor
Dim conditions As Object
Set conditions = extractor.ExtractConditions(parser)
' 调试输出
Debug.Print "【GetValidMaterialsByModel】"
Debug.Print "型号: " & modelStr
Debug.Print "提取的条件:"
Dim key As Variant
For Each key In conditions.Keys
Debug.Print " " & key & " = " & conditions(key)
Next key
Debug.Print ""
' 条件匹配器
Dim matcher As New clsConditionMatcher
' 遍历所有根类别,检查完整性
isComplete = True
Dim categoryCheckResults As Object
Set categoryCheckResults = CreateObject("Scripting.Dictionary")
Dim rootCat As clsCategory
Dim i As Long
For i = 1 To rootCategories.count
Set rootCat = rootCategories(i)
' 检查该类别及其子类别的完整性
Dim catResult As Object
Set catResult = CheckCategoryCompleteness(rootCat, matcher, conditions)
' 合并物料
Dim mat As clsMaterialItem
Dim matCollection As collection
Set matCollection = catResult("Materials")
For Each mat In matCollection
allMaterials.Add mat
Next mat
' 检查完整性
If Not catResult("IsComplete") Then
isComplete = False
Debug.Print "【不完整】类别 """ & rootCat.categoryName & """ 物料不完整"
End If
' 保存类别检查结果(用于调试)
categoryCheckResults(rootCat.categoryName) = catResult("IsComplete")
Next i
' 调试输出总结
Debug.Print ""
Debug.Print "【完整性检查结果】"
Debug.Print "总物料数: " & allMaterials.count
Debug.Print "是否完整: " & IIf(isComplete, "是", "否")
For Each key In categoryCheckResults.Keys
Debug.Print " " & key & ": " & IIf(categoryCheckResults(key), "完整", "不完整")
Next key
Debug.Print String(60, "=")
' 返回结果
result.Add allMaterials, "Materials"
result.Add isComplete, "IsComplete"
Set GetValidMaterialsByModel = result
End Function
' ========================================
' CheckCategoryCompleteness 方法 (私有)
' 功能: 检查单个类别的完整性(递归处理子类别)
' 参数:
' cat - 类别对象
' matcher - 条件匹配器
' conditions - 提取的条件字典
' 返回: Dictionary对象
' "Materials" - Collection,包含该类别匹配的物料
' "IsComplete" - Boolean,该类别是否完整
'
' 完整性判断逻辑:
' 1. 如果类别没有子类别(叶子类别):
' - 必须有且仅有1个物料匹配 → 完整
' - 0个或多于1个物料 → 不完整
'
' 2. 如果类别有子类别:
' a) 先尝试在父类别查找物料
' b) 如果父类别有且仅有1个匹配物料 → 使用父类别,完整
' c) 如果父类别没有匹配物料 → 降级到所有子类别
' - 每个子类别都必须有且仅有1个匹配物料 → 完整
' - 任一子类别不满足 → 不完整
' ========================================
Private Function CheckCategoryCompleteness(cat As clsCategory, _
matcher As clsConditionMatcher, _
conditions As Object) As Object
Dim result As Object
Set result = CreateObject("Scripting.Dictionary")
Dim materials As New collection
Dim isComplete As Boolean
' 首先在当前类别查找匹配的物料
Dim mat As clsMaterialItem
Dim matchCount As Long
matchCount = 0
Dim i As Long
For i = 1 To cat.materials.count
Set mat = cat.materials(i)
If matcher.IsMatch(mat.Condition, conditions) Then
materials.Add mat
matchCount = matchCount + 1
End If
Next i
' 判断完整性
If Not cat.HasSubCategories Then
' ========================================
' 情况1: 叶子类别(无子类别)
' 必须有且仅有1个物料
' ========================================
If matchCount = 1 Then
isComplete = True
Debug.Print "【完整】叶子类别 """ & cat.categoryName & """ 有1个匹配物料"
Else
isComplete = False
If matchCount = 0 Then
Debug.Print "【不完整】叶子类别 """ & cat.categoryName & """ 无匹配物料"
Else
Debug.Print "【不完整】叶子类别 """ & cat.categoryName & """ 有" & matchCount & "个匹配物料(应为1个)"
End If
End If
Else
' ========================================
' 情况2: 有子类别
' 先检查父类别,如果父类别满足则使用父类别
' 否则降级到子类别,每个子类别都必须满足
' ========================================
If matchCount = 1 Then
' 父类别有且仅有1个物料,使用父类别
isComplete = True
Debug.Print "【完整】父类别 """ & cat.categoryName & """ 有1个匹配物料,使用父类别"
ElseIf matchCount = 0 Then
' 父类别无匹配物料,降级到子类别
Debug.Print "【降级】父类别 """ & cat.categoryName & """ 无匹配物料,检查子类别..."
' 清空物料集合,准备收集子类别物料
Set materials = New collection
isComplete = True ' 假设完整,如果任一子类别不完整则设为False
Dim subCat As clsCategory
Dim j As Long
For j = 1 To cat.SubCategories.count
Set subCat = cat.SubCategories(j)
' 递归检查子类别
Dim subResult As Object
Set subResult = CheckCategoryCompleteness(subCat, matcher, conditions)
' 合并子类别物料
Dim subMaterials As collection
Set subMaterials = subResult("Materials")
Dim k As Long
For k = 1 To subMaterials.count
materials.Add subMaterials(k)
Next k
' 检查子类别是否完整
If Not subResult("IsComplete") Then
isComplete = False
End If
Next j
If isComplete Then
Debug.Print "【完整】类别 """ & cat.categoryName & """ 所有子类别都完整"
Else
Debug.Print "【不完整】类别 """ & cat.categoryName & """ 存在不完整的子类别"
End If
Else
' 父类别有多个匹配物料,不完整
isComplete = False
Debug.Print "【不完整】父类别 """ & cat.categoryName & """ 有" & matchCount & "个匹配物料(应为0或1个)"
End If
End If
' 返回结果
Set result("Materials") = materials
result("IsComplete") = isComplete
Set CheckCategoryCompleteness = result
End Function
```
## ClassModules\clsCategory.cls
<a id="classmodules-clscategory-cls"></a>
```
' ========================================
' 类模块: clsCategory
' 用途: 表示物料类别及其层级关系
' ========================================
Option Explicit
Public categoryName As String
Public ParentCategoryName As String
Public materials As collection ' 存储 clsMaterialItem 对象
Public SubCategories As collection ' 存储子类别 clsCategory 对象
Public IsLeafCategory As Boolean ' 是否叶子类别(无子类别)
Private Sub Class_Initialize()
Set materials = New collection
Set SubCategories = New collection
IsLeafCategory = True
End Sub
' 添加物料
Public Sub AddMaterial(mat As clsMaterialItem)
materials.Add mat, mat.code
End Sub
' 添加子类别
Public Sub AddSubCategory(cat As clsCategory)
SubCategories.Add cat, cat.categoryName
IsLeafCategory = False
End Sub
' 获取物料(按代号)
Public Function GetMaterial(code As String) As clsMaterialItem
On Error Resume Next
Set GetMaterial = materials(code)
On Error GoTo 0
End Function
' 检查是否有子类别
Public Function HasSubCategories() As Boolean
HasSubCategories = (SubCategories.Count > 0)
End Function
```
## ClassModules\clsConditionExtractor.cls
<a id="classmodules-clsconditionextractor-cls"></a>
```
' ========================================
' 类模块: clsConditionExtractor
' 用途: 从型号解析器中提取物料选择条件
' ========================================
Option Explicit
' ========================================
' 私有成员变量
' ========================================
Private m_Conditions As Object ' Dictionary: 变量名 -> 条件值
Private m_ExtractionRules As Object ' Dictionary: 提取规则配置
' ========================================
' 类初始化
' ========================================
Private Sub Class_Initialize()
Set m_Conditions = CreateObject("Scripting.Dictionary")
Set m_ExtractionRules = CreateObject("Scripting.Dictionary")
' 初始化提取规则
InitializeRules
End Sub
' ========================================
' InitializeRules 方法 (私有)
' 功能: 初始化条件提取规则
' 说明: 这里配置所有需要提取的条件及其提取方法
' ========================================
Private Sub InitializeRules()
' 规则格式: Dictionary(变量名) = Array(源字段, 提取方法)
' 规则1: 过程连接 (gclj)
' 从 ConnectionCode 中提取,去掉最后一位数字
m_ExtractionRules("gclj") = Array("ConnectionCode", "RemoveLastDigit")
' 规则2: 接液材质 (jycz)
' 从 ConnectionCode 中提取,取最后一位数字
m_ExtractionRules("jycz") = Array("ConnectionCode", "GetLastDigit")
' 规则3: 量程范围 (lcfw)
' 从 RangeCode 中直接提取
m_ExtractionRules("lcfw") = Array("RangeCode", "Direct")
' 未来可以在这里添加更多提取规则...
' 例如:
' m_ExtractionRules("bplx") = Array("DialCode", "Direct") ' 表盘类型
End Sub
' ========================================
' ExtractConditions 方法
' 功能: 从型号解析器中提取所有条件
' 参数: parser - clsModelParser对象
' 返回: Dictionary对象包含所有提取的条件
' ========================================
Public Function ExtractConditions(parser As clsModelParser) As Object
' 清空现有条件
Set m_Conditions = CreateObject("Scripting.Dictionary")
' 验证解析器有效性
If Not parser.IsValid Then
Set ExtractConditions = m_Conditions
Exit Function
End If
' 遍历所有提取规则
Dim varName As Variant
For Each varName In m_ExtractionRules.Keys
Dim ruleInfo As Variant
ruleInfo = m_ExtractionRules(varName)
Dim sourceField As String
Dim extractMethod As String
sourceField = ruleInfo(0)
extractMethod = ruleInfo(1)
' 提取条件值
Dim conditionValue As String
conditionValue = ExtractValue(parser, sourceField, extractMethod)
' 添加到条件字典
If conditionValue <> "" Then
m_Conditions(CStr(varName)) = conditionValue
End If
Next varName
Set ExtractConditions = m_Conditions
End Function
' ========================================
' ExtractValue 方法 (私有)
' 功能: 根据规则从解析器中提取单个值
' 参数:
' parser - clsModelParser对象
' sourceField - 源字段名称
' extractMethod - 提取方法名称
' 返回: 提取的条件值
' ========================================
Private Function ExtractValue(parser As clsModelParser, _
sourceField As String, _
extractMethod As String) As String
Dim sourceValue As String
' 获取源字段值
Select Case sourceField
Case "ConnectionCode"
sourceValue = parser.ConnectionCode
Case "RangeCode"
sourceValue = parser.RangeCode
Case "ModelType"
sourceValue = parser.ModelType
Case "Diameter"
sourceValue = parser.Diameter
Case "InstallForm"
sourceValue = parser.InstallForm
Case "ShellForm"
sourceValue = parser.ShellForm
Case "Characteristics"
sourceValue = parser.Characteristics
Case Else
sourceValue = ""
End Select
' 应用提取方法
Select Case extractMethod
Case "Direct"
' 直接使用
ExtractValue = sourceValue
Case "RemoveLastDigit"
' 去掉最后一位字符
If Len(sourceValue) > 1 Then
ExtractValue = Left(sourceValue, Len(sourceValue) - 1)
Else
ExtractValue = sourceValue
End If
Case "GetLastDigit"
' 取最后一位字符
If Len(sourceValue) > 0 Then
ExtractValue = Right(sourceValue, 1)
Else
ExtractValue = ""
End If
Case "GetFirstChar"
' 取第一个字符
If Len(sourceValue) > 0 Then
ExtractValue = Left(sourceValue, 1)
Else
ExtractValue = ""
End If
Case Else
' 未知方法,返回空
ExtractValue = ""
End Select
End Function
' ========================================
' GetConditionValue 方法
' 功能: 获取单个条件值
' 参数: varName - 变量名
' 返回: 条件值,如果不存在返回空字符串
' ========================================
Public Function GetConditionValue(varName As String) As String
If m_Conditions.Exists(varName) Then
GetConditionValue = m_Conditions(varName)
Else
GetConditionValue = ""
End If
End Function
' ========================================
' AddCondition 方法
' 功能: 手动添加条件 (用于特殊情况)
' 参数:
' varName - 变量名
' value - 条件值
' ========================================
Public Sub AddCondition(varName As String, value As String)
m_Conditions(varName) = value
End Sub
' ========================================
' GetConditions 属性
' 功能: 获取所有条件的Dictionary对象
' ========================================
Public Property Get conditions() As Object
Set conditions = m_Conditions
End Property
' ========================================
' ToString 方法
' 功能: 返回条件的字符串表示 (用于调试)
' ========================================
Public Function ToString() As String
Dim result As String
result = "【提取的条件】" & vbCrLf
If m_Conditions.Count = 0 Then
result = result & " (无条件)" & vbCrLf
Else
Dim key As Variant
For Each key In m_Conditions.Keys
result = result & " " & key & " = " & m_Conditions(key) & vbCrLf
Next key
End If
ToString = result
End Function
' ========================================
' AddExtractionRule 方法
' 功能: 动态添加新的提取规则 (用于扩展)
' 参数:
' varName - 变量名
' sourceField - 源字段名称
' extractMethod - 提取方法名称
' 示例: extractor.AddExtractionRule "bplx", "DialCode", "Direct"
' ========================================
Public Sub AddExtractionRule(varName As String, _
sourceField As String, _
extractMethod As String)
m_ExtractionRules(varName) = Array(sourceField, extractMethod)
End Sub
' ========================================
' GetExtractionRules 方法
' 功能: 获取当前所有提取规则 (用于调试)
' 返回: Dictionary对象
' ========================================
Public Function GetExtractionRules() As Object
Set GetExtractionRules = m_ExtractionRules
End Function
```
## ClassModules\clsConditionMatcher.cls
<a id="classmodules-clsconditionmatcher-cls"></a>
```
' ========================================
' 类模块: clsConditionMatcher
' 用途: 解析物料的选择条件表达式,并判断是否匹配
' 支持: AND, OR, NOT(!=), 括号优先级
' ========================================
Option Explicit
' ========================================
' IsMatch 方法
' 功能: 判断条件表达式是否匹配
' 参数:
' conditionExpr - 条件表达式字符串
' conditions - Dictionary对象包含变量名->值的映射
' 返回: Boolean - 是否匹配
' 示例:
' IsMatch("lcfw=M16 AND gclj=M20", conditions) -> True/False
' ========================================
Public Function IsMatch(conditionExpr As String, conditions As Object) As Boolean
On Error GoTo ErrorHandler
' 空条件表示无条件,始终匹配
If Trim(conditionExpr) = "" Then
IsMatch = True
Exit Function
End If
' 解析并计算表达式
IsMatch = EvaluateExpression(Trim(conditionExpr), conditions)
Exit Function
ErrorHandler:
' 出错时返回False保守处理
Debug.Print "条件匹配出错: " & conditionExpr & " - " & Err.description
IsMatch = False
End Function
' ========================================
' EvaluateExpression 方法 (私有)
' 功能: 递归计算逻辑表达式
' 优先级: 括号 > NOT(!=) > AND > OR
' ========================================
Private Function EvaluateExpression(expr As String, conditions As Object) As Boolean
expr = Trim(expr)
' 处理括号 (最高优先级)
If InStr(expr, "(") > 0 Then
EvaluateExpression = EvaluateWithParentheses(expr, conditions)
Exit Function
End If
' 处理 OR (最低优先级)
If InStr(expr, " OR ") > 0 Then
EvaluateExpression = EvaluateOR(expr, conditions)
Exit Function
End If
' 处理 AND (中等优先级)
If InStr(expr, " AND ") > 0 Then
EvaluateExpression = EvaluateAND(expr, conditions)
Exit Function
End If
' 处理单个条件 (最高优先级)
EvaluateExpression = EvaluateSimpleCondition(expr, conditions)
End Function
' ========================================
' EvaluateWithParentheses 方法 (私有)
' 功能: 处理包含括号的表达式
' 策略: 找到最内层括号,递归计算,然后替换为结果
' ========================================
Private Function EvaluateWithParentheses(expr As String, conditions As Object) As Boolean
Dim pos As Long, level As Long, startPos As Long
Dim i As Long
Dim innerExpr As String
Dim innerResult As Boolean
Dim newExpr As String
' 查找最内层的括号对
startPos = 0
level = 0
For i = 1 To Len(expr)
If Mid(expr, i, 1) = "(" Then
If level = 0 Then startPos = i
level = level + 1
ElseIf Mid(expr, i, 1) = ")" Then
level = level - 1
If level = 0 And startPos > 0 Then
' 找到一对括号
innerExpr = Mid(expr, startPos + 1, i - startPos - 1)
innerResult = EvaluateExpression(innerExpr, conditions)
' 替换括号部分为结果
newExpr = Left(expr, startPos - 1) & _
IIf(innerResult, "TRUE", "FALSE") & _
Mid(expr, i + 1)
' 递归处理剩余部分
EvaluateWithParentheses = EvaluateExpression(newExpr, conditions)
Exit Function
End If
End If
Next i
' 如果没有找到有效括号,直接计算
EvaluateWithParentheses = EvaluateExpression(expr, conditions)
End Function
' ========================================
' EvaluateOR 方法 (私有)
' 功能: 处理 OR 逻辑运算
' 规则: 任一为真则为真
' ========================================
Private Function EvaluateOR(expr As String, conditions As Object) As Boolean
Dim parts() As String
Dim part As Variant
' 按 OR 分割
parts = Split(expr, " OR ")
' 任一部分为真则返回真
For Each part In parts
If EvaluateExpression(Trim(CStr(part)), conditions) Then
EvaluateOR = True
Exit Function
End If
Next part
EvaluateOR = False
End Function
' ========================================
' EvaluateAND 方法 (私有)
' 功能: 处理 AND 逻辑运算
' 规则: 全部为真才为真
' ========================================
Private Function EvaluateAND(expr As String, conditions As Object) As Boolean
Dim parts() As String
Dim part As Variant
' 按 AND 分割
parts = Split(expr, " AND ")
' 全部部分为真才返回真
For Each part In parts
If Not EvaluateExpression(Trim(CStr(part)), conditions) Then
EvaluateAND = False
Exit Function
End If
Next part
EvaluateAND = True
End Function
' ========================================
' EvaluateSimpleCondition 方法 (私有)
' 功能: 计算单个条件表达式
' 支持: = (等于), != (不等于)
' 格式: varName=value 或 varName!=value
' ========================================
Private Function EvaluateSimpleCondition(cond As String, conditions As Object) As Boolean
cond = Trim(cond)
' 处理特殊值 TRUE/FALSE (括号计算的结果)
If UCase(cond) = "TRUE" Then
EvaluateSimpleCondition = True
Exit Function
ElseIf UCase(cond) = "FALSE" Then
EvaluateSimpleCondition = False
Exit Function
End If
Dim varName As String
Dim expectedValue As String
Dim actualValue As String
Dim isNotEqual As Boolean
' 判断是 != 还是 =
If InStr(cond, "!=") > 0 Then
isNotEqual = True
Dim parts1() As String
parts1 = Split(cond, "!=")
If UBound(parts1) < 1 Then
EvaluateSimpleCondition = False
Exit Function
End If
varName = Trim(parts1(0))
expectedValue = Trim(parts1(1))
ElseIf InStr(cond, "=") > 0 Then
isNotEqual = False
Dim parts2() As String
parts2 = Split(cond, "=")
If UBound(parts2) < 1 Then
EvaluateSimpleCondition = False
Exit Function
End If
varName = Trim(parts2(0))
expectedValue = Trim(parts2(1))
Else
' 无效的条件格式
EvaluateSimpleCondition = False
Exit Function
End If
' 获取实际值
If conditions.Exists(varName) Then
actualValue = Trim(CStr(conditions(varName)))
Else
actualValue = ""
End If
' 比较值 (不区分大小写)
Dim isEqual As Boolean
isEqual = (UCase(actualValue) = UCase(expectedValue))
' 返回结果
If isNotEqual Then
EvaluateSimpleCondition = Not isEqual
Else
EvaluateSimpleCondition = isEqual
End If
End Function
' ========================================
' TestExpression 方法
' 功能: 测试表达式是否有效 (用于调试)
' 参数: expr - 表达式字符串
' 返回: String - "有效" 或 错误信息
' ========================================
Public Function TestExpression(expr As String) As String
On Error GoTo ErrorHandler
' 创建测试条件
Dim testConditions As Object
Set testConditions = CreateObject("Scripting.Dictionary")
testConditions("gclj") = "M20"
testConditions("jycz") = "3"
testConditions("lcfw") = "M16"
' 尝试计算
Dim result As Boolean
result = IsMatch(expr, testConditions)
TestExpression = "有效 (结果: " & IIf(result, "True", "False") & ")"
Exit Function
ErrorHandler:
TestExpression = "无效: " & Err.description
End Function
```
## ClassModules\clsMaterialItem.cls
<a id="classmodules-clsmaterialitem-cls"></a>
```
' ========================================
' 类模块: clsMaterialItem
' 用途: 表示单个物料项
' ========================================
Option Explicit
Public code As String ' 代号
Public Name As String ' 名称
Public Quantity As Double ' 数量
Public Condition As String ' 选择条件
Public Category As String ' 类别
Public ParentCategory As String ' 上层类别
```
## ClassModules\clsModelParser.cls
<a id="classmodules-clsmodelparser-cls"></a>
```
' ========================================
' 类模块: clsModelParser
' 用途: 解析产品型号,提取各部分代码
' 示例: YTHN-100.A0.532.M203.M16.Y3|BP-095.2312.M16.PA3
' ========================================
Option Explicit
' ========================================
' 公共属性
' ========================================
Public RawModel As String ' 原始完整型号
Public HeaderModel As String ' 表头型号部分
Public DialModel As String ' 表盘型号部分
Public AccessoryModel As String ' 附件型号部分
Public FlangeModel As String ' 法兰隔膜型号部分
' 表头各部分
Public ModelType As String ' 型号 (如 YTHN)
Public Diameter As String ' 公称外径 (如 100)
Public InstallForm As String ' 安装形式 (如 A0)
Public ShellForm As String ' 壳体形式 (如 532)
Public ConnectionCode As String ' 过程连接&材质代码 (如 M203)
Public RangeCode As String ' 量程范围代码 (如 M16)
Public Characteristics As String ' 仪表特性 (如 Y3)
' ========================================
' 私有变量
' ========================================
Private m_IsValid As Boolean ' 解析是否成功
Private m_ErrorMessage As String ' 错误信息
' ========================================
' ParseModel 方法
' 功能: 解析产品型号字符串
' 参数: modelStr - 完整的产品型号字符串
' 返回: Boolean - 解析是否成功
' 示例: parser.ParseModel("YTHN-100.A0.532.M203.M16.Y3|BP-095.2312.M16.PA3")
' ========================================
Public Function ParseModel(modelStr As String) As Boolean
On Error GoTo ErrorHandler
' 初始化
m_IsValid = False
m_ErrorMessage = ""
RawModel = Trim(modelStr)
' 验证输入
If RawModel = "" Then
m_ErrorMessage = "型号字符串为空"
ParseModel = False
Exit Function
End If
' 第一步: 按 | 分割各部分
Dim parts() As String
parts = Split(RawModel, "|")
If UBound(parts) >= 0 Then HeaderModel = Trim(parts(0))
If UBound(parts) >= 1 Then DialModel = Trim(parts(1))
If UBound(parts) >= 2 Then AccessoryModel = Trim(parts(2))
If UBound(parts) >= 3 Then FlangeModel = Trim(parts(3))
' 第二步: 解析表头部分 (必须存在)
If HeaderModel = "" Then
m_ErrorMessage = "表头型号为空"
ParseModel = False
Exit Function
End If
' 解析表头
If Not ParseHeader(HeaderModel) Then
ParseModel = False
Exit Function
End If
m_IsValid = True
ParseModel = True
Exit Function
ErrorHandler:
m_ErrorMessage = "解析出错: " & Err.description
m_IsValid = False
ParseModel = False
End Function
' ========================================
' ParseHeader 方法 (私有)
' 功能: 解析表头型号部分
' 格式: [型号]-[公称外径].[安装形式].[壳体形式].[过程连接&材质].[量程范围].[仪表特性]
' 示例: YTHN-100.A0.532.M203.M16.Y3
' ========================================
Private Function ParseHeader(headerStr As String) As Boolean
On Error GoTo ErrorHandler
' 按 - 分割型号和参数部分
Dim mainParts() As String
mainParts = Split(headerStr, "-")
If UBound(mainParts) < 1 Then
m_ErrorMessage = "表头格式错误: 缺少 - 分隔符"
ParseHeader = False
Exit Function
End If
' 提取型号
ModelType = Trim(mainParts(0))
' 按 . 分割参数部分
Dim params() As String
params = Split(mainParts(1), ".")
' 验证参数数量 (至少应该有5个部分)
If UBound(params) < 4 Then
m_ErrorMessage = "表头参数不足: 需要至少5个参数段"
ParseHeader = False
Exit Function
End If
' 提取各参数
Diameter = Trim(params(0)) ' 公称外径
InstallForm = Trim(params(1)) ' 安装形式
ShellForm = Trim(params(2)) ' 壳体形式
ConnectionCode = Trim(params(3)) ' 过程连接&材质
RangeCode = Trim(params(4)) ' 量程范围
' 仪表特性 (可选)
If UBound(params) >= 5 Then
Characteristics = Trim(params(5))
Else
Characteristics = ""
End If
ParseHeader = True
Exit Function
ErrorHandler:
m_ErrorMessage = "解析表头出错: " & Err.description
ParseHeader = False
End Function
' ========================================
' GetThreadCode 方法
' 功能: 从过程连接代码中提取螺纹代码
' 规则: 去掉最后一位数字
' 示例: M203 -> M20
' ========================================
Public Function GetThreadCode() As String
If ConnectionCode = "" Then
GetThreadCode = ""
Exit Function
End If
' 去掉最后一位字符 (假设最后一位是材质代码)
If Len(ConnectionCode) > 1 Then
GetThreadCode = Left(ConnectionCode, Len(ConnectionCode) - 1)
Else
GetThreadCode = ConnectionCode
End If
End Function
' ========================================
' GetMaterialCode 方法
' 功能: 从过程连接代码中提取材质代码
' 规则: 取最后一位数字
' 示例: M203 -> 3
' ========================================
Public Function GetMaterialCode() As String
If ConnectionCode = "" Then
GetMaterialCode = ""
Exit Function
End If
' 取最后一位字符
GetMaterialCode = Right(ConnectionCode, 1)
End Function
' ========================================
' GetRangeCode 方法
' 功能: 获取量程代码
' 规则: 直接返回
' 示例: M16 -> M16
' ========================================
Public Function GetRangeCode() As String
GetRangeCode = RangeCode
End Function
' ========================================
' IsValid 属性
' 功能: 返回解析是否成功
' ========================================
Public Property Get IsValid() As Boolean
IsValid = m_IsValid
End Property
' ========================================
' ErrorMessage 属性
' 功能: 返回错误信息
' ========================================
Public Property Get ErrorMessage() As String
ErrorMessage = m_ErrorMessage
End Property
' ========================================
' ToString 方法
' 功能: 返回解析结果的字符串表示 (用于调试)
' ========================================
Public Function ToString() As String
Dim result As String
result = "【型号解析结果】" & vbCrLf
result = result & "原始型号: " & RawModel & vbCrLf
result = result & "表头型号: " & HeaderModel & vbCrLf
result = result & "表盘型号: " & DialModel & vbCrLf
result = result & vbCrLf
result = result & "【表头各部分】" & vbCrLf
result = result & " 型号: " & ModelType & vbCrLf
result = result & " 公称外径: " & Diameter & vbCrLf
result = result & " 安装形式: " & InstallForm & vbCrLf
result = result & " 壳体形式: " & ShellForm & vbCrLf
result = result & " 过程连接&材质: " & ConnectionCode & vbCrLf
result = result & " 量程范围: " & RangeCode & vbCrLf
result = result & " 仪表特性: " & Characteristics & vbCrLf
result = result & vbCrLf
result = result & "【提取代码】" & vbCrLf
result = result & " 螺纹代码: " & GetThreadCode() & vbCrLf
result = result & " 材质代码: " & GetMaterialCode() & vbCrLf
result = result & " 量程代码: " & GetRangeCode() & vbCrLf
ToString = result
End Function
```
## Modules\modBOMProcessor.bas
<a id="modules-modbomprocessor-bas"></a>
```
' ========================================
' 模块: modBOMTest
' 用途: 测试和使用BOM数据结构
' ========================================
Option Explicit
Sub TestBOMStructure()
' 初始化BOM管理器
Dim bomMgr As New clsBOMManager
' 加载数据(假设工作表名称)
Dim wsConfig As Worksheet
Dim wsPlatform As Worksheet
Set wsConfig = ThisWorkbook.Worksheets("领料配置")
Set wsPlatform = ThisWorkbook.Worksheets("平台配置清单")
' 加载数据
bomMgr.LoadData wsConfig, wsPlatform
' 打印类别树结构到新工作表
Dim wsOutput As Worksheet
On Error Resume Next
Application.DisplayAlerts = False
ThisWorkbook.Worksheets("BOM结构").Delete
Application.DisplayAlerts = True
On Error GoTo 0
Set wsOutput = ThisWorkbook.Worksheets.Add
wsOutput.Name = "BOM结构"
'bomMgr.PrintCategoryTree wsOutput
Dim cats As collection
Dim i As Integer
Set cats = bomMgr.GetRootCategories()
For i = 1 To cats.Count
Debug.Print cats(i).categoryName
Next i
MsgBox "BOM数据结构加载完成!" & vbCrLf & _
"请查看 'BOM结构' 工作表", vbInformation
End Sub
' 示例: 获取特定类别的物料
Sub GetCategoryMaterials()
Dim bomMgr As New clsBOMManager
Dim wsConfig As Worksheet, wsPlatform As Worksheet
Set wsConfig = ThisWorkbook.Worksheets("领料配置")
Set wsPlatform = ThisWorkbook.Worksheets("平台配置清单")
bomMgr.LoadData wsConfig, wsPlatform
' 获取"部件"类别的物料(使用父类别)
Dim materials As collection
Set materials = bomMgr.GetMaterialsForPicking("部件", True)
Debug.Print "部件类别物料数量(父类别): " & materials.Count
' 获取"部件"类别的物料(使用子类别)
Set materials = bomMgr.GetMaterialsForPicking("部件", False)
Debug.Print "部件类别物料数量(子类别展开): " & materials.Count
' 遍历物料
Dim mat As clsMaterialItem
For Each mat In materials
Debug.Print mat.code & " - " & mat.Name & _
" | 数量:" & mat.Quantity & _
" | 条件:" & mat.Condition
Next mat
End Sub
' 示例: 根据产品型号生成领料清单
Sub GeneratePickingList()
Dim bomMgr As New clsBOMManager
Dim wsConfig As Worksheet, wsPlatform As Worksheet
Set wsConfig = ThisWorkbook.Worksheets("领料配置")
Set wsPlatform = ThisWorkbook.Worksheets("平台配置清单")
bomMgr.LoadData wsConfig, wsPlatform
' 创建领料清单工作表
Dim wsPickList As Worksheet
On Error Resume Next
Application.DisplayAlerts = False
ThisWorkbook.Worksheets("领料清单").Delete
Application.DisplayAlerts = True
On Error GoTo 0
Set wsPickList = ThisWorkbook.Worksheets.Add
wsPickList.Name = "领料清单"
' 写入表头
Dim row As Long
row = 1
wsPickList.Cells(row, 1).value = "代号"
wsPickList.Cells(row, 2).value = "名称"
wsPickList.Cells(row, 3).value = "类别"
wsPickList.Cells(row, 4).value = "数量"
wsPickList.Cells(row, 5).value = "选择条件"
wsPickList.Cells(row, 6).value = "领料方式"
row = row + 1
' 遍历所有根类别
Dim rootCats As collection
Set rootCats = bomMgr.GetRootCategories
Dim cat As clsCategory
Dim materials As collection
Dim mat As clsMaterialItem
Dim i As Long, j As Long
For i = 1 To rootCats.Count
Set cat = rootCats(i)
' 默认使用父类别物料
Set materials = bomMgr.GetMaterialsForPicking(cat.categoryName, True)
For j = 1 To materials.Count
Set mat = materials(j)
wsPickList.Cells(row, 1).value = mat.code
wsPickList.Cells(row, 2).value = mat.Name
wsPickList.Cells(row, 3).value = mat.Category
wsPickList.Cells(row, 4).value = mat.Quantity
wsPickList.Cells(row, 5).value = mat.Condition
wsPickList.Cells(row, 6).value = "父类别"
row = row + 1
Next j
Next i
' 格式化表格
wsPickList.Range("A1:F1").Font.Bold = True
wsPickList.Columns("A:F").AutoFit
MsgBox "领料清单生成完成!", vbInformation
End Sub
' 示例: 查询特定物料信息
Sub QueryMaterialInfo()
Dim bomMgr As New clsBOMManager
Dim wsConfig As Worksheet, wsPlatform As Worksheet
Set wsConfig = ThisWorkbook.Worksheets("领料配置")
Set wsPlatform = ThisWorkbook.Worksheets("平台配置清单")
bomMgr.LoadData wsConfig, wsPlatform
' 查询特定类别
Dim cat As clsCategory
Set cat = bomMgr.GetCategory("部件")
If Not cat Is Nothing Then
Debug.Print "类别: " & cat.categoryName
Debug.Print "父类别: " & cat.ParentCategoryName
Debug.Print "物料数: " & cat.materials.Count
Debug.Print "子类别数: " & cat.SubCategories.Count
Debug.Print "是否叶子类别: " & cat.IsLeafCategory
' 列出所有物料
Dim mat As clsMaterialItem
Dim i As Long
For i = 1 To cat.materials.Count
Set mat = cat.materials(i)
Debug.Print " - " & mat.code & ": " & mat.Name
Next i
' 列出所有子类别
Dim subCat As clsCategory
Dim j As Long
For j = 1 To cat.SubCategories.Count
Set subCat = cat.SubCategories(j)
Debug.Print " 子类别: " & subCat.categoryName & _
" (物料数:" & subCat.materials.Count & ")"
Next j
End If
End Sub
```
## Modules\modModelParserExamples.bas
<a id="modules-modmodelparserexamples-bas"></a>
```
' ========================================
' 模块: modModelParserExamples
' 用途: 型号解析与物料匹配的实际应用示例
' ========================================
Option Explicit
' ========================================
' 示例1: 根据型号生成完整的领料清单
' ========================================
Sub Example1_GeneratePickingListByModel()
Dim bomMgr As New clsBOMManager
Dim wsConfig As Worksheet
Dim wsPlatform As Worksheet
' 加载配置
Set wsConfig = ThisWorkbook.Worksheets("领料配置")
Set wsPlatform = ThisWorkbook.Worksheets("平台配置清单")
bomMgr.LoadData wsConfig, wsPlatform
' 产品型号
Dim modelStr As String
modelStr = "YTHN-100.A0.532.M203.M16.Y3|BP-095.2312.M16.PA3"
' 创建领料清单工作表
Dim wsPickList As Worksheet
On Error Resume Next
Application.DisplayAlerts = False
ThisWorkbook.Worksheets("型号领料清单").Delete
Application.DisplayAlerts = True
On Error GoTo 0
Set wsPickList = ThisWorkbook.Worksheets.Add
wsPickList.Name = "型号领料清单"
' 写入表头
Dim row As Long
row = 1
wsPickList.Cells(row, 1).value = "产品型号"
wsPickList.Cells(row, 2).value = modelStr
row = row + 1
' 提取条件并显示
Dim conditions As Object
Set conditions = bomMgr.ParseModelAndExtractConditions(modelStr)
wsPickList.Cells(row, 1).value = "提取条件"
Dim condStr As String
Dim key As Variant
For Each key In conditions.Keys
condStr = condStr & key & "=" & conditions(key) & "; "
Next key
wsPickList.Cells(row, 2).value = condStr
row = row + 2
' 表头
wsPickList.Cells(row, 1).value = "类别"
wsPickList.Cells(row, 2).value = "代号"
wsPickList.Cells(row, 3).value = "名称"
wsPickList.Cells(row, 4).value = "数量"
wsPickList.Cells(row, 5).value = "选择条件"
wsPickList.Cells(row, 6).value = "匹配状态"
wsPickList.Range("A" & row & ":F" & row).Font.Bold = True
row = row + 1
' 遍历所有根类别
Dim rootCats As collection
Set rootCats = bomMgr.GetRootCategories
Dim cat As clsCategory
Dim materials As collection
Dim mat As clsMaterialItem
Dim i As Long
For i = 1 To rootCats.Count
Set cat = rootCats(i)
' 获取该类别符合条件的物料
Set materials = bomMgr.GetMaterialsByModel(modelStr, cat.categoryName)
' 写入物料
Dim j As Long
For j = 1 To materials.Count
Set mat = materials(j)
' 使用物料自己的Category属性而不是外层循环的类别名称
' 这样当GetMaterialsByModel降级到子类别查找时能正确显示子类别名称
wsPickList.Cells(row, 1).value = mat.Category
wsPickList.Cells(row, 2).value = mat.code
wsPickList.Cells(row, 3).value = mat.Name
wsPickList.Cells(row, 4).value = mat.Quantity
wsPickList.Cells(row, 5).value = IIf(mat.Condition = "", "(无条件)", mat.Condition)
wsPickList.Cells(row, 6).value = "✓"
row = row + 1
Next j
Next i
' 格式化
wsPickList.Columns("A:F").AutoFit
MsgBox "领料清单生成完成!" & vbCrLf & _
"请查看工作表: 型号领料清单", vbInformation
End Sub
' ========================================
' 示例2: 批量处理多个型号
' ========================================
Sub Example2_BatchProcessModels()
Dim bomMgr As New clsBOMManager
Dim wsConfig As Worksheet
Dim wsPlatform As Worksheet
' 加载配置
Set wsConfig = ThisWorkbook.Worksheets("领料配置")
Set wsPlatform = ThisWorkbook.Worksheets("平台配置清单")
bomMgr.LoadData wsConfig, wsPlatform
' 型号列表
Dim models() As String
models = Split("YTHN-100.A0.532.M203.M16.Y3|BP-095.2312.M16.PA3," & _
"YTHN-100.A0.532.M203.M02.Y3|BP-095.2312.M02.PA3," & _
"YTHN-100.A0.532.M203.M12.Y3|BP-095.2312.M12.PA3", ",")
' 创建汇总表
Dim wsReport As Worksheet
On Error Resume Next
Application.DisplayAlerts = False
ThisWorkbook.Worksheets("批量型号汇总").Delete
Application.DisplayAlerts = True
On Error GoTo 0
Set wsReport = ThisWorkbook.Worksheets.Add
wsReport.Name = "批量型号汇总"
' 表头
Dim row As Long
row = 1
wsReport.Cells(row, 1).value = "型号"
wsReport.Cells(row, 2).value = "提取条件"
wsReport.Cells(row, 3).value = "匹配物料数"
wsReport.Cells(row, 4).value = "部件物料"
wsReport.Range("A1:D1").Font.Bold = True
row = row + 1
' 处理每个型号
Dim modelStr As String
Dim i As Long
For i = LBound(models) To UBound(models)
modelStr = Trim(models(i))
If modelStr <> "" Then
' 提取条件
Dim conditions As Object
Set conditions = bomMgr.ParseModelAndExtractConditions(modelStr)
Dim condStr As String
condStr = ""
Dim key As Variant
For Each key In conditions.Keys
condStr = condStr & key & "=" & conditions(key) & "; "
Next key
' 获取物料
Dim materials As collection
Set materials = bomMgr.GetMaterialsByModel(modelStr, "部件")
' 写入结果
wsReport.Cells(row, 1).value = modelStr
wsReport.Cells(row, 2).value = condStr
wsReport.Cells(row, 3).value = materials.Count
' 列出部件物料
Dim matList As String
matList = ""
Dim mat As clsMaterialItem
For Each mat In materials
matList = matList & mat.code & "(" & mat.Name & "); "
Next mat
wsReport.Cells(row, 4).value = matList
row = row + 1
End If
Next i
' 格式化
wsReport.Columns("A:D").AutoFit
MsgBox "批量处理完成!", vbInformation
End Sub
' ========================================
' 示例3: 查询并显示某个型号的详细信息
' ========================================
Sub Example3_ShowModelDetails()
' 弹出输入框
Dim modelStr As String
modelStr = InputBox("请输入产品型号:", "型号查询", _
"YTHN-100.A0.532.M203.M16.Y3")
If modelStr = "" Then Exit Sub
' 解析型号
Dim parser As New clsModelParser
If Not parser.ParseModel(modelStr) Then
MsgBox "型号解析失败: " & parser.ErrorMessage, vbCritical
Exit Sub
End If
' 提取条件
Dim extractor As New clsConditionExtractor
Dim conditions As Object
Set conditions = extractor.ExtractConditions(parser)
' 显示详细信息
Dim msg As String
msg = "【型号解析结果】" & vbCrLf & vbCrLf
msg = msg & "原始型号: " & parser.RawModel & vbCrLf
msg = msg & "表头型号: " & parser.HeaderModel & vbCrLf
msg = msg & "表盘型号: " & parser.DialModel & vbCrLf & vbCrLf
msg = msg & "【表头各部分】" & vbCrLf
msg = msg & "型号: " & parser.ModelType & vbCrLf
msg = msg & "公称外径: " & parser.Diameter & vbCrLf
msg = msg & "安装形式: " & parser.InstallForm & vbCrLf
msg = msg & "壳体形式: " & parser.ShellForm & vbCrLf
msg = msg & "过程连接&材质: " & parser.ConnectionCode & vbCrLf
msg = msg & "量程范围: " & parser.RangeCode & vbCrLf
msg = msg & "仪表特性: " & parser.Characteristics & vbCrLf & vbCrLf
msg = msg & "【提取的物料选择条件】" & vbCrLf
Dim key As Variant
For Each key In conditions.Keys
msg = msg & key & " = " & conditions(key) & vbCrLf
Next key
MsgBox msg, vbInformation, "型号详细信息"
End Sub
' ========================================
' 示例4: 对比两个型号的差异
' ========================================
Sub Example4_CompareModels()
Dim model1 As String, model2 As String
model1 = InputBox("请输入第一个型号:", "型号对比", _
"YTHN-100.A0.532.M203.M16.Y3")
If model1 = "" Then Exit Sub
model2 = InputBox("请输入第二个型号:", "型号对比", _
"YTHN-100.A0.532.M203.M02.Y3")
If model2 = "" Then Exit Sub
' 解析两个型号
Dim parser1 As New clsModelParser
Dim parser2 As New clsModelParser
Dim extractor As New clsConditionExtractor
parser1.ParseModel model1
parser2.ParseModel model2
Dim cond1 As Object, cond2 As Object
Set cond1 = extractor.ExtractConditions(parser1)
Set extractor = New clsConditionExtractor
Set cond2 = extractor.ExtractConditions(parser2)
' 对比
Dim msg As String
msg = "【型号对比】" & vbCrLf & vbCrLf
msg = msg & "型号1: " & model1 & vbCrLf
msg = msg & "型号2: " & model2 & vbCrLf & vbCrLf
msg = msg & "【条件差异】" & vbCrLf
Dim key As Variant
Dim allKeys As Object
Set allKeys = CreateObject("Scripting.Dictionary")
For Each key In cond1.Keys
allKeys(key) = True
Next key
For Each key In cond2.Keys
allKeys(key) = True
Next key
For Each key In allKeys.Keys
Dim val1 As String, val2 As String
val1 = ""
val2 = ""
If cond1.Exists(key) Then val1 = cond1(key)
If cond2.Exists(key) Then val2 = cond2(key)
If val1 <> val2 Then
msg = msg & key & ": " & val1 & " → " & val2 & " ?" & vbCrLf
Else
msg = msg & key & ": " & val1 & " ?" & vbCrLf
End If
Next key
MsgBox msg, vbInformation, "型号对比结果"
End Sub
' ========================================
' 示例5: 验证物料选择条件的有效性
' ========================================
Sub Example5_ValidateMaterialConditions()
Dim bomMgr As New clsBOMManager
Dim wsConfig As Worksheet
Dim wsPlatform As Worksheet
' 加载配置
Set wsConfig = ThisWorkbook.Worksheets("领料配置")
Set wsPlatform = ThisWorkbook.Worksheets("平台配置清单")
bomMgr.LoadData wsConfig, wsPlatform
' 创建验证结果表
Dim wsValidation As Worksheet
On Error Resume Next
Application.DisplayAlerts = False
ThisWorkbook.Worksheets("条件验证结果").Delete
Application.DisplayAlerts = True
On Error GoTo 0
Set wsValidation = ThisWorkbook.Worksheets.Add
wsValidation.Name = "条件验证结果"
' 表头
Dim row As Long
row = 1
wsValidation.Cells(row, 1).value = "代号"
wsValidation.Cells(row, 2).value = "名称"
wsValidation.Cells(row, 3).value = "选择条件"
wsValidation.Cells(row, 4).value = "验证结果"
wsValidation.Range("A1:D1").Font.Bold = True
row = row + 1
' 获取所有根类别
Dim rootCats As collection
Set rootCats = bomMgr.GetRootCategories
Dim cat As clsCategory
Dim materials As collection
Dim mat As clsMaterialItem
Dim matcher As New clsConditionMatcher
Dim i As Long
' 遍历所有物料
For i = 1 To rootCats.Count
Set cat = rootCats(i)
Set materials = New collection
' 收集该类别的所有物料
Dim j As Long
For j = 1 To cat.materials.Count
materials.Add cat.materials(j)
Next j
' 验证每个物料的条件
For Each mat In materials
wsValidation.Cells(row, 1).value = mat.code
wsValidation.Cells(row, 2).value = mat.Name
wsValidation.Cells(row, 3).value = IIf(mat.Condition = "", "(无)", mat.Condition)
If mat.Condition = "" Then
wsValidation.Cells(row, 4).value = "? 无条件"
Else
Dim validResult As String
validResult = matcher.TestExpression(mat.Condition)
wsValidation.Cells(row, 4).value = validResult
End If
row = row + 1
Next mat
Next i
' 格式化
wsValidation.Columns("A:D").AutoFit
MsgBox "条件验证完成!", vbInformation
End Sub
' ========================================
' 示例6: 对比启用/禁用自动降级的效果
' ========================================
Sub Example6_CompareAutoFallback()
Dim bomMgr As New clsBOMManager
Dim wsConfig As Worksheet
Dim wsPlatform As Worksheet
' 加载配置
Set wsConfig = ThisWorkbook.Worksheets("领料配置")
Set wsPlatform = ThisWorkbook.Worksheets("平台配置清单")
bomMgr.LoadData wsConfig, wsPlatform
' 产品型号(使用可能导致类别缺失的型号)
Dim modelStr As String
modelStr = "YTHN-100.A0.532.M203.M17.Y3|BP-095.2312.M16.PA3"
' 创建对比结果表
Dim wsCompare As Worksheet
On Error Resume Next
Application.DisplayAlerts = False
ThisWorkbook.Worksheets("降级模式对比").Delete
Application.DisplayAlerts = True
On Error GoTo 0
Set wsCompare = ThisWorkbook.Worksheets.Add
wsCompare.Name = "降级模式对比"
' 表头
Dim row As Long
row = 1
wsCompare.Cells(row, 1).value = "型号"
wsCompare.Cells(row, 2).value = modelStr
row = row + 2
' 测试1: 启用自动降级
wsCompare.Cells(row, 1).value = "模式1: 启用自动降级 (autoFallback=True)"
wsCompare.Cells(row, 1).Font.Bold = True
row = row + 1
wsCompare.Cells(row, 1).value = "类别"
wsCompare.Cells(row, 2).value = "代号"
wsCompare.Cells(row, 3).value = "名称"
wsCompare.Cells(row, 4).value = "数量"
wsCompare.Cells(row, 5).value = "选择条件"
wsCompare.Range("A" & row & ":E" & row).Font.Bold = True
row = row + 1
Dim materials As collection
Dim mat As clsMaterialItem
Dim i As Long
' 获取启用自动降级的物料
Set materials = bomMgr.GetMaterialsByModel(modelStr, "部件", True)
If materials.Count = 0 Then
wsCompare.Cells(row, 1).value = "(无物料)"
row = row + 1
Else
For i = 1 To materials.Count
Set mat = materials(i)
wsCompare.Cells(row, 1).value = mat.Category
wsCompare.Cells(row, 2).value = mat.code
wsCompare.Cells(row, 3).value = mat.Name
wsCompare.Cells(row, 4).value = mat.Quantity
wsCompare.Cells(row, 5).value = IIf(mat.Condition = "", "(无)", mat.Condition)
row = row + 1
Next i
End If
row = row + 1
' 测试2: 禁用自动降级
wsCompare.Cells(row, 1).value = "模式2: 禁用自动降级 (autoFallback=False)"
wsCompare.Cells(row, 1).Font.Bold = True
row = row + 1
wsCompare.Cells(row, 1).value = "类别"
wsCompare.Cells(row, 2).value = "代号"
wsCompare.Cells(row, 3).value = "名称"
wsCompare.Cells(row, 4).value = "数量"
wsCompare.Cells(row, 5).value = "选择条件"
wsCompare.Range("A" & row & ":E" & row).Font.Bold = True
row = row + 1
' 获取禁用自动降级的物料
Set materials = bomMgr.GetMaterialsByModel(modelStr, "部件", False)
If materials.Count = 0 Then
wsCompare.Cells(row, 1).value = "(无物料 - 因为禁用降级)"
wsCompare.Cells(row, 2).value = "说明"
wsCompare.Cells(row, 3).value = "当父类别无匹配物料时,不会降级到子类别查找"
row = row + 1
Else
For i = 1 To materials.Count
Set mat = materials(i)
wsCompare.Cells(row, 1).value = mat.Category
wsCompare.Cells(row, 2).value = mat.code
wsCompare.Cells(row, 3).value = mat.Name
wsCompare.Cells(row, 4).value = mat.Quantity
wsCompare.Cells(row, 5).value = IIf(mat.Condition = "", "(无)", mat.Condition)
row = row + 1
Next i
End If
' 添加说明
row = row + 1
wsCompare.Cells(row, 1).value = "说明:"
wsCompare.Cells(row, 1).Font.Bold = True
row = row + 1
wsCompare.Cells(row, 1).value = "• autoFallback=True (默认): 当""部件""类别无匹配物料时,自动降级到子类别""接头""、""弹性元件""等查找"
row = row + 1
wsCompare.Cells(row, 1).value = "• autoFallback=False: 仅在""部件""类别查找,即使有子类别也不降级,可能返回空集合"
' 格式化
wsCompare.Columns("A:E").AutoFit
wsCompare.Range("A1").Font.Bold = True
MsgBox "降级模式对比完成!" & vbCrLf & _
"请查看工作表: 降级模式对比" & vbCrLf & vbCrLf & _
"启用降级: " & bomMgr.GetMaterialsByModel(modelStr, "部件", True).Count & " 个物料" & vbCrLf & _
"禁用降级: " & bomMgr.GetMaterialsByModel(modelStr, "部件", False).Count & " 个物料", _
vbInformation
End Sub
' ========================================
' 示例7: 测试GetValidMaterialsByModel方法
' ========================================
Sub Example7_TestGetValidMaterialsByModel()
Dim bomMgr As New clsBOMManager
Dim wsConfig As Worksheet
Dim wsPlatform As Worksheet
' 加载配置
Set wsConfig = ThisWorkbook.Worksheets("领料配置")
Set wsPlatform = ThisWorkbook.Worksheets("平台配置清单")
bomMgr.LoadData wsConfig, wsPlatform
' 创建测试结果工作表
Dim wsTest As Worksheet
On Error Resume Next
Application.DisplayAlerts = False
ThisWorkbook.Worksheets("物料完整性测试").Delete
Application.DisplayAlerts = True
On Error GoTo 0
Set wsTest = ThisWorkbook.Worksheets.Add
wsTest.Name = "物料完整性测试"
' 写入标题
Dim row As Long
row = 1
wsTest.Cells(row, 1).value = "物料完整性测试报告"
wsTest.Cells(row, 1).Font.Bold = True
wsTest.Cells(row, 1).Font.Size = 14
row = row + 2
' 测试多个型号
Dim testModels() As Variant
testModels = Array( _
Array("YTHN-100.A0.532.M203.M16.Y3|BP-095.2312.M16.PA3", "完整型号测试1"), _
Array("YTHN-100.A0.532.M203.M02.Y3|BP-095.2312.M02.PA3", "完整型号测试2"), _
Array("YTHN-100.A0.532.M203.M12.Y3|BP-095.2312.M12.PA3", "完整型号测试3"), _
Array("YTHN-100.A0.532.M203.M17.Y3", "可能不完整的型号") _
)
Dim i As Long
For i = LBound(testModels) To UBound(testModels)
Dim modelStr As String
Dim testName As String
modelStr = testModels(i)(0)
testName = testModels(i)(1)
' 调用GetValidMaterialsByModel
Dim result As collection
Set result = bomMgr.GetValidMaterialsByModel(modelStr)
Dim materials As collection
Dim isComplete As Boolean
Set materials = result("Materials")
isComplete = result("IsComplete")
' 写入测试名称和型号
wsTest.Cells(row, 1).value = "测试 " & (i + 1) & ": " & testName
wsTest.Cells(row, 1).Font.Bold = True
row = row + 1
wsTest.Cells(row, 1).value = "型号:"
wsTest.Cells(row, 2).value = modelStr
row = row + 1
wsTest.Cells(row, 1).value = "完整性:"
wsTest.Cells(row, 2).value = IIf(isComplete, "?? 完整", "?? 不完整")
If isComplete Then
wsTest.Cells(row, 2).Font.Color = RGB(0, 128, 0) ' 绿色
Else
wsTest.Cells(row, 2).Font.Color = RGB(255, 0, 0) ' 红色
End If
wsTest.Cells(row, 2).Font.Bold = True
row = row + 1
wsTest.Cells(row, 1).value = "物料数量:"
wsTest.Cells(row, 2).value = materials.count
row = row + 1
' 写入物料明细表头
wsTest.Cells(row, 1).value = "类别"
wsTest.Cells(row, 2).value = "代号"
wsTest.Cells(row, 3).value = "名称"
wsTest.Cells(row, 4).value = "数量"
wsTest.Cells(row, 5).value = "选择条件"
wsTest.Range(wsTest.Cells(row, 1), wsTest.Cells(row, 5)).Font.Bold = True
wsTest.Range(wsTest.Cells(row, 1), wsTest.Cells(row, 5)).Interior.Color = RGB(200, 200, 200)
row = row + 1
' 写入每个物料
Dim mat As clsMaterialItem
Dim j As Long
For j = 1 To materials.count
Set mat = materials(j)
wsTest.Cells(row, 1).value = mat.Category
wsTest.Cells(row, 2).value = mat.code
wsTest.Cells(row, 3).value = mat.Name
wsTest.Cells(row, 4).value = mat.Quantity
wsTest.Cells(row, 5).value = IIf(mat.Condition = "", "(无条件)", mat.Condition)
row = row + 1
Next j
' 添加分隔行
row = row + 1
wsTest.Cells(row, 1).value = String(80, "-")
row = row + 2
Next i
' 添加统计汇总
wsTest.Cells(row, 1).value = "测试汇总"
wsTest.Cells(row, 1).Font.Bold = True
wsTest.Cells(row, 1).Font.Size = 12
row = row + 1
Dim completeCount As Long
Dim incompleteCount As Long
completeCount = 0
incompleteCount = 0
For i = LBound(testModels) To UBound(testModels)
modelStr = testModels(i)(0)
Set result = bomMgr.GetValidMaterialsByModel(modelStr)
If result("IsComplete") Then
completeCount = completeCount + 1
Else
incompleteCount = incompleteCount + 1
End If
Next i
wsTest.Cells(row, 1).value = "总测试数:"
wsTest.Cells(row, 2).value = UBound(testModels) - LBound(testModels) + 1
row = row + 1
wsTest.Cells(row, 1).value = "完整:"
wsTest.Cells(row, 2).value = completeCount
wsTest.Cells(row, 2).Font.Color = RGB(0, 128, 0)
row = row + 1
wsTest.Cells(row, 1).value = "不完整:"
wsTest.Cells(row, 2).value = incompleteCount
wsTest.Cells(row, 2).Font.Color = RGB(255, 0, 0)
' 格式化
wsTest.Columns("A:E").AutoFit
MsgBox "物料完整性测试完成!" & vbCrLf & _
"完整: " & completeCount & " 个" & vbCrLf & _
"不完整: " & incompleteCount & " 个" & vbCrLf & vbCrLf & _
"请查看工作表: 物料完整性测试", vbInformation
End Sub
```
## Modules\modModelParserTest.bas
<a id="modules-modmodelparsertest-bas"></a>
```
' ========================================
' 模块: modModelParserTest
' 用途: 测试型号解析和物料匹配功能
' ========================================
Option Explicit
' ========================================
' 测试1: 型号解析器基础功能
' ========================================
Sub Test1_ModelParser()
Debug.Print String(80, "=")
Debug.Print "测试1: 型号解析器基础功能"
Debug.Print String(80, "=")
Dim parser As New clsModelParser
Dim modelStr As String
' 测试用例1: 完整型号
modelStr = "YTHN-100.A0.532.M203.M16.Y3|BP-095.2312.M16.PA3"
Debug.Print "【测试用例1】完整型号"
Debug.Print "输入: " & modelStr
If parser.ParseModel(modelStr) Then
Debug.Print parser.ToString()
Debug.Print "? 解析成功"
Else
Debug.Print "? 解析失败: " & parser.ErrorMessage
End If
Debug.Print ""
' 测试用例2: 仅表头
modelStr = "YTHN-100.A0.532.M203.M16.Y3"
Debug.Print "【测试用例2】仅表头"
Debug.Print "输入: " & modelStr
If parser.ParseModel(modelStr) Then
Debug.Print " 螺纹代码: " & parser.GetThreadCode()
Debug.Print " 材质代码: " & parser.GetMaterialCode()
Debug.Print " 量程代码: " & parser.GetRangeCode()
Debug.Print "? 解析成功"
Else
Debug.Print "? 解析失败: " & parser.ErrorMessage
End If
Debug.Print String(80, "=")
Debug.Print ""
End Sub
' ========================================
' 测试2: 条件提取器
' ========================================
Sub Test2_ConditionExtractor()
Debug.Print String(80, "=")
Debug.Print "测试2: 条件提取器"
Debug.Print String(80, "=")
Dim parser As New clsModelParser
Dim extractor As New clsConditionExtractor
Dim modelStr As String
modelStr = "YTHN-100.A0.532.M203.M16.Y3"
Debug.Print "输入型号: " & modelStr
If parser.ParseModel(modelStr) Then
Dim conditions As Object
Set conditions = extractor.ExtractConditions(parser)
Debug.Print extractor.ToString()
' 验证提取结果
Debug.Print "【验证】"
Debug.Print " gclj = " & extractor.GetConditionValue("gclj") & _
IIf(extractor.GetConditionValue("gclj") = "M20", " ?", " ?")
Debug.Print " jycz = " & extractor.GetConditionValue("jycz") & _
IIf(extractor.GetConditionValue("jycz") = "3", " ?", " ?")
Debug.Print " lcfw = " & extractor.GetConditionValue("lcfw") & _
IIf(extractor.GetConditionValue("lcfw") = "M16", " ?", " ?")
Else
Debug.Print "? 型号解析失败"
End If
Debug.Print String(80, "=")
Debug.Print ""
End Sub
' ========================================
' 测试3: 条件匹配器
' ========================================
Sub Test3_ConditionMatcher()
Debug.Print String(80, "=")
Debug.Print "测试3: 条件匹配器"
Debug.Print String(80, "=")
Dim matcher As New clsConditionMatcher
Dim conditions As Object
Set conditions = CreateObject("Scripting.Dictionary")
conditions("gclj") = "M20"
conditions("jycz") = "3"
conditions("lcfw") = "M16"
Debug.Print "【测试条件】"
Debug.Print " gclj = M20"
Debug.Print " jycz = 3"
Debug.Print " lcfw = M16"
Debug.Print ""
' 测试用例
Dim testCases As Variant
testCases = Array( _
Array("", True, "空条件"), _
Array("lcfw=M16", True, "简单等于"), _
Array("lcfw=M17", False, "简单不匹配"), _
Array("lcfw=M16 AND gclj=M20", True, "AND 全真"), _
Array("lcfw=M16 AND gclj=M10", False, "AND 一假"), _
Array("lcfw=M16 OR lcfw=M17", True, "OR 一真"), _
Array("lcfw=M15 OR lcfw=M17", False, "OR 全假"), _
Array("gclj!=M10", True, "不等于 真"), _
Array("gclj!=M20", False, "不等于 假"), _
Array("lcfw=M02 AND gclj!=M20", False, "复合条件1"), _
Array("lcfw=M16 AND gclj!=M10", True, "复合条件2"), _
Array("gclj=M20 AND (lcfw=M16 OR lcfw=M17)", True, "括号优先级1"), _
Array("gclj=M20 AND (lcfw=M15 OR lcfw=M17)", False, "括号优先级2") _
)
Dim i As Long
Dim testCase As Variant
Dim expr As String
Dim expected As Boolean
Dim actual As Boolean
Dim description As String
Dim passCount As Long
Dim failCount As Long
passCount = 0
failCount = 0
Debug.Print "【测试用例】"
For i = LBound(testCases) To UBound(testCases)
testCase = testCases(i)
expr = testCase(0)
expected = testCase(1)
description = testCase(2)
actual = matcher.IsMatch(expr, conditions)
If actual = expected Then
Debug.Print " ? " & description & ": " & IIf(expr = "", "(空)", expr)
passCount = passCount + 1
Else
Debug.Print " ? " & description & ": " & expr
Debug.Print " 预期: " & expected & ", 实际: " & actual
failCount = failCount + 1
End If
Next i
Debug.Print ""
Debug.Print "【统计】"
Debug.Print " 通过: " & passCount
Debug.Print " 失败: " & failCount
Debug.Print String(80, "=")
Debug.Print ""
End Sub
' ========================================
' 测试4: 完整流程 - 根据型号获取物料
' ========================================
Sub Test4_GetMaterialsByModel()
Debug.Print String(80, "=")
Debug.Print "测试4: 根据型号获取物料(完整流程)"
Debug.Print String(80, "=")
' 加载BOM数据
Dim bomMgr As New clsBOMManager
Dim wsConfig As Worksheet
Dim wsPlatform As Worksheet
On Error Resume Next
Set wsConfig = ThisWorkbook.Worksheets("领料配置")
Set wsPlatform = ThisWorkbook.Worksheets("平台配置清单")
On Error GoTo 0
If wsConfig Is Nothing Or wsPlatform Is Nothing Then
Debug.Print "? 错误: 找不到必需的工作表"
Exit Sub
End If
bomMgr.LoadData wsConfig, wsPlatform
Debug.Print "? BOM数据加载完成"
Debug.Print ""
' 测试型号
Dim modelStr As String
modelStr = "YTHN-100.A0.532.M203.M16.Y3|BP-095.2312.M16.PA3"
' 获取"部件"类别的符合条件的物料
Dim materials As collection
Set materials = bomMgr.GetMaterialsByModel(modelStr, "部件")
Debug.Print ""
Debug.Print "【结果验证】"
If materials.Count = 1 Then
Dim mat As clsMaterialItem
Set mat = materials(1)
If mat.code = "01011019018" And mat.Name = "高压接头部件" Then
Debug.Print "? 测试通过!成功匹配到正确的物料"
Debug.Print " 代号: " & mat.code
Debug.Print " 名称: " & mat.Name
Debug.Print " 条件: " & mat.Condition
Else
Debug.Print "? 匹配到的物料不正确"
End If
Else
Debug.Print "? 匹配数量不正确预期1个实际" & materials.Count & "个"
End If
Debug.Print String(80, "=")
Debug.Print ""
End Sub
' ========================================
' 测试5: 获取所有类别的符合条件的物料
' ========================================
Sub Test5_GetAllMaterialsByModel()
Debug.Print String(80, "=")
Debug.Print "测试5: 获取所有类别的符合条件的物料"
Debug.Print String(80, "=")
' 加载BOM数据
Dim bomMgr As New clsBOMManager
Dim wsConfig As Worksheet
Dim wsPlatform As Worksheet
On Error Resume Next
Set wsConfig = ThisWorkbook.Worksheets("领料配置")
Set wsPlatform = ThisWorkbook.Worksheets("平台配置清单")
On Error GoTo 0
If wsConfig Is Nothing Or wsPlatform Is Nothing Then
Debug.Print "? 错误: 找不到必需的工作表"
Exit Sub
End If
bomMgr.LoadData wsConfig, wsPlatform
' 测试型号
Dim modelStr As String
modelStr = "YTHN-100.A0.532.M203.M16.Y3"
' 获取所有类别的符合条件的物料
Dim materials As collection
Set materials = bomMgr.GetMaterialsByModel(modelStr)
Debug.Print ""
Debug.Print "【匹配结果汇总】"
Debug.Print " 共匹配 " & materials.Count & " 个物料"
Debug.Print String(80, "=")
Debug.Print ""
End Sub
' ========================================
' 运行所有测试
' ========================================
Sub RunAllTests()
Debug.Print vbCrLf & vbCrLf
Debug.Print "╔" & String(78, "═") & "╗"
Debug.Print "║" & Space(20) & "型号解析与物料匹配 - 完整测试套件" & Space(20) & "║"
Debug.Print "╚" & String(78, "═") & "╝"
Debug.Print ""
Test1_ModelParser
Test2_ConditionExtractor
Test3_ConditionMatcher
Test4_GetMaterialsByModel
Test5_GetAllMaterialsByModel
Debug.Print "╔" & String(78, "═") & "╗"
Debug.Print "║" & Space(30) & "所有测试完成" & Space(30) & "║"
Debug.Print "╚" & String(78, "═") & "╝"
End Sub
' ========================================
' 测试6: 条件提取规则展示
' ========================================
Sub Test6_ShowExtractionRules()
Debug.Print String(80, "=")
Debug.Print "测试6: 当前配置的条件提取规则"
Debug.Print String(80, "=")
Dim extractor As New clsConditionExtractor
Dim rules As Object
Set rules = extractor.GetExtractionRules()
Debug.Print "【提取规则配置】"
Dim key As Variant
For Each key In rules.Keys
Dim ruleInfo As Variant
ruleInfo = rules(key)
Debug.Print " 变量名: " & key
Debug.Print " 源字段: " & ruleInfo(0)
Debug.Print " 提取方法: " & ruleInfo(1)
Debug.Print ""
Next key
Debug.Print String(80, "=")
Debug.Print ""
End Sub
```